@shrkcrft/graph 0.1.0-alpha.17 → 0.1.0-alpha.19
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/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/indexer/call-graph-support.d.ts +3 -0
- package/dist/indexer/call-graph-support.d.ts.map +1 -0
- package/dist/indexer/call-graph-support.js +32 -0
- package/dist/indexer/extract-ts-file.d.ts +26 -0
- package/dist/indexer/extract-ts-file.d.ts.map +1 -1
- package/dist/indexer/extract-ts-file.js +162 -20
- package/dist/indexer/incremental-updater.d.ts +21 -3
- package/dist/indexer/incremental-updater.d.ts.map +1 -1
- package/dist/indexer/incremental-updater.js +45 -10
- package/dist/indexer/index-builder.d.ts.map +1 -1
- package/dist/indexer/index-builder.js +12 -4
- package/dist/indexer/resolve-imports.d.ts +1 -0
- package/dist/indexer/resolve-imports.d.ts.map +1 -1
- package/dist/indexer/resolve-imports.js +26 -1
- package/dist/indexer/resolve-reexports.d.ts +32 -0
- package/dist/indexer/resolve-reexports.d.ts.map +1 -0
- package/dist/indexer/resolve-reexports.js +123 -0
- package/dist/query/graph-api-cache.d.ts +21 -0
- package/dist/query/graph-api-cache.d.ts.map +1 -0
- package/dist/query/graph-api-cache.js +54 -0
- package/dist/query/query-api.d.ts +126 -0
- package/dist/query/query-api.d.ts.map +1 -1
- package/dist/query/query-api.js +334 -0
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,9 @@ export * from './indexer/resolve-imports.js';
|
|
|
25
25
|
export * from './indexer/index-builder.js';
|
|
26
26
|
export * from './indexer/incremental-updater.js';
|
|
27
27
|
export * from './indexer/unresolved-imports.js';
|
|
28
|
+
export * from './indexer/call-graph-support.js';
|
|
29
|
+
export * from './indexer/resolve-reexports.js';
|
|
28
30
|
export * from './query/query-api.js';
|
|
31
|
+
export * from './query/graph-api-cache.js';
|
|
29
32
|
export * from './query/cycle-detection.js';
|
|
30
33
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAE3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AAEvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kCAAkC,CAAC;AACjD,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAE3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC;AAEvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kCAAkC,CAAC;AACjD,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kCAAkC,CAAC;AACjD,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,gCAAgC,CAAC;AAE/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -28,5 +28,8 @@ export * from "./indexer/resolve-imports.js";
|
|
|
28
28
|
export * from "./indexer/index-builder.js";
|
|
29
29
|
export * from "./indexer/incremental-updater.js";
|
|
30
30
|
export * from "./indexer/unresolved-imports.js";
|
|
31
|
+
export * from "./indexer/call-graph-support.js";
|
|
32
|
+
export * from "./indexer/resolve-reexports.js";
|
|
31
33
|
export * from "./query/query-api.js";
|
|
34
|
+
export * from "./query/graph-api-cache.js";
|
|
32
35
|
export * from "./query/cycle-detection.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-graph-support.d.ts","sourceRoot":"","sources":["../../src/indexer/call-graph-support.ts"],"names":[],"mappings":"AA6BA,2EAA2E;AAC3E,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE5E"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Languages whose dedicated extractor does NOT emit call/reference edges
|
|
3
|
+
* (`CallsSymbol` / `ReferencesSymbol`). Only the TS-family extractor
|
|
4
|
+
* (`extractTsFile`, for ts/tsx/js/jsx) walks an AST to build the call graph;
|
|
5
|
+
* the per-language extractors (Go, Python, Java, …) and the single-file
|
|
6
|
+
* component / SDL formats produce symbol + import nodes but no reference edges.
|
|
7
|
+
*
|
|
8
|
+
* Consumers (`graph callers`, `code_find_usages`) use this to tell an agent
|
|
9
|
+
* that an EMPTY caller list for a symbol in one of these files means
|
|
10
|
+
* "not tracked", not "nothing calls it" — so it doesn't read silence as proof.
|
|
11
|
+
*/
|
|
12
|
+
const NON_CALL_GRAPH_LANGUAGES = new Set([
|
|
13
|
+
'python',
|
|
14
|
+
'go',
|
|
15
|
+
'java',
|
|
16
|
+
'rust',
|
|
17
|
+
'kotlin',
|
|
18
|
+
'ruby',
|
|
19
|
+
'csharp',
|
|
20
|
+
'elixir',
|
|
21
|
+
'php',
|
|
22
|
+
'dart',
|
|
23
|
+
'swift',
|
|
24
|
+
'vue',
|
|
25
|
+
'svelte',
|
|
26
|
+
'astro',
|
|
27
|
+
'graphql',
|
|
28
|
+
]);
|
|
29
|
+
/** True when call/reference edges are extracted for this file language. */
|
|
30
|
+
export function hasCallGraphReferences(language) {
|
|
31
|
+
return !language || !NON_CALL_GRAPH_LANGUAGES.has(language);
|
|
32
|
+
}
|
|
@@ -22,6 +22,24 @@ export interface IExtractedFile {
|
|
|
22
22
|
* symbols and emits `references-symbol` / `calls-symbol` edges.
|
|
23
23
|
*/
|
|
24
24
|
identifierReferences: readonly IIdentifierReference[];
|
|
25
|
+
/**
|
|
26
|
+
* `class C extends B` / `class C implements I` / `interface I extends J`
|
|
27
|
+
* heritage relationships. The post-pass resolves each base name (like a
|
|
28
|
+
* reference) and emits a typed `extends-symbol` / `implements-symbol` edge
|
|
29
|
+
* FROM the subject symbol TO the base — richer than the generic reference
|
|
30
|
+
* edge the base identifier also produces, so "who implements I" / "is C a
|
|
31
|
+
* subtype of B" are first-class graph queries. Optional: only the TS/TSX/JS
|
|
32
|
+
* extractor populates it — the per-language extractors omit it.
|
|
33
|
+
*/
|
|
34
|
+
heritageReferences?: readonly IHeritageReference[];
|
|
35
|
+
}
|
|
36
|
+
export interface IHeritageReference {
|
|
37
|
+
/** The declaring class/interface name (resolves to `symbol:<path>#<subject>`). */
|
|
38
|
+
subjectName: string;
|
|
39
|
+
/** The referenced base type name (resolved like an identifier reference). */
|
|
40
|
+
baseName: string;
|
|
41
|
+
kind: 'extends' | 'implements';
|
|
42
|
+
line: number;
|
|
25
43
|
}
|
|
26
44
|
export interface IRawImportSpecifier {
|
|
27
45
|
specifier: string;
|
|
@@ -37,6 +55,14 @@ export interface IImportBinding {
|
|
|
37
55
|
specifier: string;
|
|
38
56
|
isDefault: boolean;
|
|
39
57
|
line: number;
|
|
58
|
+
/**
|
|
59
|
+
* `import type { X }` / `import { type X }` — a type-only binding. These are
|
|
60
|
+
* NOT used to resolve value references (a type-only import is not a runtime
|
|
61
|
+
* use), but ARE used to resolve heritage clauses (`implements X` where `X` is
|
|
62
|
+
* an interface, near-always imported `import type`). Without this an
|
|
63
|
+
* interface implemented across files would never connect to its implementers.
|
|
64
|
+
*/
|
|
65
|
+
isTypeOnly?: boolean;
|
|
40
66
|
}
|
|
41
67
|
export interface IIdentifierReference {
|
|
42
68
|
/** Identifier text at the use site. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extract-ts-file.d.ts","sourceRoot":"","sources":["../../src/indexer/extract-ts-file.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG/C,eAAO,MAAM,sBAAsB,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"extract-ts-file.d.ts","sourceRoot":"","sources":["../../src/indexer/extract-ts-file.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG/C,eAAO,MAAM,sBAAsB,uBAAuB,CAAC;AAqB3D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,KAAK,CAAC;IAChB,WAAW,EAAE,SAAS,KAAK,EAAE,CAAC;IAC9B,KAAK,EAAE,SAAS,KAAK,EAAE,CAAC;IACxB,uEAAuE;IACvE,mBAAmB,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACpD;;;;;;OAMG;IACH,cAAc,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C;;;;OAIG;IACH,oBAAoB,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACtD;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACpD;AAED,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,YAAY,CAAC;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,gBAAgB,EAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACf,cAAc,CA+EhB;AAgQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,cAAc,CAAC;IAC1B,4DAA4D;IAC5D,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACtD,iDAAiD;IACjD,uBAAuB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACjE,yEAAyE;IACzE,0BAA0B,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzD,GAAG,SAAS,KAAK,EAAE,CAkDnB"}
|
|
@@ -7,11 +7,18 @@ import { EdgeKind } from "../schema/edge-kind.js";
|
|
|
7
7
|
import { NodeKind } from "../schema/node-kind.js";
|
|
8
8
|
export const EXTRACT_TS_FILE_SOURCE = 'extract-ts-file@v1';
|
|
9
9
|
/**
|
|
10
|
-
* Per-file import scan
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* Per-file import scan for NON-TS single-file-component formats
|
|
11
|
+
* (Vue/Svelte/Astro `<script>` blocks) where no TypeScript AST is
|
|
12
|
+
* available.
|
|
13
|
+
*
|
|
14
|
+
* TS/TSX/JS/JSX files derive their import specifiers from the AST in
|
|
15
|
+
* `walkAst` instead — that is comment- and string-literal-safe, so an
|
|
16
|
+
* `import ... from '...'` that appears inside a comment, JSDoc block, a
|
|
17
|
+
* template-literal body (e.g. a generator template), rule documentation,
|
|
18
|
+
* or a test fixture string is never mistaken for a real import (the
|
|
19
|
+
* regexes below would, and that produced phantom `unresolved:` edges and
|
|
20
|
+
* phantom arch violations). The regexes still mirror
|
|
21
|
+
* `boundaries/scan-imports.ts` for the SFC formats that need them.
|
|
15
22
|
*/
|
|
16
23
|
const IMPORT_RE = /(?:^|\s)(?:import|export)\s+[^'"`]*?from\s+['"]([^'"`]+)['"]/g;
|
|
17
24
|
const SIDE_EFFECT_IMPORT_RE = /(?:^|\s)import\s+['"]([^'"`]+)['"]/g;
|
|
@@ -79,8 +86,7 @@ export function extractTsFile(fingerprint, absPath, content) {
|
|
|
79
86
|
line: re.line,
|
|
80
87
|
}));
|
|
81
88
|
}
|
|
82
|
-
const rawImportSpecifiers =
|
|
83
|
-
const { importBindings, identifierReferences } = walkAst(absPath, text);
|
|
89
|
+
const { importBindings, identifierReferences, rawImportSpecifiers, heritageReferences } = walkAst(absPath, text);
|
|
84
90
|
return {
|
|
85
91
|
fileNode,
|
|
86
92
|
symbolNodes,
|
|
@@ -88,6 +94,7 @@ export function extractTsFile(fingerprint, absPath, content) {
|
|
|
88
94
|
rawImportSpecifiers,
|
|
89
95
|
importBindings,
|
|
90
96
|
identifierReferences,
|
|
97
|
+
heritageReferences,
|
|
91
98
|
};
|
|
92
99
|
}
|
|
93
100
|
function pickScriptKind(ext) {
|
|
@@ -116,10 +123,52 @@ function walkAst(absPath, text) {
|
|
|
116
123
|
sf = ts.createSourceFile(absPath, text, ts.ScriptTarget.Latest, true, pickScriptKind(ext));
|
|
117
124
|
}
|
|
118
125
|
catch {
|
|
119
|
-
|
|
126
|
+
// AST unavailable — fall back to the raw-text regex so we don't lose
|
|
127
|
+
// import edges entirely (rare; createSourceFile is very lenient).
|
|
128
|
+
return {
|
|
129
|
+
importBindings: [],
|
|
130
|
+
identifierReferences: [],
|
|
131
|
+
rawImportSpecifiers: scanFileImports(text),
|
|
132
|
+
heritageReferences: [],
|
|
133
|
+
};
|
|
120
134
|
}
|
|
121
135
|
const bindings = [];
|
|
122
136
|
const refs = [];
|
|
137
|
+
const rawSpecs = [];
|
|
138
|
+
const heritage = [];
|
|
139
|
+
// AST-sourced import specifiers (comment- and string-literal-safe). Every
|
|
140
|
+
// real import shape the regex caught must be reproduced here or a genuine
|
|
141
|
+
// unresolved/cross-package import would stop being flagged: static + side
|
|
142
|
+
// effect + type-only imports, `export ... from`, `import x = require()`,
|
|
143
|
+
// and dynamic `import()` / `require()` calls (the last two collected in
|
|
144
|
+
// the visitor below).
|
|
145
|
+
for (const stmt of sf.statements) {
|
|
146
|
+
if (ts.isImportDeclaration(stmt) && ts.isStringLiteral(stmt.moduleSpecifier)) {
|
|
147
|
+
rawSpecs.push({
|
|
148
|
+
specifier: stmt.moduleSpecifier.text,
|
|
149
|
+
line: lineOf(sf, stmt),
|
|
150
|
+
kind: stmt.importClause ? 'static' : 'side-effect',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else if (ts.isExportDeclaration(stmt) &&
|
|
154
|
+
stmt.moduleSpecifier &&
|
|
155
|
+
ts.isStringLiteral(stmt.moduleSpecifier)) {
|
|
156
|
+
rawSpecs.push({
|
|
157
|
+
specifier: stmt.moduleSpecifier.text,
|
|
158
|
+
line: lineOf(sf, stmt),
|
|
159
|
+
kind: 'static',
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
else if (ts.isImportEqualsDeclaration(stmt) &&
|
|
163
|
+
ts.isExternalModuleReference(stmt.moduleReference) &&
|
|
164
|
+
ts.isStringLiteral(stmt.moduleReference.expression)) {
|
|
165
|
+
rawSpecs.push({
|
|
166
|
+
specifier: stmt.moduleReference.expression.text,
|
|
167
|
+
line: lineOf(sf, stmt),
|
|
168
|
+
kind: 'require',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
123
172
|
for (const stmt of sf.statements) {
|
|
124
173
|
if (!ts.isImportDeclaration(stmt))
|
|
125
174
|
continue;
|
|
@@ -129,8 +178,9 @@ function walkAst(absPath, text) {
|
|
|
129
178
|
const clause = stmt.importClause;
|
|
130
179
|
if (!clause)
|
|
131
180
|
continue;
|
|
132
|
-
|
|
133
|
-
|
|
181
|
+
// Type-only imports are TAGGED (not skipped) so heritage resolution can use
|
|
182
|
+
// them; the value-reference pass still ignores them (see stitchPerFileReferences).
|
|
183
|
+
const clauseTypeOnly = clause.isTypeOnly;
|
|
134
184
|
if (clause.name) {
|
|
135
185
|
bindings.push({
|
|
136
186
|
localName: clause.name.text,
|
|
@@ -138,18 +188,19 @@ function walkAst(absPath, text) {
|
|
|
138
188
|
specifier,
|
|
139
189
|
isDefault: true,
|
|
140
190
|
line: lineOf(sf, clause.name),
|
|
191
|
+
...(clauseTypeOnly ? { isTypeOnly: true } : {}),
|
|
141
192
|
});
|
|
142
193
|
}
|
|
143
194
|
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
144
195
|
for (const elem of clause.namedBindings.elements) {
|
|
145
|
-
|
|
146
|
-
continue;
|
|
196
|
+
const typeOnly = clauseTypeOnly || elem.isTypeOnly;
|
|
147
197
|
bindings.push({
|
|
148
198
|
localName: elem.name.text,
|
|
149
199
|
importedName: elem.propertyName ? elem.propertyName.text : elem.name.text,
|
|
150
200
|
specifier,
|
|
151
201
|
isDefault: false,
|
|
152
202
|
line: lineOf(sf, elem),
|
|
203
|
+
...(typeOnly ? { isTypeOnly: true } : {}),
|
|
153
204
|
});
|
|
154
205
|
}
|
|
155
206
|
}
|
|
@@ -159,6 +210,36 @@ function walkAst(absPath, text) {
|
|
|
159
210
|
// Skip the declaration sites we already harvested.
|
|
160
211
|
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node))
|
|
161
212
|
return;
|
|
213
|
+
// Dynamic `import('...')` and `require('...')` calls — real call nodes,
|
|
214
|
+
// so text inside comments/strings never matches.
|
|
215
|
+
if (ts.isCallExpression(node)) {
|
|
216
|
+
const arg0 = node.arguments[0];
|
|
217
|
+
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
218
|
+
if (arg0 && ts.isStringLiteral(arg0)) {
|
|
219
|
+
rawSpecs.push({ specifier: arg0.text, line: lineOf(sf, node), kind: 'dynamic' });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
|
|
223
|
+
if (arg0 && ts.isStringLiteral(arg0)) {
|
|
224
|
+
rawSpecs.push({ specifier: arg0.text, line: lineOf(sf, node), kind: 'require' });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// `class C extends B implements I` / `interface I extends J` — record the
|
|
229
|
+
// typed heritage relationship. The base identifier is ALSO captured as a
|
|
230
|
+
// generic reference below (kept, so reference-based queries still see it);
|
|
231
|
+
// the post-pass adds the richer extends/implements edge on top.
|
|
232
|
+
if ((ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && node.name && node.heritageClauses) {
|
|
233
|
+
const subjectName = node.name.text;
|
|
234
|
+
for (const clause of node.heritageClauses) {
|
|
235
|
+
const hkind = clause.token === ts.SyntaxKind.ExtendsKeyword ? 'extends' : 'implements';
|
|
236
|
+
for (const t of clause.types) {
|
|
237
|
+
const baseName = headIdentifierName(t.expression);
|
|
238
|
+
if (baseName)
|
|
239
|
+
heritage.push({ subjectName, baseName, kind: hkind, line: lineOf(sf, t.expression) });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
162
243
|
if (ts.isIdentifier(node) && !isDeclarationName(node)) {
|
|
163
244
|
refs.push({
|
|
164
245
|
name: node.text,
|
|
@@ -180,7 +261,46 @@ function walkAst(absPath, text) {
|
|
|
180
261
|
seen.add(k);
|
|
181
262
|
dedupedRefs.push(r);
|
|
182
263
|
}
|
|
183
|
-
|
|
264
|
+
// De-dupe identical (kind, specifier, line) — same contract scanFileImports
|
|
265
|
+
// applied to the regex output.
|
|
266
|
+
const seenSpecs = new Set();
|
|
267
|
+
const dedupedSpecs = [];
|
|
268
|
+
for (const s of rawSpecs) {
|
|
269
|
+
const k = `${s.kind}|${s.specifier}|${s.line}`;
|
|
270
|
+
if (seenSpecs.has(k))
|
|
271
|
+
continue;
|
|
272
|
+
seenSpecs.add(k);
|
|
273
|
+
dedupedSpecs.push(s);
|
|
274
|
+
}
|
|
275
|
+
// De-dupe identical (subject, base, kind) heritage triples.
|
|
276
|
+
const seenHeritage = new Set();
|
|
277
|
+
const dedupedHeritage = [];
|
|
278
|
+
for (const h of heritage) {
|
|
279
|
+
const k = `${h.subjectName}|${h.baseName}|${h.kind}`;
|
|
280
|
+
if (seenHeritage.has(k))
|
|
281
|
+
continue;
|
|
282
|
+
seenHeritage.add(k);
|
|
283
|
+
dedupedHeritage.push(h);
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
importBindings: bindings,
|
|
287
|
+
identifierReferences: dedupedRefs,
|
|
288
|
+
rawImportSpecifiers: dedupedSpecs,
|
|
289
|
+
heritageReferences: dedupedHeritage,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* The leftmost identifier name of a heritage base expression: `Base` for
|
|
294
|
+
* `Base`, `Base<T>` (already unwrapped — `t.expression` is the head), and the
|
|
295
|
+
* leftmost for a qualified `ns.Base` (returns `ns`, matching how the generic
|
|
296
|
+
* reference walk resolves `ns` — namespace-qualified bases stay best-effort).
|
|
297
|
+
*/
|
|
298
|
+
function headIdentifierName(expr) {
|
|
299
|
+
if (ts.isIdentifier(expr))
|
|
300
|
+
return expr.text;
|
|
301
|
+
if (ts.isPropertyAccessExpression(expr))
|
|
302
|
+
return headIdentifierName(expr.expression);
|
|
303
|
+
return undefined;
|
|
184
304
|
}
|
|
185
305
|
function isDeclarationName(id) {
|
|
186
306
|
const parent = id.parent;
|
|
@@ -247,18 +367,21 @@ function lineOf(sf, node) {
|
|
|
247
367
|
*/
|
|
248
368
|
export function stitchPerFileReferences(input) {
|
|
249
369
|
const { fileNodeId, extracted, resolvedSpec, defaultExportNameByPath, localSymbolNamesInThisFile } = input;
|
|
370
|
+
// `bindings` resolves VALUE references (type-only imports excluded — a type
|
|
371
|
+
// import is not a runtime use). `heritageBindings` additionally includes
|
|
372
|
+
// type-only imports, used only for `implements`/`extends` resolution.
|
|
250
373
|
const bindings = new Map();
|
|
374
|
+
const heritageBindings = new Map();
|
|
251
375
|
for (const b of extracted.importBindings) {
|
|
252
376
|
const targetPath = resolvedSpec.get(b.specifier);
|
|
253
377
|
if (!targetPath)
|
|
254
378
|
continue;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
bindings.set(b.localName,
|
|
261
|
-
}
|
|
379
|
+
const id = b.isDefault
|
|
380
|
+
? `symbol:${targetPath}#${defaultExportNameByPath.get(targetPath) ?? 'default'}`
|
|
381
|
+
: `symbol:${targetPath}#${b.importedName}`;
|
|
382
|
+
heritageBindings.set(b.localName, id);
|
|
383
|
+
if (!b.isTypeOnly)
|
|
384
|
+
bindings.set(b.localName, id);
|
|
262
385
|
}
|
|
263
386
|
const out = [];
|
|
264
387
|
const seen = new Set();
|
|
@@ -281,6 +404,25 @@ export function stitchPerFileReferences(input) {
|
|
|
281
404
|
seen.add(edgeKey);
|
|
282
405
|
out.push(buildEdge(fileNodeId, target, kind, { line: r.line }));
|
|
283
406
|
}
|
|
407
|
+
// Typed heritage edges, FROM the subject symbol TO the resolved base symbol.
|
|
408
|
+
// Resolved like a reference (import binding or local symbol); a base in an
|
|
409
|
+
// external lib (`extends Error`) resolves to nothing and is skipped, so only
|
|
410
|
+
// intra-project subtype relationships become edges.
|
|
411
|
+
const filePath = fileNodeId.replace(/^file:/, '');
|
|
412
|
+
for (const h of extracted.heritageReferences ?? []) {
|
|
413
|
+
const target = heritageBindings.get(h.baseName) ?? localSymbolNamesInThisFile.get(h.baseName);
|
|
414
|
+
if (!target)
|
|
415
|
+
continue;
|
|
416
|
+
const subjectId = `symbol:${filePath}#${h.subjectName}`;
|
|
417
|
+
if (subjectId === target)
|
|
418
|
+
continue; // ignore self-reference
|
|
419
|
+
const kind = h.kind === 'extends' ? EdgeKind.ExtendsSymbol : EdgeKind.ImplementsSymbol;
|
|
420
|
+
const edgeKey = `${subjectId}|${target}|${kind}`;
|
|
421
|
+
if (seen.has(edgeKey))
|
|
422
|
+
continue;
|
|
423
|
+
seen.add(edgeKey);
|
|
424
|
+
out.push(buildEdge(subjectId, target, kind, { line: h.line }));
|
|
425
|
+
}
|
|
284
426
|
return out;
|
|
285
427
|
}
|
|
286
428
|
function makeFileNodeForNonTs(fp) {
|
|
@@ -24,10 +24,28 @@ export interface IIncrementalUpdateResult {
|
|
|
24
24
|
* append/compact is the optimisation when the cold-rewrite cost is felt.
|
|
25
25
|
*/
|
|
26
26
|
export declare function updateChanged(options: IIncrementalUpdateOptions): IIncrementalUpdateResult;
|
|
27
|
+
export interface IGraphFreshness {
|
|
28
|
+
hasIndex: boolean;
|
|
29
|
+
lastIndexedAt?: string;
|
|
30
|
+
/** Indexed files whose on-disk content changed since the index was built. */
|
|
31
|
+
modified: readonly string[];
|
|
32
|
+
/** Source files on disk that are not in the index yet. */
|
|
33
|
+
added: readonly string[];
|
|
34
|
+
/** Indexed files that no longer exist on disk. */
|
|
35
|
+
deleted: readonly string[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Walk the project and categorise every source file against the stored
|
|
39
|
+
* snapshot: `modified` (indexed but content changed), `added` (on disk, not
|
|
40
|
+
* indexed), `deleted` (indexed, gone from disk). This is the per-file truth
|
|
41
|
+
* behind honest `graph status` freshness and the targeted per-query staleness
|
|
42
|
+
* check — so an agent never gets a silently-stale answer for a file it just
|
|
43
|
+
* edited. Outputs are sorted for determinism.
|
|
44
|
+
*/
|
|
45
|
+
export declare function detectGraphFreshness(projectRoot: string): IGraphFreshness;
|
|
27
46
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* `shrk graph index --changed` when no explicit file list is provided.
|
|
47
|
+
* Back-compat adapter for `shrk graph index --changed`: `changed` is the
|
|
48
|
+
* union of modified + added (both need re-extraction); `deleted` unchanged.
|
|
31
49
|
*/
|
|
32
50
|
export declare function detectChangedAndDeleted(projectRoot: string): {
|
|
33
51
|
changed: readonly string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"incremental-updater.d.ts","sourceRoot":"","sources":["../../src/indexer/incremental-updater.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"incremental-updater.d.ts","sourceRoot":"","sources":["../../src/indexer/incremental-updater.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA0C5D,MAAM,WAAW,yBAAyB;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,2CAA2C;IAC3C,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,oCAAoC;IACpC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,qEAAqE;IACrE,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,yBAAyB,GACjC,wBAAwB,CAiN1B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,0DAA0D;IAC1D,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,kDAAkD;IAClD,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CA+EzE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG;IAC5D,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,CAMA;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAcrF"}
|
|
@@ -8,6 +8,7 @@ import { fingerprintFile } from "../store/file-fingerprint.js";
|
|
|
8
8
|
import { GraphStore } from "../store/graph-store.js";
|
|
9
9
|
import { summarizeCycles } from "../query/cycle-detection.js";
|
|
10
10
|
import { summarizeUnresolvedImports } from "./unresolved-imports.js";
|
|
11
|
+
import { resolveReExportedReferenceEdges } from "./resolve-reexports.js";
|
|
11
12
|
import { detectWorkspacePackages, } from "./detect-workspace.js";
|
|
12
13
|
import { EXTRACT_TS_FILE_SOURCE, extractTsFile, stitchPerFileReferences, } from "./extract-ts-file.js";
|
|
13
14
|
import { extractPythonFile } from "./extract-python-file.js";
|
|
@@ -179,7 +180,17 @@ export function updateChanged(options) {
|
|
|
179
180
|
edges.set(e.id, e);
|
|
180
181
|
}
|
|
181
182
|
const nodeList = [...nodes.values()];
|
|
182
|
-
|
|
183
|
+
// Resolve barrel re-export chains (rewrites phantom cross-package
|
|
184
|
+
// reference/call targets to the real symbol), then de-dupe since a rewrite
|
|
185
|
+
// can collide a rewritten edge id with an existing one.
|
|
186
|
+
const seenEdge = new Set();
|
|
187
|
+
const edgeList = [];
|
|
188
|
+
for (const e of resolveReExportedReferenceEdges(nodeList, [...edges.values()])) {
|
|
189
|
+
if (seenEdge.has(e.id))
|
|
190
|
+
continue;
|
|
191
|
+
seenEdge.add(e.id);
|
|
192
|
+
edgeList.push(e);
|
|
193
|
+
}
|
|
183
194
|
const cycles = summarizeCycles(nodeList, edgeList);
|
|
184
195
|
const unresolved = summarizeUnresolvedImports(edgeList);
|
|
185
196
|
const manifest = store.writeSnapshot(nodeList, edgeList, [...files.values()], {
|
|
@@ -206,18 +217,22 @@ export function updateChanged(options) {
|
|
|
206
217
|
};
|
|
207
218
|
}
|
|
208
219
|
/**
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* `
|
|
220
|
+
* Walk the project and categorise every source file against the stored
|
|
221
|
+
* snapshot: `modified` (indexed but content changed), `added` (on disk, not
|
|
222
|
+
* indexed), `deleted` (indexed, gone from disk). This is the per-file truth
|
|
223
|
+
* behind honest `graph status` freshness and the targeted per-query staleness
|
|
224
|
+
* check — so an agent never gets a silently-stale answer for a file it just
|
|
225
|
+
* edited. Outputs are sorted for determinism.
|
|
212
226
|
*/
|
|
213
|
-
export function
|
|
227
|
+
export function detectGraphFreshness(projectRoot) {
|
|
214
228
|
const store = new GraphStore(projectRoot);
|
|
215
229
|
if (!store.exists()) {
|
|
216
|
-
return {
|
|
230
|
+
return { hasIndex: false, modified: [], added: [], deleted: [] };
|
|
217
231
|
}
|
|
218
232
|
const snap = store.loadSnapshot();
|
|
219
233
|
const seen = new Set();
|
|
220
|
-
const
|
|
234
|
+
const modified = [];
|
|
235
|
+
const added = [];
|
|
221
236
|
const fsStack = [projectRoot];
|
|
222
237
|
const skip = new Set([
|
|
223
238
|
'node_modules',
|
|
@@ -269,7 +284,7 @@ export function detectChangedAndDeleted(projectRoot) {
|
|
|
269
284
|
seen.add(rel);
|
|
270
285
|
const oldFp = snap.files.get(rel);
|
|
271
286
|
if (!oldFp) {
|
|
272
|
-
|
|
287
|
+
added.push(rel);
|
|
273
288
|
continue;
|
|
274
289
|
}
|
|
275
290
|
// Cheap check: mtime first. If equal, trust it (assumes mtime
|
|
@@ -279,7 +294,7 @@ export function detectChangedAndDeleted(projectRoot) {
|
|
|
279
294
|
continue;
|
|
280
295
|
const newFp = fingerprintFile(full, projectRoot);
|
|
281
296
|
if (newFp.sha1 !== oldFp.sha1)
|
|
282
|
-
|
|
297
|
+
modified.push(rel);
|
|
283
298
|
}
|
|
284
299
|
}
|
|
285
300
|
const deleted = [];
|
|
@@ -287,7 +302,27 @@ export function detectChangedAndDeleted(projectRoot) {
|
|
|
287
302
|
if (!seen.has(path))
|
|
288
303
|
deleted.push(path);
|
|
289
304
|
}
|
|
290
|
-
|
|
305
|
+
modified.sort((a, b) => a.localeCompare(b));
|
|
306
|
+
added.sort((a, b) => a.localeCompare(b));
|
|
307
|
+
deleted.sort((a, b) => a.localeCompare(b));
|
|
308
|
+
return {
|
|
309
|
+
hasIndex: true,
|
|
310
|
+
lastIndexedAt: snap.manifest.lastIndexedAt,
|
|
311
|
+
modified,
|
|
312
|
+
added,
|
|
313
|
+
deleted,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Back-compat adapter for `shrk graph index --changed`: `changed` is the
|
|
318
|
+
* union of modified + added (both need re-extraction); `deleted` unchanged.
|
|
319
|
+
*/
|
|
320
|
+
export function detectChangedAndDeleted(projectRoot) {
|
|
321
|
+
const f = detectGraphFreshness(projectRoot);
|
|
322
|
+
return {
|
|
323
|
+
changed: [...f.modified, ...f.added].sort((a, b) => a.localeCompare(b)),
|
|
324
|
+
deleted: f.deleted,
|
|
325
|
+
};
|
|
291
326
|
}
|
|
292
327
|
/**
|
|
293
328
|
* Get the list of files changed since a git ref (e.g. `main`, `HEAD~5`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index-builder.d.ts","sourceRoot":"","sources":["../../src/indexer/index-builder.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index-builder.d.ts","sourceRoot":"","sources":["../../src/indexer/index-builder.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA8D5D,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,oBAAoB,GAAG,gBAAgB,CAkJ9E"}
|
|
@@ -21,6 +21,7 @@ import { extractPhpFile } from "./extract-php-file.js";
|
|
|
21
21
|
import { extractDartFile } from "./extract-dart-file.js";
|
|
22
22
|
import { extractSwiftFile } from "./extract-swift-file.js";
|
|
23
23
|
import { createImportResolverContext, ImportResolution, resolveImport, } from "./resolve-imports.js";
|
|
24
|
+
import { resolveReExportedReferenceEdges } from "./resolve-reexports.js";
|
|
24
25
|
const SOURCE_EXTS = new Set([
|
|
25
26
|
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts',
|
|
26
27
|
// Web component formats — parsed by framework-scanners; the TS-AST
|
|
@@ -122,7 +123,9 @@ export function buildFullIndex(options) {
|
|
|
122
123
|
else {
|
|
123
124
|
const externalId = r.kind === ImportResolution.External
|
|
124
125
|
? `external:${r.specifier}`
|
|
125
|
-
:
|
|
126
|
+
: r.kind === ImportResolution.Asset
|
|
127
|
+
? `asset:${r.specifier}`
|
|
128
|
+
: `unresolved:${r.specifier}`;
|
|
126
129
|
edges.push(buildEdge(fp.nodeId, externalId, EdgeKind.ImportsFile, EXTRACT_TS_FILE_SOURCE, data));
|
|
127
130
|
}
|
|
128
131
|
}
|
|
@@ -144,15 +147,20 @@ export function buildFullIndex(options) {
|
|
|
144
147
|
for (const e of refEdges)
|
|
145
148
|
edges.push(e);
|
|
146
149
|
}
|
|
150
|
+
// Resolve barrel re-export chains so reference/call edges that point at a
|
|
151
|
+
// phantom `symbol:<barrel>#name` are rewritten to the real declaring
|
|
152
|
+
// symbol — otherwise cross-package consumers (which import from a package
|
|
153
|
+
// barrel) never show up in `graph callers` / impact.
|
|
154
|
+
const resolvedEdges = resolveReExportedReferenceEdges(nodes, edges);
|
|
147
155
|
// PackageDependsOn aggregates: collapse internal ImportsFile edges to
|
|
148
156
|
// their owning package on both sides.
|
|
149
|
-
collectPackageDependsOn(
|
|
157
|
+
collectPackageDependsOn(resolvedEdges, packageDirIndex);
|
|
150
158
|
// Drop duplicate edges (extractor may emit identical edges for `export
|
|
151
159
|
// { foo } from './foo'` and an `import` re-using the same line — same
|
|
152
|
-
// hashed id, last write wins
|
|
160
|
+
// hashed id, last write wins; a re-export rewrite can also collide ids).
|
|
153
161
|
const seen = new Set();
|
|
154
162
|
const dedupedEdges = [];
|
|
155
|
-
for (const e of
|
|
163
|
+
for (const e of resolvedEdges) {
|
|
156
164
|
if (seen.has(e.id))
|
|
157
165
|
continue;
|
|
158
166
|
seen.add(e.id);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-imports.d.ts","sourceRoot":"","sources":["../../src/indexer/resolve-imports.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"resolve-imports.d.ts","sourceRoot":"","sources":["../../src/indexer/resolve-imports.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAmB/D,oBAAY,gBAAgB;IAC1B,QAAQ,aAAa;IACrB,KAAK,UAAU;IACf,SAAS,cAAc;IACvB,QAAQ,aAAa;IACrB,KAAK,UAAU;IACf,UAAU,eAAe;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAChD,aAAa,EAAE,iBAAiB,CAAC;CAClC;AAED,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,SAAS,iBAAiB,EAAE,GAC9C,sBAAsB,CAMxB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,sBAAsB,GAC1B,eAAe,CAkDjB"}
|
|
@@ -2,12 +2,27 @@ import { existsSync, statSync } from 'node:fs';
|
|
|
2
2
|
import * as nodePath from 'node:path';
|
|
3
3
|
import { loadTsconfigPaths, resolveAliasCandidates, } from '@shrkcrft/boundaries';
|
|
4
4
|
const PROBE_EXTS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'];
|
|
5
|
+
/**
|
|
6
|
+
* Extensions for non-code assets a TS/JS file can legitimately import
|
|
7
|
+
* (CSS modules, JSON, images, fonts, wasm). The graph only indexes source
|
|
8
|
+
* files, so these never resolve to a file node — but an asset that EXISTS
|
|
9
|
+
* on disk is NOT an unresolved import (the bundler handles it), whereas a
|
|
10
|
+
* missing one still is a real broken reference.
|
|
11
|
+
*/
|
|
12
|
+
const NON_CODE_ASSET_EXTS = new Set([
|
|
13
|
+
'.css', '.scss', '.sass', '.less', '.styl',
|
|
14
|
+
'.json', '.json5',
|
|
15
|
+
'.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.ico', '.bmp',
|
|
16
|
+
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
17
|
+
'.wasm',
|
|
18
|
+
]);
|
|
5
19
|
export var ImportResolution;
|
|
6
20
|
(function (ImportResolution) {
|
|
7
21
|
ImportResolution["Relative"] = "relative";
|
|
8
22
|
ImportResolution["Alias"] = "alias";
|
|
9
23
|
ImportResolution["Workspace"] = "workspace";
|
|
10
24
|
ImportResolution["External"] = "external";
|
|
25
|
+
ImportResolution["Asset"] = "asset";
|
|
11
26
|
ImportResolution["Unresolved"] = "unresolved";
|
|
12
27
|
})(ImportResolution || (ImportResolution = {}));
|
|
13
28
|
export function createImportResolverContext(projectRoot, workspacePackages) {
|
|
@@ -32,7 +47,17 @@ export function createImportResolverContext(projectRoot, workspacePackages) {
|
|
|
32
47
|
export function resolveImport(specifier, fromAbsPath, ctx) {
|
|
33
48
|
if (specifier.startsWith('.')) {
|
|
34
49
|
const dir = nodePath.dirname(fromAbsPath);
|
|
35
|
-
const
|
|
50
|
+
const abs = nodePath.resolve(dir, specifier);
|
|
51
|
+
const ext = nodePath.extname(specifier).toLowerCase();
|
|
52
|
+
if (NON_CODE_ASSET_EXTS.has(ext)) {
|
|
53
|
+
// Existing asset → resolved-enough (not counted as unresolved); a
|
|
54
|
+
// missing asset stays Unresolved so a real broken reference is caught.
|
|
55
|
+
if (existsSafe(abs) && isFile(abs)) {
|
|
56
|
+
return { specifier, kind: ImportResolution.Asset };
|
|
57
|
+
}
|
|
58
|
+
return { specifier, kind: ImportResolution.Unresolved };
|
|
59
|
+
}
|
|
60
|
+
const probe = probeCandidate(abs);
|
|
36
61
|
if (probe) {
|
|
37
62
|
return {
|
|
38
63
|
specifier,
|