gitnexus 1.5.3 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/_shared/graph/types.d.ts +1 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +1 -0
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +2 -0
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/languages.d.ts +1 -0
- package/dist/_shared/languages.d.ts.map +1 -1
- package/dist/_shared/languages.js +1 -0
- package/dist/_shared/languages.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +3 -1
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/mro-strategy.d.ts +19 -0
- package/dist/_shared/mro-strategy.d.ts.map +1 -0
- package/dist/_shared/mro-strategy.js +2 -0
- package/dist/_shared/mro-strategy.js.map +1 -0
- package/dist/cli/ai-context.d.ts +1 -0
- package/dist/cli/ai-context.js +28 -4
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +2 -1
- package/dist/cli/group.d.ts +2 -0
- package/dist/cli/group.js +233 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/serve.js +4 -1
- package/dist/cli/setup.js +34 -3
- package/dist/config/ignore-service.js +8 -3
- package/dist/core/augmentation/engine.js +1 -1
- package/dist/core/git-staleness.d.ts +13 -0
- package/dist/core/git-staleness.js +29 -0
- package/dist/core/group/bridge-db.d.ts +82 -0
- package/dist/core/group/bridge-db.js +460 -0
- package/dist/core/group/bridge-schema.d.ts +27 -0
- package/dist/core/group/bridge-schema.js +55 -0
- package/dist/core/group/config-parser.d.ts +3 -0
- package/dist/core/group/config-parser.js +83 -0
- package/dist/core/group/contract-extractor.d.ts +7 -0
- package/dist/core/group/contract-extractor.js +1 -0
- package/dist/core/group/extractors/grpc-extractor.d.ts +16 -0
- package/dist/core/group/extractors/grpc-extractor.js +264 -0
- package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
- package/dist/core/group/extractors/http-route-extractor.js +428 -0
- package/dist/core/group/extractors/topic-extractor.d.ts +9 -0
- package/dist/core/group/extractors/topic-extractor.js +234 -0
- package/dist/core/group/matching.d.ts +13 -0
- package/dist/core/group/matching.js +198 -0
- package/dist/core/group/normalization.d.ts +3 -0
- package/dist/core/group/normalization.js +115 -0
- package/dist/core/group/service-boundary-detector.d.ts +8 -0
- package/dist/core/group/service-boundary-detector.js +155 -0
- package/dist/core/group/service.d.ts +46 -0
- package/dist/core/group/service.js +160 -0
- package/dist/core/group/storage.d.ts +9 -0
- package/dist/core/group/storage.js +91 -0
- package/dist/core/group/sync.d.ts +21 -0
- package/dist/core/group/sync.js +148 -0
- package/dist/core/group/types.d.ts +130 -0
- package/dist/core/group/types.js +1 -0
- package/dist/core/ingestion/binding-accumulator.d.ts +207 -0
- package/dist/core/ingestion/binding-accumulator.js +332 -0
- package/dist/core/ingestion/call-processor.d.ts +155 -24
- package/dist/core/ingestion/call-processor.js +1129 -247
- package/dist/core/ingestion/class-extractors/generic.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/generic.js +135 -0
- package/dist/core/ingestion/class-types.d.ts +34 -0
- package/dist/core/ingestion/class-types.js +1 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +1 -0
- package/dist/core/ingestion/entry-point-scoring.js +1 -0
- package/dist/core/ingestion/field-types.d.ts +2 -2
- package/dist/core/ingestion/filesystem-walker.js +8 -0
- package/dist/core/ingestion/framework-detection.d.ts +1 -0
- package/dist/core/ingestion/framework-detection.js +1 -0
- package/dist/core/ingestion/heritage-processor.d.ts +8 -15
- package/dist/core/ingestion/heritage-processor.js +15 -28
- package/dist/core/ingestion/import-processor.d.ts +1 -11
- package/dist/core/ingestion/import-processor.js +0 -12
- package/dist/core/ingestion/import-resolvers/utils.js +1 -0
- package/dist/core/ingestion/import-resolvers/vue.d.ts +8 -0
- package/dist/core/ingestion/import-resolvers/vue.js +9 -0
- package/dist/core/ingestion/language-provider.d.ts +6 -3
- package/dist/core/ingestion/languages/c-cpp.js +168 -1
- package/dist/core/ingestion/languages/csharp.js +20 -0
- package/dist/core/ingestion/languages/dart.js +26 -4
- package/dist/core/ingestion/languages/go.js +22 -0
- package/dist/core/ingestion/languages/index.d.ts +1 -0
- package/dist/core/ingestion/languages/index.js +2 -0
- package/dist/core/ingestion/languages/java.js +17 -0
- package/dist/core/ingestion/languages/kotlin.js +24 -1
- package/dist/core/ingestion/languages/php.js +23 -11
- package/dist/core/ingestion/languages/python.js +9 -0
- package/dist/core/ingestion/languages/ruby.js +28 -0
- package/dist/core/ingestion/languages/rust.js +38 -0
- package/dist/core/ingestion/languages/swift.js +31 -0
- package/dist/core/ingestion/languages/typescript.d.ts +1 -0
- package/dist/core/ingestion/languages/typescript.js +52 -3
- package/dist/core/ingestion/languages/vue.d.ts +13 -0
- package/dist/core/ingestion/languages/vue.js +81 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.d.ts +3 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +387 -0
- package/dist/core/ingestion/method-extractors/configs/csharp.js +5 -1
- package/dist/core/ingestion/method-extractors/configs/dart.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/dart.js +376 -0
- package/dist/core/ingestion/method-extractors/configs/go.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/go.js +176 -0
- package/dist/core/ingestion/method-extractors/configs/jvm.js +13 -4
- package/dist/core/ingestion/method-extractors/configs/php.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/php.js +304 -0
- package/dist/core/ingestion/method-extractors/configs/python.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/python.js +309 -0
- package/dist/core/ingestion/method-extractors/configs/ruby.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/ruby.js +285 -0
- package/dist/core/ingestion/method-extractors/configs/rust.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/rust.js +195 -0
- package/dist/core/ingestion/method-extractors/configs/swift.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/swift.js +277 -0
- package/dist/core/ingestion/method-extractors/configs/typescript-javascript.js +85 -8
- package/dist/core/ingestion/method-extractors/generic.js +38 -15
- package/dist/core/ingestion/method-types.d.ts +25 -0
- package/dist/core/ingestion/model/field-registry.d.ts +18 -0
- package/dist/core/ingestion/model/field-registry.js +22 -0
- package/dist/core/ingestion/model/heritage-map.d.ts +70 -0
- package/dist/core/ingestion/model/heritage-map.js +159 -0
- package/dist/core/ingestion/model/index.d.ts +20 -0
- package/dist/core/ingestion/model/index.js +41 -0
- package/dist/core/ingestion/model/method-registry.d.ts +62 -0
- package/dist/core/ingestion/model/method-registry.js +130 -0
- package/dist/core/ingestion/model/registration-table.d.ts +139 -0
- package/dist/core/ingestion/model/registration-table.js +224 -0
- package/dist/core/ingestion/model/resolution-context.d.ts +93 -0
- package/dist/core/ingestion/model/resolution-context.js +337 -0
- package/dist/core/ingestion/model/resolve.d.ts +56 -0
- package/dist/core/ingestion/model/resolve.js +242 -0
- package/dist/core/ingestion/model/semantic-model.d.ts +86 -0
- package/dist/core/ingestion/model/semantic-model.js +120 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +222 -0
- package/dist/core/ingestion/model/symbol-table.js +206 -0
- package/dist/core/ingestion/model/type-registry.d.ts +39 -0
- package/dist/core/ingestion/model/type-registry.js +62 -0
- package/dist/core/ingestion/mro-processor.d.ts +4 -3
- package/dist/core/ingestion/mro-processor.js +310 -106
- package/dist/core/ingestion/parsing-processor.d.ts +5 -4
- package/dist/core/ingestion/parsing-processor.js +210 -85
- package/dist/core/ingestion/pipeline.d.ts +2 -0
- package/dist/core/ingestion/pipeline.js +192 -68
- package/dist/core/ingestion/tree-sitter-queries.d.ts +5 -5
- package/dist/core/ingestion/tree-sitter-queries.js +21 -0
- package/dist/core/ingestion/type-env.d.ts +15 -2
- package/dist/core/ingestion/type-env.js +163 -102
- package/dist/core/ingestion/type-extractors/csharp.js +17 -0
- package/dist/core/ingestion/type-extractors/jvm.js +11 -0
- package/dist/core/ingestion/type-extractors/php.js +0 -55
- package/dist/core/ingestion/type-extractors/ruby.js +0 -32
- package/dist/core/ingestion/type-extractors/swift.js +13 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +8 -8
- package/dist/core/ingestion/type-extractors/typescript.js +66 -69
- package/dist/core/ingestion/utils/ast-helpers.d.ts +33 -43
- package/dist/core/ingestion/utils/ast-helpers.js +129 -572
- package/dist/core/ingestion/utils/method-props.d.ts +32 -0
- package/dist/core/ingestion/utils/method-props.js +147 -0
- package/dist/core/ingestion/vue-sfc-extractor.d.ts +44 -0
- package/dist/core/ingestion/vue-sfc-extractor.js +94 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +31 -19
- package/dist/core/ingestion/workers/parse-worker.js +463 -198
- package/dist/core/lbug/lbug-adapter.d.ts +6 -0
- package/dist/core/lbug/lbug-adapter.js +68 -3
- package/dist/core/lbug/pool-adapter.d.ts +76 -0
- package/dist/core/lbug/pool-adapter.js +522 -0
- package/dist/core/run-analyze.d.ts +2 -0
- package/dist/core/run-analyze.js +1 -1
- package/dist/core/search/bm25-index.js +1 -1
- package/dist/core/tree-sitter/parser-loader.js +1 -0
- package/dist/core/wiki/graph-queries.js +1 -1
- package/dist/mcp/core/embedder.js +6 -5
- package/dist/mcp/core/lbug-adapter.d.ts +3 -63
- package/dist/mcp/core/lbug-adapter.js +3 -484
- package/dist/mcp/local/local-backend.d.ts +31 -2
- package/dist/mcp/local/local-backend.js +255 -46
- package/dist/mcp/resources.js +5 -4
- package/dist/mcp/staleness.d.ts +3 -13
- package/dist/mcp/staleness.js +2 -31
- package/dist/mcp/tools.js +80 -4
- package/dist/server/analyze-job.d.ts +2 -0
- package/dist/server/analyze-job.js +4 -0
- package/dist/server/api.d.ts +20 -1
- package/dist/server/api.js +306 -71
- package/dist/server/git-clone.d.ts +2 -1
- package/dist/server/git-clone.js +98 -5
- package/dist/storage/git.d.ts +13 -0
- package/dist/storage/git.js +25 -0
- package/dist/storage/repo-manager.js +1 -1
- package/package.json +8 -2
- package/scripts/patch-tree-sitter-swift.cjs +78 -0
- package/dist/core/ingestion/named-binding-processor.d.ts +0 -18
- package/dist/core/ingestion/named-binding-processor.js +0 -42
- package/dist/core/ingestion/resolution-context.d.ts +0 -58
- package/dist/core/ingestion/resolution-context.js +0 -135
- package/dist/core/ingestion/symbol-table.d.ts +0 -79
- package/dist/core/ingestion/symbol-table.js +0 -115
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FUNCTION_NODE_TYPES,
|
|
1
|
+
import { FUNCTION_NODE_TYPES, CLASS_CONTAINER_TYPES, genericFuncName, } from './utils/ast-helpers.js';
|
|
2
2
|
import { CALL_EXPRESSION_TYPES } from './utils/call-analysis.js';
|
|
3
3
|
import { TYPED_PARAMETER_TYPES } from './type-extractors/shared.js';
|
|
4
4
|
import { getProvider } from './languages/index.js';
|
|
@@ -6,7 +6,21 @@ import { extractSimpleTypeName, extractVarName, stripNullable, extractReturnType
|
|
|
6
6
|
/** File-level scope key */
|
|
7
7
|
const FILE_SCOPE = '';
|
|
8
8
|
/** Shared empty map for files with no file-scope bindings. */
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Create a fresh empty Map for the "no file-scope bindings" fallback.
|
|
11
|
+
*
|
|
12
|
+
* **Why not a shared sentinel**: we previously used a module-level
|
|
13
|
+
* `const EMPTY_FILE_SCOPE = new Map()` typed as `ReadonlyMap` and shared
|
|
14
|
+
* across every TypeEnv instance. That was a latent singleton-poisoning
|
|
15
|
+
* footgun: any caller that did `(fileScope() as Map).set(...)` — or any
|
|
16
|
+
* future refactor that widened the return type — would silently corrupt
|
|
17
|
+
* every subsequent "empty" return for the process lifetime. A Proxy
|
|
18
|
+
* wrapper was considered but broke Map's internal-slot methods (`.size`,
|
|
19
|
+
* iteration protocol). Allocating a fresh empty Map per call is a few
|
|
20
|
+
* bytes per file — immediately GC'd, no measurable cost even at 10k files
|
|
21
|
+
* — and eliminates the shared-mutation hazard entirely.
|
|
22
|
+
*/
|
|
23
|
+
const emptyFileScope = () => new Map();
|
|
10
24
|
/** Fallback for languages where class names aren't in a 'name' field (e.g. Kotlin uses type_identifier). */
|
|
11
25
|
const findTypeIdentifierChild = (node) => {
|
|
12
26
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -53,7 +67,7 @@ const fastStripNullable = (typeName) => {
|
|
|
53
67
|
: stripNullable(typeName);
|
|
54
68
|
};
|
|
55
69
|
/** Implementation of the lookup logic — shared between TypeEnvironment and the legacy export. */
|
|
56
|
-
const lookupInEnv = (env, varName, callNode, patternOverrides, enclosingFunctionFinder) => {
|
|
70
|
+
const lookupInEnv = (env, varName, callNode, patternOverrides, enclosingFunctionFinder, extractFunctionNameHook) => {
|
|
57
71
|
// Self/this receiver: resolve to enclosing class name via AST walk
|
|
58
72
|
if (varName === 'self' || varName === 'this' || varName === '$this') {
|
|
59
73
|
return findEnclosingClassName(callNode);
|
|
@@ -64,7 +78,7 @@ const lookupInEnv = (env, varName, callNode, patternOverrides, enclosingFunction
|
|
|
64
78
|
return findEnclosingParentClassName(callNode);
|
|
65
79
|
}
|
|
66
80
|
// Determine the enclosing function scope for the call
|
|
67
|
-
const scopeKey = findEnclosingScopeKey(callNode, enclosingFunctionFinder);
|
|
81
|
+
const scopeKey = findEnclosingScopeKey(callNode, enclosingFunctionFinder, extractFunctionNameHook);
|
|
68
82
|
// Check position-indexed pattern overrides first (e.g., Kotlin when/is smart casts).
|
|
69
83
|
// These take priority over flat scopeEnv because they represent per-branch narrowing.
|
|
70
84
|
if (scopeKey && patternOverrides) {
|
|
@@ -272,11 +286,11 @@ const extractParentClassFromNode = (classNode) => {
|
|
|
272
286
|
* it is consulted for each ancestor before the default FUNCTION_NODE_TYPES check.
|
|
273
287
|
* This handles languages like Dart where the function body is a sibling of the
|
|
274
288
|
* signature instead of a child. */
|
|
275
|
-
const findEnclosingScopeKey = (node, enclosingFunctionFinder) => {
|
|
289
|
+
const findEnclosingScopeKey = (node, enclosingFunctionFinder, extractFunctionNameHook) => {
|
|
276
290
|
let current = node.parent;
|
|
277
291
|
while (current) {
|
|
278
292
|
if (FUNCTION_NODE_TYPES.has(current.type)) {
|
|
279
|
-
const
|
|
293
|
+
const funcName = extractFunctionNameHook?.(current)?.funcName ?? genericFuncName(current);
|
|
280
294
|
if (funcName)
|
|
281
295
|
return `${funcName}@${current.startIndex}`;
|
|
282
296
|
}
|
|
@@ -300,10 +314,10 @@ const findEnclosingScopeKey = (node, enclosingFunctionFinder) => {
|
|
|
300
314
|
* using cross-file type information when available.
|
|
301
315
|
*
|
|
302
316
|
* Only `.has()` is exposed — the SymbolTable doesn't support iteration.
|
|
303
|
-
* Results are memoized to avoid redundant
|
|
317
|
+
* Results are memoized to avoid redundant class-index scans across declarations.
|
|
304
318
|
*/
|
|
305
|
-
const createClassNameLookup = (localNames,
|
|
306
|
-
if (!
|
|
319
|
+
const createClassNameLookup = (localNames, model) => {
|
|
320
|
+
if (!model)
|
|
307
321
|
return localNames;
|
|
308
322
|
const memo = new Map();
|
|
309
323
|
return {
|
|
@@ -313,8 +327,8 @@ const createClassNameLookup = (localNames, symbolTable) => {
|
|
|
313
327
|
const cached = memo.get(name);
|
|
314
328
|
if (cached !== undefined)
|
|
315
329
|
return cached;
|
|
316
|
-
const result =
|
|
317
|
-
.
|
|
330
|
+
const result = model.types
|
|
331
|
+
.lookupClassByName(name)
|
|
318
332
|
.some((def) => def.type === 'Class' || def.type === 'Enum' || def.type === 'Struct');
|
|
319
333
|
memo.set(name, result);
|
|
320
334
|
return result;
|
|
@@ -361,17 +375,16 @@ const SKIP_SUBTREE_TYPES = new Set([
|
|
|
361
375
|
'regex_pattern',
|
|
362
376
|
]);
|
|
363
377
|
const CLASS_LIKE_TYPES = new Set(['Class', 'Struct', 'Interface']);
|
|
378
|
+
const lookupClassDefsByName = (model, name, allowedTypes = CLASS_LIKE_TYPES) => model.types.lookupClassByName(name).filter((d) => allowedTypes.has(d.type));
|
|
364
379
|
/** Memoize class definition lookups during fixpoint iteration.
|
|
365
380
|
* SymbolTable is immutable during type resolution, so results never change.
|
|
366
381
|
* Eliminates redundant array allocations + filter scans across iterations. */
|
|
367
|
-
const createClassDefCache = (
|
|
382
|
+
const createClassDefCache = (model) => {
|
|
368
383
|
const cache = new Map();
|
|
369
384
|
return (typeName) => {
|
|
370
385
|
let result = cache.get(typeName);
|
|
371
386
|
if (result === undefined) {
|
|
372
|
-
result =
|
|
373
|
-
? symbolTable.lookupFuzzy(typeName).filter((d) => CLASS_LIKE_TYPES.has(d.type))
|
|
374
|
-
: [];
|
|
387
|
+
result = model ? lookupClassDefsByName(model, typeName) : [];
|
|
375
388
|
cache.set(typeName, result);
|
|
376
389
|
}
|
|
377
390
|
return result;
|
|
@@ -484,60 +497,75 @@ const walkParentChain = (typeName, parentMap, getClassDefs, lookupOnClass) => {
|
|
|
484
497
|
* Uses SymbolTable to find the class nodeId for the receiver's type, then
|
|
485
498
|
* looks up the field via the eagerly-populated fieldByOwner index.
|
|
486
499
|
* Falls back to MRO parent chain walking if direct lookup fails (Phase 11A). */
|
|
487
|
-
const resolveFieldType = (receiver, field, scopeEnv,
|
|
488
|
-
if (!
|
|
500
|
+
const resolveFieldType = (receiver, field, scopeEnv, model, getClassDefs, parentMap) => {
|
|
501
|
+
if (!model)
|
|
489
502
|
return undefined;
|
|
490
503
|
const receiverType = scopeEnv.get(receiver);
|
|
491
504
|
if (!receiverType)
|
|
492
505
|
return undefined;
|
|
493
|
-
const lookup = getClassDefs ??
|
|
494
|
-
((name) => symbolTable.lookupFuzzy(name).filter((d) => CLASS_LIKE_TYPES.has(d.type)));
|
|
506
|
+
const lookup = getClassDefs ?? ((name) => lookupClassDefsByName(model, name));
|
|
495
507
|
const classDefs = lookup(receiverType);
|
|
496
508
|
if (classDefs.length !== 1)
|
|
497
509
|
return undefined;
|
|
498
510
|
// Direct lookup first
|
|
499
|
-
const fieldDef =
|
|
511
|
+
const fieldDef = model.fields.lookupFieldByOwner(classDefs[0].nodeId, field);
|
|
500
512
|
if (fieldDef?.declaredType)
|
|
501
513
|
return extractReturnTypeName(fieldDef.declaredType);
|
|
502
514
|
// MRO parent chain walking on miss
|
|
503
515
|
const inherited = walkParentChain(receiverType, parentMap, lookup, (nodeId) => {
|
|
504
|
-
const f =
|
|
516
|
+
const f = model.fields.lookupFieldByOwner(nodeId, field);
|
|
505
517
|
return f?.declaredType ? extractReturnTypeName(f.declaredType) : undefined;
|
|
506
518
|
});
|
|
507
519
|
return inherited;
|
|
508
520
|
};
|
|
509
521
|
/** Resolve a method's return type given a receiver variable and method name.
|
|
510
522
|
* Uses SymbolTable to find class nodeIds for the receiver's type, then
|
|
511
|
-
* looks up the method via
|
|
523
|
+
* looks up the method via owner-scoped lookupMethodByOwner.
|
|
512
524
|
* Falls back to MRO parent chain walking if direct lookup fails (Phase 11A). */
|
|
513
|
-
const resolveMethodReturnType = (receiver, method, scopeEnv,
|
|
514
|
-
if (!
|
|
525
|
+
const resolveMethodReturnType = (receiver, method, scopeEnv, model, getClassDefs, parentMap) => {
|
|
526
|
+
if (!model)
|
|
515
527
|
return undefined;
|
|
516
|
-
|
|
528
|
+
let receiverType = scopeEnv.get(receiver);
|
|
529
|
+
// When substituteThisReceiver replaced $this/self with the enclosing class name,
|
|
530
|
+
// the receiver IS the type — look it up directly as a class name.
|
|
531
|
+
if (!receiverType) {
|
|
532
|
+
const lookup = getClassDefs ?? ((name) => lookupClassDefsByName(model, name));
|
|
533
|
+
if (lookup(receiver).length > 0)
|
|
534
|
+
receiverType = receiver;
|
|
535
|
+
}
|
|
517
536
|
if (!receiverType)
|
|
518
537
|
return undefined;
|
|
519
|
-
const lookup = getClassDefs ??
|
|
520
|
-
((name) => symbolTable.lookupFuzzy(name).filter((d) => CLASS_LIKE_TYPES.has(d.type)));
|
|
538
|
+
const lookup = getClassDefs ?? ((name) => lookupClassDefsByName(model, name));
|
|
521
539
|
const classDefs = lookup(receiverType);
|
|
522
540
|
if (classDefs.length === 0)
|
|
523
541
|
return undefined;
|
|
524
542
|
// Direct lookup first
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
.
|
|
528
|
-
|
|
543
|
+
const directMethodLookups = classDefs.map((d) => ({
|
|
544
|
+
classDef: d,
|
|
545
|
+
methodDef: model.methods.lookupMethodByOwner(d.nodeId, method),
|
|
546
|
+
}));
|
|
547
|
+
const hasAmbiguousDirectLookup = directMethodLookups.some(({ classDef, methodDef }) => {
|
|
548
|
+
if (methodDef)
|
|
549
|
+
return false;
|
|
550
|
+
return model.symbols
|
|
551
|
+
.lookupExactAll(classDef.filePath, method)
|
|
552
|
+
.some((d) => d.ownerId === classDef.nodeId);
|
|
553
|
+
});
|
|
554
|
+
if (hasAmbiguousDirectLookup)
|
|
555
|
+
return undefined;
|
|
556
|
+
const methods = directMethodLookups
|
|
557
|
+
.map(({ methodDef }) => methodDef)
|
|
558
|
+
.filter((d) => d !== undefined);
|
|
529
559
|
if (methods.length === 1 && methods[0].returnType) {
|
|
530
560
|
return extractReturnTypeName(methods[0].returnType);
|
|
531
561
|
}
|
|
532
562
|
// MRO parent chain walking on miss
|
|
533
563
|
if (methods.length === 0) {
|
|
534
564
|
const inherited = walkParentChain(receiverType, parentMap, lookup, (nodeId) => {
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
.filter((d) => d.ownerId === nodeId);
|
|
538
|
-
if (parentMethods.length !== 1 || !parentMethods[0].returnType)
|
|
565
|
+
const parentMethod = model.methods.lookupMethodByOwner(nodeId, method);
|
|
566
|
+
if (!parentMethod?.returnType)
|
|
539
567
|
return undefined;
|
|
540
|
-
return extractReturnTypeName(
|
|
568
|
+
return extractReturnTypeName(parentMethod.returnType);
|
|
541
569
|
});
|
|
542
570
|
return inherited;
|
|
543
571
|
}
|
|
@@ -555,10 +583,10 @@ const resolveMethodReturnType = (receiver, method, scopeEnv, symbolTable, getCla
|
|
|
555
583
|
* Termination: finite entries, each bound at most once (first-writer-wins), max 10 iterations.
|
|
556
584
|
*/
|
|
557
585
|
const MAX_FIXPOINT_ITERATIONS = 10;
|
|
558
|
-
const resolveFixpointBindings = (pendingItems, env, returnTypeLookup,
|
|
586
|
+
const resolveFixpointBindings = (pendingItems, env, returnTypeLookup, model, parentMap) => {
|
|
559
587
|
if (pendingItems.length === 0)
|
|
560
588
|
return;
|
|
561
|
-
const getClassDefs = createClassDefCache(
|
|
589
|
+
const getClassDefs = createClassDefCache(model);
|
|
562
590
|
const resolved = new Set();
|
|
563
591
|
for (let iter = 0; iter < MAX_FIXPOINT_ITERATIONS; iter++) {
|
|
564
592
|
let changed = false;
|
|
@@ -583,10 +611,10 @@ const resolveFixpointBindings = (pendingItems, env, returnTypeLookup, symbolTabl
|
|
|
583
611
|
typeName = scopeEnv.get(item.rhs) ?? env.get(FILE_SCOPE)?.get(item.rhs);
|
|
584
612
|
break;
|
|
585
613
|
case 'fieldAccess':
|
|
586
|
-
typeName = resolveFieldType(item.receiver, item.field, scopeEnv,
|
|
614
|
+
typeName = resolveFieldType(item.receiver, item.field, scopeEnv, model, getClassDefs, parentMap);
|
|
587
615
|
break;
|
|
588
616
|
case 'methodCallResult':
|
|
589
|
-
typeName = resolveMethodReturnType(item.receiver, item.method, scopeEnv,
|
|
617
|
+
typeName = resolveMethodReturnType(item.receiver, item.method, scopeEnv, model, getClassDefs, parentMap);
|
|
590
618
|
break;
|
|
591
619
|
default: {
|
|
592
620
|
// Exhaustive check: TypeScript will error here if a new PendingAssignment
|
|
@@ -630,50 +658,68 @@ export const buildTypeEnv = (tree, language, options) => {
|
|
|
630
658
|
// Clear per-file memoization caches from the previous file.
|
|
631
659
|
enclosingClassNameCache.clear();
|
|
632
660
|
enclosingParentClassNameCache.clear();
|
|
633
|
-
const
|
|
661
|
+
const model = options?.model;
|
|
634
662
|
const parentMap = options?.parentMap;
|
|
663
|
+
const extractFuncNameHook = options?.extractFunctionName;
|
|
635
664
|
const env = new Map();
|
|
665
|
+
let flushed = false;
|
|
636
666
|
const patternOverrides = new Map();
|
|
637
667
|
// Phase P: maps `scope\0varName` → constructor type when a declaration has BOTH
|
|
638
668
|
// a base type annotation AND a more specific constructor initializer.
|
|
639
669
|
// e.g., `Animal a = new Dog()` → constructorTypeMap.set('func@42\0a', 'Dog')
|
|
640
670
|
const constructorTypeMap = new Map();
|
|
641
671
|
const localClassNames = new Set();
|
|
642
|
-
const classNames = createClassNameLookup(localClassNames,
|
|
672
|
+
const classNames = createClassNameLookup(localClassNames, model);
|
|
643
673
|
const provider = getProvider(language);
|
|
644
674
|
const config = provider.typeConfig;
|
|
645
675
|
const bindings = [];
|
|
646
676
|
// Build ReturnTypeLookup: SymbolTable is authoritative when it has an unambiguous match.
|
|
647
677
|
// Cross-file importedReturnTypes are consulted ONLY when SymbolTable has 0 matches.
|
|
648
678
|
// Ambiguous (2+) → undefined, no cross-file fallback (conservative, local-first principle).
|
|
679
|
+
// Post-A4 Unit 4: callableByName no longer holds Method/Constructor, so
|
|
680
|
+
// for-loop binding inference must also consult methodsByName to find
|
|
681
|
+
// return types on class methods (e.g. `user.getItems()` iteration).
|
|
682
|
+
// Take `model` as an explicit argument so the non-null precondition
|
|
683
|
+
// is visible at the type level. Callers must enter these via an
|
|
684
|
+
// `if (model)` guard on their side and pass the narrowed reference.
|
|
685
|
+
const getCallableUnionCount = (m, callee) => {
|
|
686
|
+
return (m.symbols.lookupCallableByName(callee).length + m.methods.lookupMethodByName(callee).length);
|
|
687
|
+
};
|
|
688
|
+
const getFirstCallable = (m, callee) => {
|
|
689
|
+
const free = m.symbols.lookupCallableByName(callee);
|
|
690
|
+
if (free.length > 0)
|
|
691
|
+
return free[0];
|
|
692
|
+
const methods = m.methods.lookupMethodByName(callee);
|
|
693
|
+
return methods.length > 0 ? methods[0] : undefined;
|
|
694
|
+
};
|
|
649
695
|
const returnTypeLookup = {
|
|
650
696
|
lookupReturnType(callee) {
|
|
651
697
|
// SymbolTable is authoritative when it has an unambiguous match
|
|
652
|
-
if (
|
|
698
|
+
if (model) {
|
|
653
699
|
if (provider.isBuiltInName(callee))
|
|
654
700
|
return undefined;
|
|
655
|
-
const
|
|
656
|
-
if (
|
|
657
|
-
const rawReturn =
|
|
701
|
+
const count = getCallableUnionCount(model, callee);
|
|
702
|
+
if (count === 1) {
|
|
703
|
+
const rawReturn = getFirstCallable(model, callee)?.returnType;
|
|
658
704
|
if (rawReturn)
|
|
659
705
|
return extractReturnTypeName(rawReturn);
|
|
660
706
|
}
|
|
661
707
|
// Ambiguous (2+) → return undefined (conservative, no cross-file fallback)
|
|
662
|
-
if (
|
|
708
|
+
if (count > 1)
|
|
663
709
|
return undefined;
|
|
664
710
|
}
|
|
665
711
|
// No match (0 results or no symbolTable) → fall back to cross-file
|
|
666
712
|
return options?.importedReturnTypes?.get(callee);
|
|
667
713
|
},
|
|
668
714
|
lookupRawReturnType(callee) {
|
|
669
|
-
if (
|
|
715
|
+
if (model) {
|
|
670
716
|
if (provider.isBuiltInName(callee))
|
|
671
717
|
return undefined;
|
|
672
|
-
const
|
|
673
|
-
if (
|
|
674
|
-
return
|
|
718
|
+
const count = getCallableUnionCount(model, callee);
|
|
719
|
+
if (count === 1)
|
|
720
|
+
return getFirstCallable(model, callee)?.returnType;
|
|
675
721
|
// Ambiguous (2+) → return undefined (conservative, no cross-file fallback)
|
|
676
|
-
if (
|
|
722
|
+
if (count > 1)
|
|
677
723
|
return undefined;
|
|
678
724
|
}
|
|
679
725
|
// Cross-file fallback uses importedRawReturnTypes (raw declared types, e.g., 'User[]')
|
|
@@ -789,47 +835,18 @@ export const buildTypeEnv = (tree, language, options) => {
|
|
|
789
835
|
// This decouples type node capture from scopeEnv success — container types
|
|
790
836
|
// (User[], []User, List[User]) that fail extractSimpleTypeName still get
|
|
791
837
|
// their AST type node recorded for Strategy 1 for-loop resolution.
|
|
792
|
-
//
|
|
793
|
-
//
|
|
794
|
-
//
|
|
795
|
-
let typeNode = node.childForFieldName('type');
|
|
838
|
+
//
|
|
839
|
+
// Prefer language-specific locator when provided (keeps buildTypeEnv generic),
|
|
840
|
+
// then fall back to a small set of safe, cross-grammar heuristics.
|
|
841
|
+
let typeNode = config.getDeclarationTypeNode?.(node) ?? node.childForFieldName('type') ?? null;
|
|
842
|
+
// Fallback: some grammars wrap type annotations in a `type_annotation` child
|
|
843
|
+
// instead of exposing a named `type` field on the declaration node.
|
|
796
844
|
if (!typeNode) {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
const c = node.namedChild(i);
|
|
803
|
-
if (c?.type === 'variable_declaration') {
|
|
804
|
-
wrapped = c;
|
|
805
|
-
break;
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
if (wrapped) {
|
|
810
|
-
typeNode = wrapped.childForFieldName('type');
|
|
811
|
-
// Kotlin: variable_declaration stores the type as user_type / nullable_type
|
|
812
|
-
// child rather than a named 'type' field.
|
|
813
|
-
if (!typeNode) {
|
|
814
|
-
for (let i = 0; i < wrapped.namedChildCount; i++) {
|
|
815
|
-
const c = wrapped.namedChild(i);
|
|
816
|
-
if (c && (c.type === 'user_type' || c.type === 'nullable_type')) {
|
|
817
|
-
typeNode = c;
|
|
818
|
-
break;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
// Swift: property_declaration has type_annotation as a direct child (not a 'type' field).
|
|
824
|
-
// Extract the inner type node (array_type, user_type, etc.) for declarationTypeNodes.
|
|
825
|
-
if (!typeNode) {
|
|
826
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
827
|
-
const c = node.namedChild(i);
|
|
828
|
-
if (c?.type === 'type_annotation') {
|
|
829
|
-
// Use the inner type (array_type, user_type) rather than the annotation wrapper
|
|
830
|
-
typeNode = c.firstNamedChild ?? c;
|
|
831
|
-
break;
|
|
832
|
-
}
|
|
845
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
846
|
+
const c = node.namedChild(i);
|
|
847
|
+
if (c?.type === 'type_annotation') {
|
|
848
|
+
typeNode = c.firstNamedChild ?? c;
|
|
849
|
+
break;
|
|
833
850
|
}
|
|
834
851
|
}
|
|
835
852
|
}
|
|
@@ -900,7 +917,10 @@ export const buildTypeEnv = (tree, language, options) => {
|
|
|
900
917
|
}
|
|
901
918
|
}
|
|
902
919
|
};
|
|
903
|
-
const
|
|
920
|
+
const stack = [
|
|
921
|
+
{ node: tree.rootNode, scope: FILE_SCOPE },
|
|
922
|
+
];
|
|
923
|
+
const processNode = (node, currentScope) => {
|
|
904
924
|
// Fast skip: subtrees that can never contain type-relevant nodes (leaf-like literals).
|
|
905
925
|
if (SKIP_SUBTREE_TYPES.has(node.type))
|
|
906
926
|
return;
|
|
@@ -916,7 +936,7 @@ export const buildTypeEnv = (tree, language, options) => {
|
|
|
916
936
|
// Detect scope boundaries (function/method definitions)
|
|
917
937
|
let scope = currentScope;
|
|
918
938
|
if (FUNCTION_NODE_TYPES.has(node.type)) {
|
|
919
|
-
const
|
|
939
|
+
const funcName = extractFuncNameHook?.(node)?.funcName ?? genericFuncName(node);
|
|
920
940
|
if (funcName)
|
|
921
941
|
scope = `${funcName}@${node.startIndex}`;
|
|
922
942
|
}
|
|
@@ -1014,20 +1034,25 @@ export const buildTypeEnv = (tree, language, options) => {
|
|
|
1014
1034
|
}
|
|
1015
1035
|
}
|
|
1016
1036
|
}
|
|
1017
|
-
//
|
|
1018
|
-
for (let i =
|
|
1037
|
+
// Push children onto stack (reverse order so first child is processed first)
|
|
1038
|
+
for (let i = node.childCount - 1; i >= 0; i--) {
|
|
1019
1039
|
const child = node.child(i);
|
|
1020
1040
|
if (child)
|
|
1021
|
-
|
|
1041
|
+
stack.push({ node: child, scope });
|
|
1022
1042
|
}
|
|
1023
1043
|
};
|
|
1024
|
-
|
|
1044
|
+
// Iterative traversal using explicit stack instead of recursion
|
|
1045
|
+
// to avoid "Maximum call stack size exceeded" on large files (2000+ lines)
|
|
1046
|
+
while (stack.length > 0) {
|
|
1047
|
+
const { node, scope } = stack.pop();
|
|
1048
|
+
processNode(node, scope);
|
|
1049
|
+
}
|
|
1025
1050
|
// Phase 14: Seed cross-file bindings from upstream files AFTER walk
|
|
1026
1051
|
// (local declarations from walk() take precedence — first-writer-wins)
|
|
1027
1052
|
if (options?.importedBindings && options.importedBindings.size > 0) {
|
|
1028
1053
|
seedImportedBindings(env, options.importedBindings);
|
|
1029
1054
|
}
|
|
1030
|
-
resolveFixpointBindings(pendingItems, env, returnTypeLookup,
|
|
1055
|
+
resolveFixpointBindings(pendingItems, env, returnTypeLookup, model, parentMap);
|
|
1031
1056
|
// Post-fixpoint for-loop replay (Phase 10 / ex-9B loop-fixpoint bridge):
|
|
1032
1057
|
// For-loop nodes whose iterables were unresolved at walk-time may now be
|
|
1033
1058
|
// resolvable because the fixpoint bound the iterable's type.
|
|
@@ -1054,14 +1079,50 @@ export const buildTypeEnv = (tree, language, options) => {
|
|
|
1054
1079
|
return scopeEnv && !scopeEnv.has(item.lhs);
|
|
1055
1080
|
});
|
|
1056
1081
|
if (unresolvedBefore.length > 0) {
|
|
1057
|
-
resolveFixpointBindings(unresolvedBefore, env, returnTypeLookup,
|
|
1082
|
+
resolveFixpointBindings(unresolvedBefore, env, returnTypeLookup, model);
|
|
1058
1083
|
}
|
|
1059
1084
|
}
|
|
1060
1085
|
return {
|
|
1061
|
-
lookup: (varName, callNode) => lookupInEnv(env, varName, callNode, patternOverrides, options?.enclosingFunctionFinder),
|
|
1086
|
+
lookup: (varName, callNode) => lookupInEnv(env, varName, callNode, patternOverrides, options?.enclosingFunctionFinder, extractFuncNameHook),
|
|
1062
1087
|
constructorBindings: bindings,
|
|
1063
|
-
fileScope: () => env.get(FILE_SCOPE) ??
|
|
1088
|
+
fileScope: () => env.get(FILE_SCOPE) ?? emptyFileScope(),
|
|
1064
1089
|
allScopes: () => env,
|
|
1065
1090
|
constructorTypeMap,
|
|
1091
|
+
flush(filePath, accumulator) {
|
|
1092
|
+
if (flushed) {
|
|
1093
|
+
throw new Error(`[TypeEnvironment] flush called twice for ${filePath} — flush is single-use`);
|
|
1094
|
+
}
|
|
1095
|
+
// Narrow flush() to iterate only the FILE_SCOPE entry, mirroring the
|
|
1096
|
+
// worker-path narrowing in parse-worker.ts (commit 803631fe). Before
|
|
1097
|
+
// this change, both execution paths had the same asymmetry bug: the
|
|
1098
|
+
// worker path was fixed but the sequential path (this code) still
|
|
1099
|
+
// wrote function-scope entries into long-lived accumulator storage
|
|
1100
|
+
// that no consumer reads until Phase 9 lands.
|
|
1101
|
+
//
|
|
1102
|
+
// Phase 9 reversion: when a downstream consumer of function-scope
|
|
1103
|
+
// bindings exists, restore the nested iteration:
|
|
1104
|
+
//
|
|
1105
|
+
// for (const [scope, scopeMap] of env) {
|
|
1106
|
+
// for (const [varName, typeName] of scopeMap) {
|
|
1107
|
+
// entries.push({ scope, varName, typeName });
|
|
1108
|
+
// }
|
|
1109
|
+
// }
|
|
1110
|
+
//
|
|
1111
|
+
// See BindingAccumulator class JSDoc and FileScopeBindings JSDoc in
|
|
1112
|
+
// parse-worker.ts for the full reversion checklist.
|
|
1113
|
+
const fileScope = env.get(FILE_SCOPE) ?? emptyFileScope();
|
|
1114
|
+
const entries = [];
|
|
1115
|
+
for (const [varName, typeName] of fileScope) {
|
|
1116
|
+
entries.push({ scope: '', varName, typeName });
|
|
1117
|
+
}
|
|
1118
|
+
if (entries.length > 0) {
|
|
1119
|
+
accumulator.appendFile(filePath, entries);
|
|
1120
|
+
}
|
|
1121
|
+
// Mark the env as flushed AFTER the successful append. If appendFile
|
|
1122
|
+
// throws (e.g., accumulator is already finalized due to a lifecycle
|
|
1123
|
+
// ordering bug), the caller can catch and retry — the single-use
|
|
1124
|
+
// guard now tracks "data was written", not "flush was attempted".
|
|
1125
|
+
flushed = true;
|
|
1126
|
+
},
|
|
1066
1127
|
};
|
|
1067
1128
|
};
|
|
@@ -549,6 +549,23 @@ const inferLiteralType = (node) => {
|
|
|
549
549
|
};
|
|
550
550
|
export const typeConfig = {
|
|
551
551
|
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
|
552
|
+
getDeclarationTypeNode: (node) => {
|
|
553
|
+
// C# field_declaration / local_declaration_statement wrap type inside variable_declaration.
|
|
554
|
+
// Prefer the wrapper node's `type` field when present.
|
|
555
|
+
const direct = node.childForFieldName('type');
|
|
556
|
+
if (direct)
|
|
557
|
+
return direct;
|
|
558
|
+
const wrapped = node.childForFieldName('declaration') ??
|
|
559
|
+
(() => {
|
|
560
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
561
|
+
const c = node.namedChild(i);
|
|
562
|
+
if (c?.type === 'variable_declaration')
|
|
563
|
+
return c;
|
|
564
|
+
}
|
|
565
|
+
return null;
|
|
566
|
+
})();
|
|
567
|
+
return wrapped?.childForFieldName('type') ?? null;
|
|
568
|
+
},
|
|
552
569
|
forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
|
|
553
570
|
patternBindingNodeTypes: new Set([
|
|
554
571
|
'is_pattern_expression',
|
|
@@ -831,6 +831,17 @@ const extractKotlinPatternBinding = (node, scopeEnv, declarationTypeNodes, scope
|
|
|
831
831
|
export const kotlinTypeConfig = {
|
|
832
832
|
allowPatternBindingOverwrite: true,
|
|
833
833
|
declarationNodeTypes: KOTLIN_DECLARATION_NODE_TYPES,
|
|
834
|
+
getDeclarationTypeNode: (node) => {
|
|
835
|
+
// Kotlin property_declaration wraps the actual declaration in variable_declaration.
|
|
836
|
+
// The type is commonly a user_type / nullable_type child (positional, not 'type' field).
|
|
837
|
+
const varDecl = node.type === 'property_declaration' ? findChild(node, 'variable_declaration') : node;
|
|
838
|
+
if (varDecl) {
|
|
839
|
+
return (varDecl.childForFieldName('type') ??
|
|
840
|
+
findChild(varDecl, 'user_type') ??
|
|
841
|
+
findChild(varDecl, 'nullable_type'));
|
|
842
|
+
}
|
|
843
|
+
return node.childForFieldName('type') ?? findChild(node, 'user_type') ?? null;
|
|
844
|
+
},
|
|
834
845
|
forLoopNodeTypes: KOTLIN_FOR_LOOP_NODE_TYPES,
|
|
835
846
|
patternBindingNodeTypes: new Set(['type_test', 'equality_expression']),
|
|
836
847
|
extractDeclaration: extractKotlinDeclaration,
|
|
@@ -323,60 +323,6 @@ const scanConstructorBinding = (node) => {
|
|
|
323
323
|
}
|
|
324
324
|
return undefined;
|
|
325
325
|
};
|
|
326
|
-
/** Regex to extract PHPDoc @return annotations: `@return User` */
|
|
327
|
-
const PHPDOC_RETURN_RE = /@return\s+(\S+)/;
|
|
328
|
-
/**
|
|
329
|
-
* Normalize a PHPDoc return type for storage in the SymbolTable.
|
|
330
|
-
* Unlike normalizePhpType (which strips User[] → User for scopeEnv), this preserves
|
|
331
|
-
* array notation so lookupRawReturnType can extract element types for for-loop resolution.
|
|
332
|
-
* \App\Models\User[] → User[]
|
|
333
|
-
* ?User → User
|
|
334
|
-
* Collection<User> → Collection<User> (preserved for extractElementTypeFromString)
|
|
335
|
-
*/
|
|
336
|
-
const normalizePhpReturnType = (raw) => {
|
|
337
|
-
// Strip nullable prefix: ?User[] → User[]
|
|
338
|
-
let type = raw.startsWith('?') ? raw.slice(1) : raw;
|
|
339
|
-
// Strip union with null/false/void: User[]|null → User[]
|
|
340
|
-
const parts = type
|
|
341
|
-
.split('|')
|
|
342
|
-
.filter((p) => p !== 'null' && p !== 'false' && p !== 'void' && p !== 'mixed');
|
|
343
|
-
if (parts.length !== 1)
|
|
344
|
-
return undefined;
|
|
345
|
-
type = parts[0];
|
|
346
|
-
// Strip namespace: \App\Models\User[] → User[]
|
|
347
|
-
const segments = type.split('\\');
|
|
348
|
-
type = segments[segments.length - 1];
|
|
349
|
-
// Skip uninformative types
|
|
350
|
-
if (type === 'mixed' ||
|
|
351
|
-
type === 'void' ||
|
|
352
|
-
type === 'self' ||
|
|
353
|
-
type === 'static' ||
|
|
354
|
-
type === 'object' ||
|
|
355
|
-
type === 'array')
|
|
356
|
-
return undefined;
|
|
357
|
-
if (/^\w+(\[\])?$/.test(type) || /^\w+\s*</.test(type))
|
|
358
|
-
return type;
|
|
359
|
-
return undefined;
|
|
360
|
-
};
|
|
361
|
-
/**
|
|
362
|
-
* Extract return type from PHPDoc `@return Type` annotation preceding a method.
|
|
363
|
-
* Walks backwards through preceding siblings looking for comment nodes.
|
|
364
|
-
* Preserves array notation (e.g., User[]) for for-loop element type extraction.
|
|
365
|
-
*/
|
|
366
|
-
const extractReturnType = (node) => {
|
|
367
|
-
let sibling = node.previousSibling;
|
|
368
|
-
while (sibling) {
|
|
369
|
-
if (sibling.type === 'comment') {
|
|
370
|
-
const match = PHPDOC_RETURN_RE.exec(sibling.text);
|
|
371
|
-
if (match)
|
|
372
|
-
return normalizePhpReturnType(match[1]);
|
|
373
|
-
}
|
|
374
|
-
else if (sibling.isNamed && !SKIP_NODE_TYPES.has(sibling.type))
|
|
375
|
-
break;
|
|
376
|
-
sibling = sibling.previousSibling;
|
|
377
|
-
}
|
|
378
|
-
return undefined;
|
|
379
|
-
};
|
|
380
326
|
/** PHP: $alias = $user → assignment_expression with variable_name left/right.
|
|
381
327
|
* PHP TypeEnv stores variables WITH $ prefix ($user → User), so we keep $ in lhs/rhs. */
|
|
382
328
|
const extractPendingAssignment = (node, scopeEnv) => {
|
|
@@ -583,7 +529,6 @@ export const typeConfig = {
|
|
|
583
529
|
extractParameter,
|
|
584
530
|
extractInitializer,
|
|
585
531
|
scanConstructorBinding,
|
|
586
|
-
extractReturnType,
|
|
587
532
|
extractForLoopBinding,
|
|
588
533
|
extractPendingAssignment,
|
|
589
534
|
};
|
|
@@ -23,8 +23,6 @@ import { extractRubyConstructorAssignment, extractSimpleTypeName, extractElement
|
|
|
23
23
|
const YARD_PARAM_RE = /@param\s+(\w+)\s+\[([^\]]+)\]/g;
|
|
24
24
|
/** Alternate YARD order: `@param [Type] name` */
|
|
25
25
|
const YARD_PARAM_ALT_RE = /@param\s+\[([^\]]+)\]\s+(\w+)/g;
|
|
26
|
-
/** Regex to extract @return annotations: `@return [Type]` */
|
|
27
|
-
const YARD_RETURN_RE = /@return\s+\[([^\]]+)\]/;
|
|
28
26
|
/**
|
|
29
27
|
* Extract the simple type name from a YARD type string.
|
|
30
28
|
* Handles:
|
|
@@ -190,35 +188,6 @@ const extractInitializer = (node, env, classNames) => {
|
|
|
190
188
|
env.set(result.varName, result.calleeName);
|
|
191
189
|
}
|
|
192
190
|
};
|
|
193
|
-
/**
|
|
194
|
-
* Extract return type from YARD `@return [Type]` annotation preceding a method.
|
|
195
|
-
* Reuses the same comment-walking strategy as collectYardParams: try direct
|
|
196
|
-
* siblings first, fall back to parent (body_statement) siblings for class methods.
|
|
197
|
-
*/
|
|
198
|
-
const extractReturnType = (node) => {
|
|
199
|
-
const search = (startNode) => {
|
|
200
|
-
let sibling = startNode.previousSibling;
|
|
201
|
-
while (sibling) {
|
|
202
|
-
if (sibling.type === 'comment') {
|
|
203
|
-
const match = YARD_RETURN_RE.exec(sibling.text);
|
|
204
|
-
if (match)
|
|
205
|
-
return extractYardTypeName(match[1]);
|
|
206
|
-
}
|
|
207
|
-
else if (sibling.isNamed) {
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
sibling = sibling.previousSibling;
|
|
211
|
-
}
|
|
212
|
-
return undefined;
|
|
213
|
-
};
|
|
214
|
-
const result = search(node);
|
|
215
|
-
if (result)
|
|
216
|
-
return result;
|
|
217
|
-
if (node.parent?.type === 'body_statement') {
|
|
218
|
-
return search(node.parent);
|
|
219
|
-
}
|
|
220
|
-
return undefined;
|
|
221
|
-
};
|
|
222
191
|
/**
|
|
223
192
|
* Ruby constructor binding scanner: captures both `user = User.new` and
|
|
224
193
|
* plain call assignments like `user = get_user()`.
|
|
@@ -403,7 +372,6 @@ export const typeConfig = {
|
|
|
403
372
|
extractParameter,
|
|
404
373
|
extractInitializer,
|
|
405
374
|
scanConstructorBinding,
|
|
406
|
-
extractReturnType,
|
|
407
375
|
extractForLoopBinding,
|
|
408
376
|
extractPendingAssignment,
|
|
409
377
|
};
|
|
@@ -461,6 +461,19 @@ function extractSwiftElementTypeFromTypeNode(typeNode) {
|
|
|
461
461
|
}
|
|
462
462
|
export const typeConfig = {
|
|
463
463
|
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
|
464
|
+
getDeclarationTypeNode: (node) => {
|
|
465
|
+
// Swift: many declarations store type as a type_annotation child (not a 'type' field).
|
|
466
|
+
// Prefer a direct 'type' field if present, else unwrap type_annotation to its inner type.
|
|
467
|
+
const direct = node.childForFieldName('type');
|
|
468
|
+
if (direct)
|
|
469
|
+
return direct;
|
|
470
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
471
|
+
const c = node.namedChild(i);
|
|
472
|
+
if (c?.type === 'type_annotation')
|
|
473
|
+
return c.firstNamedChild ?? c;
|
|
474
|
+
}
|
|
475
|
+
return null;
|
|
476
|
+
},
|
|
464
477
|
forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
|
|
465
478
|
extractDeclaration,
|
|
466
479
|
extractParameter,
|