lat.md 0.10.1 → 0.10.3
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.
- package/dist/src/cli/check.js +2 -1
- package/dist/src/source-parser.d.ts +2 -0
- package/dist/src/source-parser.js +113 -39
- package/package.json +1 -1
package/dist/src/cli/check.js
CHANGED
|
@@ -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
|
-
|
|
481
|
-
|
|
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') {
|
|
@@ -530,8 +546,13 @@ function extractCSymbols(tree) {
|
|
|
530
546
|
return symbols;
|
|
531
547
|
}
|
|
532
548
|
/**
|
|
533
|
-
* Walk C AST nodes, collecting symbols. Recurses into
|
|
534
|
-
*
|
|
549
|
+
* Walk C AST nodes, collecting symbols. Recurses into preprocessor
|
|
550
|
+
* conditional blocks (ifdef/ifndef/if), linkage specifications
|
|
551
|
+
* (extern "C" { ... }), and declaration lists so that include guards
|
|
552
|
+
* and conditional compilation don't hide declarations.
|
|
553
|
+
*
|
|
554
|
+
* For #if/#ifdef/#ifndef, only the "then" branch is traversed —
|
|
555
|
+
* preproc_else and preproc_elif children are skipped.
|
|
535
556
|
*/
|
|
536
557
|
function collectCNodes(parent, symbols) {
|
|
537
558
|
for (let i = 0; i < parent.childCount; i++) {
|
|
@@ -576,7 +597,12 @@ function collectCNodes(parent, symbols) {
|
|
|
576
597
|
}
|
|
577
598
|
}
|
|
578
599
|
else if (node.type === 'type_definition') {
|
|
579
|
-
|
|
600
|
+
let declarator = node.childForFieldName('declarator');
|
|
601
|
+
// Unwrap pointer_declarator for pointer typedefs
|
|
602
|
+
// e.g. `typedef struct __JSValue *JSValue;`
|
|
603
|
+
while (declarator?.type === 'pointer_declarator') {
|
|
604
|
+
declarator = declarator.childForFieldName('declarator') ?? null;
|
|
605
|
+
}
|
|
580
606
|
const name = declarator?.type === 'type_identifier' ? declarator.text : null;
|
|
581
607
|
if (name) {
|
|
582
608
|
symbols.push({
|
|
@@ -590,18 +616,33 @@ function collectCNodes(parent, symbols) {
|
|
|
590
616
|
}
|
|
591
617
|
else if (node.type === 'declaration') {
|
|
592
618
|
const declarator = node.childForFieldName('declarator');
|
|
593
|
-
|
|
594
|
-
|
|
619
|
+
// Try as function declaration first (e.g. `void greet(const char *name);`
|
|
620
|
+
// in headers), then fall back to variable.
|
|
621
|
+
const funcName = declarator ? cFuncName(declarator) : null;
|
|
622
|
+
if (funcName) {
|
|
595
623
|
symbols.push({
|
|
596
|
-
name,
|
|
597
|
-
kind: '
|
|
624
|
+
name: funcName,
|
|
625
|
+
kind: 'function',
|
|
598
626
|
startLine,
|
|
599
627
|
endLine,
|
|
600
628
|
signature: firstLine(node.text),
|
|
601
629
|
});
|
|
602
630
|
}
|
|
631
|
+
else {
|
|
632
|
+
const name = declarator ? cVarName(declarator) : null;
|
|
633
|
+
if (name) {
|
|
634
|
+
symbols.push({
|
|
635
|
+
name,
|
|
636
|
+
kind: 'variable',
|
|
637
|
+
startLine,
|
|
638
|
+
endLine,
|
|
639
|
+
signature: firstLine(node.text),
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
603
643
|
}
|
|
604
|
-
else if (node.type === 'preproc_def'
|
|
644
|
+
else if (node.type === 'preproc_def' ||
|
|
645
|
+
node.type === 'preproc_function_def') {
|
|
605
646
|
const name = extractName(node);
|
|
606
647
|
if (name) {
|
|
607
648
|
symbols.push({
|
|
@@ -614,10 +655,21 @@ function collectCNodes(parent, symbols) {
|
|
|
614
655
|
}
|
|
615
656
|
}
|
|
616
657
|
else if (node.type === 'preproc_ifdef' ||
|
|
617
|
-
node.type === 'preproc_ifndef'
|
|
618
|
-
|
|
658
|
+
node.type === 'preproc_ifndef' ||
|
|
659
|
+
node.type === 'preproc_if') {
|
|
660
|
+
// Recurse into conditional blocks (then-branch only).
|
|
661
|
+
// preproc_else / preproc_elif children are skipped.
|
|
662
|
+
collectCNodes(node, symbols);
|
|
663
|
+
}
|
|
664
|
+
else if (node.type === 'linkage_specification' ||
|
|
665
|
+
node.type === 'declaration_list') {
|
|
666
|
+
// extern "C" { ... } wraps declarations in linkage_specification
|
|
667
|
+
// containing a declaration_list — recurse through both.
|
|
619
668
|
collectCNodes(node, symbols);
|
|
620
669
|
}
|
|
670
|
+
else if (node.type === 'preproc_else' || node.type === 'preproc_elif') {
|
|
671
|
+
// Skip else/elif branches of preprocessor conditionals.
|
|
672
|
+
}
|
|
621
673
|
}
|
|
622
674
|
}
|
|
623
675
|
function firstLine(text) {
|
|
@@ -634,19 +686,32 @@ export async function parseSourceSymbols(filePath, content) {
|
|
|
634
686
|
const tree = p.parse(content);
|
|
635
687
|
if (!tree)
|
|
636
688
|
return [];
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
689
|
+
try {
|
|
690
|
+
if (ext === '.py') {
|
|
691
|
+
return extractPySymbols(tree);
|
|
692
|
+
}
|
|
693
|
+
if (ext === '.rs') {
|
|
694
|
+
return extractRustSymbols(tree);
|
|
695
|
+
}
|
|
696
|
+
if (ext === '.go') {
|
|
697
|
+
return extractGoSymbols(tree);
|
|
698
|
+
}
|
|
699
|
+
if (ext === '.c' || ext === '.h') {
|
|
700
|
+
return extractCSymbols(tree);
|
|
701
|
+
}
|
|
702
|
+
return extractTsSymbols(tree);
|
|
645
703
|
}
|
|
646
|
-
|
|
647
|
-
|
|
704
|
+
finally {
|
|
705
|
+
tree.delete();
|
|
648
706
|
}
|
|
649
|
-
|
|
707
|
+
}
|
|
708
|
+
// Per-invocation cache for parsed source symbols, keyed by absolute file path.
|
|
709
|
+
// Prevents re-parsing the same file when multiple wiki links reference it
|
|
710
|
+
// (e.g. 20+ links to quickjs.c would otherwise parse a 60K-line file 20 times).
|
|
711
|
+
const symbolCache = new Map();
|
|
712
|
+
/** Clear the symbol cache. Call between top-level operations. */
|
|
713
|
+
export function clearSymbolCache() {
|
|
714
|
+
symbolCache.clear();
|
|
650
715
|
}
|
|
651
716
|
/**
|
|
652
717
|
* Check whether a source file path (relative to projectRoot) has a given symbol.
|
|
@@ -654,24 +719,33 @@ export async function parseSourceSymbols(filePath, content) {
|
|
|
654
719
|
*/
|
|
655
720
|
export async function resolveSourceSymbol(filePath, symbolPath, projectRoot) {
|
|
656
721
|
const absPath = join(projectRoot, filePath);
|
|
657
|
-
let
|
|
658
|
-
|
|
659
|
-
content
|
|
722
|
+
let cached = symbolCache.get(absPath);
|
|
723
|
+
if (!cached) {
|
|
724
|
+
let content;
|
|
725
|
+
try {
|
|
726
|
+
content = readFileSync(absPath, 'utf-8');
|
|
727
|
+
}
|
|
728
|
+
catch {
|
|
729
|
+
cached = { symbols: [] };
|
|
730
|
+
symbolCache.set(absPath, cached);
|
|
731
|
+
return { found: false, symbols: [] };
|
|
732
|
+
}
|
|
733
|
+
try {
|
|
734
|
+
const symbols = await parseSourceSymbols(filePath, content);
|
|
735
|
+
cached = { symbols };
|
|
736
|
+
}
|
|
737
|
+
catch (err) {
|
|
738
|
+
cached = {
|
|
739
|
+
symbols: [],
|
|
740
|
+
error: `failed to parse "${filePath}": ${err instanceof Error ? err.message : String(err)}`,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
symbolCache.set(absPath, cached);
|
|
660
744
|
}
|
|
661
|
-
|
|
662
|
-
return { found: false, symbols:
|
|
663
|
-
}
|
|
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
|
-
};
|
|
745
|
+
if (cached.error) {
|
|
746
|
+
return { found: false, symbols: cached.symbols, error: cached.error };
|
|
674
747
|
}
|
|
748
|
+
const { symbols } = cached;
|
|
675
749
|
const parts = symbolPath.split('#');
|
|
676
750
|
if (parts.length === 1) {
|
|
677
751
|
// Simple symbol: getConfigDir
|