lat.md 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@ import { existsSync } from 'node:fs';
3
3
  import { basename, dirname, extname, join, relative } from 'node:path';
4
4
  import { listLatticeFiles, loadAllSections, extractRefs, flattenSections, parseFrontmatter, parseSections, buildFileIndex, resolveRef, } from '../lattice.js';
5
5
  import { scanCodeRefs } from '../code-refs.js';
6
- import { SOURCE_EXTENSIONS } from '../source-parser.js';
6
+ import { SOURCE_EXTENSIONS, clearSymbolCache } from '../source-parser.js';
7
7
  import { walkEntries } from '../walk.js';
8
8
  import { INIT_VERSION, readInitVersion } from '../init-version.js';
9
9
  function filePart(id) {
@@ -82,6 +82,7 @@ async function tryResolveSourceRef(target, projectRoot) {
82
82
  }
83
83
  }
84
84
  export async function checkMd(latticeDir) {
85
+ clearSymbolCache();
85
86
  const projectRoot = dirname(latticeDir);
86
87
  const files = await listLatticeFiles(latticeDir);
87
88
  const allSections = await loadAllSections(latticeDir);
@@ -10,6 +10,8 @@ export type SourceSymbol = {
10
10
  /** All source file extensions that lat can parse (derived from grammarMap). */
11
11
  export declare const SOURCE_EXTENSIONS: ReadonlySet<string>;
12
12
  export declare function parseSourceSymbols(filePath: string, content: string): Promise<SourceSymbol[]>;
13
+ /** Clear the symbol cache. Call between top-level operations. */
14
+ export declare function clearSymbolCache(): void;
13
15
  /**
14
16
  * Check whether a source file path (relative to projectRoot) has a given symbol.
15
17
  * Used by lat check to validate source code wiki links lazily.
@@ -477,8 +477,17 @@ function extractGoSymbols(tree) {
477
477
  * Handles plain identifiers and pointer declarators (*name).
478
478
  */
479
479
  function cFuncName(declarator) {
480
- if (declarator.type === 'function_declarator') {
481
- const inner = declarator.childForFieldName('declarator');
480
+ // Unwrap pointer_declarator layers (for functions returning pointers,
481
+ // e.g. `JSRuntime *JS_NewRuntime(void)` → pointer_declarator > function_declarator)
482
+ let node = declarator;
483
+ while (node.type === 'pointer_declarator') {
484
+ const child = node.childForFieldName('declarator');
485
+ if (!child)
486
+ return null;
487
+ node = child;
488
+ }
489
+ if (node.type === 'function_declarator') {
490
+ const inner = node.childForFieldName('declarator');
482
491
  if (!inner)
483
492
  return null;
484
493
  if (inner.type === 'identifier')
@@ -510,6 +519,13 @@ function cVarName(declarator) {
510
519
  return null;
511
520
  node = inner;
512
521
  }
522
+ // Unwrap array_declarator (e.g. `char js_version[]`)
523
+ if (node.type === 'array_declarator') {
524
+ const inner = node.childForFieldName('declarator');
525
+ if (!inner)
526
+ return null;
527
+ node = inner;
528
+ }
513
529
  if (node.type === 'identifier')
514
530
  return node.text;
515
531
  if (node.type === 'pointer_declarator') {
@@ -601,7 +617,8 @@ function collectCNodes(parent, symbols) {
601
617
  });
602
618
  }
603
619
  }
604
- else if (node.type === 'preproc_def') {
620
+ else if (node.type === 'preproc_def' ||
621
+ node.type === 'preproc_function_def') {
605
622
  const name = extractName(node);
606
623
  if (name) {
607
624
  symbols.push({
@@ -634,19 +651,32 @@ export async function parseSourceSymbols(filePath, content) {
634
651
  const tree = p.parse(content);
635
652
  if (!tree)
636
653
  return [];
637
- if (ext === '.py') {
638
- return extractPySymbols(tree);
639
- }
640
- if (ext === '.rs') {
641
- return extractRustSymbols(tree);
642
- }
643
- if (ext === '.go') {
644
- return extractGoSymbols(tree);
654
+ try {
655
+ if (ext === '.py') {
656
+ return extractPySymbols(tree);
657
+ }
658
+ if (ext === '.rs') {
659
+ return extractRustSymbols(tree);
660
+ }
661
+ if (ext === '.go') {
662
+ return extractGoSymbols(tree);
663
+ }
664
+ if (ext === '.c' || ext === '.h') {
665
+ return extractCSymbols(tree);
666
+ }
667
+ return extractTsSymbols(tree);
645
668
  }
646
- if (ext === '.c' || ext === '.h') {
647
- return extractCSymbols(tree);
669
+ finally {
670
+ tree.delete();
648
671
  }
649
- return extractTsSymbols(tree);
672
+ }
673
+ // Per-invocation cache for parsed source symbols, keyed by absolute file path.
674
+ // Prevents re-parsing the same file when multiple wiki links reference it
675
+ // (e.g. 20+ links to quickjs.c would otherwise parse a 60K-line file 20 times).
676
+ const symbolCache = new Map();
677
+ /** Clear the symbol cache. Call between top-level operations. */
678
+ export function clearSymbolCache() {
679
+ symbolCache.clear();
650
680
  }
651
681
  /**
652
682
  * Check whether a source file path (relative to projectRoot) has a given symbol.
@@ -654,24 +684,33 @@ export async function parseSourceSymbols(filePath, content) {
654
684
  */
655
685
  export async function resolveSourceSymbol(filePath, symbolPath, projectRoot) {
656
686
  const absPath = join(projectRoot, filePath);
657
- let content;
658
- try {
659
- content = readFileSync(absPath, 'utf-8');
660
- }
661
- catch {
662
- return { found: false, symbols: [] };
687
+ let cached = symbolCache.get(absPath);
688
+ if (!cached) {
689
+ let content;
690
+ try {
691
+ content = readFileSync(absPath, 'utf-8');
692
+ }
693
+ catch {
694
+ cached = { symbols: [] };
695
+ symbolCache.set(absPath, cached);
696
+ return { found: false, symbols: [] };
697
+ }
698
+ try {
699
+ const symbols = await parseSourceSymbols(filePath, content);
700
+ cached = { symbols };
701
+ }
702
+ catch (err) {
703
+ cached = {
704
+ symbols: [],
705
+ error: `failed to parse "${filePath}": ${err instanceof Error ? err.message : String(err)}`,
706
+ };
707
+ }
708
+ symbolCache.set(absPath, cached);
663
709
  }
664
- let symbols;
665
- try {
666
- symbols = await parseSourceSymbols(filePath, content);
667
- }
668
- catch (err) {
669
- return {
670
- found: false,
671
- symbols: [],
672
- error: `failed to parse "${filePath}": ${err instanceof Error ? err.message : String(err)}`,
673
- };
710
+ if (cached.error) {
711
+ return { found: false, symbols: cached.symbols, error: cached.error };
674
712
  }
713
+ const { symbols } = cached;
675
714
  const parts = symbolPath.split('#');
676
715
  if (parts.length === 1) {
677
716
  // Simple symbol: getConfigDir
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lat.md",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "A knowledge graph for your codebase, written in markdown",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.30.2",