Skip to content

Commit eb77abb

Browse files
idoshamunclaude
andauthored
fix: use link text as URL fallback when href is invalid (#3392)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 73f351f commit eb77abb

3 files changed

Lines changed: 69 additions & 1 deletion

File tree

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ This file provides guidance to coding agents when working with code in this repo
9090
- Database reset before each test run via pretest hook
9191
- Fixtures in `__tests__/fixture/` for test data
9292
- Mercurius integration testing for GraphQL endpoints
93+
- Avoid creating multiple overlapping tests for the same scenario; a single test per key scenario is preferred
9394

9495
**Infrastructure Concerns:**
9596
- OpenTelemetry for distributed tracing and metrics
@@ -114,4 +115,4 @@ Hooks are configured in `.claude/settings.json`:
114115

115116
- **File Protection** (PreToolUse): Blocks edits to `pnpm-lock.yaml`, `src/migration/`, `.infra/Pulumi.*`, `.env`, `.git/`
116117
- **Prevent Force Push** (PreToolUse): Blocks `git push --force` and `git push -f`
117-
- **Auto-Lint** (PostToolUse): Runs `eslint --fix` on TypeScript files after edits
118+
- **Auto-Lint** (PostToolUse): Runs `eslint --fix` on TypeScript files after edits

__tests__/comments.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,18 @@ describe('query commentPreview', () => {
710710
);
711711
});
712712

713+
it('should use link text as URL fallback when href is invalid', async () => {
714+
loggedUser = '1';
715+
const res = await client.query(QUERY, {
716+
variables: {
717+
content: '[www.google.com](url)',
718+
},
719+
});
720+
expect(res.errors).toBeFalsy();
721+
// The invalid "url" href should be replaced with the link text
722+
expect(res.data.commentPreview).toContain('href="https://www.google.com"');
723+
});
724+
713725
it('should only render markdown not HTML', async () => {
714726
loggedUser = '1';
715727
await saveCommentMentionFixtures();

src/common/markdown.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { DataSource, EntityManager } from 'typeorm';
66
import { MentionedUser } from '../schema/comments';
77
import { EntityTarget } from 'typeorm/common/EntityTarget';
88
import { ghostUser } from './utils';
9+
import { isValidHttpUrl } from './links';
910

1011
export const markdown: MarkdownIt = MarkdownIt({
1112
html: false,
@@ -126,7 +127,61 @@ markdown.renderer.rules.text = function (tokens, idx, options, env, self) {
126127
return renderMentions(content, mentions);
127128
};
128129

130+
/**
131+
* Extracts the text content from a link by looking at tokens following link_open.
132+
* The link text appears in 'text' tokens between link_open and link_close.
133+
*/
134+
const getLinkText = (tokens: Token[], linkOpenIdx: number): string => {
135+
let text = '';
136+
for (let i = linkOpenIdx + 1; i < tokens.length; i++) {
137+
if (tokens[i].type === 'link_close') {
138+
break;
139+
}
140+
if (tokens[i].type === 'text') {
141+
text += tokens[i].content;
142+
}
143+
}
144+
return text.trim();
145+
};
146+
147+
/**
148+
* Attempts to convert text to a valid HTTP URL.
149+
* Handles cases like "www.example.com" by prepending "https://".
150+
*/
151+
const toValidHttpUrl = (text: string): string | null => {
152+
if (!text) return null;
153+
154+
// Already a valid HTTP URL
155+
if (isValidHttpUrl(text)) {
156+
return text;
157+
}
158+
159+
// Try adding https:// prefix for URLs like www.example.com or example.com
160+
const withProtocol = `https://${text}`;
161+
if (isValidHttpUrl(withProtocol)) {
162+
return withProtocol;
163+
}
164+
165+
return null;
166+
};
167+
129168
markdown.renderer.rules.link_open = function (tokens, idx, options, env, self) {
169+
const token = tokens[idx];
170+
const hrefIndex = token.attrIndex('href');
171+
172+
if (hrefIndex >= 0 && token.attrs) {
173+
const href = token.attrs[hrefIndex][1];
174+
175+
// If the href is not a valid HTTP URL, try using the link text as the URL
176+
if (!isValidHttpUrl(href)) {
177+
const linkText = getLinkText(tokens, idx);
178+
const validUrl = toValidHttpUrl(linkText);
179+
if (validUrl) {
180+
token.attrs[hrefIndex][1] = validUrl;
181+
}
182+
}
183+
}
184+
130185
tokens[idx] = setTokenAttribute(tokens[idx], 'target', '_blank');
131186
tokens[idx] = setTokenAttribute(tokens[idx], 'rel', 'noopener nofollow');
132187
return defaultRender(tokens, idx, options, env, self);

0 commit comments

Comments
 (0)