diff --git a/__tests__/unit/node/markdown/plugins/include.test.ts b/__tests__/unit/node/markdown/plugins/include.test.ts index 5a7813ee..b9567b26 100644 --- a/__tests__/unit/node/markdown/plugins/include.test.ts +++ b/__tests__/unit/node/markdown/plugins/include.test.ts @@ -571,9 +571,9 @@ describe('node/markdown/plugins/include', () => { '---\ntitle: x\n---\n\n# Guide\n\n\n\npara [a](./a)\nand [b](./b)\n' ) expect(locs).toEqual([ - { file: path.join(root, 'sub/part.md'), line: 5, column: 1 }, - { file: path.join(root, 'index.md'), line: 9, column: 6 }, - { file: path.join(root, 'index.md'), line: 10, column: 5 } + { file: slash(path.join(root, 'sub/part.md')), line: 5, column: 1 }, + { file: slash(path.join(root, 'index.md')), line: 9, column: 6 }, + { file: slash(path.join(root, 'index.md')), line: 10, column: 5 } ]) }) @@ -583,7 +583,7 @@ describe('node/markdown/plugins/include', () => { const { locs } = await renderLocs('\n') expect(locs).toEqual([ - { file: path.join(root, 'b/two.md'), line: 1, column: 5 } + { file: slash(path.join(root, 'b/two.md')), line: 1, column: 5 } ]) }) @@ -597,7 +597,7 @@ describe('node/markdown/plugins/include', () => { '\n' ) expect(locs).toEqual([ - { file: path.join(root, 'sub/part.md'), line: 6, column: 4 } + { file: slash(path.join(root, 'sub/part.md')), line: 6, column: 4 } ]) }) @@ -608,7 +608,7 @@ describe('node/markdown/plugins/include', () => { '\n' ) expect(locs).toEqual([ - { file: path.join(root, 'sub/part.md'), line: 2, column: 5 } + { file: slash(path.join(root, 'sub/part.md')), line: 2, column: 5 } ]) }) @@ -623,7 +623,7 @@ describe('node/markdown/plugins/include', () => { // location at all; the included file's own subsequent lines resolve // exactly expect(locs).toEqual([ - { file: path.join(root, 'sub/part.md'), line: 2, column: 6 } + { file: slash(path.join(root, 'sub/part.md')), line: 2, column: 6 } ]) }) @@ -656,7 +656,7 @@ describe('node/markdown/plugins/include', () => { include: { silent: true } }) expect(env.lineMap!.resolve(0)).toEqual({ - file: path.join(root, 'index.md'), + file: slash(path.join(root, 'index.md')), line: 0 }) }) @@ -666,14 +666,14 @@ describe('node/markdown/plugins/include', () => { const { locs } = await renderLocs('\n') expect(locs).toEqual([ - { file: path.join(root, 'sub/part.md'), line: 2, column: 8 } + { file: slash(path.join(root, 'sub/part.md')), line: 2, column: 8 } ]) }) test('pages without includes get an identity line map', async () => { const { env } = await render('# Hi\n\n[a](./a)\n') expect(env.lineMap!.resolve(2)).toEqual({ - file: path.join(root, 'index.md'), + file: slash(path.join(root, 'index.md')), line: 2 }) }) diff --git a/__tests__/unit/node/markdownToVue.test.ts b/__tests__/unit/node/markdownToVue.test.ts index add8a2d3..e14a9462 100644 --- a/__tests__/unit/node/markdownToVue.test.ts +++ b/__tests__/unit/node/markdownToVue.test.ts @@ -5,6 +5,7 @@ import path from 'node:path' import { resolveConfig } from 'node/config' import { disposeMdItInstance } from 'node/markdown/markdown' import { createMarkdownToVueRenderFn } from 'node/markdownToVue' +import { slash } from 'node/shared' describe('node/markdownToVue', () => { let root: string | undefined @@ -39,7 +40,7 @@ describe('node/markdownToVue', () => { expect(result.deadLinks).toContainEqual({ url: './missing.md', resolved: '/missing', - file, + file: slash(file), line: 5, column: 1 }) @@ -69,7 +70,7 @@ describe('node/markdownToVue', () => { expect(result.deadLinks).toContainEqual({ url: './missing.md', resolved: '/missing', - file, + file: slash(file), line: 8, column: 1 }) @@ -105,13 +106,13 @@ describe('node/markdownToVue', () => { { url: './nope', resolved: '/nope', - file: partial, + file: slash(partial), line: 5, column: 1, - via: file + via: slash(file) }, - { url: './a', resolved: '/a', file, line: 9, column: 6 }, - { url: './b', resolved: '/b', file, line: 10, column: 5 } + { url: './a', resolved: '/a', file: slash(file), line: 9, column: 6 }, + { url: './b', resolved: '/b', file: slash(file), line: 10, column: 5 } ]) }) @@ -173,8 +174,8 @@ describe('node/markdownToVue', () => { const result = await render(src, file) expect(calls).toEqual([ - ['./skip.md', { file, line: 1, column: 1, url: '/skip' }], - ['./keep.md', { file, line: 2, column: 5, url: '/keep' }] + ['./skip.md', { file: slash(file), line: 1, column: 1, url: '/skip' }], + ['./keep.md', { file: slash(file), line: 2, column: 5, url: '/keep' }] ]) expect(result.deadLinks.map((l) => l.url)).toEqual(['./keep.md']) }) diff --git a/src/node/markdown/plugins/include.ts b/src/node/markdown/plugins/include.ts index e9cc4526..22706790 100644 --- a/src/node/markdown/plugins/include.ts +++ b/src/node/markdown/plugins/include.ts @@ -66,8 +66,11 @@ export function includePlugin( const renderAsync = md.renderAsync.bind(md) md.renderAsync = async (src, env?) => { const mdEnv = env as MarkdownEnv | undefined - const file = mdEnv?.realPath ?? mdEnv?.path - if (file == null) return renderAsync(src, env) + const rawFile = mdEnv?.realPath ?? mdEnv?.path + if (rawFile == null) return renderAsync(src, env) + // one separator style for everything stored, compared or reported - + // segment files, the ancestor chain and includePath are all posix + const file = slash(rawFile) mdEnv!.includes ??= [] const expanded = await processIncludes( @@ -365,7 +368,7 @@ function registerRebaseRules(md: MarkdownItAsync) { // the physical file the construct was authored in, resolved through // the line map — different from the page means it came from an include const sourceFile: string | undefined = token.meta?.vpLoc?.file - if (page && sourceFile && sourceFile !== origin) { + if (page && sourceFile && origin && sourceFile !== slash(origin)) { const attr = rule === 'image' ? 'src' : 'href' const url = token.attrGet(attr) // a destination resolved from `$frontmatter` belongs to the page the diff --git a/src/node/markdown/plugins/sourcePositions.ts b/src/node/markdown/plugins/sourcePositions.ts index 5c85c1d2..3ed1a831 100644 --- a/src/node/markdown/plugins/sourcePositions.ts +++ b/src/node/markdown/plugins/sourcePositions.ts @@ -3,7 +3,7 @@ import type MarkdownIt from 'markdown-it' import type StateCore from 'markdown-it/lib/rules_core/state_core.mjs' import type Token from 'markdown-it/lib/token.mjs' -import type { MarkdownEnv, MarkdownSourceLoc } from '../../shared' +import { slash, type MarkdownEnv, type MarkdownSourceLoc } from '../../shared' // consumed source range of an inline token, [start, end) offsets into the // inline parser's src. Symbols ride on the token objects themselves, so they @@ -220,9 +220,13 @@ function sourceLocs(state: StateCore): void { // it would be wrong in whichever file it names, so it gets none if (resolved?.spliced) continue + const fallbackFile = env.realPath ?? env.path const loc: MarkdownSourceLoc = resolved ? { file: resolved.file, line: resolved.line + 1 } - : { file: env.realPath ?? env.path, line: line + 1 } + : { + file: fallbackFile == null ? undefined : slash(fallbackFile), + line: line + 1 + } // block parsing only ever strips a prefix per line, so the inline line // is a suffix of the raw source line; re-align to get the true column diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index f12031e7..bb55d978 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -60,7 +60,7 @@ export interface DeadLink { url: string /** the site page path it resolved to, for internal links */ resolved?: string - /** absolute path of the file the link was authored in */ + /** absolute path of the file the link was authored in, posix-style */ file: string /** 1-based position in `file`, when known */ line?: number @@ -204,6 +204,8 @@ export async function createMarkdownToVueRenderFn( // validate data.links const deadLinks: MarkdownCompileResult['deadLinks'] = [] + // reported alongside line-map files, which are posix-style + const sourceFile = slash(fileOrig) function shouldIgnoreDeadLink(link: MarkdownLink, resolved: string) { if (!siteConfig?.ignoreDeadLinks) { @@ -217,7 +219,7 @@ export async function createMarkdownToVueRenderFn( } const context: DeadLinkContext = { - file: link.loc?.file ?? fileOrig, + file: link.loc?.file ?? sourceFile, line: link.loc?.line, column: link.loc?.column, url: resolved @@ -273,10 +275,11 @@ export async function createMarkdownToVueRenderFn( deadLinks.push({ url: link.raw, ...(resolvedPath != null && { resolved: resolvedPath }), - file: loc?.file ?? fileOrig, + file: loc?.file ?? sourceFile, ...(loc != null && { line: loc.line }), ...(loc?.column != null && { column: loc.column }), - ...(loc?.file != null && loc.file !== fileOrig && { via: fileOrig }) + ...(loc?.file != null && + loc.file !== sourceFile && { via: sourceFile }) }) } }