gitnexus 1.4.7 → 1.4.9
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 +29 -1
- package/dist/cli/ai-context.d.ts +1 -1
- package/dist/cli/ai-context.js +1 -1
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +54 -21
- package/dist/cli/index-repo.d.ts +15 -0
- package/dist/cli/index-repo.js +115 -0
- package/dist/cli/index.js +13 -3
- package/dist/cli/setup.js +90 -10
- package/dist/cli/wiki.d.ts +4 -0
- package/dist/cli/wiki.js +174 -53
- package/dist/config/supported-languages.d.ts +33 -1
- package/dist/config/supported-languages.js +32 -0
- package/dist/core/embeddings/embedder.d.ts +6 -1
- package/dist/core/embeddings/embedder.js +65 -5
- package/dist/core/embeddings/embedding-pipeline.js +11 -9
- package/dist/core/embeddings/http-client.d.ts +31 -0
- package/dist/core/embeddings/http-client.js +179 -0
- package/dist/core/embeddings/index.d.ts +1 -0
- package/dist/core/embeddings/index.js +1 -0
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/graph.js +9 -1
- package/dist/core/graph/types.d.ts +11 -2
- package/dist/core/ingestion/call-processor.d.ts +66 -2
- package/dist/core/ingestion/call-processor.js +650 -30
- package/dist/core/ingestion/call-routing.d.ts +9 -18
- package/dist/core/ingestion/call-routing.js +0 -19
- package/dist/core/ingestion/cobol/cobol-copy-expander.d.ts +57 -0
- package/dist/core/ingestion/cobol/cobol-copy-expander.js +385 -0
- package/dist/core/ingestion/cobol/cobol-preprocessor.d.ts +210 -0
- package/dist/core/ingestion/cobol/cobol-preprocessor.js +1509 -0
- package/dist/core/ingestion/cobol/jcl-parser.d.ts +68 -0
- package/dist/core/ingestion/cobol/jcl-parser.js +217 -0
- package/dist/core/ingestion/cobol/jcl-processor.d.ts +33 -0
- package/dist/core/ingestion/cobol/jcl-processor.js +229 -0
- package/dist/core/ingestion/cobol-processor.d.ts +54 -0
- package/dist/core/ingestion/cobol-processor.js +1186 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +17 -0
- package/dist/core/ingestion/entry-point-scoring.js +52 -28
- package/dist/core/ingestion/export-detection.d.ts +47 -8
- package/dist/core/ingestion/export-detection.js +29 -50
- package/dist/core/ingestion/field-extractor.d.ts +29 -0
- package/dist/core/ingestion/field-extractor.js +25 -0
- package/dist/core/ingestion/field-extractors/configs/c-cpp.d.ts +3 -0
- package/dist/core/ingestion/field-extractors/configs/c-cpp.js +108 -0
- package/dist/core/ingestion/field-extractors/configs/csharp.d.ts +8 -0
- package/dist/core/ingestion/field-extractors/configs/csharp.js +73 -0
- package/dist/core/ingestion/field-extractors/configs/dart.d.ts +8 -0
- package/dist/core/ingestion/field-extractors/configs/dart.js +76 -0
- package/dist/core/ingestion/field-extractors/configs/go.d.ts +11 -0
- package/dist/core/ingestion/field-extractors/configs/go.js +64 -0
- package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +44 -0
- package/dist/core/ingestion/field-extractors/configs/helpers.js +134 -0
- package/dist/core/ingestion/field-extractors/configs/jvm.d.ts +3 -0
- package/dist/core/ingestion/field-extractors/configs/jvm.js +118 -0
- package/dist/core/ingestion/field-extractors/configs/php.d.ts +8 -0
- package/dist/core/ingestion/field-extractors/configs/php.js +67 -0
- package/dist/core/ingestion/field-extractors/configs/python.d.ts +12 -0
- package/dist/core/ingestion/field-extractors/configs/python.js +91 -0
- package/dist/core/ingestion/field-extractors/configs/ruby.d.ts +16 -0
- package/dist/core/ingestion/field-extractors/configs/ruby.js +75 -0
- package/dist/core/ingestion/field-extractors/configs/rust.d.ts +9 -0
- package/dist/core/ingestion/field-extractors/configs/rust.js +55 -0
- package/dist/core/ingestion/field-extractors/configs/swift.d.ts +8 -0
- package/dist/core/ingestion/field-extractors/configs/swift.js +63 -0
- package/dist/core/ingestion/field-extractors/configs/typescript-javascript.d.ts +3 -0
- package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +60 -0
- package/dist/core/ingestion/field-extractors/generic.d.ts +46 -0
- package/dist/core/ingestion/field-extractors/generic.js +111 -0
- package/dist/core/ingestion/field-extractors/typescript.d.ts +77 -0
- package/dist/core/ingestion/field-extractors/typescript.js +291 -0
- package/dist/core/ingestion/field-types.d.ts +59 -0
- package/dist/core/ingestion/field-types.js +2 -0
- package/dist/core/ingestion/framework-detection.d.ts +97 -2
- package/dist/core/ingestion/framework-detection.js +114 -14
- package/dist/core/ingestion/heritage-processor.js +62 -66
- package/dist/core/ingestion/import-processor.d.ts +9 -10
- package/dist/core/ingestion/import-processor.js +150 -196
- package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.d.ts +6 -9
- package/dist/core/ingestion/{resolvers → import-resolvers}/csharp.js +20 -2
- package/dist/core/ingestion/import-resolvers/dart.d.ts +7 -0
- package/dist/core/ingestion/import-resolvers/dart.js +44 -0
- package/dist/core/ingestion/{resolvers → import-resolvers}/go.d.ts +4 -5
- package/dist/core/ingestion/{resolvers → import-resolvers}/go.js +17 -0
- package/dist/core/ingestion/{resolvers → import-resolvers}/jvm.d.ts +10 -1
- package/dist/core/ingestion/import-resolvers/jvm.js +159 -0
- package/dist/core/ingestion/import-resolvers/php.d.ts +25 -0
- package/dist/core/ingestion/import-resolvers/php.js +80 -0
- package/dist/core/ingestion/{resolvers → import-resolvers}/python.d.ts +9 -3
- package/dist/core/ingestion/{resolvers → import-resolvers}/python.js +35 -3
- package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.d.ts +5 -2
- package/dist/core/ingestion/{resolvers → import-resolvers}/ruby.js +7 -2
- package/dist/core/ingestion/{resolvers → import-resolvers}/rust.d.ts +5 -2
- package/dist/core/ingestion/{resolvers → import-resolvers}/rust.js +41 -2
- package/dist/core/ingestion/{resolvers → import-resolvers}/standard.d.ts +15 -7
- package/dist/core/ingestion/{resolvers → import-resolvers}/standard.js +22 -3
- package/dist/core/ingestion/import-resolvers/swift.d.ts +7 -0
- package/dist/core/ingestion/import-resolvers/swift.js +23 -0
- package/dist/core/ingestion/import-resolvers/types.d.ts +44 -0
- package/dist/core/ingestion/import-resolvers/types.js +6 -0
- package/dist/core/ingestion/{resolvers → import-resolvers}/utils.d.ts +2 -0
- package/dist/core/ingestion/{resolvers → import-resolvers}/utils.js +7 -0
- package/dist/core/ingestion/language-config.d.ts +6 -0
- package/dist/core/ingestion/language-config.js +13 -0
- package/dist/core/ingestion/language-provider.d.ts +121 -0
- package/dist/core/ingestion/language-provider.js +24 -0
- package/dist/core/ingestion/languages/c-cpp.d.ts +12 -0
- package/dist/core/ingestion/languages/c-cpp.js +71 -0
- package/dist/core/ingestion/languages/cobol.d.ts +1 -0
- package/dist/core/ingestion/languages/cobol.js +26 -0
- package/dist/core/ingestion/languages/csharp.d.ts +8 -0
- package/dist/core/ingestion/languages/csharp.js +49 -0
- package/dist/core/ingestion/languages/dart.d.ts +12 -0
- package/dist/core/ingestion/languages/dart.js +58 -0
- package/dist/core/ingestion/languages/go.d.ts +11 -0
- package/dist/core/ingestion/languages/go.js +28 -0
- package/dist/core/ingestion/languages/index.d.ts +38 -0
- package/dist/core/ingestion/languages/index.js +63 -0
- package/dist/core/ingestion/languages/java.d.ts +9 -0
- package/dist/core/ingestion/languages/java.js +29 -0
- package/dist/core/ingestion/languages/kotlin.d.ts +9 -0
- package/dist/core/ingestion/languages/kotlin.js +53 -0
- package/dist/core/ingestion/languages/php.d.ts +8 -0
- package/dist/core/ingestion/languages/php.js +145 -0
- package/dist/core/ingestion/languages/python.d.ts +12 -0
- package/dist/core/ingestion/languages/python.js +39 -0
- package/dist/core/ingestion/languages/ruby.d.ts +9 -0
- package/dist/core/ingestion/languages/ruby.js +44 -0
- package/dist/core/ingestion/languages/rust.d.ts +12 -0
- package/dist/core/ingestion/languages/rust.js +44 -0
- package/dist/core/ingestion/languages/swift.d.ts +12 -0
- package/dist/core/ingestion/languages/swift.js +133 -0
- package/dist/core/ingestion/languages/typescript.d.ts +10 -0
- package/dist/core/ingestion/languages/typescript.js +60 -0
- package/dist/core/ingestion/markdown-processor.d.ts +17 -0
- package/dist/core/ingestion/markdown-processor.js +124 -0
- package/dist/core/ingestion/mro-processor.js +22 -18
- package/dist/core/ingestion/named-binding-processor.d.ts +18 -0
- package/dist/core/ingestion/named-binding-processor.js +42 -0
- package/dist/core/ingestion/named-bindings/csharp.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/csharp.js +37 -0
- package/dist/core/ingestion/named-bindings/java.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/java.js +29 -0
- package/dist/core/ingestion/named-bindings/kotlin.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/kotlin.js +36 -0
- package/dist/core/ingestion/named-bindings/php.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/php.js +61 -0
- package/dist/core/ingestion/named-bindings/python.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/python.js +49 -0
- package/dist/core/ingestion/named-bindings/rust.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/rust.js +64 -0
- package/dist/core/ingestion/named-bindings/types.d.ts +16 -0
- package/dist/core/ingestion/named-bindings/types.js +6 -0
- package/dist/core/ingestion/named-bindings/typescript.d.ts +3 -0
- package/dist/core/ingestion/named-bindings/typescript.js +58 -0
- package/dist/core/ingestion/parsing-processor.d.ts +6 -2
- package/dist/core/ingestion/parsing-processor.js +125 -85
- package/dist/core/ingestion/pipeline.d.ts +10 -0
- package/dist/core/ingestion/pipeline.js +1235 -317
- package/dist/core/ingestion/resolution-context.d.ts +5 -0
- package/dist/core/ingestion/resolution-context.js +8 -5
- package/dist/core/ingestion/route-extractors/expo.d.ts +1 -0
- package/dist/core/ingestion/route-extractors/expo.js +36 -0
- package/dist/core/ingestion/route-extractors/middleware.d.ts +47 -0
- package/dist/core/ingestion/route-extractors/middleware.js +143 -0
- package/dist/core/ingestion/route-extractors/nextjs.d.ts +3 -0
- package/dist/core/ingestion/route-extractors/nextjs.js +76 -0
- package/dist/core/ingestion/route-extractors/php.d.ts +7 -0
- package/dist/core/ingestion/route-extractors/php.js +21 -0
- package/dist/core/ingestion/route-extractors/response-shapes.d.ts +20 -0
- package/dist/core/ingestion/route-extractors/response-shapes.js +290 -0
- package/dist/core/ingestion/symbol-table.d.ts +16 -0
- package/dist/core/ingestion/symbol-table.js +20 -6
- package/dist/core/ingestion/tree-sitter-queries.d.ts +10 -9
- package/dist/core/ingestion/tree-sitter-queries.js +274 -11
- package/dist/core/ingestion/type-env.d.ts +42 -18
- package/dist/core/ingestion/type-env.js +481 -106
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +119 -0
- package/dist/core/ingestion/type-extractors/csharp.js +149 -16
- package/dist/core/ingestion/type-extractors/dart.d.ts +15 -0
- package/dist/core/ingestion/type-extractors/dart.js +371 -0
- package/dist/core/ingestion/type-extractors/jvm.js +169 -66
- package/dist/core/ingestion/type-extractors/rust.js +35 -1
- package/dist/core/ingestion/type-extractors/shared.d.ts +1 -15
- package/dist/core/ingestion/type-extractors/shared.js +14 -112
- package/dist/core/ingestion/type-extractors/swift.js +338 -7
- package/dist/core/ingestion/type-extractors/types.d.ts +40 -8
- package/dist/core/ingestion/type-extractors/typescript.js +141 -9
- package/dist/core/ingestion/utils/ast-helpers.d.ts +83 -0
- package/dist/core/ingestion/utils/ast-helpers.js +817 -0
- package/dist/core/ingestion/utils/call-analysis.d.ts +73 -0
- package/dist/core/ingestion/utils/call-analysis.js +527 -0
- package/dist/core/ingestion/utils/event-loop.d.ts +5 -0
- package/dist/core/ingestion/utils/event-loop.js +5 -0
- package/dist/core/ingestion/utils/language-detection.d.ts +9 -0
- package/dist/core/ingestion/utils/language-detection.js +70 -0
- package/dist/core/ingestion/utils/verbose.d.ts +1 -0
- package/dist/core/ingestion/utils/verbose.js +7 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +55 -5
- package/dist/core/ingestion/workers/parse-worker.js +415 -225
- package/dist/core/lbug/csv-generator.js +51 -1
- package/dist/core/lbug/lbug-adapter.d.ts +10 -0
- package/dist/core/lbug/lbug-adapter.js +75 -4
- package/dist/core/lbug/schema.d.ts +8 -4
- package/dist/core/lbug/schema.js +65 -4
- package/dist/core/tree-sitter/parser-loader.js +7 -1
- package/dist/core/wiki/cursor-client.d.ts +31 -0
- package/dist/core/wiki/cursor-client.js +127 -0
- package/dist/core/wiki/generator.d.ts +28 -9
- package/dist/core/wiki/generator.js +115 -18
- package/dist/core/wiki/graph-queries.d.ts +4 -0
- package/dist/core/wiki/graph-queries.js +7 -1
- package/dist/core/wiki/llm-client.d.ts +2 -0
- package/dist/core/wiki/llm-client.js +8 -4
- package/dist/core/wiki/prompts.d.ts +3 -3
- package/dist/core/wiki/prompts.js +6 -0
- package/dist/mcp/core/embedder.js +11 -3
- package/dist/mcp/core/lbug-adapter.d.ts +5 -0
- package/dist/mcp/core/lbug-adapter.js +23 -2
- package/dist/mcp/local/local-backend.d.ts +38 -5
- package/dist/mcp/local/local-backend.js +804 -63
- package/dist/mcp/resources.js +2 -0
- package/dist/mcp/tools.js +73 -4
- package/dist/server/api.d.ts +19 -1
- package/dist/server/api.js +66 -6
- package/dist/storage/git.d.ts +12 -0
- package/dist/storage/git.js +21 -0
- package/dist/storage/repo-manager.d.ts +3 -0
- package/package.json +25 -16
- package/dist/core/ingestion/named-binding-extraction.d.ts +0 -61
- package/dist/core/ingestion/named-binding-extraction.js +0 -363
- package/dist/core/ingestion/resolvers/index.d.ts +0 -18
- package/dist/core/ingestion/resolvers/index.js +0 -13
- package/dist/core/ingestion/resolvers/jvm.js +0 -87
- package/dist/core/ingestion/resolvers/php.d.ts +0 -15
- package/dist/core/ingestion/resolvers/php.js +0 -35
- package/dist/core/ingestion/type-extractors/index.d.ts +0 -22
- package/dist/core/ingestion/type-extractors/index.js +0 -31
- package/dist/core/ingestion/utils.d.ts +0 -138
- package/dist/core/ingestion/utils.js +0 -1290
- package/scripts/patch-tree-sitter-swift.cjs +0 -74
|
@@ -1,14 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { findChild } from '../utils/ast-helpers.js';
|
|
2
|
+
import { extractSimpleTypeName, extractVarName, hasTypeAnnotation, extractElementTypeFromString, resolveIterableElementType } from './shared.js';
|
|
2
3
|
const DECLARATION_NODE_TYPES = new Set([
|
|
3
4
|
'property_declaration',
|
|
5
|
+
'if_statement',
|
|
6
|
+
'guard_statement',
|
|
4
7
|
]);
|
|
8
|
+
const FOR_LOOP_NODE_TYPES = new Set([
|
|
9
|
+
'for_statement',
|
|
10
|
+
]);
|
|
11
|
+
/**
|
|
12
|
+
* Unwrap Swift `await_expression` and `try_expression` nodes to find the inner
|
|
13
|
+
* call_expression or other value node. `try` nodes contain a `try_operator` child
|
|
14
|
+
* that must be skipped.
|
|
15
|
+
*/
|
|
16
|
+
function unwrapSwiftExpression(node) {
|
|
17
|
+
if (node.type === 'await_expression' || node.type === 'try_expression') {
|
|
18
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
19
|
+
const child = node.namedChild(i);
|
|
20
|
+
if (child && child.type !== 'try_operator')
|
|
21
|
+
return unwrapSwiftExpression(child);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return node;
|
|
25
|
+
}
|
|
5
26
|
/** Swift: let x: Foo = ... */
|
|
6
27
|
const extractDeclaration = (node, env) => {
|
|
7
28
|
// Swift property_declaration has pattern and type_annotation
|
|
8
29
|
const pattern = node.childForFieldName('pattern')
|
|
9
|
-
??
|
|
30
|
+
?? findChild(node, 'pattern');
|
|
10
31
|
const typeAnnotation = node.childForFieldName('type')
|
|
11
|
-
??
|
|
32
|
+
?? findChild(node, 'type_annotation');
|
|
12
33
|
if (!pattern || !typeAnnotation)
|
|
13
34
|
return;
|
|
14
35
|
const varName = extractVarName(pattern) ?? pattern.text;
|
|
@@ -43,17 +64,30 @@ const extractInitializer = (node, env, classNames) => {
|
|
|
43
64
|
if (node.type !== 'property_declaration')
|
|
44
65
|
return;
|
|
45
66
|
// Skip if has type annotation — extractDeclaration handled it
|
|
46
|
-
if (node.childForFieldName('type') ||
|
|
67
|
+
if (node.childForFieldName('type') || findChild(node, 'type_annotation'))
|
|
47
68
|
return;
|
|
48
69
|
// Find pattern (variable name)
|
|
49
|
-
const pattern = node.childForFieldName('pattern') ??
|
|
70
|
+
const pattern = node.childForFieldName('pattern') ?? findChild(node, 'pattern');
|
|
50
71
|
if (!pattern)
|
|
51
72
|
return;
|
|
52
73
|
const varName = extractVarName(pattern) ?? pattern.text;
|
|
53
74
|
if (!varName || env.has(varName))
|
|
54
75
|
return;
|
|
55
|
-
// Find call_expression in the value
|
|
56
|
-
|
|
76
|
+
// Find call_expression in the value (unwrap await/try)
|
|
77
|
+
let callExpr = findChild(node, 'call_expression');
|
|
78
|
+
if (!callExpr) {
|
|
79
|
+
// Check for await_expression or try_expression wrapping a call_expression
|
|
80
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
81
|
+
const child = node.namedChild(i);
|
|
82
|
+
if (child && (child.type === 'await_expression' || child.type === 'try_expression')) {
|
|
83
|
+
const unwrapped = unwrapSwiftExpression(child);
|
|
84
|
+
if (unwrapped.type === 'call_expression') {
|
|
85
|
+
callExpr = unwrapped;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
57
91
|
if (!callExpr)
|
|
58
92
|
return;
|
|
59
93
|
const callee = callExpr.firstNamedChild;
|
|
@@ -98,6 +132,14 @@ const scanConstructorBinding = (node) => {
|
|
|
98
132
|
callExpr = child;
|
|
99
133
|
break;
|
|
100
134
|
}
|
|
135
|
+
// Unwrap await/try to find inner call_expression
|
|
136
|
+
if (child && (child.type === 'await_expression' || child.type === 'try_expression')) {
|
|
137
|
+
const unwrapped = unwrapSwiftExpression(child);
|
|
138
|
+
if (unwrapped.type === 'call_expression') {
|
|
139
|
+
callExpr = unwrapped;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
101
143
|
}
|
|
102
144
|
if (!callExpr)
|
|
103
145
|
return undefined;
|
|
@@ -128,10 +170,299 @@ const scanConstructorBinding = (node) => {
|
|
|
128
170
|
}
|
|
129
171
|
return undefined;
|
|
130
172
|
};
|
|
173
|
+
/**
|
|
174
|
+
* Extract the variable name from an if_statement or guard_statement with optional binding.
|
|
175
|
+
* Pattern: `if let varName = expr` / `guard let varName = expr`
|
|
176
|
+
* AST: if_statement/guard_statement contains value_binding_pattern, then simple_identifier (varName),
|
|
177
|
+
* then call_expression/simple_identifier/navigation_expression (value).
|
|
178
|
+
*/
|
|
179
|
+
function extractIfGuardBinding(node, scopeEnv) {
|
|
180
|
+
// Find value_binding_pattern to confirm this is an optional binding
|
|
181
|
+
let hasValueBinding = false;
|
|
182
|
+
let varName;
|
|
183
|
+
let valueNode = null;
|
|
184
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
185
|
+
const child = node.namedChild(i);
|
|
186
|
+
if (!child)
|
|
187
|
+
continue;
|
|
188
|
+
if (child.type === 'value_binding_pattern') {
|
|
189
|
+
hasValueBinding = true;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (hasValueBinding && !varName && child.type === 'simple_identifier') {
|
|
193
|
+
varName = child.text;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (varName && !valueNode) {
|
|
197
|
+
// Skip type annotations and binding operators
|
|
198
|
+
if (child.type === 'type_annotation')
|
|
199
|
+
continue;
|
|
200
|
+
valueNode = child;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!hasValueBinding || !varName || !valueNode || scopeEnv.has(varName))
|
|
205
|
+
return undefined;
|
|
206
|
+
// Unwrap await/try
|
|
207
|
+
valueNode = unwrapSwiftExpression(valueNode);
|
|
208
|
+
// simple_identifier → copy
|
|
209
|
+
if (valueNode.type === 'simple_identifier') {
|
|
210
|
+
return { kind: 'copy', lhs: varName, rhs: valueNode.text };
|
|
211
|
+
}
|
|
212
|
+
// navigation_expression → fieldAccess
|
|
213
|
+
if (valueNode.type === 'navigation_expression') {
|
|
214
|
+
const receiver = valueNode.firstNamedChild;
|
|
215
|
+
const suffix = valueNode.lastNamedChild;
|
|
216
|
+
if (receiver?.type === 'simple_identifier' && suffix?.type === 'navigation_suffix') {
|
|
217
|
+
const field = suffix.lastNamedChild;
|
|
218
|
+
if (field?.type === 'simple_identifier') {
|
|
219
|
+
return { kind: 'fieldAccess', lhs: varName, receiver: receiver.text, field: field.text };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
// call_expression → callResult or methodCallResult
|
|
225
|
+
if (valueNode.type === 'call_expression') {
|
|
226
|
+
const callee = valueNode.firstNamedChild;
|
|
227
|
+
if (!callee)
|
|
228
|
+
return undefined;
|
|
229
|
+
if (callee.type === 'simple_identifier') {
|
|
230
|
+
return { kind: 'callResult', lhs: varName, callee: callee.text };
|
|
231
|
+
}
|
|
232
|
+
if (callee.type === 'navigation_expression') {
|
|
233
|
+
const receiver = callee.firstNamedChild;
|
|
234
|
+
const suffix = callee.lastNamedChild;
|
|
235
|
+
if (receiver?.type === 'simple_identifier' && suffix?.type === 'navigation_suffix') {
|
|
236
|
+
const method = suffix.lastNamedChild;
|
|
237
|
+
if (method?.type === 'simple_identifier') {
|
|
238
|
+
return { kind: 'methodCallResult', lhs: varName, receiver: receiver.text, method: method.text };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Swift: extract pending assignments for Tier 2 return-type propagation.
|
|
247
|
+
* Handles:
|
|
248
|
+
* let user = getUser() → callResult
|
|
249
|
+
* let result = user.save() → methodCallResult
|
|
250
|
+
* let name = user.name → fieldAccess
|
|
251
|
+
* let copy = user → copy
|
|
252
|
+
* let user = await getUser() → callResult (unwrapped)
|
|
253
|
+
* let user = try getUser() → callResult (unwrapped)
|
|
254
|
+
* if let user = getUser() → callResult (optional binding)
|
|
255
|
+
* guard let user = getUser() → callResult (optional binding)
|
|
256
|
+
*/
|
|
257
|
+
const extractPendingAssignment = (node, scopeEnv) => {
|
|
258
|
+
// Handle if_statement and guard_statement optional bindings
|
|
259
|
+
if (node.type === 'if_statement' || node.type === 'guard_statement') {
|
|
260
|
+
return extractIfGuardBinding(node, scopeEnv);
|
|
261
|
+
}
|
|
262
|
+
if (node.type !== 'property_declaration')
|
|
263
|
+
return undefined;
|
|
264
|
+
// Skip if type annotation exists — extractDeclaration handles it
|
|
265
|
+
if (hasTypeAnnotation(node))
|
|
266
|
+
return undefined;
|
|
267
|
+
// Find the variable name from the pattern child
|
|
268
|
+
let lhs;
|
|
269
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
270
|
+
const child = node.namedChild(i);
|
|
271
|
+
if (child?.type === 'pattern') {
|
|
272
|
+
lhs = child.text;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (!lhs || scopeEnv.has(lhs))
|
|
277
|
+
return undefined;
|
|
278
|
+
// Find the value expression (last meaningful named child after pattern)
|
|
279
|
+
let valueNode = null;
|
|
280
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
281
|
+
const child = node.namedChild(i);
|
|
282
|
+
if (!child)
|
|
283
|
+
continue;
|
|
284
|
+
if (child.type === 'pattern' || child.type === 'value_binding_pattern' || child.type === 'type_annotation')
|
|
285
|
+
continue;
|
|
286
|
+
valueNode = child;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
if (!valueNode)
|
|
290
|
+
return undefined;
|
|
291
|
+
// Unwrap await/try expressions (Feature 2)
|
|
292
|
+
valueNode = unwrapSwiftExpression(valueNode);
|
|
293
|
+
// let copy = user → copy
|
|
294
|
+
if (valueNode.type === 'simple_identifier') {
|
|
295
|
+
return { kind: 'copy', lhs, rhs: valueNode.text };
|
|
296
|
+
}
|
|
297
|
+
// let name = user.name → fieldAccess
|
|
298
|
+
if (valueNode.type === 'navigation_expression') {
|
|
299
|
+
const receiver = valueNode.firstNamedChild;
|
|
300
|
+
const suffix = valueNode.lastNamedChild;
|
|
301
|
+
if (receiver?.type === 'simple_identifier' && suffix?.type === 'navigation_suffix') {
|
|
302
|
+
const field = suffix.lastNamedChild;
|
|
303
|
+
if (field?.type === 'simple_identifier') {
|
|
304
|
+
return { kind: 'fieldAccess', lhs, receiver: receiver.text, field: field.text };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
// Call expressions
|
|
310
|
+
if (valueNode.type === 'call_expression') {
|
|
311
|
+
const callee = valueNode.firstNamedChild;
|
|
312
|
+
if (!callee)
|
|
313
|
+
return undefined;
|
|
314
|
+
// let user = getUser() → callResult
|
|
315
|
+
if (callee.type === 'simple_identifier') {
|
|
316
|
+
return { kind: 'callResult', lhs, callee: callee.text };
|
|
317
|
+
}
|
|
318
|
+
// let result = user.save() → methodCallResult
|
|
319
|
+
if (callee.type === 'navigation_expression') {
|
|
320
|
+
const receiver = callee.firstNamedChild;
|
|
321
|
+
const suffix = callee.lastNamedChild;
|
|
322
|
+
if (receiver?.type === 'simple_identifier' && suffix?.type === 'navigation_suffix') {
|
|
323
|
+
const method = suffix.lastNamedChild;
|
|
324
|
+
if (method?.type === 'simple_identifier') {
|
|
325
|
+
return { kind: 'methodCallResult', lhs, receiver: receiver.text, method: method.text };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return undefined;
|
|
331
|
+
};
|
|
332
|
+
/**
|
|
333
|
+
* Swift: extract loop variable type binding from `for item in collection`.
|
|
334
|
+
* AST: for_statement with pattern > simple_identifier (loop var) and
|
|
335
|
+
* a simple_identifier/call_expression (collection).
|
|
336
|
+
*/
|
|
337
|
+
const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, returnTypeLookup }) => {
|
|
338
|
+
if (node.type !== 'for_statement')
|
|
339
|
+
return;
|
|
340
|
+
// Find the loop variable from the pattern child
|
|
341
|
+
let loopVarName;
|
|
342
|
+
let iterableNode = null;
|
|
343
|
+
// for_statement children: pattern (loop var), then the iterable expression, then the body
|
|
344
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
345
|
+
const child = node.namedChild(i);
|
|
346
|
+
if (!child)
|
|
347
|
+
continue;
|
|
348
|
+
if (child.type === 'pattern' || child.type === 'simple_identifier') {
|
|
349
|
+
if (!loopVarName) {
|
|
350
|
+
// Extract a simple identifier from the pattern. Skip non-trivial patterns
|
|
351
|
+
// (e.g. tuple destructuring `for (a, b) in ...`) to avoid polluting scopeEnv.
|
|
352
|
+
const varName = extractVarName(child) ?? (child.type === 'simple_identifier' ? child.text : undefined);
|
|
353
|
+
if (!varName)
|
|
354
|
+
return; // Non-simple pattern — bail out
|
|
355
|
+
loopVarName = varName;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// After we found the loop var, the next expression-like node is the iterable
|
|
360
|
+
if (loopVarName && !iterableNode) {
|
|
361
|
+
if (child.type === 'simple_identifier' || child.type === 'call_expression' ||
|
|
362
|
+
child.type === 'navigation_expression') {
|
|
363
|
+
iterableNode = child;
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (!loopVarName || !iterableNode)
|
|
369
|
+
return;
|
|
370
|
+
let iterableName;
|
|
371
|
+
let callExprElementType;
|
|
372
|
+
if (iterableNode.type === 'simple_identifier') {
|
|
373
|
+
iterableName = iterableNode.text;
|
|
374
|
+
}
|
|
375
|
+
else if (iterableNode.type === 'navigation_expression') {
|
|
376
|
+
// collection.property
|
|
377
|
+
const suffix = iterableNode.lastNamedChild;
|
|
378
|
+
if (suffix?.type === 'navigation_suffix') {
|
|
379
|
+
const prop = suffix.lastNamedChild;
|
|
380
|
+
if (prop?.type === 'simple_identifier')
|
|
381
|
+
iterableName = prop.text;
|
|
382
|
+
}
|
|
383
|
+
else if (suffix?.type === 'simple_identifier') {
|
|
384
|
+
iterableName = suffix.text;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
else if (iterableNode.type === 'call_expression') {
|
|
388
|
+
// getItems() or collection.values()
|
|
389
|
+
const fn = iterableNode.firstNamedChild;
|
|
390
|
+
let callee;
|
|
391
|
+
if (fn?.type === 'simple_identifier') {
|
|
392
|
+
callee = fn.text;
|
|
393
|
+
}
|
|
394
|
+
else if (fn?.type === 'navigation_expression') {
|
|
395
|
+
const obj = fn.firstNamedChild;
|
|
396
|
+
const suffix = fn.lastNamedChild;
|
|
397
|
+
if (obj?.type === 'simple_identifier')
|
|
398
|
+
iterableName = obj.text;
|
|
399
|
+
if (suffix?.type === 'navigation_suffix') {
|
|
400
|
+
const m = suffix.lastNamedChild;
|
|
401
|
+
if (m?.type === 'simple_identifier')
|
|
402
|
+
callee = m.text;
|
|
403
|
+
}
|
|
404
|
+
else if (suffix?.type === 'simple_identifier') {
|
|
405
|
+
callee = suffix.text;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (callee) {
|
|
409
|
+
const rawReturn = returnTypeLookup.lookupRawReturnType(callee);
|
|
410
|
+
if (rawReturn)
|
|
411
|
+
callExprElementType = extractElementTypeFromString(rawReturn);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (!iterableName && !callExprElementType)
|
|
415
|
+
return;
|
|
416
|
+
let elementType;
|
|
417
|
+
if (callExprElementType) {
|
|
418
|
+
elementType = callExprElementType;
|
|
419
|
+
}
|
|
420
|
+
else if (iterableName) {
|
|
421
|
+
// Try to resolve element type from the iterable's declared type
|
|
422
|
+
elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractSwiftElementTypeFromTypeNode);
|
|
423
|
+
}
|
|
424
|
+
if (elementType && !scopeEnv.has(loopVarName)) {
|
|
425
|
+
scopeEnv.set(loopVarName, elementType);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
/**
|
|
429
|
+
* Extract element type from a Swift type annotation AST node.
|
|
430
|
+
* Handles: [User] (array sugar), Array<User>, Set<User>, etc.
|
|
431
|
+
*/
|
|
432
|
+
function extractSwiftElementTypeFromTypeNode(typeNode) {
|
|
433
|
+
// Swift array sugar: [User] — parsed as array_type > user_type > type_identifier
|
|
434
|
+
if (typeNode.type === 'array_type') {
|
|
435
|
+
const inner = typeNode.firstNamedChild;
|
|
436
|
+
if (inner)
|
|
437
|
+
return extractSimpleTypeName(inner);
|
|
438
|
+
}
|
|
439
|
+
// Generic type: Array<User>, Set<User>
|
|
440
|
+
if (typeNode.type === 'user_type') {
|
|
441
|
+
// Check for generic args: user_type > type_identifier + type_arguments
|
|
442
|
+
for (let i = 0; i < typeNode.namedChildCount; i++) {
|
|
443
|
+
const child = typeNode.namedChild(i);
|
|
444
|
+
if (child?.type === 'type_arguments') {
|
|
445
|
+
const lastArg = child.lastNamedChild;
|
|
446
|
+
if (lastArg)
|
|
447
|
+
return extractSimpleTypeName(lastArg);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// type_annotation wrapping
|
|
452
|
+
if (typeNode.type === 'type_annotation') {
|
|
453
|
+
const inner = typeNode.firstNamedChild;
|
|
454
|
+
if (inner)
|
|
455
|
+
return extractSwiftElementTypeFromTypeNode(inner);
|
|
456
|
+
}
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
131
459
|
export const typeConfig = {
|
|
132
460
|
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
|
461
|
+
forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
|
|
133
462
|
extractDeclaration,
|
|
134
463
|
extractParameter,
|
|
135
464
|
extractInitializer,
|
|
136
465
|
scanConstructorBinding,
|
|
466
|
+
extractPendingAssignment,
|
|
467
|
+
extractForLoopBinding,
|
|
137
468
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SyntaxNode } from '../utils.js';
|
|
1
|
+
import type { SyntaxNode } from '../utils/ast-helpers.js';
|
|
2
2
|
/** Extracts type bindings from a declaration node into the env map */
|
|
3
3
|
export type TypeBindingExtractor = (node: SyntaxNode, env: Map<string, string>) => void;
|
|
4
4
|
/** Extracts type bindings from a parameter node into the env map */
|
|
@@ -23,6 +23,20 @@ export type ConstructorBindingScanner = (node: SyntaxNode) => {
|
|
|
23
23
|
* Used for languages where return types are expressed in comments (e.g. YARD @return [Type])
|
|
24
24
|
* rather than in AST fields. Returns undefined if no return type can be determined. */
|
|
25
25
|
export type ReturnTypeExtractor = (node: SyntaxNode) => string | undefined;
|
|
26
|
+
/** Infer the type name of a literal AST node for overload disambiguation.
|
|
27
|
+
* Returns the canonical type name (e.g. 'int', 'String', 'boolean') or undefined
|
|
28
|
+
* for non-literal nodes. Only used when resolveCallTarget has multiple candidates
|
|
29
|
+
* with parameterTypes — ~1-3% of call sites. */
|
|
30
|
+
export type LiteralTypeInferrer = (node: SyntaxNode) => string | undefined;
|
|
31
|
+
/** Detect constructor-style call expressions that don't use `new` keyword.
|
|
32
|
+
* Returns the constructor class name if the node's initializer is a constructor call,
|
|
33
|
+
* or undefined otherwise. Used for virtual dispatch in languages like Kotlin
|
|
34
|
+
* where constructors are syntactically identical to function calls, and C++
|
|
35
|
+
* where smart pointer factory functions (make_shared/make_unique) wrap constructors. */
|
|
36
|
+
export type ConstructorTypeDetector = (node: SyntaxNode, classNames: ClassNameLookup) => string | undefined;
|
|
37
|
+
/** Unwrap a declared type name to its inner type for virtual dispatch comparison.
|
|
38
|
+
* E.g., C++ shared_ptr<Animal> → Animal. Returns undefined if no unwrapping applies. */
|
|
39
|
+
export type DeclaredTypeUnwrapper = (declaredType: string, typeNode: SyntaxNode) => string | undefined;
|
|
26
40
|
/** Narrow lookup interface for resolving a callee name → return type name.
|
|
27
41
|
* Backed by SymbolTable.lookupFuzzyCallable; passed via ForLoopExtractorContext.
|
|
28
42
|
* Conservative: returns undefined when the callee is ambiguous (0 or 2+ matches). */
|
|
@@ -61,6 +75,8 @@ export type PendingAssignment = {
|
|
|
61
75
|
kind: 'callResult';
|
|
62
76
|
lhs: string;
|
|
63
77
|
callee: string;
|
|
78
|
+
calleeFqn?: string;
|
|
79
|
+
line?: number;
|
|
64
80
|
} | {
|
|
65
81
|
kind: 'fieldAccess';
|
|
66
82
|
lhs: string;
|
|
@@ -76,11 +92,27 @@ export type PendingAssignment = {
|
|
|
76
92
|
* Returns a PendingAssignment when the RHS is a bare identifier (`copy`), a
|
|
77
93
|
* call expression (`callResult`), a field access (`fieldAccess`), or a
|
|
78
94
|
* method call with receiver (`methodCallResult`) and the LHS has no resolved type yet.
|
|
95
|
+
* May return an array of PendingAssignment items for destructuring patterns
|
|
96
|
+
* (e.g., `const { a, b } = obj` emits N fieldAccess items).
|
|
79
97
|
* Returns undefined if the node is not a matching assignment. */
|
|
80
|
-
export type PendingAssignmentExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>) => PendingAssignment | undefined;
|
|
98
|
+
export type PendingAssignmentExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>) => PendingAssignment | PendingAssignment[] | undefined;
|
|
99
|
+
/** Result of a pattern binding extraction. */
|
|
100
|
+
export interface PatternBindingResult {
|
|
101
|
+
varName: string;
|
|
102
|
+
typeName: string;
|
|
103
|
+
/** Optional: AST node whose position range should be used for the patternOverride.
|
|
104
|
+
* When present, the override uses this node's range instead of the auto-detected
|
|
105
|
+
* branch scope. Used by null-check narrowing to target the if-body specifically. */
|
|
106
|
+
narrowingRange?: {
|
|
107
|
+
startIndex: number;
|
|
108
|
+
endIndex: number;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
81
111
|
/** Extracts a typed variable binding from a pattern-matching construct.
|
|
82
|
-
* Returns { varName, typeName } for patterns that introduce NEW variables
|
|
83
|
-
*
|
|
112
|
+
* Returns { varName, typeName } for patterns that introduce NEW variables
|
|
113
|
+
* or narrow existing variables (null-check narrowing).
|
|
114
|
+
* Examples: `if let Some(user) = opt` (Rust), `x instanceof User user` (Java),
|
|
115
|
+
* `if (x != null)` (null-check narrowing in TS/Kotlin/C#).
|
|
84
116
|
* Conservative: returns undefined when the source variable's type is unknown.
|
|
85
117
|
*
|
|
86
118
|
* @param scopeEnv Read-only view of already-resolved type bindings in the current scope.
|
|
@@ -88,10 +120,7 @@ export type PendingAssignmentExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMa
|
|
|
88
120
|
* annotation AST node. Allows extracting generic type arguments (e.g., T from Result<T,E>)
|
|
89
121
|
* that are stripped during normal TypeEnv extraction.
|
|
90
122
|
* @param scope Current scope key (e.g. `"process@42"`) for declarationTypeNodes lookups. */
|
|
91
|
-
export type PatternBindingExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>, declarationTypeNodes: ReadonlyMap<string, SyntaxNode>, scope: string) =>
|
|
92
|
-
varName: string;
|
|
93
|
-
typeName: string;
|
|
94
|
-
} | undefined;
|
|
123
|
+
export type PatternBindingExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>, declarationTypeNodes: ReadonlyMap<string, SyntaxNode>, scope: string) => PatternBindingResult | undefined;
|
|
95
124
|
/** Per-language type extraction configuration */
|
|
96
125
|
export interface LanguageTypeConfig {
|
|
97
126
|
/** Allow pattern binding to overwrite existing scopeEnv entries.
|
|
@@ -137,4 +166,7 @@ export interface LanguageTypeConfig {
|
|
|
137
166
|
* The extractor receives the current scope's resolved bindings (read-only) to look up the
|
|
138
167
|
* source variable's type. Returns undefined for non-matching nodes or unknown source types. */
|
|
139
168
|
extractPatternBinding?: PatternBindingExtractor;
|
|
169
|
+
inferLiteralType?: LiteralTypeInferrer;
|
|
170
|
+
detectConstructorType?: ConstructorTypeDetector;
|
|
171
|
+
unwrapDeclaredType?: DeclaredTypeUnwrapper;
|
|
140
172
|
}
|
|
@@ -444,7 +444,8 @@ const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, re
|
|
|
444
444
|
if (loopVarName)
|
|
445
445
|
scopeEnv.set(loopVarName, elementType);
|
|
446
446
|
};
|
|
447
|
-
/** TS/JS: const alias = u → variable_declarator with name/value fields
|
|
447
|
+
/** TS/JS: const alias = u → variable_declarator with name/value fields.
|
|
448
|
+
* Also handles destructuring: `const { a, b } = obj` → N fieldAccess items. */
|
|
448
449
|
const extractPendingAssignment = (node, scopeEnv) => {
|
|
449
450
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
450
451
|
const child = node.namedChild(i);
|
|
@@ -454,6 +455,39 @@ const extractPendingAssignment = (node, scopeEnv) => {
|
|
|
454
455
|
const valueNode = child.childForFieldName('value');
|
|
455
456
|
if (!nameNode || !valueNode)
|
|
456
457
|
continue;
|
|
458
|
+
// Object destructuring: `const { address, name } = user`
|
|
459
|
+
// Emits N fieldAccess items — one per destructured binding.
|
|
460
|
+
if (nameNode.type === 'object_pattern' && valueNode.type === 'identifier') {
|
|
461
|
+
const receiver = valueNode.text;
|
|
462
|
+
const items = [];
|
|
463
|
+
for (let j = 0; j < nameNode.namedChildCount; j++) {
|
|
464
|
+
const prop = nameNode.namedChild(j);
|
|
465
|
+
if (!prop)
|
|
466
|
+
continue;
|
|
467
|
+
if (prop.type === 'shorthand_property_identifier_pattern') {
|
|
468
|
+
// `const { name } = user` → shorthand: varName = fieldName
|
|
469
|
+
const varName = prop.text;
|
|
470
|
+
if (!scopeEnv.has(varName)) {
|
|
471
|
+
items.push({ kind: 'fieldAccess', lhs: varName, receiver, field: varName });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
else if (prop.type === 'pair_pattern') {
|
|
475
|
+
// `const { address: addr } = user` → pair_pattern: key=field, value=varName
|
|
476
|
+
const keyNode = prop.childForFieldName('key');
|
|
477
|
+
const valNode = prop.childForFieldName('value');
|
|
478
|
+
if (keyNode && valNode) {
|
|
479
|
+
const fieldName = keyNode.text;
|
|
480
|
+
const varName = valNode.text;
|
|
481
|
+
if (!scopeEnv.has(varName)) {
|
|
482
|
+
items.push({ kind: 'fieldAccess', lhs: varName, receiver, field: fieldName });
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (items.length > 0)
|
|
488
|
+
return items;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
457
491
|
const lhs = nameNode.text;
|
|
458
492
|
if (scopeEnv.has(lhs))
|
|
459
493
|
continue;
|
|
@@ -492,22 +526,119 @@ const extractPendingAssignment = (node, scopeEnv) => {
|
|
|
492
526
|
}
|
|
493
527
|
return undefined;
|
|
494
528
|
};
|
|
529
|
+
/** Null-check keywords that indicate a null-comparison in binary expressions. */
|
|
530
|
+
const NULL_CHECK_KEYWORDS = new Set(['null', 'undefined']);
|
|
531
|
+
/**
|
|
532
|
+
* Find the if-body (consequence) block for a null-check binary_expression.
|
|
533
|
+
* Walks up from the binary_expression through parenthesized_expression to if_statement,
|
|
534
|
+
* then returns the consequence block (statement_block).
|
|
535
|
+
*
|
|
536
|
+
* AST structure: if_statement > parenthesized_expression > binary_expression
|
|
537
|
+
* if_statement > statement_block (consequence)
|
|
538
|
+
*/
|
|
539
|
+
const findIfConsequenceBlock = (binaryExpr) => {
|
|
540
|
+
// Walk up to find the if_statement (typically: binary_expression > parenthesized_expression > if_statement)
|
|
541
|
+
let current = binaryExpr.parent;
|
|
542
|
+
while (current) {
|
|
543
|
+
if (current.type === 'if_statement') {
|
|
544
|
+
// The consequence is the first statement_block child of if_statement
|
|
545
|
+
for (let i = 0; i < current.childCount; i++) {
|
|
546
|
+
const child = current.child(i);
|
|
547
|
+
if (child?.type === 'statement_block')
|
|
548
|
+
return child;
|
|
549
|
+
}
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
// Stop climbing at function/block boundaries — don't cross scope
|
|
553
|
+
if (current.type === 'function_declaration' || current.type === 'function_expression'
|
|
554
|
+
|| current.type === 'arrow_function' || current.type === 'method_definition')
|
|
555
|
+
return undefined;
|
|
556
|
+
current = current.parent;
|
|
557
|
+
}
|
|
558
|
+
return undefined;
|
|
559
|
+
};
|
|
495
560
|
/** TS instanceof narrowing: `x instanceof User` → bind x to User.
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
const extractPatternBinding = (node) => {
|
|
561
|
+
* Also handles null-check narrowing: `x !== null`, `x != undefined` etc.
|
|
562
|
+
* instanceof: first-writer-wins (no prior type binding).
|
|
563
|
+
* null-check: position-indexed narrowing via narrowingRange. */
|
|
564
|
+
const extractPatternBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
|
|
500
565
|
if (node.type !== 'binary_expression')
|
|
501
566
|
return undefined;
|
|
502
|
-
|
|
567
|
+
// Check for instanceof first (existing behavior)
|
|
568
|
+
const instanceofOp = node.children.find(c => !c.isNamed && c.text === 'instanceof');
|
|
569
|
+
if (instanceofOp) {
|
|
570
|
+
const left = node.namedChild(0);
|
|
571
|
+
const right = node.namedChild(1);
|
|
572
|
+
if (left?.type !== 'identifier' || right?.type !== 'identifier')
|
|
573
|
+
return undefined;
|
|
574
|
+
return { varName: left.text, typeName: right.text };
|
|
575
|
+
}
|
|
576
|
+
// Null-check narrowing: x !== null, x != null, x !== undefined, x != undefined
|
|
577
|
+
const op = node.children.find(c => !c.isNamed && (c.text === '!==' || c.text === '!='));
|
|
503
578
|
if (!op)
|
|
504
579
|
return undefined;
|
|
505
|
-
// binary_expression children are positional — no left/right fields
|
|
506
580
|
const left = node.namedChild(0);
|
|
507
581
|
const right = node.namedChild(1);
|
|
508
|
-
if (left
|
|
582
|
+
if (!left || !right)
|
|
583
|
+
return undefined;
|
|
584
|
+
// Determine which side is the variable and which is null/undefined
|
|
585
|
+
let varNode;
|
|
586
|
+
let isNullCheck = false;
|
|
587
|
+
if (left.type === 'identifier' && NULL_CHECK_KEYWORDS.has(right.text)) {
|
|
588
|
+
varNode = left;
|
|
589
|
+
isNullCheck = true;
|
|
590
|
+
}
|
|
591
|
+
else if (right.type === 'identifier' && NULL_CHECK_KEYWORDS.has(left.text)) {
|
|
592
|
+
varNode = right;
|
|
593
|
+
isNullCheck = true;
|
|
594
|
+
}
|
|
595
|
+
if (!isNullCheck || !varNode)
|
|
509
596
|
return undefined;
|
|
510
|
-
|
|
597
|
+
const varName = varNode.text;
|
|
598
|
+
// Look up the variable's resolved type (already stripped of nullable by extractSimpleTypeName)
|
|
599
|
+
const resolvedType = scopeEnv.get(varName);
|
|
600
|
+
if (!resolvedType)
|
|
601
|
+
return undefined;
|
|
602
|
+
// Check if the original declaration type was nullable by looking at the raw AST type node.
|
|
603
|
+
// extractSimpleTypeName already strips nullable markers, so we need the original to know
|
|
604
|
+
// if narrowing is meaningful (i.e., the variable was declared as nullable).
|
|
605
|
+
const declTypeNode = declarationTypeNodes.get(`${scope}\0${varName}`);
|
|
606
|
+
if (!declTypeNode)
|
|
607
|
+
return undefined;
|
|
608
|
+
const declText = declTypeNode.text;
|
|
609
|
+
// Only narrow if the original declaration was nullable
|
|
610
|
+
if (!declText.includes('null') && !declText.includes('undefined'))
|
|
611
|
+
return undefined;
|
|
612
|
+
// Find the if-body block to scope the narrowing
|
|
613
|
+
const ifBody = findIfConsequenceBlock(node);
|
|
614
|
+
if (!ifBody)
|
|
615
|
+
return undefined;
|
|
616
|
+
return {
|
|
617
|
+
varName,
|
|
618
|
+
typeName: resolvedType,
|
|
619
|
+
narrowingRange: { startIndex: ifBody.startIndex, endIndex: ifBody.endIndex },
|
|
620
|
+
};
|
|
621
|
+
};
|
|
622
|
+
/** Infer the type of a literal AST node for TypeScript overload disambiguation. */
|
|
623
|
+
const inferTsLiteralType = (node) => {
|
|
624
|
+
switch (node.type) {
|
|
625
|
+
case 'number':
|
|
626
|
+
return 'number';
|
|
627
|
+
case 'string':
|
|
628
|
+
case 'template_string':
|
|
629
|
+
return 'string';
|
|
630
|
+
case 'true':
|
|
631
|
+
case 'false':
|
|
632
|
+
return 'boolean';
|
|
633
|
+
case 'null':
|
|
634
|
+
return 'null';
|
|
635
|
+
case 'undefined':
|
|
636
|
+
return 'undefined';
|
|
637
|
+
case 'regex':
|
|
638
|
+
return 'RegExp';
|
|
639
|
+
default:
|
|
640
|
+
return undefined;
|
|
641
|
+
}
|
|
511
642
|
};
|
|
512
643
|
export const typeConfig = {
|
|
513
644
|
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
|
@@ -521,4 +652,5 @@ export const typeConfig = {
|
|
|
521
652
|
extractForLoopBinding,
|
|
522
653
|
extractPendingAssignment,
|
|
523
654
|
extractPatternBinding,
|
|
655
|
+
inferLiteralType: inferTsLiteralType,
|
|
524
656
|
};
|