( tokens: ReturnType<Lexer['lex']>, basePath: string, )
| 553 | // absolute paths. Skips html tokens so @paths inside block comments are |
| 554 | // ignored — the caller may pass pre-strip tokens. |
| 555 | function extractIncludePathsFromTokens( |
| 556 | tokens: ReturnType<Lexer['lex']>, |
| 557 | basePath: string, |
| 558 | ): string[] { |
| 559 | const absolutePaths = new Set<string>() |
| 560 | |
| 561 | // Extract @paths from a text string and add resolved paths to absolutePaths. |
| 562 | function extractPathsFromText(textContent: string) { |
| 563 | const includeRegex = /(?:^|\s)@((?:[^\s\\]|\\ )+)/g |
| 564 | let match |
| 565 | while ((match = includeRegex.exec(textContent)) !== null) { |
| 566 | let path = match[1] |
| 567 | if (!path) continue |
| 568 | |
| 569 | // Strip fragment identifiers (#heading, #section-name, etc.) |
| 570 | const hashIndex = path.indexOf('#') |
| 571 | if (hashIndex !== -1) { |
| 572 | path = path.substring(0, hashIndex) |
| 573 | } |
| 574 | if (!path) continue |
| 575 | |
| 576 | // Unescape the spaces in the path |
| 577 | path = path.replace(/\\ /g, ' ') |
| 578 | |
| 579 | // Accept @path, @./path, @~/path, or @/path |
| 580 | if (path) { |
| 581 | const isValidPath = |
| 582 | path.startsWith('./') || |
| 583 | path.startsWith('~/') || |
| 584 | (path.startsWith('/') && path !== '/') || |
| 585 | (!path.startsWith('@') && |
| 586 | !path.match(/^[#%^&*()]+/) && |
| 587 | path.match(/^[a-zA-Z0-9._-]/)) |
| 588 | |
| 589 | if (isValidPath) { |
| 590 | const resolvedPath = expandPath(path, dirname(basePath)) |
| 591 | absolutePaths.add(resolvedPath) |
| 592 | } |
| 593 | } |
| 594 | } |
| 595 | } |
| 596 | |
| 597 | // Recursively process elements to find text nodes |
| 598 | function processElements(elements: MarkdownToken[]) { |
| 599 | for (const element of elements) { |
| 600 | if (element.type === 'code' || element.type === 'codespan') { |
| 601 | continue |
| 602 | } |
| 603 | |
| 604 | // For html tokens that contain comments, strip the comment spans and |
| 605 | // check the residual for @paths (e.g. `<!-- note --> @./file.md`). |
| 606 | // Other html tokens (non-comment tags) are skipped entirely. |
| 607 | if (element.type === 'html') { |
| 608 | const raw = element.raw || '' |
| 609 | const trimmed = raw.trimStart() |
| 610 | if (trimmed.startsWith('<!--') && trimmed.includes('-->')) { |
| 611 | const commentSpan = /<!--[\s\S]*?-->/g |
| 612 | const residue = raw.replace(commentSpan, '') |
no test coverage detected