@optave/codegraph 3.11.0 → 3.11.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.
- package/README.md +38 -31
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +91 -60
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts +3 -0
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +83 -49
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +78 -62
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +61 -42
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/embed.d.ts.map +1 -1
- package/dist/cli/commands/embed.js +49 -4
- package/dist/cli/commands/embed.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +106 -80
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +77 -52
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +132 -121
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/graph/builder/call-resolver.d.ts +71 -0
- package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -0
- package/dist/domain/graph/builder/call-resolver.js +130 -0
- package/dist/domain/graph/builder/call-resolver.js.map +1 -0
- package/dist/domain/graph/builder/helpers.d.ts +4 -4
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +47 -33
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts +6 -0
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +214 -127
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts +1 -44
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +10 -766
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +151 -192
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +82 -65
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +84 -56
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +60 -51
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts +8 -6
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +107 -122
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts +14 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js +77 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts +62 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js +747 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -0
- package/dist/domain/graph/cycles.d.ts +6 -4
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +50 -55
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/journal.d.ts.map +1 -1
- package/dist/domain/graph/journal.js +89 -70
- package/dist/domain/graph/journal.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +10 -4
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +12 -23
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +126 -79
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.d.ts +3 -1
- package/dist/domain/search/generator.d.ts.map +1 -1
- package/dist/domain/search/generator.js +68 -45
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +2 -0
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +37 -3
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/search/search/hybrid.d.ts.map +1 -1
- package/dist/domain/search/search/hybrid.js +49 -40
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/domain/search/search/semantic.d.ts.map +1 -1
- package/dist/domain/search/search/semantic.js +69 -49
- package/dist/domain/search/search/semantic.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +201 -136
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/extractors/elixir.js +95 -71
- package/dist/extractors/elixir.js.map +1 -1
- package/dist/extractors/gleam.d.ts.map +1 -1
- package/dist/extractors/gleam.js +23 -31
- package/dist/extractors/gleam.js.map +1 -1
- package/dist/extractors/helpers.d.ts +79 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +137 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +37 -49
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +44 -44
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/julia.js +27 -34
- package/dist/extractors/julia.js.map +1 -1
- package/dist/extractors/r.d.ts.map +1 -1
- package/dist/extractors/r.js +33 -58
- package/dist/extractors/r.js.map +1 -1
- package/dist/extractors/solidity.d.ts.map +1 -1
- package/dist/extractors/solidity.js +38 -61
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/features/boundaries.d.ts.map +1 -1
- package/dist/features/boundaries.js +49 -39
- package/dist/features/boundaries.js.map +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +90 -63
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +43 -34
- package/dist/features/check.js.map +1 -1
- package/dist/features/cochange.d.ts.map +1 -1
- package/dist/features/cochange.js +68 -56
- package/dist/features/cochange.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +105 -75
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +37 -29
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +31 -22
- package/dist/features/flow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +77 -70
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/owners.d.ts +17 -26
- package/dist/features/owners.d.ts.map +1 -1
- package/dist/features/owners.js +120 -109
- package/dist/features/owners.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +59 -54
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +60 -60
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +149 -52
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +100 -69
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +63 -59
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/infrastructure/config.d.ts +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +1 -1
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/presentation/cfg.d.ts.map +1 -1
- package/dist/presentation/cfg.js +44 -29
- package/dist/presentation/cfg.js.map +1 -1
- package/dist/presentation/flow.d.ts.map +1 -1
- package/dist/presentation/flow.js +58 -38
- package/dist/presentation/flow.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/package.json +9 -9
- package/src/ast-analysis/engine.ts +145 -61
- package/src/ast-analysis/visitor-utils.ts +86 -46
- package/src/ast-analysis/visitors/ast-store-visitor.ts +104 -69
- package/src/ast-analysis/visitors/dataflow-visitor.ts +86 -47
- package/src/cli/commands/embed.ts +54 -4
- package/src/domain/analysis/dependencies.ts +166 -85
- package/src/domain/analysis/fn-impact.ts +120 -50
- package/src/domain/analysis/module-map.ts +175 -140
- package/src/domain/graph/builder/call-resolver.ts +181 -0
- package/src/domain/graph/builder/helpers.ts +85 -76
- package/src/domain/graph/builder/incremental.ts +321 -152
- package/src/domain/graph/builder/pipeline.ts +19 -957
- package/src/domain/graph/builder/stages/build-edges.ts +229 -275
- package/src/domain/graph/builder/stages/build-structure.ts +115 -82
- package/src/domain/graph/builder/stages/detect-changes.ts +107 -64
- package/src/domain/graph/builder/stages/finalize.ts +72 -70
- package/src/domain/graph/builder/stages/insert-nodes.ts +154 -120
- package/src/domain/graph/builder/stages/native-db-lifecycle.ts +74 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +942 -0
- package/src/domain/graph/cycles.ts +51 -49
- package/src/domain/graph/journal.ts +84 -69
- package/src/domain/graph/watcher.ts +12 -4
- package/src/domain/parser.ts +143 -66
- package/src/domain/search/generator.ts +132 -74
- package/src/domain/search/models.ts +39 -3
- package/src/domain/search/search/hybrid.ts +53 -42
- package/src/domain/search/search/semantic.ts +105 -65
- package/src/domain/wasm-worker-entry.ts +235 -152
- package/src/extractors/elixir.ts +91 -64
- package/src/extractors/gleam.ts +33 -37
- package/src/extractors/helpers.ts +205 -1
- package/src/extractors/java.ts +42 -45
- package/src/extractors/javascript.ts +44 -43
- package/src/extractors/julia.ts +28 -35
- package/src/extractors/r.ts +38 -56
- package/src/extractors/solidity.ts +43 -71
- package/src/features/boundaries.ts +64 -46
- package/src/features/cfg.ts +145 -74
- package/src/features/check.ts +60 -43
- package/src/features/cochange.ts +95 -72
- package/src/features/complexity.ts +134 -79
- package/src/features/dataflow.ts +57 -34
- package/src/features/flow.ts +48 -24
- package/src/features/graph-enrichment.ts +105 -70
- package/src/features/owners.ts +186 -146
- package/src/features/sequence.ts +99 -69
- package/src/features/structure-query.ts +94 -79
- package/src/features/structure.ts +199 -79
- package/src/graph/algorithms/leiden/optimiser.ts +142 -87
- package/src/graph/classifiers/roles.ts +64 -54
- package/src/infrastructure/config.ts +1 -1
- package/src/presentation/cfg.ts +48 -32
- package/src/presentation/flow.ts +100 -52
- package/src/types.ts +1 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared call-edge resolution — used by both the full build pipeline
|
|
3
|
+
* (build-edges.ts) and the incremental watch path (incremental.ts).
|
|
4
|
+
*
|
|
5
|
+
* Both callers supply a `CallNodeLookup` adapter that abstracts their
|
|
6
|
+
* node-lookup mechanism (pre-loaded Maps vs. per-query SQLite statements).
|
|
7
|
+
* The resolution logic lives here exactly once.
|
|
8
|
+
*/
|
|
9
|
+
import { computeConfidence } from '../resolve.js';
|
|
10
|
+
|
|
11
|
+
// ── Public interface ─────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export interface CallNodeLookup {
|
|
14
|
+
byNameAndFile(
|
|
15
|
+
name: string,
|
|
16
|
+
file: string,
|
|
17
|
+
): ReadonlyArray<{ id: number; file: string; kind?: string }>;
|
|
18
|
+
byName(name: string): ReadonlyArray<{ id: number; file: string; kind?: string }>;
|
|
19
|
+
isBarrel(file: string): boolean;
|
|
20
|
+
resolveBarrel(barrelFile: string, symbolName: string): string | null;
|
|
21
|
+
nodeId(name: string, kind: string, file: string, line: number): { id: number } | undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const RECEIVER_KINDS = new Set(['class', 'struct', 'interface', 'type', 'module']);
|
|
25
|
+
|
|
26
|
+
// ── Shared resolution functions ──────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
export function findCaller(
|
|
29
|
+
lookup: CallNodeLookup,
|
|
30
|
+
call: { line: number },
|
|
31
|
+
definitions: ReadonlyArray<{
|
|
32
|
+
name: string;
|
|
33
|
+
kind: string;
|
|
34
|
+
line: number;
|
|
35
|
+
endLine?: number | null;
|
|
36
|
+
}>,
|
|
37
|
+
relPath: string,
|
|
38
|
+
fileNodeRow: { id: number },
|
|
39
|
+
): { id: number } {
|
|
40
|
+
let caller: { id: number } | null = null;
|
|
41
|
+
let callerSpan = Infinity;
|
|
42
|
+
for (const def of definitions) {
|
|
43
|
+
if (def.line <= call.line) {
|
|
44
|
+
const end = def.endLine || Infinity;
|
|
45
|
+
if (call.line <= end) {
|
|
46
|
+
const span = end - def.line;
|
|
47
|
+
if (span < callerSpan) {
|
|
48
|
+
const row = lookup.nodeId(def.name, def.kind, relPath, def.line);
|
|
49
|
+
if (row) {
|
|
50
|
+
caller = row;
|
|
51
|
+
callerSpan = span;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return caller ?? fileNodeRow;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function resolveByMethodOrGlobal(
|
|
61
|
+
lookup: CallNodeLookup,
|
|
62
|
+
call: { name: string; receiver?: string | null },
|
|
63
|
+
relPath: string,
|
|
64
|
+
typeMap: Map<string, unknown>,
|
|
65
|
+
): ReadonlyArray<{ id: number; file: string }> {
|
|
66
|
+
if (call.receiver) {
|
|
67
|
+
const typeEntry = typeMap.get(call.receiver);
|
|
68
|
+
const typeName = typeEntry
|
|
69
|
+
? typeof typeEntry === 'string'
|
|
70
|
+
? typeEntry
|
|
71
|
+
: (typeEntry as { type?: string }).type
|
|
72
|
+
: null;
|
|
73
|
+
if (typeName) {
|
|
74
|
+
const typed = lookup.byName(`${typeName}.${call.name}`).filter((n) => n.kind === 'method');
|
|
75
|
+
if (typed.length > 0) return typed;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (
|
|
79
|
+
!call.receiver ||
|
|
80
|
+
call.receiver === 'this' ||
|
|
81
|
+
call.receiver === 'self' ||
|
|
82
|
+
call.receiver === 'super'
|
|
83
|
+
) {
|
|
84
|
+
return lookup.byName(call.name).filter((t) => computeConfidence(relPath, t.file, null) >= 0.5);
|
|
85
|
+
}
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function resolveCallTargets(
|
|
90
|
+
lookup: CallNodeLookup,
|
|
91
|
+
call: { name: string; receiver?: string | null },
|
|
92
|
+
relPath: string,
|
|
93
|
+
importedNames: Map<string, string>,
|
|
94
|
+
typeMap: Map<string, unknown>,
|
|
95
|
+
): { targets: Array<{ id: number; file: string }>; importedFrom: string | undefined } {
|
|
96
|
+
const importedFrom = importedNames.get(call.name);
|
|
97
|
+
let targets: ReadonlyArray<{ id: number; file: string }> | undefined;
|
|
98
|
+
|
|
99
|
+
if (importedFrom) {
|
|
100
|
+
targets = lookup.byNameAndFile(call.name, importedFrom);
|
|
101
|
+
if (targets.length === 0 && lookup.isBarrel(importedFrom)) {
|
|
102
|
+
const actualSource = lookup.resolveBarrel(importedFrom, call.name);
|
|
103
|
+
if (actualSource) {
|
|
104
|
+
targets = lookup.byNameAndFile(call.name, actualSource);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!targets || targets.length === 0) {
|
|
110
|
+
targets = lookup.byNameAndFile(call.name, relPath);
|
|
111
|
+
if (targets.length === 0) {
|
|
112
|
+
targets = resolveByMethodOrGlobal(lookup, call, relPath, typeMap);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const resolved = [...(targets ?? [])];
|
|
117
|
+
if (resolved.length > 1) {
|
|
118
|
+
resolved.sort((a, b) => {
|
|
119
|
+
const confA = computeConfidence(relPath, a.file, importedFrom ?? null);
|
|
120
|
+
const confB = computeConfidence(relPath, b.file, importedFrom ?? null);
|
|
121
|
+
return confB - confA;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return { targets: resolved, importedFrom };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Resolve the receiver-type edge for a call site.
|
|
129
|
+
* Returns the edge tuple to insert, or null if nothing matched or the edge
|
|
130
|
+
* was already seen. Callers are responsible for the actual DB/array insert.
|
|
131
|
+
*
|
|
132
|
+
* Receiver resolution collects all same-file candidates first (no kind
|
|
133
|
+
* filter), falls back to global candidates only when the same-file set is
|
|
134
|
+
* entirely empty, then filters the chosen set by RECEIVER_KINDS. This
|
|
135
|
+
* matches the native Rust build path: if a file imports a name that happens
|
|
136
|
+
* to be emitted as `kind='function'` in the importer, the same-file set is
|
|
137
|
+
* non-empty and blocks the global fallback, so no receiver edge is emitted.
|
|
138
|
+
* Keeping this behaviour identical to the Rust path maintains engine parity.
|
|
139
|
+
*/
|
|
140
|
+
export function resolveReceiverEdge(
|
|
141
|
+
lookup: CallNodeLookup,
|
|
142
|
+
call: { name: string; receiver: string },
|
|
143
|
+
caller: { id: number },
|
|
144
|
+
relPath: string,
|
|
145
|
+
typeMap: Map<string, unknown>,
|
|
146
|
+
seenCallEdges: Set<string>,
|
|
147
|
+
): { callerId: number; receiverId: number; confidence: number } | null {
|
|
148
|
+
const typeEntry = typeMap.get(call.receiver);
|
|
149
|
+
const typeName = typeEntry
|
|
150
|
+
? typeof typeEntry === 'string'
|
|
151
|
+
? typeEntry
|
|
152
|
+
: ((typeEntry as { type?: string }).type ?? null)
|
|
153
|
+
: null;
|
|
154
|
+
const typeConfidence =
|
|
155
|
+
typeEntry && typeof typeEntry !== 'string'
|
|
156
|
+
? ((typeEntry as { confidence?: number }).confidence ?? null)
|
|
157
|
+
: null;
|
|
158
|
+
const effectiveReceiver = typeName || call.receiver;
|
|
159
|
+
// Filter-before: apply RECEIVER_KINDS to same-file candidates first, then
|
|
160
|
+
// fall back to global candidates (also filtered) only when same-file yields
|
|
161
|
+
// nothing. This prevents an imported name emitted as kind='function' in the
|
|
162
|
+
// importing file from blocking the fallback to the actual class/struct/etc.
|
|
163
|
+
// node in the defining file.
|
|
164
|
+
const sameFileCandidates = lookup
|
|
165
|
+
.byNameAndFile(effectiveReceiver, relPath)
|
|
166
|
+
.filter((n) => RECEIVER_KINDS.has(n.kind ?? ''));
|
|
167
|
+
const candidates =
|
|
168
|
+
sameFileCandidates.length > 0
|
|
169
|
+
? sameFileCandidates
|
|
170
|
+
: lookup.byName(effectiveReceiver).filter((n) => RECEIVER_KINDS.has(n.kind ?? ''));
|
|
171
|
+
if (candidates.length === 0) return null;
|
|
172
|
+
const recvTarget = candidates[0]!;
|
|
173
|
+
const recvKey = `recv|${caller.id}|${recvTarget.id}`;
|
|
174
|
+
if (seenCallEdges.has(recvKey)) return null;
|
|
175
|
+
seenCallEdges.add(recvKey);
|
|
176
|
+
return {
|
|
177
|
+
callerId: caller.id,
|
|
178
|
+
receiverId: recvTarget.id,
|
|
179
|
+
confidence: typeConfidence ?? (typeName ? 0.9 : 0.7),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -76,108 +76,117 @@ export function passesIncludeExclude(
|
|
|
76
76
|
return true;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/** Per-walk state computed once at the top-level invocation. */
|
|
80
|
+
interface CollectContext {
|
|
81
|
+
readonly rootDir: string;
|
|
82
|
+
readonly includeRegexes: readonly RegExp[];
|
|
83
|
+
readonly excludeRegexes: readonly RegExp[];
|
|
84
|
+
readonly hasGlobFilters: boolean;
|
|
85
|
+
readonly extraIgnore: Set<string> | null;
|
|
86
|
+
readonly visited: Set<string>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Detect a symlink loop for `dir`. Returns true if `dir` was already visited. */
|
|
90
|
+
function isSymlinkLoop(dir: string, visited: Set<string>): boolean {
|
|
91
|
+
let realDir: string;
|
|
92
|
+
try {
|
|
93
|
+
realDir = fs.realpathSync(dir);
|
|
94
|
+
} catch {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (visited.has(realDir)) {
|
|
98
|
+
warn(`Symlink loop detected, skipping: ${dir}`);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
visited.add(realDir);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Read directory entries, returning null on error (already logged). */
|
|
106
|
+
function readDirSafe(dir: string): fs.Dirent[] | null {
|
|
107
|
+
try {
|
|
108
|
+
return fs.readdirSync(dir, { withFileTypes: true });
|
|
109
|
+
} catch (err: unknown) {
|
|
110
|
+
warn(`Cannot read directory ${dir}: ${(err as Error).message}`);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** True if `entry` is a source file we should collect under `ctx`. */
|
|
116
|
+
function isCollectableSourceFile(full: string, entry: fs.Dirent, ctx: CollectContext): boolean {
|
|
117
|
+
if (!EXTENSIONS.has(path.extname(entry.name))) return false;
|
|
118
|
+
if (!ctx.hasGlobFilters) return true;
|
|
119
|
+
const rel = normalizePath(path.relative(ctx.rootDir, full));
|
|
120
|
+
return passesIncludeExclude(rel, ctx.includeRegexes, ctx.excludeRegexes);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function walkCollect(
|
|
124
|
+
dir: string,
|
|
125
|
+
files: string[],
|
|
126
|
+
directories: Set<string> | null,
|
|
127
|
+
ctx: CollectContext,
|
|
128
|
+
): void {
|
|
129
|
+
if (isSymlinkLoop(dir, ctx.visited)) return;
|
|
130
|
+
|
|
131
|
+
const entries = readDirSafe(dir);
|
|
132
|
+
if (!entries) return;
|
|
133
|
+
|
|
134
|
+
let hasFiles = false;
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
if (shouldSkipEntry(entry, ctx.extraIgnore)) continue;
|
|
137
|
+
|
|
138
|
+
const full = path.join(dir, entry.name);
|
|
139
|
+
if (entry.isDirectory()) {
|
|
140
|
+
walkCollect(full, files, directories, ctx);
|
|
141
|
+
} else if (isCollectableSourceFile(full, entry, ctx)) {
|
|
142
|
+
files.push(full);
|
|
143
|
+
hasFiles = true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (directories && hasFiles) {
|
|
147
|
+
directories.add(dir);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
79
151
|
/**
|
|
80
152
|
* Recursively collect all source files under `dir`.
|
|
81
153
|
* When `directories` is a Set, also tracks which directories contain files.
|
|
82
154
|
*
|
|
83
|
-
*
|
|
84
|
-
* `config.
|
|
155
|
+
* `dir` establishes the project root against which `config.include` /
|
|
156
|
+
* `config.exclude` globs are matched.
|
|
85
157
|
*/
|
|
86
158
|
export function collectFiles(
|
|
87
159
|
dir: string,
|
|
88
160
|
files: string[],
|
|
89
161
|
config: Partial<CodegraphConfig>,
|
|
90
162
|
directories: Set<string>,
|
|
91
|
-
_visited?: Set<string>,
|
|
92
|
-
_rootDir?: string,
|
|
93
|
-
_includeRegexes?: readonly RegExp[],
|
|
94
|
-
_excludeRegexes?: readonly RegExp[],
|
|
95
163
|
): { files: string[]; directories: Set<string> };
|
|
96
164
|
export function collectFiles(
|
|
97
165
|
dir: string,
|
|
98
166
|
files?: string[],
|
|
99
167
|
config?: Partial<CodegraphConfig>,
|
|
100
168
|
directories?: null,
|
|
101
|
-
_visited?: Set<string>,
|
|
102
|
-
_rootDir?: string,
|
|
103
|
-
_includeRegexes?: readonly RegExp[],
|
|
104
|
-
_excludeRegexes?: readonly RegExp[],
|
|
105
169
|
): string[];
|
|
106
170
|
export function collectFiles(
|
|
107
171
|
dir: string,
|
|
108
172
|
files: string[] = [],
|
|
109
173
|
config: Partial<CodegraphConfig> = {},
|
|
110
174
|
directories: Set<string> | null = null,
|
|
111
|
-
_visited: Set<string> = new Set(),
|
|
112
|
-
_rootDir?: string,
|
|
113
|
-
_includeRegexes?: readonly RegExp[],
|
|
114
|
-
_excludeRegexes?: readonly RegExp[],
|
|
115
175
|
): string[] | { files: string[]; directories: Set<string> } {
|
|
116
176
|
const trackDirs = directories instanceof Set;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const extraIgnore = config.ignoreDirs ? new Set(config.ignoreDirs) : null;
|
|
128
|
-
|
|
129
|
-
// Detect symlink loops (before I/O to avoid wasted readdirSync)
|
|
130
|
-
let realDir: string;
|
|
131
|
-
try {
|
|
132
|
-
realDir = fs.realpathSync(dir);
|
|
133
|
-
} catch {
|
|
134
|
-
return trackDirs ? { files, directories: directories as Set<string> } : files;
|
|
135
|
-
}
|
|
136
|
-
if (_visited.has(realDir)) {
|
|
137
|
-
warn(`Symlink loop detected, skipping: ${dir}`);
|
|
138
|
-
return trackDirs ? { files, directories: directories as Set<string> } : files;
|
|
139
|
-
}
|
|
140
|
-
_visited.add(realDir);
|
|
141
|
-
|
|
142
|
-
let entries: fs.Dirent[];
|
|
143
|
-
try {
|
|
144
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
145
|
-
} catch (err: unknown) {
|
|
146
|
-
warn(`Cannot read directory ${dir}: ${(err as Error).message}`);
|
|
147
|
-
return trackDirs ? { files, directories: directories as Set<string> } : files;
|
|
148
|
-
}
|
|
177
|
+
const includeRegexes = compileGlobs(config.include);
|
|
178
|
+
const excludeRegexes = compileGlobs(config.exclude);
|
|
179
|
+
const ctx: CollectContext = {
|
|
180
|
+
rootDir: dir,
|
|
181
|
+
includeRegexes,
|
|
182
|
+
excludeRegexes,
|
|
183
|
+
hasGlobFilters: includeRegexes.length > 0 || excludeRegexes.length > 0,
|
|
184
|
+
extraIgnore: config.ignoreDirs ? new Set(config.ignoreDirs) : null,
|
|
185
|
+
visited: new Set(),
|
|
186
|
+
};
|
|
149
187
|
|
|
150
|
-
|
|
151
|
-
if (shouldSkipEntry(entry, extraIgnore)) continue;
|
|
188
|
+
walkCollect(dir, files, trackDirs ? (directories as Set<string>) : null, ctx);
|
|
152
189
|
|
|
153
|
-
const full = path.join(dir, entry.name);
|
|
154
|
-
if (entry.isDirectory()) {
|
|
155
|
-
if (trackDirs) {
|
|
156
|
-
collectFiles(
|
|
157
|
-
full,
|
|
158
|
-
files,
|
|
159
|
-
config,
|
|
160
|
-
directories as Set<string>,
|
|
161
|
-
_visited,
|
|
162
|
-
rootDir,
|
|
163
|
-
includeRegexes,
|
|
164
|
-
excludeRegexes,
|
|
165
|
-
);
|
|
166
|
-
} else {
|
|
167
|
-
collectFiles(full, files, config, null, _visited, rootDir, includeRegexes, excludeRegexes);
|
|
168
|
-
}
|
|
169
|
-
} else if (EXTENSIONS.has(path.extname(entry.name))) {
|
|
170
|
-
if (hasGlobFilters) {
|
|
171
|
-
const rel = normalizePath(path.relative(rootDir, full));
|
|
172
|
-
if (!passesIncludeExclude(rel, includeRegexes, excludeRegexes)) continue;
|
|
173
|
-
}
|
|
174
|
-
files.push(full);
|
|
175
|
-
hasFiles = true;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (trackDirs && hasFiles) {
|
|
179
|
-
(directories as Set<string>).add(dir);
|
|
180
|
-
}
|
|
181
190
|
return trackDirs ? { files, directories: directories as Set<string> } : files;
|
|
182
191
|
}
|
|
183
192
|
|