gitnexus 1.6.4-rc.3 → 1.6.4-rc.5
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/_shared/scope-resolution/types.d.ts +10 -2
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/types.js +10 -2
- package/dist/_shared/scope-resolution/types.js.map +1 -1
- package/dist/core/ingestion/call-processor.js +2 -2
- package/dist/core/ingestion/constants.d.ts +4 -3
- package/dist/core/ingestion/constants.js +8 -3
- package/dist/core/ingestion/finalize-orchestrator.js +5 -0
- package/dist/core/ingestion/heritage-processor.js +2 -2
- package/dist/core/ingestion/import-processor.js +1 -1
- package/dist/core/ingestion/languages/csharp/captures.js +4 -1
- package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +14 -13
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +62 -50
- package/dist/core/ingestion/languages/python/captures.js +4 -1
- package/dist/core/ingestion/languages/typescript/captures.js +4 -1
- package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +14 -1
- package/dist/core/ingestion/parsing-processor.js +3 -3
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +39 -8
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +39 -8
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +9 -4
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +3 -2
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +10 -0
- package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.d.ts +39 -0
- package/dist/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.js +65 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +54 -11
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +105 -30
- package/dist/core/ingestion/utils/env.d.ts +10 -0
- package/dist/core/ingestion/utils/env.js +14 -0
- package/dist/core/ingestion/workers/parse-worker.js +3 -3
- package/package.json +1 -1
|
@@ -10,8 +10,16 @@
|
|
|
10
10
|
* Lifecycle contract (RFC §2.8): scopes are **constructed during extraction,
|
|
11
11
|
* linked during finalize, immutable after finalize**. All fields are
|
|
12
12
|
* `readonly` at the type level; `Object.freeze` is applied at runtime in dev
|
|
13
|
-
* builds.
|
|
14
|
-
*
|
|
13
|
+
* builds.
|
|
14
|
+
*
|
|
15
|
+
* Two structures are populated after freeze:
|
|
16
|
+
* 1. `ReferenceIndex` — by resolution, before emission.
|
|
17
|
+
* 2. `ScopeResolutionIndexes.bindingAugmentations` — the dedicated
|
|
18
|
+
* append-only post-finalize binding channel (e.g. C# same-namespace
|
|
19
|
+
* cross-file fanout). The companion `indexes.bindings` is the
|
|
20
|
+
* finalize-output channel and is deep-frozen by `materializeBindings`;
|
|
21
|
+
* walkers consult both via `lookupBindingsAt`. See `ScopeResolver`
|
|
22
|
+
* Invariant I8 for the full lifecycle contract.
|
|
15
23
|
*/
|
|
16
24
|
import type { NodeLabel } from '../graph/types.js';
|
|
17
25
|
import type { SymbolDefinition } from './symbol-definition.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,8 +10,16 @@
|
|
|
10
10
|
* Lifecycle contract (RFC §2.8): scopes are **constructed during extraction,
|
|
11
11
|
* linked during finalize, immutable after finalize**. All fields are
|
|
12
12
|
* `readonly` at the type level; `Object.freeze` is applied at runtime in dev
|
|
13
|
-
* builds.
|
|
14
|
-
*
|
|
13
|
+
* builds.
|
|
14
|
+
*
|
|
15
|
+
* Two structures are populated after freeze:
|
|
16
|
+
* 1. `ReferenceIndex` — by resolution, before emission.
|
|
17
|
+
* 2. `ScopeResolutionIndexes.bindingAugmentations` — the dedicated
|
|
18
|
+
* append-only post-finalize binding channel (e.g. C# same-namespace
|
|
19
|
+
* cross-file fanout). The companion `indexes.bindings` is the
|
|
20
|
+
* finalize-output channel and is deep-frozen by `materializeBindings`;
|
|
21
|
+
* walkers consult both via `lookupBindingsAt`. See `ScopeResolver`
|
|
22
|
+
* Invariant I8 for the full lifecycle contract.
|
|
15
23
|
*/
|
|
16
24
|
export {};
|
|
17
25
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/scope-resolution/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/scope-resolution/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG"}
|
|
@@ -613,7 +613,7 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
|
|
|
613
613
|
if (!tree) {
|
|
614
614
|
try {
|
|
615
615
|
tree = parser.parse(file.content, undefined, {
|
|
616
|
-
bufferSize: getTreeSitterBufferSize(file.content
|
|
616
|
+
bufferSize: getTreeSitterBufferSize(file.content),
|
|
617
617
|
});
|
|
618
618
|
}
|
|
619
619
|
catch (parseError) {
|
|
@@ -2591,7 +2591,7 @@ export const extractFetchCallsFromFiles = async (files, astCache) => {
|
|
|
2591
2591
|
if (!tree) {
|
|
2592
2592
|
try {
|
|
2593
2593
|
tree = parser.parse(file.content, undefined, {
|
|
2594
|
-
bufferSize: getTreeSitterBufferSize(file.content
|
|
2594
|
+
bufferSize: getTreeSitterBufferSize(file.content),
|
|
2595
2595
|
});
|
|
2596
2596
|
}
|
|
2597
2597
|
catch {
|
|
@@ -10,7 +10,8 @@ export declare const TREE_SITTER_BUFFER_SIZE: number;
|
|
|
10
10
|
export declare const TREE_SITTER_MAX_BUFFER: number;
|
|
11
11
|
/**
|
|
12
12
|
* Compute adaptive buffer size for tree-sitter parsing.
|
|
13
|
-
* Uses
|
|
14
|
-
*
|
|
13
|
+
* Uses 2x UTF-8 byte size, clamped between 512 KB and 32 MB.
|
|
14
|
+
* Keeps tree-sitter's byte-sized buffer above large ASCII and multibyte sources.
|
|
15
15
|
*/
|
|
16
|
-
export declare const
|
|
16
|
+
export declare const getTreeSitterContentByteLength: (sourceText: string) => number;
|
|
17
|
+
export declare const getTreeSitterBufferSize: (sourceText: string) => number;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
1
2
|
/**
|
|
2
3
|
* Default minimum buffer size for tree-sitter parsing (512 KB).
|
|
3
4
|
* tree-sitter requires bufferSize >= file size in bytes.
|
|
@@ -10,7 +11,11 @@ export const TREE_SITTER_BUFFER_SIZE = 512 * 1024;
|
|
|
10
11
|
export const TREE_SITTER_MAX_BUFFER = 32 * 1024 * 1024;
|
|
11
12
|
/**
|
|
12
13
|
* Compute adaptive buffer size for tree-sitter parsing.
|
|
13
|
-
* Uses
|
|
14
|
-
*
|
|
14
|
+
* Uses 2x UTF-8 byte size, clamped between 512 KB and 32 MB.
|
|
15
|
+
* Keeps tree-sitter's byte-sized buffer above large ASCII and multibyte sources.
|
|
15
16
|
*/
|
|
16
|
-
export const
|
|
17
|
+
export const getTreeSitterContentByteLength = (sourceText) => Buffer.byteLength(sourceText, 'utf8');
|
|
18
|
+
export const getTreeSitterBufferSize = (sourceText) => {
|
|
19
|
+
const byteLength = getTreeSitterContentByteLength(sourceText);
|
|
20
|
+
return Math.min(Math.max(byteLength * 2, TREE_SITTER_BUFFER_SIZE), TREE_SITTER_MAX_BUFFER);
|
|
21
|
+
};
|
|
@@ -91,6 +91,11 @@ export function finalizeScopeModel(parsedFiles, options = {}) {
|
|
|
91
91
|
methodDispatch,
|
|
92
92
|
imports: finalizeOut.imports,
|
|
93
93
|
bindings: finalizeOut.bindings,
|
|
94
|
+
// Empty post-finalize augmentation channel. Populated (if at all)
|
|
95
|
+
// by language hooks like `populateCsharpNamespaceSiblings` running
|
|
96
|
+
// AFTER `finalizeScopeModel` returns, before `resolveReferenceSites`
|
|
97
|
+
// consumes the bundle. Most languages leave it empty.
|
|
98
|
+
bindingAugmentations: new Map(),
|
|
94
99
|
referenceSites: Object.freeze([...allReferenceSites]),
|
|
95
100
|
sccs: finalizeOut.sccs,
|
|
96
101
|
stats: finalizeOut.stats,
|
|
@@ -148,7 +148,7 @@ export const processHeritage = async (graph, files, astCache, ctx, onProgress) =
|
|
|
148
148
|
// Use larger bufferSize for files > 32KB
|
|
149
149
|
try {
|
|
150
150
|
tree = parser.parse(file.content, undefined, {
|
|
151
|
-
bufferSize: getTreeSitterBufferSize(file.content
|
|
151
|
+
bufferSize: getTreeSitterBufferSize(file.content),
|
|
152
152
|
});
|
|
153
153
|
}
|
|
154
154
|
catch (parseError) {
|
|
@@ -295,7 +295,7 @@ export async function extractExtractedHeritageFromFiles(files, astCache) {
|
|
|
295
295
|
if (!tree) {
|
|
296
296
|
try {
|
|
297
297
|
tree = parser.parse(file.content, undefined, {
|
|
298
|
-
bufferSize: getTreeSitterBufferSize(file.content
|
|
298
|
+
bufferSize: getTreeSitterBufferSize(file.content),
|
|
299
299
|
});
|
|
300
300
|
}
|
|
301
301
|
catch {
|
|
@@ -242,7 +242,7 @@ export const processImports = async (graph, files, astCache, ctx, onProgress, re
|
|
|
242
242
|
if (!tree) {
|
|
243
243
|
try {
|
|
244
244
|
tree = parser.parse(file.content, undefined, {
|
|
245
|
-
bufferSize: getTreeSitterBufferSize(file.content
|
|
245
|
+
bufferSize: getTreeSitterBufferSize(file.content),
|
|
246
246
|
});
|
|
247
247
|
}
|
|
248
248
|
catch (parseError) {
|
|
@@ -21,6 +21,7 @@ import { computeCsharpArityMetadata } from './arity-metadata.js';
|
|
|
21
21
|
import { synthesizeCsharpReceiverBinding } from './receiver-binding.js';
|
|
22
22
|
import { getCsharpParser, getCsharpScopeQuery } from './query.js';
|
|
23
23
|
import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
|
|
24
|
+
import { getTreeSitterBufferSize } from '../../constants.js';
|
|
24
25
|
/** Declaration anchors that carry function-like arity metadata. */
|
|
25
26
|
const FUNCTION_DECL_TAGS = [
|
|
26
27
|
'@declaration.method',
|
|
@@ -43,7 +44,9 @@ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
43
44
|
// the LanguageProvider contract layer; cast here at the use site.
|
|
44
45
|
let tree = cachedTree;
|
|
45
46
|
if (tree === undefined) {
|
|
46
|
-
tree = getCsharpParser().parse(sourceText
|
|
47
|
+
tree = getCsharpParser().parse(sourceText, undefined, {
|
|
48
|
+
bufferSize: getTreeSitterBufferSize(sourceText),
|
|
49
|
+
});
|
|
47
50
|
recordCacheMiss();
|
|
48
51
|
}
|
|
49
52
|
else {
|
|
@@ -11,17 +11,18 @@
|
|
|
11
11
|
* field-chain resolution fails at `findClassBindingInScope('User')`
|
|
12
12
|
* in the Service.cs scope chain.
|
|
13
13
|
*
|
|
14
|
-
* Implementation: after the finalize pass populates
|
|
15
|
-
* (from explicit `using` directives), walk each
|
|
16
|
-
* AST for `namespace_declaration` /
|
|
17
|
-
* and `using_directive` nodes.
|
|
18
|
-
* `treeCache` so files already parsed
|
|
19
|
-
* re-used instead of re-parsed —
|
|
20
|
-
* the single source of truth.
|
|
21
|
-
* cross-file sibling classes
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
14
|
+
* Implementation: after the finalize pass populates immutable
|
|
15
|
+
* `indexes.bindings` (from explicit `using` directives), walk each
|
|
16
|
+
* file's tree-sitter AST for `namespace_declaration` /
|
|
17
|
+
* `file_scoped_namespace_declaration` and `using_directive` nodes.
|
|
18
|
+
* The orchestrator hands us its `treeCache` so files already parsed
|
|
19
|
+
* by `extractParsedFile` are re-used instead of re-parsed —
|
|
20
|
+
* `ParsedFile`'s underlying tree is the single source of truth.
|
|
21
|
+
* Group classes by namespace, and append cross-file sibling classes
|
|
22
|
+
* into each Namespace scope's `bindingAugmentations` bucket with
|
|
23
|
+
* `origin: 'namespace'`. Finalized bindings remain first in
|
|
24
|
+
* `lookupBindingsAt`, and local lexical `Scope.bindings` remains the
|
|
25
|
+
* first-tier shadowing channel.
|
|
25
26
|
*
|
|
26
27
|
* The tree-sitter walk is authoritative: it sees `global using static`,
|
|
27
28
|
* aliased `using static X = Y.Z;`, attributed namespace declarations,
|
|
@@ -42,8 +43,8 @@ export interface CsharpSiblingInputs {
|
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
46
|
+
* Append cross-file sibling class defs to each Namespace scope's
|
|
47
|
+
* `bindingAugmentations` bucket. Class-like defs (Class / Interface /
|
|
47
48
|
* Struct / Record / Enum) are visible cross-file; method / field
|
|
48
49
|
* members are not.
|
|
49
50
|
*/
|
|
@@ -11,17 +11,18 @@
|
|
|
11
11
|
* field-chain resolution fails at `findClassBindingInScope('User')`
|
|
12
12
|
* in the Service.cs scope chain.
|
|
13
13
|
*
|
|
14
|
-
* Implementation: after the finalize pass populates
|
|
15
|
-
* (from explicit `using` directives), walk each
|
|
16
|
-
* AST for `namespace_declaration` /
|
|
17
|
-
* and `using_directive` nodes.
|
|
18
|
-
* `treeCache` so files already parsed
|
|
19
|
-
* re-used instead of re-parsed —
|
|
20
|
-
* the single source of truth.
|
|
21
|
-
* cross-file sibling classes
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
14
|
+
* Implementation: after the finalize pass populates immutable
|
|
15
|
+
* `indexes.bindings` (from explicit `using` directives), walk each
|
|
16
|
+
* file's tree-sitter AST for `namespace_declaration` /
|
|
17
|
+
* `file_scoped_namespace_declaration` and `using_directive` nodes.
|
|
18
|
+
* The orchestrator hands us its `treeCache` so files already parsed
|
|
19
|
+
* by `extractParsedFile` are re-used instead of re-parsed —
|
|
20
|
+
* `ParsedFile`'s underlying tree is the single source of truth.
|
|
21
|
+
* Group classes by namespace, and append cross-file sibling classes
|
|
22
|
+
* into each Namespace scope's `bindingAugmentations` bucket with
|
|
23
|
+
* `origin: 'namespace'`. Finalized bindings remain first in
|
|
24
|
+
* `lookupBindingsAt`, and local lexical `Scope.bindings` remains the
|
|
25
|
+
* first-tier shadowing channel.
|
|
25
26
|
*
|
|
26
27
|
* The tree-sitter walk is authoritative: it sees `global using static`,
|
|
27
28
|
* aliased `using static X = Y.Z;`, attributed namespace declarations,
|
|
@@ -30,13 +31,17 @@
|
|
|
30
31
|
* coincidences).
|
|
31
32
|
*/
|
|
32
33
|
import { getCsharpParser } from './query.js';
|
|
34
|
+
import { getTreeSitterBufferSize } from '../../constants.js';
|
|
33
35
|
/** Build a structural view of a C# file by walking the tree-sitter
|
|
34
36
|
* AST. Prefers `cachedTree` (handed in via `treeCache`) so we don't
|
|
35
37
|
* re-parse files the orchestrator already parsed for `extractParsedFile`;
|
|
36
38
|
* falls back to a fresh parse on cache miss. Parser singleton is
|
|
37
39
|
* shared across calls. */
|
|
38
40
|
function extractFileStructure(content, cachedTree) {
|
|
39
|
-
const tree = cachedTree ??
|
|
41
|
+
const tree = cachedTree ??
|
|
42
|
+
getCsharpParser().parse(content, undefined, {
|
|
43
|
+
bufferSize: getTreeSitterBufferSize(content),
|
|
44
|
+
});
|
|
40
45
|
const namespaces = [];
|
|
41
46
|
const usingStaticPaths = [];
|
|
42
47
|
const visit = (node) => {
|
|
@@ -81,8 +86,8 @@ function extractFileStructure(content, cachedTree) {
|
|
|
81
86
|
return { namespaces, usingStaticPaths };
|
|
82
87
|
}
|
|
83
88
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
89
|
+
* Append cross-file sibling class defs to each Namespace scope's
|
|
90
|
+
* `bindingAugmentations` bucket. Class-like defs (Class / Interface /
|
|
86
91
|
* Struct / Record / Enum) are visible cross-file; method / field
|
|
87
92
|
* members are not.
|
|
88
93
|
*/
|
|
@@ -161,12 +166,15 @@ export function populateCsharpNamespaceSiblings(parsedFiles, indexes, inputs) {
|
|
|
161
166
|
}
|
|
162
167
|
}
|
|
163
168
|
}
|
|
164
|
-
// Inject cross-file siblings into each namespace scope's
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
|
|
169
|
+
// Inject cross-file siblings into each namespace scope's
|
|
170
|
+
// post-finalize augmentation channel (per I8). The
|
|
171
|
+
// `indexes.bindingAugmentations` map is the dedicated mutable
|
|
172
|
+
// append-only buffer for post-finalize hooks: inner `BindingRef[]`
|
|
173
|
+
// arrays here are NEVER frozen (unlike `indexes.bindings`, which
|
|
174
|
+
// `materializeBindings` freezes). Walkers consult both channels
|
|
175
|
+
// via `lookupBindingsAt`; we never need to consult or mutate
|
|
176
|
+
// `indexes.bindings`.
|
|
177
|
+
const augmentations = indexes.bindingAugmentations;
|
|
170
178
|
// Cross-namespace type-binding propagation: for each file, mirror
|
|
171
179
|
// method return-type bindings from same-namespace sibling files and
|
|
172
180
|
// from files in namespaces the importer `using`s, into the
|
|
@@ -268,18 +276,14 @@ export function populateCsharpNamespaceSiblings(parsedFiles, indexes, inputs) {
|
|
|
268
276
|
const simpleName = mq.includes('.') ? mq.slice(mq.lastIndexOf('.') + 1) : mq;
|
|
269
277
|
if (simpleName === '')
|
|
270
278
|
continue;
|
|
271
|
-
//
|
|
272
|
-
// `findCallableBindingInScope`
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
const existing = scopeBindings.get(simpleName) ?? [];
|
|
279
|
-
if (existing.some((b) => b.def.nodeId === memberDef.nodeId))
|
|
279
|
+
// Append to the augmentation bucket for the importer's module
|
|
280
|
+
// scope. `findCallableBindingInScope` reads via
|
|
281
|
+
// `lookupBindingsAt`, which fans out across `bindings` +
|
|
282
|
+
// `bindingAugmentations`.
|
|
283
|
+
const bucketArr = getAugmentationBucket(augmentations, moduleScope.id, simpleName);
|
|
284
|
+
if (bucketArr.some((b) => b.def.nodeId === memberDef.nodeId))
|
|
280
285
|
continue;
|
|
281
|
-
|
|
282
|
-
scopeBindings.set(simpleName, existing);
|
|
286
|
+
bucketArr.push({ def: memberDef, origin: 'import' });
|
|
283
287
|
}
|
|
284
288
|
}
|
|
285
289
|
}
|
|
@@ -310,16 +314,10 @@ export function populateCsharpNamespaceSiblings(parsedFiles, indexes, inputs) {
|
|
|
310
314
|
const simpleName = q.includes('.') ? q.slice(q.lastIndexOf('.') + 1) : q;
|
|
311
315
|
if (simpleName === '')
|
|
312
316
|
continue;
|
|
313
|
-
|
|
314
|
-
if (
|
|
315
|
-
scopeBindings = new Map();
|
|
316
|
-
finalized.set(moduleScope.id, scopeBindings);
|
|
317
|
-
}
|
|
318
|
-
const existing = scopeBindings.get(simpleName) ?? [];
|
|
319
|
-
if (existing.some((b) => b.def.nodeId === def.nodeId))
|
|
317
|
+
const bucketArr = getAugmentationBucket(augmentations, moduleScope.id, simpleName);
|
|
318
|
+
if (bucketArr.some((b) => b.def.nodeId === def.nodeId))
|
|
320
319
|
continue;
|
|
321
|
-
|
|
322
|
-
scopeBindings.set(simpleName, existing);
|
|
320
|
+
bucketArr.push({ def, origin: 'namespace' });
|
|
323
321
|
}
|
|
324
322
|
}
|
|
325
323
|
}
|
|
@@ -339,11 +337,6 @@ export function populateCsharpNamespaceSiblings(parsedFiles, indexes, inputs) {
|
|
|
339
337
|
defsByName.set(key, arr);
|
|
340
338
|
}
|
|
341
339
|
for (const { scopeId, filePath } of bucket.scopes) {
|
|
342
|
-
let scopeBindings = finalized.get(scopeId);
|
|
343
|
-
if (scopeBindings === undefined) {
|
|
344
|
-
scopeBindings = new Map();
|
|
345
|
-
finalized.set(scopeId, scopeBindings);
|
|
346
|
-
}
|
|
347
340
|
for (const [name, defs] of defsByName) {
|
|
348
341
|
// Skip names already present locally — `origin: 'local'` in
|
|
349
342
|
// scope.bindings would naturally shadow the cross-file
|
|
@@ -351,20 +344,39 @@ export function populateCsharpNamespaceSiblings(parsedFiles, indexes, inputs) {
|
|
|
351
344
|
const local = bucket.scopes.find((s) => s.filePath === filePath)?.scope.bindings.get(name);
|
|
352
345
|
if (local !== undefined && local.some((b) => b.origin === 'local'))
|
|
353
346
|
continue;
|
|
354
|
-
|
|
347
|
+
let bucketArr = null;
|
|
355
348
|
for (const def of defs) {
|
|
356
349
|
if (def.filePath === filePath)
|
|
357
350
|
continue; // don't self-reference
|
|
358
|
-
if (
|
|
351
|
+
if (bucketArr === null)
|
|
352
|
+
bucketArr = getAugmentationBucket(augmentations, scopeId, name);
|
|
353
|
+
if (bucketArr.some((b) => b.def.nodeId === def.nodeId))
|
|
359
354
|
continue;
|
|
360
|
-
|
|
355
|
+
bucketArr.push({ def, origin: 'namespace' });
|
|
361
356
|
}
|
|
362
|
-
if (existing.length > 0)
|
|
363
|
-
scopeBindings.set(name, existing);
|
|
364
357
|
}
|
|
365
358
|
}
|
|
366
359
|
}
|
|
367
360
|
}
|
|
361
|
+
/** Get-or-create a mutable inner bucket inside the `bindingAugmentations`
|
|
362
|
+
* channel. The inner arrays here are mutable by contract (see
|
|
363
|
+
* `ScopeResolutionIndexes.bindingAugmentations` doc + scope-resolver I8);
|
|
364
|
+
* callers may `push` directly. Allocating the outer/inner Maps lazily
|
|
365
|
+
* keeps the augmentation footprint zero for files with no cross-file
|
|
366
|
+
* fanout. */
|
|
367
|
+
function getAugmentationBucket(augmentations, scopeId, name) {
|
|
368
|
+
let scopeBindings = augmentations.get(scopeId);
|
|
369
|
+
if (scopeBindings === undefined) {
|
|
370
|
+
scopeBindings = new Map();
|
|
371
|
+
augmentations.set(scopeId, scopeBindings);
|
|
372
|
+
}
|
|
373
|
+
let bucketArr = scopeBindings.get(name);
|
|
374
|
+
if (bucketArr === undefined) {
|
|
375
|
+
bucketArr = [];
|
|
376
|
+
scopeBindings.set(name, bucketArr);
|
|
377
|
+
}
|
|
378
|
+
return bucketArr;
|
|
379
|
+
}
|
|
368
380
|
function isTypeDef(def) {
|
|
369
381
|
return (def.type === 'Class' ||
|
|
370
382
|
def.type === 'Interface' ||
|
|
@@ -21,6 +21,7 @@ import { getPythonParser, getPythonScopeQuery } from './query.js';
|
|
|
21
21
|
import { synthesizeReceiverTypeBinding } from './receiver-binding.js';
|
|
22
22
|
import { computePythonArityMetadata } from './arity-metadata.js';
|
|
23
23
|
import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
|
|
24
|
+
import { getTreeSitterBufferSize } from '../../constants.js';
|
|
24
25
|
export function emitPythonScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
25
26
|
// Skip the parse when the caller (parse phase's ASTCache) already
|
|
26
27
|
// produced a Tree for this source. Cache miss = re-parse, same as
|
|
@@ -29,7 +30,9 @@ export function emitPythonScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
29
30
|
// here at the use site.
|
|
30
31
|
let tree = cachedTree;
|
|
31
32
|
if (tree === undefined) {
|
|
32
|
-
tree = getPythonParser().parse(sourceText
|
|
33
|
+
tree = getPythonParser().parse(sourceText, undefined, {
|
|
34
|
+
bufferSize: getTreeSitterBufferSize(sourceText),
|
|
35
|
+
});
|
|
33
36
|
recordCacheMiss();
|
|
34
37
|
}
|
|
35
38
|
else {
|
|
@@ -30,6 +30,7 @@ import { getTsParser, getTsScopeQuery, tsCachedTreeMatchesGrammar } from './quer
|
|
|
30
30
|
import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
|
|
31
31
|
import { synthesizeTsReceiverBinding } from './receiver-binding.js';
|
|
32
32
|
import { computeTsArityMetadata } from './arity-metadata.js';
|
|
33
|
+
import { getTreeSitterBufferSize } from '../../constants.js';
|
|
33
34
|
/** tree-sitter-typescript node types for function-like scopes that may
|
|
34
35
|
* carry a synthesized `this` binding. Kept in sync with the
|
|
35
36
|
* `@scope.function` patterns in `query.ts`. */
|
|
@@ -110,7 +111,9 @@ export function emitTsScopeCaptures(sourceText, filePath, cachedTree) {
|
|
|
110
111
|
tree = undefined;
|
|
111
112
|
}
|
|
112
113
|
if (tree === undefined) {
|
|
113
|
-
tree = getTsParser(filePath).parse(sourceText
|
|
114
|
+
tree = getTsParser(filePath).parse(sourceText, undefined, {
|
|
115
|
+
bufferSize: getTreeSitterBufferSize(sourceText),
|
|
116
|
+
});
|
|
114
117
|
recordCacheMiss();
|
|
115
118
|
}
|
|
116
119
|
else {
|
|
@@ -48,8 +48,21 @@ export interface ScopeResolutionIndexes {
|
|
|
48
48
|
readonly methodDispatch: MethodDispatchIndex;
|
|
49
49
|
/** Finalized `ImportEdge[]` per module scope. */
|
|
50
50
|
readonly imports: ReadonlyMap<ScopeId, readonly ImportEdge[]>;
|
|
51
|
-
/**
|
|
51
|
+
/** Finalize-output bindings (local + imports + wildcards) per module scope.
|
|
52
|
+
* Inner `BindingRef[]` arrays are frozen by `materializeBindings`;
|
|
53
|
+
* this channel is permanently immutable post-finalize. Consumers
|
|
54
|
+
* MUST read via `lookupBindingsAt` so the augmentation channel is
|
|
55
|
+
* consulted alongside. See I8 in `contract/scope-resolver.ts`. */
|
|
52
56
|
readonly bindings: ReadonlyMap<ScopeId, ReadonlyMap<string, readonly BindingRef[]>>;
|
|
57
|
+
/** Append-only post-finalize augmentation channel. Populated by
|
|
58
|
+
* language hooks such as `populateNamespaceSiblings` for cross-file
|
|
59
|
+
* bindings synthesized after finalize (e.g. C# same-namespace
|
|
60
|
+
* visibility, `using static` member exposure). Inner arrays are
|
|
61
|
+
* NOT frozen — hooks `push()` directly. Walkers must consult both
|
|
62
|
+
* this map and `bindings` via `lookupBindingsAt`; finalized refs
|
|
63
|
+
* are returned first and win duplicate `def.nodeId` metadata, with
|
|
64
|
+
* unique augmentations appended after. See I8. */
|
|
65
|
+
readonly bindingAugmentations: ReadonlyMap<ScopeId, ReadonlyMap<string, readonly BindingRef[]>>;
|
|
53
66
|
/** Pre-resolution usage facts; consumed by the resolution phase. */
|
|
54
67
|
readonly referenceSites: readonly ReferenceSite[];
|
|
55
68
|
/** SCC condensation of the file-level import graph — callers that want
|
|
@@ -10,7 +10,7 @@ import { getDefinitionNodeFromCaptures, findEnclosingClassInfo, getLabelFromCapt
|
|
|
10
10
|
import { detectFrameworkFromAST } from './framework-detection.js';
|
|
11
11
|
import { buildTypeEnv } from './type-env.js';
|
|
12
12
|
import { buildMethodProps, arityForIdFromInfo, typeTagForId, constTagForId, buildCollisionGroups, } from './utils/method-props.js';
|
|
13
|
-
import { getTreeSitterBufferSize, TREE_SITTER_MAX_BUFFER } from './constants.js';
|
|
13
|
+
import { getTreeSitterBufferSize, getTreeSitterContentByteLength, TREE_SITTER_MAX_BUFFER, } from './constants.js';
|
|
14
14
|
// ============================================================================
|
|
15
15
|
// Worker-based parallel parsing
|
|
16
16
|
// ============================================================================
|
|
@@ -247,7 +247,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
|
|
|
247
247
|
continue;
|
|
248
248
|
}
|
|
249
249
|
// Skip files larger than the max tree-sitter buffer (32 MB)
|
|
250
|
-
if (file.content
|
|
250
|
+
if (getTreeSitterContentByteLength(file.content) > TREE_SITTER_MAX_BUFFER)
|
|
251
251
|
continue;
|
|
252
252
|
// Vue SFC preprocessing: extract <script> block content
|
|
253
253
|
let parseContent = file.content;
|
|
@@ -270,7 +270,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
|
|
|
270
270
|
let tree;
|
|
271
271
|
try {
|
|
272
272
|
tree = parser.parse(parseContent, undefined, {
|
|
273
|
-
bufferSize: getTreeSitterBufferSize(parseContent
|
|
273
|
+
bufferSize: getTreeSitterBufferSize(parseContent),
|
|
274
274
|
});
|
|
275
275
|
}
|
|
276
276
|
catch (parseError) {
|
|
@@ -136,14 +136,45 @@
|
|
|
136
136
|
* once per workspace at resolve time), and merging would create a
|
|
137
137
|
* god-interface that complicates future migrations.
|
|
138
138
|
*
|
|
139
|
-
* - **I8 —
|
|
140
|
-
* `indexes.bindings
|
|
141
|
-
* `
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
139
|
+
* - **I8 — Two-channel binding lifecycle.**
|
|
140
|
+
* `indexes.bindings` is the **finalize-output channel**. After
|
|
141
|
+
* `finalizeScopeModel` returns, its inner `BindingRef[]` arrays
|
|
142
|
+
* are deep-frozen by `materializeBindings` and MUST NOT be
|
|
143
|
+
* mutated by any post-finalize hook. Treat `indexes.bindings` as
|
|
144
|
+
* immutable from the moment `finalizeScopeModel` returns.
|
|
145
|
+
*
|
|
146
|
+
* `indexes.bindingAugmentations` is the **post-finalize
|
|
147
|
+
* append-only channel**. Hooks like `populateNamespaceSiblings`
|
|
148
|
+
* append cross-file bindings synthesized after finalize (C#
|
|
149
|
+
* same-namespace visibility, `using static` member exposure)
|
|
150
|
+
* into this channel, NOT into `indexes.bindings`. Inner arrays
|
|
151
|
+
* here are NEVER frozen — hooks `push()` directly. Any consumer
|
|
152
|
+
* that reads post-finalize workspace bindings MUST query both
|
|
153
|
+
* index channels via `lookupBindingsAt`
|
|
154
|
+
* (`scope-resolution/scope/walkers.ts`); the helper returns
|
|
155
|
+
* finalized refs first, appends unique augmentation refs after,
|
|
156
|
+
* and dedupes by `def.nodeId` so finalized metadata wins on
|
|
157
|
+
* duplicate defs. Per-`Scope.bindings` local declarations are the
|
|
158
|
+
* lexical extraction channel and remain a separate first-tier
|
|
159
|
+
* lookup for local shadowing.
|
|
160
|
+
*
|
|
161
|
+
* `Scope.typeBindings` remains mutable post-finalize per I6 (it
|
|
162
|
+
* is intentionally not frozen at any point).
|
|
163
|
+
*
|
|
164
|
+
* The `ReadonlyMap<...>` types on `ScopeResolutionIndexes` are
|
|
165
|
+
* compile-time read-guidance for consumers; structural mutation
|
|
166
|
+
* of `bindingAugmentations` is performed via a deliberate
|
|
167
|
+
* `as Map<...>` cast inside the hook implementations and is the
|
|
168
|
+
* ONLY sanctioned channel for post-finalize binding fanout.
|
|
169
|
+
*
|
|
170
|
+
* The dev-mode runtime validator
|
|
171
|
+
* (`validateBindingsImmutability` in
|
|
172
|
+
* `scope-resolution/validate-bindings-immutability.ts`) surfaces
|
|
173
|
+
* any drift — i.e. a hook writing to `indexes.bindings` instead
|
|
174
|
+
* of `bindingAugmentations`, or producing a non-frozen finalized
|
|
175
|
+
* bucket — via `onWarn` when explicitly enabled by
|
|
176
|
+
* `NODE_ENV === 'development' || VALIDATE_SEMANTIC_MODEL === '1'`
|
|
177
|
+
* (`VALIDATE_SEMANTIC_MODEL=0` is an explicit off switch).
|
|
147
178
|
*
|
|
148
179
|
* - **I9 — `SemanticModel` is the single authoritative symbol store.**
|
|
149
180
|
* Every symbol-indexed lookup (key = `nodeId | simpleName |
|
|
@@ -136,14 +136,45 @@
|
|
|
136
136
|
* once per workspace at resolve time), and merging would create a
|
|
137
137
|
* god-interface that complicates future migrations.
|
|
138
138
|
*
|
|
139
|
-
* - **I8 —
|
|
140
|
-
* `indexes.bindings
|
|
141
|
-
* `
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
139
|
+
* - **I8 — Two-channel binding lifecycle.**
|
|
140
|
+
* `indexes.bindings` is the **finalize-output channel**. After
|
|
141
|
+
* `finalizeScopeModel` returns, its inner `BindingRef[]` arrays
|
|
142
|
+
* are deep-frozen by `materializeBindings` and MUST NOT be
|
|
143
|
+
* mutated by any post-finalize hook. Treat `indexes.bindings` as
|
|
144
|
+
* immutable from the moment `finalizeScopeModel` returns.
|
|
145
|
+
*
|
|
146
|
+
* `indexes.bindingAugmentations` is the **post-finalize
|
|
147
|
+
* append-only channel**. Hooks like `populateNamespaceSiblings`
|
|
148
|
+
* append cross-file bindings synthesized after finalize (C#
|
|
149
|
+
* same-namespace visibility, `using static` member exposure)
|
|
150
|
+
* into this channel, NOT into `indexes.bindings`. Inner arrays
|
|
151
|
+
* here are NEVER frozen — hooks `push()` directly. Any consumer
|
|
152
|
+
* that reads post-finalize workspace bindings MUST query both
|
|
153
|
+
* index channels via `lookupBindingsAt`
|
|
154
|
+
* (`scope-resolution/scope/walkers.ts`); the helper returns
|
|
155
|
+
* finalized refs first, appends unique augmentation refs after,
|
|
156
|
+
* and dedupes by `def.nodeId` so finalized metadata wins on
|
|
157
|
+
* duplicate defs. Per-`Scope.bindings` local declarations are the
|
|
158
|
+
* lexical extraction channel and remain a separate first-tier
|
|
159
|
+
* lookup for local shadowing.
|
|
160
|
+
*
|
|
161
|
+
* `Scope.typeBindings` remains mutable post-finalize per I6 (it
|
|
162
|
+
* is intentionally not frozen at any point).
|
|
163
|
+
*
|
|
164
|
+
* The `ReadonlyMap<...>` types on `ScopeResolutionIndexes` are
|
|
165
|
+
* compile-time read-guidance for consumers; structural mutation
|
|
166
|
+
* of `bindingAugmentations` is performed via a deliberate
|
|
167
|
+
* `as Map<...>` cast inside the hook implementations and is the
|
|
168
|
+
* ONLY sanctioned channel for post-finalize binding fanout.
|
|
169
|
+
*
|
|
170
|
+
* The dev-mode runtime validator
|
|
171
|
+
* (`validateBindingsImmutability` in
|
|
172
|
+
* `scope-resolution/validate-bindings-immutability.ts`) surfaces
|
|
173
|
+
* any drift — i.e. a hook writing to `indexes.bindings` instead
|
|
174
|
+
* of `bindingAugmentations`, or producing a non-frozen finalized
|
|
175
|
+
* bucket — via `onWarn` when explicitly enabled by
|
|
176
|
+
* `NODE_ENV === 'development' || VALIDATE_SEMANTIC_MODEL === '1'`
|
|
177
|
+
* (`VALIDATE_SEMANTIC_MODEL=0` is an explicit off switch).
|
|
147
178
|
*
|
|
148
179
|
* - **I9 — `SemanticModel` is the single authoritative symbol store.**
|
|
149
180
|
* Every symbol-indexed lookup (key = `nodeId | simpleName |
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
* Generic; promoted from `languages/python/scope-resolver.ts` per the
|
|
39
39
|
* scope-resolution generalization plan.
|
|
40
40
|
*/
|
|
41
|
+
import { lookupBindingsAt, namesAtScope } from '../scope/walkers.js';
|
|
41
42
|
/**
|
|
42
43
|
* Max chain depth for the post-finalize re-follow. Effective end-to-end
|
|
43
44
|
* depth is roughly 2× this number, because chain-following runs once
|
|
@@ -126,14 +127,18 @@ export function propagateImportedReturnTypes(parsedFiles, indexes, index) {
|
|
|
126
127
|
const importerModule = moduleScopeByFile.get(filePath);
|
|
127
128
|
if (importerModule === undefined)
|
|
128
129
|
continue;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
// Iterate finalized + augmented binding names at this scope so
|
|
131
|
+
// post-finalize hooks (e.g. `using static` augmentations from
|
|
132
|
+
// `populateCsharpNamespaceSiblings`) are visible to the
|
|
133
|
+
// import-derived typeBinding mirror. Both helpers fast-path when
|
|
134
|
+
// no augmentations exist for the scope, so the common case is
|
|
135
|
+
// allocation-free. See I8.
|
|
136
|
+
for (const localName of namesAtScope(importerModule.id, indexes)) {
|
|
133
137
|
// Skip if importer already has a typeBinding for this name —
|
|
134
138
|
// an explicit local annotation must win over import-derived.
|
|
135
139
|
if (importerModule.typeBindings.has(localName))
|
|
136
140
|
continue;
|
|
141
|
+
const refs = lookupBindingsAt(importerModule.id, localName, indexes);
|
|
137
142
|
for (const ref of refs) {
|
|
138
143
|
if (ref.origin !== 'import' && ref.origin !== 'reexport')
|
|
139
144
|
continue;
|
|
@@ -32,7 +32,7 @@ import { getLanguageFromFilename } from '../../../../_shared/index.js';
|
|
|
32
32
|
import { readFileContents } from '../../filesystem-walker.js';
|
|
33
33
|
import { runScopeResolution } from './run.js';
|
|
34
34
|
import { SCOPE_RESOLVERS } from './registry.js';
|
|
35
|
-
import { isDev } from '../../utils/env.js';
|
|
35
|
+
import { isDev, isSemanticModelValidatorEnabled } from '../../utils/env.js';
|
|
36
36
|
const NOOP_OUTPUT = Object.freeze({
|
|
37
37
|
ran: false,
|
|
38
38
|
filesProcessed: 0,
|
|
@@ -101,8 +101,9 @@ export const scopeResolutionPhase = {
|
|
|
101
101
|
treeCache: scopeTreeCache,
|
|
102
102
|
resolutionConfig,
|
|
103
103
|
onWarn: (msg) => {
|
|
104
|
-
if (
|
|
104
|
+
if (isSemanticModelValidatorEnabled()) {
|
|
105
105
|
console.warn(`[scope-resolution:${lang}] ${msg}`);
|
|
106
|
+
}
|
|
106
107
|
},
|
|
107
108
|
}, provider);
|
|
108
109
|
anyRan = true;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* Plan: `docs/plans/2026-04-20-001-refactor-emit-pipeline-generalization-plan.md`.
|
|
24
24
|
*/
|
|
25
25
|
import { reconcileOwnership, validateOwnershipParity } from './reconcile-ownership.js';
|
|
26
|
+
import { validateBindingsImmutability } from './validate-bindings-immutability.js';
|
|
26
27
|
import { extractParsedFile } from '../../scope-extractor-bridge.js';
|
|
27
28
|
import { finalizeScopeModel } from '../../finalize-orchestrator.js';
|
|
28
29
|
import { resolveReferenceSites } from '../../resolve-references.js';
|
|
@@ -107,6 +108,8 @@ export function runScopeResolution(input, provider) {
|
|
|
107
108
|
// Cross-file implicit-namespace visibility (C#). Must run before
|
|
108
109
|
// propagateImportedReturnTypes so the latter pass sees siblings'
|
|
109
110
|
// class bindings when chasing return-type chains across files.
|
|
111
|
+
// The hook writes to `bindingAugmentations` only; finalized
|
|
112
|
+
// `indexes.bindings` remains immutable post-finalize (I8).
|
|
110
113
|
if (provider.populateNamespaceSiblings !== undefined) {
|
|
111
114
|
const fileContents = new Map();
|
|
112
115
|
for (const f of files)
|
|
@@ -126,6 +129,13 @@ export function runScopeResolution(input, provider) {
|
|
|
126
129
|
propagateImportedReturnTypes(parsedFiles, indexes, workspaceIndex);
|
|
127
130
|
}
|
|
128
131
|
const tPropagate = PROF ? process.hrtime.bigint() : 0n;
|
|
132
|
+
// Opt-in I8 invariant guard. Runs once after all post-finalize hooks
|
|
133
|
+
// (`populateNamespaceSiblings`, `propagateImportedReturnTypes`) have
|
|
134
|
+
// had a chance to drift, so a single sweep covers the full
|
|
135
|
+
// post-finalize surface visible to `resolveReferenceSites`. No-op in
|
|
136
|
+
// default CLI runs; enabled by NODE_ENV=development or
|
|
137
|
+
// VALIDATE_SEMANTIC_MODEL=1.
|
|
138
|
+
validateBindingsImmutability(indexes, onWarn);
|
|
129
139
|
// ── Phase 3: resolve references via Registry.lookup ────────────────────
|
|
130
140
|
const registryProviders = {
|
|
131
141
|
arityCompatibility: provider.arityCompatibility,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-mode runtime validator for the two-channel binding lifecycle
|
|
3
|
+
* (Contract Invariant I8 in `contract/scope-resolver.ts`).
|
|
4
|
+
*
|
|
5
|
+
* The two channels:
|
|
6
|
+
* - `indexes.bindings` — finalize-output channel. After
|
|
7
|
+
* `finalizeScopeModel` returns, every inner `BindingRef[]` array
|
|
8
|
+
* here is deep-frozen by `materializeBindings`. NO post-finalize
|
|
9
|
+
* hook should ever mutate this map's inner arrays — drift here
|
|
10
|
+
* manifests at runtime as the `Cannot add property N, object is
|
|
11
|
+
* not extensible` crash (issue #1066) or, more insidiously, as
|
|
12
|
+
* a hook silently mutating one of the frozen arrays (a no-op in
|
|
13
|
+
* production where freezes can be elided, a `TypeError` in dev).
|
|
14
|
+
*
|
|
15
|
+
* - `indexes.bindingAugmentations` — post-finalize append-only
|
|
16
|
+
* channel. Inner arrays here are NEVER frozen; hooks like
|
|
17
|
+
* `populateNamespaceSiblings` `push()` directly. Walkers consult
|
|
18
|
+
* both channels via `lookupBindingsAt`.
|
|
19
|
+
*
|
|
20
|
+
* This validator runs after every post-finalize hook has executed
|
|
21
|
+
* (so the dev-mode envelope captures the FULL surface area visible
|
|
22
|
+
* to `resolveReferenceSites`) and asserts:
|
|
23
|
+
*
|
|
24
|
+
* 1. Every inner `BindingRef[]` array in `indexes.bindings` is
|
|
25
|
+
* `Object.isFrozen` — i.e. finalize produced a frozen bucket
|
|
26
|
+
* AND no hook accidentally `set()`-back a mutable replacement.
|
|
27
|
+
*
|
|
28
|
+
* 2. Every inner `BindingRef[]` array in
|
|
29
|
+
* `indexes.bindingAugmentations` is NOT frozen — i.e. the
|
|
30
|
+
* hook used the augmentation channel as designed (mutable
|
|
31
|
+
* `push()`) and didn't accidentally freeze its own scratch
|
|
32
|
+
* arrays. Self-documenting; mostly a sanity net.
|
|
33
|
+
*
|
|
34
|
+
* Mirrors `validateOwnershipParity` (#909): warns via `onWarn`,
|
|
35
|
+
* never throws, and is opt-in outside development. Gated by
|
|
36
|
+
* `isSemanticModelValidatorEnabled()` (`utils/env.ts`).
|
|
37
|
+
*/
|
|
38
|
+
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
39
|
+
export declare function validateBindingsImmutability(indexes: ScopeResolutionIndexes, onWarn: (message: string) => void): number;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-mode runtime validator for the two-channel binding lifecycle
|
|
3
|
+
* (Contract Invariant I8 in `contract/scope-resolver.ts`).
|
|
4
|
+
*
|
|
5
|
+
* The two channels:
|
|
6
|
+
* - `indexes.bindings` — finalize-output channel. After
|
|
7
|
+
* `finalizeScopeModel` returns, every inner `BindingRef[]` array
|
|
8
|
+
* here is deep-frozen by `materializeBindings`. NO post-finalize
|
|
9
|
+
* hook should ever mutate this map's inner arrays — drift here
|
|
10
|
+
* manifests at runtime as the `Cannot add property N, object is
|
|
11
|
+
* not extensible` crash (issue #1066) or, more insidiously, as
|
|
12
|
+
* a hook silently mutating one of the frozen arrays (a no-op in
|
|
13
|
+
* production where freezes can be elided, a `TypeError` in dev).
|
|
14
|
+
*
|
|
15
|
+
* - `indexes.bindingAugmentations` — post-finalize append-only
|
|
16
|
+
* channel. Inner arrays here are NEVER frozen; hooks like
|
|
17
|
+
* `populateNamespaceSiblings` `push()` directly. Walkers consult
|
|
18
|
+
* both channels via `lookupBindingsAt`.
|
|
19
|
+
*
|
|
20
|
+
* This validator runs after every post-finalize hook has executed
|
|
21
|
+
* (so the dev-mode envelope captures the FULL surface area visible
|
|
22
|
+
* to `resolveReferenceSites`) and asserts:
|
|
23
|
+
*
|
|
24
|
+
* 1. Every inner `BindingRef[]` array in `indexes.bindings` is
|
|
25
|
+
* `Object.isFrozen` — i.e. finalize produced a frozen bucket
|
|
26
|
+
* AND no hook accidentally `set()`-back a mutable replacement.
|
|
27
|
+
*
|
|
28
|
+
* 2. Every inner `BindingRef[]` array in
|
|
29
|
+
* `indexes.bindingAugmentations` is NOT frozen — i.e. the
|
|
30
|
+
* hook used the augmentation channel as designed (mutable
|
|
31
|
+
* `push()`) and didn't accidentally freeze its own scratch
|
|
32
|
+
* arrays. Self-documenting; mostly a sanity net.
|
|
33
|
+
*
|
|
34
|
+
* Mirrors `validateOwnershipParity` (#909): warns via `onWarn`,
|
|
35
|
+
* never throws, and is opt-in outside development. Gated by
|
|
36
|
+
* `isSemanticModelValidatorEnabled()` (`utils/env.ts`).
|
|
37
|
+
*/
|
|
38
|
+
import { isSemanticModelValidatorEnabled } from '../../utils/env.js';
|
|
39
|
+
export function validateBindingsImmutability(indexes, onWarn) {
|
|
40
|
+
if (!isSemanticModelValidatorEnabled())
|
|
41
|
+
return 0;
|
|
42
|
+
let violations = 0;
|
|
43
|
+
for (const [scopeId, bucketMap] of indexes.bindings) {
|
|
44
|
+
for (const [name, bucket] of bucketMap) {
|
|
45
|
+
if (!Object.isFrozen(bucket)) {
|
|
46
|
+
onWarn(`binding-immutability: indexes.bindings[${scopeId}][${name}] is NOT frozen — ` +
|
|
47
|
+
`finalize produced a mutable bucket OR a post-finalize hook replaced a frozen ` +
|
|
48
|
+
`bucket with a mutable one. Hooks must write to indexes.bindingAugmentations ` +
|
|
49
|
+
`instead. See ScopeResolver Invariant I8.`);
|
|
50
|
+
violations++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const [scopeId, bucketMap] of indexes.bindingAugmentations) {
|
|
55
|
+
for (const [name, bucket] of bucketMap) {
|
|
56
|
+
if (Object.isFrozen(bucket)) {
|
|
57
|
+
onWarn(`binding-immutability: indexes.bindingAugmentations[${scopeId}][${name}] is FROZEN — ` +
|
|
58
|
+
`the augmentation channel is mutable by contract; freezing it defeats the ` +
|
|
59
|
+
`append-only purpose. See ScopeResolver Invariant I8.`);
|
|
60
|
+
violations++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return violations;
|
|
65
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scope-chain lookup primitives shared across language providers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Five functions:
|
|
5
5
|
* - `findReceiverTypeBinding` — walk scope.typeBindings up the chain
|
|
6
6
|
* for a receiver name.
|
|
7
|
-
* - `
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* - `lookupBindingsAt` — read finalized + augmented binding refs at
|
|
8
|
+
* one scope, deduped by `def.nodeId`. The dual-source-aware
|
|
9
|
+
* primitive every other binding lookup composes with.
|
|
10
|
+
* - `findClassBindingInScope` — walk scope.bindings + the indexes via
|
|
11
|
+
* `lookupBindingsAt` for a class-kind binding.
|
|
11
12
|
* - `findOwnedMember` — find a method/field owned by a class def
|
|
12
13
|
* across all parsed files by (ownerId, simpleName).
|
|
13
14
|
* - `findExportedDef` — find a file-level exported def (top-of-module
|
|
@@ -18,10 +19,44 @@
|
|
|
18
19
|
* "resolve member on owner with MRO" pattern. All four are reusable
|
|
19
20
|
* as-is for TypeScript, Java, Kotlin, Ruby, etc.
|
|
20
21
|
*/
|
|
21
|
-
import type { ParsedFile, ScopeId, SymbolDefinition, TypeRef } from '../../../../_shared/index.js';
|
|
22
|
+
import type { BindingRef, ParsedFile, ScopeId, SymbolDefinition, TypeRef } from '../../../../_shared/index.js';
|
|
22
23
|
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
23
24
|
import type { SemanticModel } from '../../model/semantic-model.js';
|
|
24
25
|
import type { WorkspaceResolutionIndex } from '../workspace-index.js';
|
|
26
|
+
/**
|
|
27
|
+
* Look up binding refs at `scopeId` for `name`, consulting both the
|
|
28
|
+
* finalize-owned `bindings` channel and the post-finalize
|
|
29
|
+
* `bindingAugmentations` channel (see invariant I8 in
|
|
30
|
+
* `contract/scope-resolver.ts`). Finalized refs come first; augmented
|
|
31
|
+
* refs append, deduped by `def.nodeId` so a sibling that's also
|
|
32
|
+
* explicitly imported doesn't double-emit.
|
|
33
|
+
*
|
|
34
|
+
* Returns a shared frozen empty array when neither channel has the
|
|
35
|
+
* name — callers can compare against `=== EMPTY_BINDINGS` if they
|
|
36
|
+
* want a fast-path miss check. The bucket arrays are returned by
|
|
37
|
+
* reference when only one channel populates them; the merged path
|
|
38
|
+
* allocates a fresh array.
|
|
39
|
+
*
|
|
40
|
+
* Walker primitives (`findClassBindingInScope`,
|
|
41
|
+
* `findCallableBindingInScope`, `findExportedDefByName`) and
|
|
42
|
+
* post-finalize passes that read finalized bindings (e.g.
|
|
43
|
+
* `propagateImportedReturnTypes`, `namespace-targets`) MUST go
|
|
44
|
+
* through this helper instead of `scopes.bindings.get(...)` directly,
|
|
45
|
+
* so the augmentation channel is always visible.
|
|
46
|
+
*/
|
|
47
|
+
export declare function lookupBindingsAt(scopeId: ScopeId, name: string, scopes: ScopeResolutionIndexes): readonly BindingRef[];
|
|
48
|
+
/**
|
|
49
|
+
* Return the union of bound names at `scopeId` across both the
|
|
50
|
+
* finalized and augmented channels. Companion to `lookupBindingsAt`
|
|
51
|
+
* for callers that need to iterate every name at a scope (e.g.
|
|
52
|
+
* `propagateImportedReturnTypes`). Order is not guaranteed; callers
|
|
53
|
+
* that need stable iteration should sort externally.
|
|
54
|
+
*
|
|
55
|
+
* Fast paths (zero allocation) when at most one channel is populated:
|
|
56
|
+
* returns the underlying `Map.keys()` iterator directly. Only when both
|
|
57
|
+
* channels carry names do we materialize a `Set` for deduplication.
|
|
58
|
+
*/
|
|
59
|
+
export declare function namesAtScope(scopeId: ScopeId, scopes: ScopeResolutionIndexes): Iterable<string>;
|
|
25
60
|
/**
|
|
26
61
|
* True when a def's `type` names a class-like declaration — every kind
|
|
27
62
|
* that collapses to `@scope.class` in the scope-extractor query contract.
|
|
@@ -48,8 +83,10 @@ export declare function findReceiverTypeBinding(startScope: ScopeId, receiverNam
|
|
|
48
83
|
* Walks the scope chain upward and consults TWO sources at each step:
|
|
49
84
|
* 1. `scope.bindings` — populated during scope-extraction Pass 2 with
|
|
50
85
|
* local declarations (`origin: 'local'`).
|
|
51
|
-
* 2.
|
|
52
|
-
*
|
|
86
|
+
* 2. The cross-file finalized + augmented bindings, via
|
|
87
|
+
* `lookupBindingsAt` (per I8: finalized = canonical immutable
|
|
88
|
+
* output; augmented = post-finalize hooks like
|
|
89
|
+
* `populateNamespaceSiblings`).
|
|
53
90
|
*
|
|
54
91
|
* Without (2) we'd miss every cross-file class-receiver call.
|
|
55
92
|
*/
|
|
@@ -57,8 +94,9 @@ export declare function findClassBindingInScope(startScope: ScopeId, receiverNam
|
|
|
57
94
|
/**
|
|
58
95
|
* Look up a callable (Function/Method/Constructor) by name in the
|
|
59
96
|
* given scope's chain. Uses the dual-source pattern (scope.bindings +
|
|
60
|
-
*
|
|
61
|
-
* free calls to imported functions
|
|
97
|
+
* `lookupBindingsAt` for finalized + augmented) so cross-file
|
|
98
|
+
* imports are visible — without it free calls to imported functions
|
|
99
|
+
* never resolve via the post-pass.
|
|
62
100
|
*
|
|
63
101
|
* Mirrors `findClassBindingInScope` exactly; only the accepted
|
|
64
102
|
* def-type predicate differs.
|
|
@@ -122,6 +160,11 @@ export declare function findOwnedMember(ownerDefId: string, memberName: string,
|
|
|
122
160
|
* excluded.
|
|
123
161
|
*
|
|
124
162
|
* Reads from `WorkspaceResolutionIndex.moduleScopeByFile` (scope-tied
|
|
125
|
-
* lookup that doesn't live on `SemanticModel`).
|
|
163
|
+
* lookup that doesn't live on `SemanticModel`). This intentionally
|
|
164
|
+
* does NOT call `lookupBindingsAt`: `findExportedDef` answers "what
|
|
165
|
+
* did the target file declare locally at module scope?", while
|
|
166
|
+
* `bindingAugmentations` models importer-side visibility created by
|
|
167
|
+
* post-finalize hooks. Callers that need importer-visible exports use
|
|
168
|
+
* `findExportedDefByName`, which is dual-channel aware.
|
|
126
169
|
*/
|
|
127
170
|
export declare function findExportedDef(targetFile: string, memberName: string, index: WorkspaceResolutionIndex): SymbolDefinition | undefined;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scope-chain lookup primitives shared across language providers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Five functions:
|
|
5
5
|
* - `findReceiverTypeBinding` — walk scope.typeBindings up the chain
|
|
6
6
|
* for a receiver name.
|
|
7
|
-
* - `
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* - `lookupBindingsAt` — read finalized + augmented binding refs at
|
|
8
|
+
* one scope, deduped by `def.nodeId`. The dual-source-aware
|
|
9
|
+
* primitive every other binding lookup composes with.
|
|
10
|
+
* - `findClassBindingInScope` — walk scope.bindings + the indexes via
|
|
11
|
+
* `lookupBindingsAt` for a class-kind binding.
|
|
11
12
|
* - `findOwnedMember` — find a method/field owned by a class def
|
|
12
13
|
* across all parsed files by (ownerId, simpleName).
|
|
13
14
|
* - `findExportedDef` — find a file-level exported def (top-of-module
|
|
@@ -18,6 +19,80 @@
|
|
|
18
19
|
* "resolve member on owner with MRO" pattern. All four are reusable
|
|
19
20
|
* as-is for TypeScript, Java, Kotlin, Ruby, etc.
|
|
20
21
|
*/
|
|
22
|
+
const EMPTY_BINDINGS = Object.freeze([]);
|
|
23
|
+
/**
|
|
24
|
+
* Look up binding refs at `scopeId` for `name`, consulting both the
|
|
25
|
+
* finalize-owned `bindings` channel and the post-finalize
|
|
26
|
+
* `bindingAugmentations` channel (see invariant I8 in
|
|
27
|
+
* `contract/scope-resolver.ts`). Finalized refs come first; augmented
|
|
28
|
+
* refs append, deduped by `def.nodeId` so a sibling that's also
|
|
29
|
+
* explicitly imported doesn't double-emit.
|
|
30
|
+
*
|
|
31
|
+
* Returns a shared frozen empty array when neither channel has the
|
|
32
|
+
* name — callers can compare against `=== EMPTY_BINDINGS` if they
|
|
33
|
+
* want a fast-path miss check. The bucket arrays are returned by
|
|
34
|
+
* reference when only one channel populates them; the merged path
|
|
35
|
+
* allocates a fresh array.
|
|
36
|
+
*
|
|
37
|
+
* Walker primitives (`findClassBindingInScope`,
|
|
38
|
+
* `findCallableBindingInScope`, `findExportedDefByName`) and
|
|
39
|
+
* post-finalize passes that read finalized bindings (e.g.
|
|
40
|
+
* `propagateImportedReturnTypes`, `namespace-targets`) MUST go
|
|
41
|
+
* through this helper instead of `scopes.bindings.get(...)` directly,
|
|
42
|
+
* so the augmentation channel is always visible.
|
|
43
|
+
*/
|
|
44
|
+
export function lookupBindingsAt(scopeId, name, scopes) {
|
|
45
|
+
const finalized = scopes.bindings.get(scopeId)?.get(name);
|
|
46
|
+
const augmented = scopes.bindingAugmentations.get(scopeId)?.get(name);
|
|
47
|
+
const fLen = finalized?.length ?? 0;
|
|
48
|
+
const aLen = augmented?.length ?? 0;
|
|
49
|
+
if (fLen === 0 && aLen === 0)
|
|
50
|
+
return EMPTY_BINDINGS;
|
|
51
|
+
if (aLen === 0)
|
|
52
|
+
return finalized;
|
|
53
|
+
if (fLen === 0)
|
|
54
|
+
return augmented;
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const r of finalized) {
|
|
58
|
+
seen.add(r.def.nodeId);
|
|
59
|
+
out.push(r);
|
|
60
|
+
}
|
|
61
|
+
for (const r of augmented) {
|
|
62
|
+
if (seen.has(r.def.nodeId))
|
|
63
|
+
continue;
|
|
64
|
+
out.push(r);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
const EMPTY_NAMES = Object.freeze([]);
|
|
69
|
+
/**
|
|
70
|
+
* Return the union of bound names at `scopeId` across both the
|
|
71
|
+
* finalized and augmented channels. Companion to `lookupBindingsAt`
|
|
72
|
+
* for callers that need to iterate every name at a scope (e.g.
|
|
73
|
+
* `propagateImportedReturnTypes`). Order is not guaranteed; callers
|
|
74
|
+
* that need stable iteration should sort externally.
|
|
75
|
+
*
|
|
76
|
+
* Fast paths (zero allocation) when at most one channel is populated:
|
|
77
|
+
* returns the underlying `Map.keys()` iterator directly. Only when both
|
|
78
|
+
* channels carry names do we materialize a `Set` for deduplication.
|
|
79
|
+
*/
|
|
80
|
+
export function namesAtScope(scopeId, scopes) {
|
|
81
|
+
const finalized = scopes.bindings.get(scopeId);
|
|
82
|
+
const augmented = scopes.bindingAugmentations.get(scopeId);
|
|
83
|
+
const fSize = finalized?.size ?? 0;
|
|
84
|
+
const aSize = augmented?.size ?? 0;
|
|
85
|
+
if (fSize === 0 && aSize === 0)
|
|
86
|
+
return EMPTY_NAMES;
|
|
87
|
+
if (aSize === 0)
|
|
88
|
+
return finalized.keys();
|
|
89
|
+
if (fSize === 0)
|
|
90
|
+
return augmented.keys();
|
|
91
|
+
const out = new Set(finalized.keys());
|
|
92
|
+
for (const name of augmented.keys())
|
|
93
|
+
out.add(name);
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
21
96
|
/**
|
|
22
97
|
* True when a def's `type` names a class-like declaration — every kind
|
|
23
98
|
* that collapses to `@scope.class` in the scope-extractor query contract.
|
|
@@ -67,8 +142,10 @@ export function findReceiverTypeBinding(startScope, receiverName, scopes) {
|
|
|
67
142
|
* Walks the scope chain upward and consults TWO sources at each step:
|
|
68
143
|
* 1. `scope.bindings` — populated during scope-extraction Pass 2 with
|
|
69
144
|
* local declarations (`origin: 'local'`).
|
|
70
|
-
* 2.
|
|
71
|
-
*
|
|
145
|
+
* 2. The cross-file finalized + augmented bindings, via
|
|
146
|
+
* `lookupBindingsAt` (per I8: finalized = canonical immutable
|
|
147
|
+
* output; augmented = post-finalize hooks like
|
|
148
|
+
* `populateNamespaceSiblings`).
|
|
72
149
|
*
|
|
73
150
|
* Without (2) we'd miss every cross-file class-receiver call.
|
|
74
151
|
*/
|
|
@@ -89,13 +166,10 @@ export function findClassBindingInScope(startScope, receiverName, scopes) {
|
|
|
89
166
|
return b.def;
|
|
90
167
|
}
|
|
91
168
|
}
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (isClassLike(b.def.type))
|
|
97
|
-
return b.def;
|
|
98
|
-
}
|
|
169
|
+
const importedBindings = lookupBindingsAt(currentId, receiverName, scopes);
|
|
170
|
+
for (const b of importedBindings) {
|
|
171
|
+
if (isClassLike(b.def.type))
|
|
172
|
+
return b.def;
|
|
99
173
|
}
|
|
100
174
|
currentId = scope.parent;
|
|
101
175
|
}
|
|
@@ -104,8 +178,9 @@ export function findClassBindingInScope(startScope, receiverName, scopes) {
|
|
|
104
178
|
/**
|
|
105
179
|
* Look up a callable (Function/Method/Constructor) by name in the
|
|
106
180
|
* given scope's chain. Uses the dual-source pattern (scope.bindings +
|
|
107
|
-
*
|
|
108
|
-
* free calls to imported functions
|
|
181
|
+
* `lookupBindingsAt` for finalized + augmented) so cross-file
|
|
182
|
+
* imports are visible — without it free calls to imported functions
|
|
183
|
+
* never resolve via the post-pass.
|
|
109
184
|
*
|
|
110
185
|
* Mirrors `findClassBindingInScope` exactly; only the accepted
|
|
111
186
|
* def-type predicate differs.
|
|
@@ -128,13 +203,10 @@ export function findCallableBindingInScope(startScope, callableName, scopes) {
|
|
|
128
203
|
}
|
|
129
204
|
}
|
|
130
205
|
}
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (b.def.type === 'Function' || b.def.type === 'Method' || b.def.type === 'Constructor') {
|
|
136
|
-
return b.def;
|
|
137
|
-
}
|
|
206
|
+
const importedBindings = lookupBindingsAt(currentId, callableName, scopes);
|
|
207
|
+
for (const b of importedBindings) {
|
|
208
|
+
if (b.def.type === 'Function' || b.def.type === 'Method' || b.def.type === 'Constructor') {
|
|
209
|
+
return b.def;
|
|
138
210
|
}
|
|
139
211
|
}
|
|
140
212
|
currentId = scope.parent;
|
|
@@ -272,12 +344,10 @@ export function findExportedDefByName(name, inScope, scopes, index) {
|
|
|
272
344
|
return b.def;
|
|
273
345
|
}
|
|
274
346
|
}
|
|
275
|
-
const finalized =
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
return b.def;
|
|
280
|
-
}
|
|
347
|
+
const finalized = lookupBindingsAt(currentId, name, scopes);
|
|
348
|
+
for (const b of finalized) {
|
|
349
|
+
if (b.def.type === 'Function' || b.def.type === 'Method')
|
|
350
|
+
return b.def;
|
|
281
351
|
}
|
|
282
352
|
currentId = scope.parent;
|
|
283
353
|
}
|
|
@@ -332,7 +402,12 @@ export function findOwnedMember(ownerDefId, memberName, model) {
|
|
|
332
402
|
* excluded.
|
|
333
403
|
*
|
|
334
404
|
* Reads from `WorkspaceResolutionIndex.moduleScopeByFile` (scope-tied
|
|
335
|
-
* lookup that doesn't live on `SemanticModel`).
|
|
405
|
+
* lookup that doesn't live on `SemanticModel`). This intentionally
|
|
406
|
+
* does NOT call `lookupBindingsAt`: `findExportedDef` answers "what
|
|
407
|
+
* did the target file declare locally at module scope?", while
|
|
408
|
+
* `bindingAugmentations` models importer-side visibility created by
|
|
409
|
+
* post-finalize hooks. Callers that need importer-visible exports use
|
|
410
|
+
* `findExportedDefByName`, which is dual-channel aware.
|
|
336
411
|
*/
|
|
337
412
|
export function findExportedDef(targetFile, memberName, index) {
|
|
338
413
|
const moduleScope = index.moduleScopeByFile.get(targetFile);
|
|
@@ -8,3 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
/** Whether we're running in development mode (enables verbose console logging). */
|
|
10
10
|
export declare const isDev: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Whether scope-resolution dev validators (e.g. `validateBindingsImmutability`)
|
|
13
|
+
* should run AND emit warnings. Off by default in CLI runs to avoid silent
|
|
14
|
+
* O(n) scans on large repos; on in `NODE_ENV=development` or when explicitly
|
|
15
|
+
* opted-in via `VALIDATE_SEMANTIC_MODEL=1`. `VALIDATE_SEMANTIC_MODEL=0` is the
|
|
16
|
+
* explicit off switch and wins over both.
|
|
17
|
+
*
|
|
18
|
+
* Read every call (not memoized) so test setups using `vi.stubEnv` work.
|
|
19
|
+
*/
|
|
20
|
+
export declare const isSemanticModelValidatorEnabled: () => boolean;
|
|
@@ -8,3 +8,17 @@
|
|
|
8
8
|
*/
|
|
9
9
|
/** Whether we're running in development mode (enables verbose console logging). */
|
|
10
10
|
export const isDev = process.env.NODE_ENV === 'development';
|
|
11
|
+
/**
|
|
12
|
+
* Whether scope-resolution dev validators (e.g. `validateBindingsImmutability`)
|
|
13
|
+
* should run AND emit warnings. Off by default in CLI runs to avoid silent
|
|
14
|
+
* O(n) scans on large repos; on in `NODE_ENV=development` or when explicitly
|
|
15
|
+
* opted-in via `VALIDATE_SEMANTIC_MODEL=1`. `VALIDATE_SEMANTIC_MODEL=0` is the
|
|
16
|
+
* explicit off switch and wins over both.
|
|
17
|
+
*
|
|
18
|
+
* Read every call (not memoized) so test setups using `vi.stubEnv` work.
|
|
19
|
+
*/
|
|
20
|
+
export const isSemanticModelValidatorEnabled = () => {
|
|
21
|
+
if (process.env.VALIDATE_SEMANTIC_MODEL === '0')
|
|
22
|
+
return false;
|
|
23
|
+
return process.env.NODE_ENV === 'development' || process.env.VALIDATE_SEMANTIC_MODEL === '1';
|
|
24
|
+
};
|
|
@@ -15,7 +15,7 @@ import Ruby from 'tree-sitter-ruby';
|
|
|
15
15
|
import { createRequire } from 'node:module';
|
|
16
16
|
import { SupportedLanguages } from '../../../_shared/index.js';
|
|
17
17
|
import { getProvider } from '../languages/index.js';
|
|
18
|
-
import { getTreeSitterBufferSize, TREE_SITTER_MAX_BUFFER } from '../constants.js';
|
|
18
|
+
import { getTreeSitterBufferSize, getTreeSitterContentByteLength, TREE_SITTER_MAX_BUFFER, } from '../constants.js';
|
|
19
19
|
// tree-sitter-swift is an optionalDependency — may not be installed
|
|
20
20
|
const _require = createRequire(import.meta.url);
|
|
21
21
|
let Swift = null;
|
|
@@ -1001,7 +1001,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1001
1001
|
}
|
|
1002
1002
|
for (const file of files) {
|
|
1003
1003
|
// Skip files larger than the max tree-sitter buffer (32 MB)
|
|
1004
|
-
if (file.content
|
|
1004
|
+
if (getTreeSitterContentByteLength(file.content) > TREE_SITTER_MAX_BUFFER)
|
|
1005
1005
|
continue;
|
|
1006
1006
|
// Vue SFC preprocessing: extract <script> block content
|
|
1007
1007
|
let parseContent = file.content;
|
|
@@ -1019,7 +1019,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1019
1019
|
let tree;
|
|
1020
1020
|
try {
|
|
1021
1021
|
tree = parser.parse(parseContent, undefined, {
|
|
1022
|
-
bufferSize: getTreeSitterBufferSize(parseContent
|
|
1022
|
+
bufferSize: getTreeSitterBufferSize(parseContent),
|
|
1023
1023
|
});
|
|
1024
1024
|
}
|
|
1025
1025
|
catch (err) {
|
package/package.json
CHANGED