( tokens: ReturnType<Lexer['lex']>, basePath: string, )
| 449 | // absolute paths. Skips html tokens so @paths inside block comments are |
| 450 | // ignored — the caller may pass pre-strip tokens. |
| 451 | function extractIncludePathsFromTokens( |
| 452 | tokens: ReturnType<Lexer['lex']>, |
| 453 | basePath: string, |
| 454 | ): string[] { |
| 455 | const absolutePaths = new Set<string>() |
| 456 | |
| 457 | // Extract @paths from a text string and add resolved paths to absolutePaths. |
| 458 | function extractPathsFromText(textContent: string) { |
| 459 | const includeRegex = /(?:^|\s)@((?:[^\s\\]|\\ )+)/g |
| 460 | let match |
| 461 | while ((match = includeRegex.exec(textContent)) !== null) { |
| 462 | let path = match[1] |
| 463 | if (!path) continue |
| 464 | |
| 465 | // Strip fragment identifiers (#heading, #section-name, etc.) |
| 466 | const hashIndex = path.indexOf('#') |
| 467 | if (hashIndex !== -1) { |
| 468 | path = path.substring(0, hashIndex) |
| 469 | } |
| 470 | if (!path) continue |
| 471 | |
| 472 | // Unescape the spaces in the path |
| 473 | path = path.replace(/\\ /g, ' ') |
| 474 | |
| 475 | // Accept @path, @./path, @~/path, or @/path |
| 476 | if (path) { |
| 477 | const isValidPath = |
| 478 | path.startsWith('./') || |
| 479 | path.startsWith('~/') || |
| 480 | (path.startsWith('/') && path !== '/') || |
| 481 | (!path.startsWith('@') && |
| 482 | !path.match(/^[#%^&*()]+/) && |
| 483 | path.match(/^[a-zA-Z0-9._-]/)) |
| 484 | |
| 485 | if (isValidPath) { |
| 486 | const resolvedPath = expandPath(path, dirname(basePath)) |
| 487 | absolutePaths.add(resolvedPath) |
| 488 | } |
| 489 | } |
| 490 | } |
| 491 | } |
| 492 | |
| 493 | // Recursively process elements to find text nodes |
| 494 | function processElements(elements: MarkdownToken[]) { |
| 495 | for (const element of elements) { |
| 496 | if (element.type === 'code' || element.type === 'codespan') { |
| 497 | continue |
| 498 | } |
| 499 | |
| 500 | // For html tokens that contain comments, strip the comment spans and |
| 501 | // check the residual for @paths (e.g. `<!-- note --> @./file.md`). |
| 502 | // Other html tokens (non-comment tags) are skipped entirely. |
| 503 | if (element.type === 'html') { |
| 504 | const raw = element.raw || '' |
| 505 | const trimmed = raw.trimStart() |
| 506 | if (trimmed.startsWith('<!--') && trimmed.includes('-->')) { |
| 507 | const commentSpan = /<!--[\s\S]*?-->/g |
| 508 | const residue = raw.replace(commentSpan, '') |
no test coverage detected