diff --git a/__tests__/unit/node/markdown/plugins/link.test.ts b/__tests__/unit/node/markdown/plugins/link.test.ts index 5e4bc7f199c2..6c9e027299d0 100644 --- a/__tests__/unit/node/markdown/plugins/link.test.ts +++ b/__tests__/unit/node/markdown/plugins/link.test.ts @@ -53,12 +53,12 @@ describe('node/markdown/plugins/link', () => { const env: { cleanUrls: boolean links?: string[] - linkLines?: number[] + linkMetadatas?: { rawLink: string; line?: number }[] } = { cleanUrls: false } await md.renderAsync('Intro\n\n[Missing](./missing.md)\n', env) - expect(env.links).toEqual(['./missing']) - expect(env.linkLines).toEqual([3]) + expect(env.links).toEqual(['./missing.html']) + expect(env.linkMetadatas).toEqual([{ rawLink: './missing.md', line: 3 }]) }) }) diff --git a/__tests__/unit/node/markdownToVue.test.ts b/__tests__/unit/node/markdownToVue.test.ts index 1d675f3112c3..b336db00ca5e 100644 --- a/__tests__/unit/node/markdownToVue.test.ts +++ b/__tests__/unit/node/markdownToVue.test.ts @@ -14,6 +14,38 @@ describe('node/markdownToVue', () => { } }) + test('records link path as written for dead links', async () => { + root = await mkdtemp(path.join(tmpdir(), 'vitepress-link-path-')) + + const file = path.join(root, 'index.md') + const src = [ + '[a](./a.md)', + '[b](./b#hash)', + '[c](./中文.md)', + '[d](/d)' + ].join('\n\n') + await writeFile(file, src) + + const siteConfig = await resolveConfig(root, 'build', 'production') + const render = await createMarkdownToVueRenderFn( + siteConfig.srcDir, + { cache: false }, + '/', + false, + false, + siteConfig + ) + + const result = await render(src, file) + + expect(result.deadLinks).toEqual([ + { url: './a.md', file, line: 1 }, + { url: './b#hash', file, line: 3 }, + { url: './中文.md', file, line: 5 }, + { url: '/d', file, line: 7 } + ]) + }) + test('records source line numbers for dead links', async () => { root = await mkdtemp(path.join(tmpdir(), 'vitepress-dead-link-')) @@ -34,7 +66,7 @@ describe('node/markdownToVue', () => { const result = await render(src, file) expect(result.deadLinks).toContainEqual({ - url: './missing', + url: './missing.md', file, line: 5 }) @@ -61,7 +93,7 @@ describe('node/markdownToVue', () => { const result = await render(src, file) expect(result.deadLinks).toContainEqual({ - url: './missing', + url: './missing.md', file, line: 8 }) diff --git a/src/node/markdown/plugins/link.ts b/src/node/markdown/plugins/link.ts index b165744a4495..ba4bfe4aa1d0 100644 --- a/src/node/markdown/plugins/link.ts +++ b/src/node/markdown/plugins/link.ts @@ -47,6 +47,7 @@ export const linkPlugin = ( token.attrGet('class') !== 'header-anchor' // header anchors are already normalized ) { const hrefAttr = token.attrs![hrefIndex] + const rawUrl = decodeURI(hrefAttr[1]) let [url, frag] = hrefAttr[1].split(':~:', 2) hrefAttr[1] = url if (isExternal(url)) { @@ -55,7 +56,7 @@ export const linkPlugin = ( }) // catch localhost links as dead link if (url.replace(EXTERNAL_URL_RE, '').startsWith('//localhost:')) { - pushLink(url, env, token.meta?.vpLine) + pushLink(url, rawUrl, env, token.meta?.vpLine) } hrefAttr[1] = url } else { @@ -95,6 +96,7 @@ export const linkPlugin = ( line?: number ) { let url = hrefAttr[1] + const rawUrl = decodeURI(url) const indexMatch = url.match(indexRE) if (indexMatch) { @@ -124,7 +126,7 @@ export const linkPlugin = ( } // export it for existence check - pushLink(url.replace(/\.html$/, ''), env, line) + pushLink(url, rawUrl, env, line) // markdown-it encodes the uri hrefAttr[1] = decodeURI(url) @@ -134,12 +136,15 @@ export const linkPlugin = ( return str ? encodeURI('#' + slugify(decodeURI(str).slice(1))) : '' } - function pushLink(link: string, env: MarkdownEnv, line?: number) { - const links = env.links || (env.links = []) - links.push(link) - if (line != null) { - const linkLines = env.linkLines || (env.linkLines = []) - linkLines[links.length - 1] = line - } + function pushLink( + link: string, + rawLink: string, + env: MarkdownEnv, + line?: number + ) { + env.links ??= [] + env.links.push(link) + env.linkMetadatas ??= [] + env.linkMetadatas.push({ rawLink, line }) } } diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index dd0f993a78ff..16761b8af74e 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -174,8 +174,8 @@ export async function createMarkdownToVueRenderFn( frontmatter = {}, headers = [], includes = [], - linkLines = [], links = [], + linkMetadatas = [], sfcBlocks, title = '' } = env @@ -215,10 +215,9 @@ export async function createMarkdownToVueRenderFn( const dir = path.dirname(file) for (const [index, rawUrl] of links.entries()) { let url = rawUrl + const metadata = linkMetadatas[index] const line = - linkLines[index] == null - ? undefined - : linkLines[index] + contentLineOffset + metadata?.line != null ? metadata.line + contentLineOffset : undefined const { pathname } = new URL(url, 'http://a.com') if (!treatAsHtml(pathname)) continue @@ -250,7 +249,7 @@ export async function createMarkdownToVueRenderFn( ) && !shouldIgnoreDeadLink(url) ) { - recordDeadLink(url, line) + recordDeadLink(metadata.rawLink, line) } } } diff --git a/types/shared.d.ts b/types/shared.d.ts index c1e280b28de1..95c77394bdc5 100644 --- a/types/shared.d.ts +++ b/types/shared.d.ts @@ -591,9 +591,11 @@ export interface MarkdownEnv { */ links?: string[] /** - * The line numbers at which each of `links` appears in the source. + * The metadata of the links collected from the page. + * - `rawLink`: The url as written + * - `line`: The line number at which the link appears in the source. */ - linkLines?: number[] + linkMetadatas?: { rawLink: string; line?: number }[] /** * The absolute paths of the files inlined via `` and * imported via `<<<` code snippets, used for watch invalidation.