pull/5316/merge
Bjorn Lu 2 days ago committed by GitHub
commit 6e40997bff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -53,13 +53,13 @@ describe('node/markdown/plugins/link', () => {
const env: { const env: {
cleanUrls: boolean cleanUrls: boolean
links?: string[] links?: string[]
linkLines?: number[] linkMetadatas?: { rawLink: string; line?: number }[]
} = { cleanUrls: false } } = { cleanUrls: false }
await md.renderAsync('Intro\n\n[Missing](./missing.md)\n', env) await md.renderAsync('Intro\n\n[Missing](./missing.md)\n', env)
expect(env.links).toEqual(['./missing']) expect(env.links).toEqual(['./missing.html'])
expect(env.linkLines).toEqual([3]) expect(env.linkMetadatas).toEqual([{ rawLink: './missing.md', line: 3 }])
}) })
}) })

@ -16,6 +16,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 () => { test('records source line numbers for dead links', async () => {
root = await mkdtemp(path.join(tmpdir(), 'vitepress-dead-link-')) root = await mkdtemp(path.join(tmpdir(), 'vitepress-dead-link-'))
@ -36,7 +68,7 @@ describe('node/markdownToVue', () => {
const result = await render(src, file) const result = await render(src, file)
expect(result.deadLinks).toContainEqual({ expect(result.deadLinks).toContainEqual({
url: './missing', url: './missing.md',
file, file,
line: 5 line: 5
}) })
@ -63,7 +95,7 @@ describe('node/markdownToVue', () => {
const result = await render(src, file) const result = await render(src, file)
expect(result.deadLinks).toContainEqual({ expect(result.deadLinks).toContainEqual({
url: './missing', url: './missing.md',
file, file,
line: 8 line: 8
}) })

@ -52,6 +52,7 @@ export const linkPlugin = (
token.attrGet('class') !== 'header-anchor' // header anchors are already normalized token.attrGet('class') !== 'header-anchor' // header anchors are already normalized
) { ) {
const hrefAttr = token.attrs![hrefIndex] const hrefAttr = token.attrs![hrefIndex]
const rawUrl = decodeURI(hrefAttr[1])
let [url, frag] = hrefAttr[1].split(':~:', 2) let [url, frag] = hrefAttr[1].split(':~:', 2)
hrefAttr[1] = url hrefAttr[1] = url
if (isExternal(url)) { if (isExternal(url)) {
@ -60,7 +61,7 @@ export const linkPlugin = (
}) })
// catch localhost links as dead link // catch localhost links as dead link
if (url.replace(EXTERNAL_URL_RE, '').startsWith('//localhost:')) { if (url.replace(EXTERNAL_URL_RE, '').startsWith('//localhost:')) {
pushLink(url, env, token.meta?.vpLine) pushLink(url, rawUrl, env, token.meta?.vpLine)
} }
hrefAttr[1] = url hrefAttr[1] = url
} else { } else {
@ -108,6 +109,7 @@ export const linkPlugin = (
line?: number line?: number
) { ) {
let url = hrefAttr[1] let url = hrefAttr[1]
const rawUrl = decodeURI(url)
// directory urls need a server to resolve them, and file:// has none // directory urls need a server to resolve them, and file:// has none
const explicitIndex = isRelativeBase(base) && !env.cleanUrls const explicitIndex = isRelativeBase(base) && !env.cleanUrls
@ -143,7 +145,7 @@ export const linkPlugin = (
} }
// export it for existence check // export it for existence check
pushLink(url.replace(/\.html$/, ''), env, line) pushLink(url, rawUrl, env, line)
// markdown-it encodes the uri // markdown-it encodes the uri
hrefAttr[1] = decodeURI(url) hrefAttr[1] = decodeURI(url)
@ -153,12 +155,15 @@ export const linkPlugin = (
return str ? encodeURI('#' + slugify(decodeURI(str).slice(1))) : '' return str ? encodeURI('#' + slugify(decodeURI(str).slice(1))) : ''
} }
function pushLink(link: string, env: MarkdownEnv, line?: number) { function pushLink(
const links = env.links || (env.links = []) link: string,
links.push(link) rawLink: string,
if (line != null) { env: MarkdownEnv,
const linkLines = env.linkLines || (env.linkLines = []) line?: number
linkLines[links.length - 1] = line ) {
} env.links ??= []
env.links.push(link)
env.linkMetadatas ??= []
env.linkMetadatas.push({ rawLink, line })
} }
} }

@ -178,8 +178,8 @@ export async function createMarkdownToVueRenderFn(
frontmatter = {}, frontmatter = {},
headers = [], headers = [],
includes = [], includes = [],
linkLines = [],
links = [], links = [],
linkMetadatas = [],
sfcBlocks, sfcBlocks,
title = '' title = ''
} = env } = env
@ -219,10 +219,9 @@ export async function createMarkdownToVueRenderFn(
const dir = path.dirname(file) const dir = path.dirname(file)
for (const [index, rawUrl] of links.entries()) { for (const [index, rawUrl] of links.entries()) {
let url = rawUrl let url = rawUrl
const metadata = linkMetadatas[index]
const line = const line =
linkLines[index] == null metadata?.line != null ? metadata.line + contentLineOffset : undefined
? undefined
: linkLines[index] + contentLineOffset
const { pathname } = new URL(url, 'http://a.com') const { pathname } = new URL(url, 'http://a.com')
if (!treatAsHtml(pathname)) continue if (!treatAsHtml(pathname)) continue
@ -254,7 +253,7 @@ export async function createMarkdownToVueRenderFn(
) && ) &&
!shouldIgnoreDeadLink(url) !shouldIgnoreDeadLink(url)
) { ) {
recordDeadLink(url, line) recordDeadLink(metadata.rawLink, line)
} }
} }
} }

6
types/shared.d.ts vendored

@ -594,9 +594,11 @@ export interface MarkdownEnv {
*/ */
links?: string[] 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 `<!--@include-->` and * The absolute paths of the files inlined via `<!--@include-->` and
* imported via `<<<` code snippets, used for watch invalidation. * imported via `<<<` code snippets, used for watch invalidation.

Loading…
Cancel
Save