diff --git a/packages/shared/src/markdownLinks.test.ts b/packages/shared/src/markdownLinks.test.ts index aba5a74c01d3b..788424168bbe7 100644 --- a/packages/shared/src/markdownLinks.test.ts +++ b/packages/shared/src/markdownLinks.test.ts @@ -40,6 +40,13 @@ describe("inlineCodeFilePathCandidate", () => { ["/tmp/.../scratchpad/notes.md", null], ["~/projects/…/notes.md", null], ["src/.../main.ts", null], + // regression: #22181 a `:line` suffix does not make an extensionless basename a file + ["I/W217:220", null], + ["bin/run:12", null], + ["docs/README:12", "docs/README:12"], + ["./bin/run:12", "./bin/run:12"], + ["src/資料.備份:12", "src/資料.備份:12"], + ["src/foo.c++:12", "src/foo.c++:12"], ])("distinguishes file paths from code and hostnames in %s", (source, candidate) => { expect(inlineCodeFilePathCandidate(source)).toBe(candidate); }); diff --git a/packages/shared/src/markdownLinks.ts b/packages/shared/src/markdownLinks.ts index edcc2cc5060d5..b0408486c8f04 100644 --- a/packages/shared/src/markdownLinks.ts +++ b/packages/shared/src/markdownLinks.ts @@ -37,6 +37,7 @@ const PATH_SEPARATOR_PATTERN = /[\\/]/; const ELIDED_PATH_SEGMENT_PATTERN = /(?:^|[\\/])(?:\.{3,}|…)(?:[\\/]|$)/; const FILE_EXTENSION_PATTERN = /\.[A-Za-z0-9_-]+$/; +const ANY_FILE_EXTENSION_PATTERN = /\.[^./\\]+$/; const VERSION_SUFFIX_PATTERN = /\d\.\d[^.]*$/; const NUMERIC_DOTTED_PATTERN = /^\d+(?:\.\d+)+$/; @@ -200,7 +201,17 @@ export function inlineCodeFilePathCandidate(codeText: string): string | null { .split(/[\\/]/) .at(-1) ?? ""; if (VERSION_SUFFIX_PATTERN.test(basename)) return null; - if (!hasPosition && !FILE_EXTENSION_PATTERN.test(basename)) return null; + if (!FILE_EXTENSION_PATTERN.test(basename)) { + if (!hasPosition) return null; + // A `:line` suffix alone does not make `I/W217:220` a file reference. + if ( + withoutPosition.includes("/") && + !ANY_FILE_EXTENSION_PATTERN.test(basename) && + !EXTENSIONLESS_FILE_NAMES.has(basename) + ) { + return null; + } + } } return candidate; }