gitnexus 1.4.6 → 1.4.8
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 +22 -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.js +2 -1
- package/dist/cli/setup.js +78 -1
- package/dist/config/supported-languages.d.ts +30 -0
- package/dist/config/supported-languages.js +30 -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/types.d.ts +4 -3
- package/dist/core/ingestion/ast-helpers.d.ts +80 -0
- package/dist/core/ingestion/ast-helpers.js +738 -0
- package/dist/core/ingestion/call-analysis.d.ts +73 -0
- package/dist/core/ingestion/call-analysis.js +490 -0
- package/dist/core/ingestion/call-processor.d.ts +55 -2
- package/dist/core/ingestion/call-processor.js +673 -108
- package/dist/core/ingestion/call-routing.d.ts +23 -2
- package/dist/core/ingestion/call-routing.js +21 -0
- package/dist/core/ingestion/entry-point-scoring.js +36 -26
- package/dist/core/ingestion/framework-detection.d.ts +10 -2
- package/dist/core/ingestion/framework-detection.js +49 -12
- package/dist/core/ingestion/heritage-processor.js +47 -49
- package/dist/core/ingestion/import-processor.d.ts +1 -1
- package/dist/core/ingestion/import-processor.js +103 -194
- package/dist/core/ingestion/import-resolution.d.ts +101 -0
- package/dist/core/ingestion/import-resolution.js +251 -0
- package/dist/core/ingestion/language-config.d.ts +3 -0
- package/dist/core/ingestion/language-config.js +13 -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 +8 -3
- package/dist/core/ingestion/named-binding-extraction.d.ts +9 -43
- package/dist/core/ingestion/named-binding-extraction.js +89 -79
- package/dist/core/ingestion/parsing-processor.d.ts +3 -2
- package/dist/core/ingestion/parsing-processor.js +27 -60
- package/dist/core/ingestion/pipeline.d.ts +10 -0
- package/dist/core/ingestion/pipeline.js +425 -4
- package/dist/core/ingestion/resolution-context.d.ts +5 -0
- package/dist/core/ingestion/resolution-context.js +7 -4
- package/dist/core/ingestion/resolvers/index.d.ts +1 -1
- package/dist/core/ingestion/resolvers/index.js +1 -1
- package/dist/core/ingestion/resolvers/jvm.d.ts +2 -1
- package/dist/core/ingestion/resolvers/jvm.js +25 -9
- package/dist/core/ingestion/resolvers/php.d.ts +14 -0
- package/dist/core/ingestion/resolvers/php.js +43 -3
- package/dist/core/ingestion/resolvers/utils.d.ts +5 -0
- package/dist/core/ingestion/resolvers/utils.js +16 -0
- package/dist/core/ingestion/symbol-table.d.ts +29 -3
- package/dist/core/ingestion/symbol-table.js +42 -9
- package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -12
- package/dist/core/ingestion/tree-sitter-queries.js +243 -2
- package/dist/core/ingestion/type-env.d.ts +28 -1
- package/dist/core/ingestion/type-env.js +451 -72
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +146 -2
- package/dist/core/ingestion/type-extractors/csharp.js +189 -16
- package/dist/core/ingestion/type-extractors/go.js +45 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +1 -1
- package/dist/core/ingestion/type-extractors/index.js +1 -1
- package/dist/core/ingestion/type-extractors/jvm.js +244 -69
- package/dist/core/ingestion/type-extractors/php.js +31 -4
- package/dist/core/ingestion/type-extractors/python.js +89 -17
- package/dist/core/ingestion/type-extractors/ruby.js +17 -2
- package/dist/core/ingestion/type-extractors/rust.js +72 -4
- package/dist/core/ingestion/type-extractors/shared.d.ts +12 -2
- package/dist/core/ingestion/type-extractors/shared.js +115 -13
- package/dist/core/ingestion/type-extractors/swift.js +7 -6
- package/dist/core/ingestion/type-extractors/types.d.ts +54 -11
- package/dist/core/ingestion/type-extractors/typescript.js +171 -9
- package/dist/core/ingestion/utils.d.ts +2 -95
- package/dist/core/ingestion/utils.js +3 -892
- package/dist/core/ingestion/workers/parse-worker.d.ts +36 -11
- package/dist/core/ingestion/workers/parse-worker.js +116 -95
- package/dist/core/lbug/csv-generator.js +18 -1
- package/dist/core/lbug/lbug-adapter.d.ts +12 -0
- package/dist/core/lbug/lbug-adapter.js +71 -4
- package/dist/core/lbug/schema.d.ts +6 -4
- package/dist/core/lbug/schema.js +27 -3
- package/dist/mcp/core/embedder.js +11 -3
- package/dist/mcp/core/lbug-adapter.d.ts +22 -0
- package/dist/mcp/core/lbug-adapter.js +178 -23
- package/dist/mcp/local/local-backend.d.ts +22 -0
- package/dist/mcp/local/local-backend.js +136 -32
- package/dist/mcp/resources.js +13 -0
- package/dist/mcp/server.js +26 -4
- package/dist/mcp/tools.js +17 -7
- 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/package.json +12 -4
|
@@ -61,6 +61,10 @@ const extractParameter = (node, env) => {
|
|
|
61
61
|
else {
|
|
62
62
|
nameNode = node.childForFieldName('name') ?? node.childForFieldName('pattern');
|
|
63
63
|
typeNode = node.childForFieldName('type');
|
|
64
|
+
// Python typed_parameter: name is a positional child (identifier), not a named field
|
|
65
|
+
if (!nameNode && node.type === 'typed_parameter') {
|
|
66
|
+
nameNode = node.firstNamedChild?.type === 'identifier' ? node.firstNamedChild : null;
|
|
67
|
+
}
|
|
64
68
|
}
|
|
65
69
|
if (!nameNode || !typeNode)
|
|
66
70
|
return;
|
|
@@ -229,6 +233,38 @@ const findPyParamElementType = (iterableName, startNode, pos = 'last') => {
|
|
|
229
233
|
}
|
|
230
234
|
return undefined;
|
|
231
235
|
};
|
|
236
|
+
/**
|
|
237
|
+
* Extracts iterableName and methodName from a call expression like `data.items()`.
|
|
238
|
+
* Returns undefined if the call doesn't match the expected pattern.
|
|
239
|
+
*/
|
|
240
|
+
const extractMethodCall = (callNode) => {
|
|
241
|
+
const fn = callNode.childForFieldName('function');
|
|
242
|
+
if (fn?.type !== 'attribute')
|
|
243
|
+
return undefined;
|
|
244
|
+
const obj = fn.firstNamedChild;
|
|
245
|
+
if (obj?.type !== 'identifier')
|
|
246
|
+
return undefined;
|
|
247
|
+
const method = fn.lastNamedChild;
|
|
248
|
+
const methodName = (method?.type === 'identifier' && method !== obj) ? method.text : undefined;
|
|
249
|
+
return { iterableName: obj.text, methodName };
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Collects all identifier nodes from a pattern, descending into nested tuple_patterns.
|
|
253
|
+
* For `i, (k, v)` returns [i, k, v]. For `key, value` returns [key, value].
|
|
254
|
+
*/
|
|
255
|
+
const collectPatternIdentifiers = (pattern) => {
|
|
256
|
+
const vars = [];
|
|
257
|
+
for (let i = 0; i < pattern.namedChildCount; i++) {
|
|
258
|
+
const child = pattern.namedChild(i);
|
|
259
|
+
if (child?.type === 'identifier') {
|
|
260
|
+
vars.push(child);
|
|
261
|
+
}
|
|
262
|
+
else if (child?.type === 'tuple_pattern') {
|
|
263
|
+
vars.push(...collectPatternIdentifiers(child));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return vars;
|
|
267
|
+
};
|
|
232
268
|
/**
|
|
233
269
|
* Python: for user in users: where users has a known container type annotation.
|
|
234
270
|
*
|
|
@@ -238,15 +274,19 @@ const findPyParamElementType = (iterableName, startNode, pos = 'last') => {
|
|
|
238
274
|
* 1. declarationTypeNodes — raw type annotation AST node (covers stored container types)
|
|
239
275
|
* 2. scopeEnv string — extractElementTypeFromString on the stored type
|
|
240
276
|
* 3. AST walk — walks up to the enclosing function's parameters to read List[User] directly
|
|
277
|
+
*
|
|
278
|
+
* Also handles `enumerate(iterable)` — unwraps the outer call and skips the integer
|
|
279
|
+
* index variable so the value variable still resolves to the element type.
|
|
241
280
|
*/
|
|
242
281
|
const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, returnTypeLookup }) => {
|
|
243
282
|
if (node.type !== 'for_statement')
|
|
244
283
|
return;
|
|
245
|
-
// The iterable is the `right` field — may be identifier, attribute, or call.
|
|
246
284
|
const rightNode = node.childForFieldName('right');
|
|
247
285
|
let iterableName;
|
|
248
286
|
let methodName;
|
|
249
287
|
let callExprElementType;
|
|
288
|
+
let isEnumerate = false;
|
|
289
|
+
// Extract iterable info from the `right` field — may be identifier, attribute, or call.
|
|
250
290
|
if (rightNode?.type === 'identifier') {
|
|
251
291
|
iterableName = rightNode.text;
|
|
252
292
|
}
|
|
@@ -256,20 +296,28 @@ const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, re
|
|
|
256
296
|
iterableName = prop.text;
|
|
257
297
|
}
|
|
258
298
|
else if (rightNode?.type === 'call') {
|
|
259
|
-
// data.items() → call > function: attribute > identifier('data') + identifier('items')
|
|
260
|
-
// get_users() → call > function: identifier (Phase 7.3 — return-type path)
|
|
261
299
|
const fn = rightNode.childForFieldName('function');
|
|
262
|
-
if (fn?.type === '
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
300
|
+
if (fn?.type === 'identifier' && fn.text === 'enumerate') {
|
|
301
|
+
// enumerate(iterable) or enumerate(d.items()) — unwrap to inner iterable.
|
|
302
|
+
isEnumerate = true;
|
|
303
|
+
const innerArg = rightNode.childForFieldName('arguments')?.firstNamedChild;
|
|
304
|
+
if (innerArg?.type === 'identifier') {
|
|
305
|
+
iterableName = innerArg.text;
|
|
306
|
+
}
|
|
307
|
+
else if (innerArg?.type === 'call') {
|
|
308
|
+
const extracted = extractMethodCall(innerArg);
|
|
309
|
+
if (extracted)
|
|
310
|
+
({ iterableName, methodName } = extracted);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else if (fn?.type === 'attribute') {
|
|
314
|
+
// data.items() → call > function: attribute > identifier('data') + identifier('items')
|
|
315
|
+
const extracted = extractMethodCall(rightNode);
|
|
316
|
+
if (extracted)
|
|
317
|
+
({ iterableName, methodName } = extracted);
|
|
270
318
|
}
|
|
271
319
|
else if (fn?.type === 'identifier') {
|
|
272
|
-
// Direct function call: for user in get_users()
|
|
320
|
+
// Direct function call: for user in get_users() (Phase 7.3 — return-type path)
|
|
273
321
|
const rawReturn = returnTypeLookup.lookupRawReturnType(fn.text);
|
|
274
322
|
if (rawReturn)
|
|
275
323
|
callExprElementType = extractElementTypeFromString(rawReturn);
|
|
@@ -292,11 +340,12 @@ const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, re
|
|
|
292
340
|
const leftNode = node.childForFieldName('left');
|
|
293
341
|
if (!leftNode)
|
|
294
342
|
return;
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
343
|
+
if (leftNode.type === 'pattern_list' || leftNode.type === 'tuple_pattern') {
|
|
344
|
+
// Tuple unpacking: `key, value` or `i, (k, v)` or `(k, v)` — bind the last identifier to element type.
|
|
345
|
+
// With enumerate, skip binding if there's only one var (just the index, no value to bind).
|
|
346
|
+
const vars = collectPatternIdentifiers(leftNode);
|
|
347
|
+
if (vars.length > 0 && (!isEnumerate || vars.length > 1)) {
|
|
348
|
+
scopeEnv.set(vars[vars.length - 1].text, elementType);
|
|
300
349
|
}
|
|
301
350
|
return;
|
|
302
351
|
}
|
|
@@ -327,6 +376,29 @@ const extractPendingAssignment = (node, scopeEnv) => {
|
|
|
327
376
|
return undefined;
|
|
328
377
|
if (right.type === 'identifier')
|
|
329
378
|
return { kind: 'copy', lhs, rhs: right.text };
|
|
379
|
+
// attribute RHS → fieldAccess (a.field)
|
|
380
|
+
if (right.type === 'attribute') {
|
|
381
|
+
const obj = right.firstNamedChild;
|
|
382
|
+
const field = right.lastNamedChild;
|
|
383
|
+
if (obj?.type === 'identifier' && field?.type === 'identifier' && obj !== field) {
|
|
384
|
+
return { kind: 'fieldAccess', lhs, receiver: obj.text, field: field.text };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// call RHS
|
|
388
|
+
if (right.type === 'call') {
|
|
389
|
+
const funcNode = right.childForFieldName('function');
|
|
390
|
+
if (funcNode?.type === 'identifier') {
|
|
391
|
+
return { kind: 'callResult', lhs, callee: funcNode.text };
|
|
392
|
+
}
|
|
393
|
+
// method call with receiver: call → function: attribute
|
|
394
|
+
if (funcNode?.type === 'attribute') {
|
|
395
|
+
const obj = funcNode.firstNamedChild;
|
|
396
|
+
const method = funcNode.lastNamedChild;
|
|
397
|
+
if (obj?.type === 'identifier' && method?.type === 'identifier' && obj !== method) {
|
|
398
|
+
return { kind: 'methodCallResult', lhs, receiver: obj.text, method: method.text };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
330
402
|
return undefined;
|
|
331
403
|
};
|
|
332
404
|
/**
|
|
@@ -372,9 +372,24 @@ const extractPendingAssignment = (node, scopeEnv) => {
|
|
|
372
372
|
if (scopeEnv.has(varName))
|
|
373
373
|
return undefined;
|
|
374
374
|
const rhsNode = node.childForFieldName('right');
|
|
375
|
-
if (!rhsNode
|
|
375
|
+
if (!rhsNode)
|
|
376
376
|
return undefined;
|
|
377
|
-
|
|
377
|
+
if (rhsNode.type === 'identifier')
|
|
378
|
+
return { kind: 'copy', lhs: varName, rhs: rhsNode.text };
|
|
379
|
+
// call/method_call RHS — Ruby uses method calls for both field access and method calls
|
|
380
|
+
if (rhsNode.type === 'call' || rhsNode.type === 'method_call') {
|
|
381
|
+
const methodNode = rhsNode.childForFieldName('method');
|
|
382
|
+
const receiverNode = rhsNode.childForFieldName('receiver');
|
|
383
|
+
if (!receiverNode && methodNode?.type === 'identifier') {
|
|
384
|
+
// No receiver → callResult (bare function call)
|
|
385
|
+
return { kind: 'callResult', lhs: varName, callee: methodNode.text };
|
|
386
|
+
}
|
|
387
|
+
if (receiverNode?.type === 'identifier' && methodNode?.type === 'identifier') {
|
|
388
|
+
// With receiver → methodCallResult (a.method)
|
|
389
|
+
return { kind: 'methodCallResult', lhs: varName, receiver: receiverNode.text, method: methodNode.text };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return undefined;
|
|
378
393
|
};
|
|
379
394
|
export const typeConfig = {
|
|
380
395
|
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
|
@@ -95,7 +95,7 @@ const extractDeclaration = (node, env) => {
|
|
|
95
95
|
env.set(varName, typeName);
|
|
96
96
|
};
|
|
97
97
|
/** Rust: let x = User::new(), let x = User::default(), or let x = User { ... } */
|
|
98
|
-
const extractInitializer = (node, env,
|
|
98
|
+
const extractInitializer = (node, env, classNames) => {
|
|
99
99
|
// Skip if there's an explicit type annotation — Tier 0 already handled it
|
|
100
100
|
if (node.childForFieldName('type') !== null)
|
|
101
101
|
return;
|
|
@@ -119,6 +119,13 @@ const extractInitializer = (node, env, _classNames) => {
|
|
|
119
119
|
env.set(varName, typeName);
|
|
120
120
|
return;
|
|
121
121
|
}
|
|
122
|
+
// Unit struct instantiation: let svc = UserService; (bare identifier, no braces or call)
|
|
123
|
+
if (value.type === 'identifier' && classNames.has(value.text)) {
|
|
124
|
+
const varName = extractVarName(pattern);
|
|
125
|
+
if (varName)
|
|
126
|
+
env.set(varName, value.text);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
122
129
|
if (value.type !== 'call_expression')
|
|
123
130
|
return;
|
|
124
131
|
const func = value.childForFieldName('function');
|
|
@@ -198,7 +205,8 @@ const scanConstructorBinding = (node) => {
|
|
|
198
205
|
return undefined;
|
|
199
206
|
return { varName: patternNode.text, calleeName };
|
|
200
207
|
};
|
|
201
|
-
/** Rust: let alias = u; → let_declaration with pattern + value fields
|
|
208
|
+
/** Rust: let alias = u; → let_declaration with pattern + value fields.
|
|
209
|
+
* Also handles struct destructuring: `let Point { x, y } = p` → N fieldAccess items. */
|
|
202
210
|
const extractPendingAssignment = (node, scopeEnv) => {
|
|
203
211
|
if (node.type !== 'let_declaration')
|
|
204
212
|
return undefined;
|
|
@@ -206,11 +214,71 @@ const extractPendingAssignment = (node, scopeEnv) => {
|
|
|
206
214
|
const value = node.childForFieldName('value');
|
|
207
215
|
if (!pattern || !value)
|
|
208
216
|
return undefined;
|
|
217
|
+
// Struct pattern destructuring: `let Point { x, y } = receiver`
|
|
218
|
+
// struct_pattern has a type child (struct name) and field_pattern children
|
|
219
|
+
if (pattern.type === 'struct_pattern' && value.type === 'identifier') {
|
|
220
|
+
const receiver = value.text;
|
|
221
|
+
const items = [];
|
|
222
|
+
for (let j = 0; j < pattern.namedChildCount; j++) {
|
|
223
|
+
const field = pattern.namedChild(j);
|
|
224
|
+
if (!field)
|
|
225
|
+
continue;
|
|
226
|
+
if (field.type === 'field_pattern') {
|
|
227
|
+
// `Point { x: local_x }` → field_pattern with name + pattern children
|
|
228
|
+
const nameNode = field.childForFieldName('name');
|
|
229
|
+
const patNode = field.childForFieldName('pattern');
|
|
230
|
+
if (nameNode && patNode) {
|
|
231
|
+
const fieldName = nameNode.text;
|
|
232
|
+
const varName = extractVarName(patNode);
|
|
233
|
+
if (varName && !scopeEnv.has(varName)) {
|
|
234
|
+
items.push({ kind: 'fieldAccess', lhs: varName, receiver, field: fieldName });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else if (nameNode) {
|
|
238
|
+
// Shorthand: `Point { x }` → field_pattern with only name (varName = fieldName)
|
|
239
|
+
const varName = nameNode.text;
|
|
240
|
+
if (!scopeEnv.has(varName)) {
|
|
241
|
+
items.push({ kind: 'fieldAccess', lhs: varName, receiver, field: varName });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (items.length > 0)
|
|
247
|
+
return items;
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
209
250
|
const lhs = extractVarName(pattern);
|
|
210
251
|
if (!lhs || scopeEnv.has(lhs))
|
|
211
252
|
return undefined;
|
|
212
|
-
|
|
213
|
-
|
|
253
|
+
// Unwrap Rust .await: `let user = get_user().await` → call_expression
|
|
254
|
+
const unwrapped = unwrapAwait(value) ?? value;
|
|
255
|
+
if (unwrapped.type === 'identifier')
|
|
256
|
+
return { kind: 'copy', lhs, rhs: unwrapped.text };
|
|
257
|
+
// field_expression RHS → fieldAccess (a.field)
|
|
258
|
+
if (unwrapped.type === 'field_expression') {
|
|
259
|
+
const obj = unwrapped.firstNamedChild;
|
|
260
|
+
const field = unwrapped.lastNamedChild;
|
|
261
|
+
if (obj?.type === 'identifier' && field?.type === 'field_identifier') {
|
|
262
|
+
return { kind: 'fieldAccess', lhs, receiver: obj.text, field: field.text };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// call_expression RHS → callResult (simple calls only)
|
|
266
|
+
if (unwrapped.type === 'call_expression') {
|
|
267
|
+
const funcNode = unwrapped.childForFieldName('function');
|
|
268
|
+
if (funcNode?.type === 'identifier') {
|
|
269
|
+
return { kind: 'callResult', lhs, callee: funcNode.text };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// method_call_expression RHS → methodCallResult (receiver.method())
|
|
273
|
+
if (unwrapped.type === 'method_call_expression') {
|
|
274
|
+
const obj = unwrapped.firstNamedChild;
|
|
275
|
+
if (obj?.type === 'identifier') {
|
|
276
|
+
const methodNode = unwrapped.childForFieldName('name') ?? unwrapped.namedChild(1);
|
|
277
|
+
if (methodNode?.type === 'field_identifier') {
|
|
278
|
+
return { kind: 'methodCallResult', lhs, receiver: obj.text, method: methodNode.text };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
214
282
|
return undefined;
|
|
215
283
|
};
|
|
216
284
|
/**
|
|
@@ -108,8 +108,6 @@ export declare const unwrapAwait: (node: SyntaxNode | null) => SyntaxNode | null
|
|
|
108
108
|
* Navigates to the 'function' field (or first named child) and extracts a simple type name.
|
|
109
109
|
*/
|
|
110
110
|
export declare const extractCalleeName: (callNode: SyntaxNode) => string | undefined;
|
|
111
|
-
/** Find the first named child with the given node type */
|
|
112
|
-
export declare const findChildByType: (node: SyntaxNode, type: string) => SyntaxNode | null;
|
|
113
111
|
/**
|
|
114
112
|
* Extract element type from a container type string.
|
|
115
113
|
* Uses bracket-balanced parsing (no regex) for generic argument extraction.
|
|
@@ -130,4 +128,16 @@ export declare const findChildByType: (node: SyntaxNode, type: string) => Syntax
|
|
|
130
128
|
*/
|
|
131
129
|
export declare function extractElementTypeFromString(typeStr: string, pos?: TypeArgPosition): string | undefined;
|
|
132
130
|
export declare const extractReturnTypeName: (raw: string, depth?: number) => string | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* Extract the declared type of a property/field from its AST definition node.
|
|
133
|
+
* Handles cross-language patterns:
|
|
134
|
+
* - TypeScript: `name: Type` → type_annotation child
|
|
135
|
+
* - Java: `Type name` → type child on field_declaration
|
|
136
|
+
* - C#: `Type Name { get; set; }` → type child on property_declaration
|
|
137
|
+
* - Go: `Name Type` → type child on field_declaration
|
|
138
|
+
* - Kotlin: `var name: Type` → variable_declaration child with type field
|
|
139
|
+
*
|
|
140
|
+
* Returns the normalized type name, or undefined if no type can be extracted.
|
|
141
|
+
*/
|
|
142
|
+
export declare const extractPropertyDeclaredType: (definitionNode: SyntaxNode | null) => string | undefined;
|
|
133
143
|
export {};
|
|
@@ -226,13 +226,22 @@ export const extractSimpleTypeName = (typeNode, depth = 0) => {
|
|
|
226
226
|
}
|
|
227
227
|
// Pointer/reference types (C++, Rust): User*, &User, &mut User
|
|
228
228
|
if (typeNode.type === 'pointer_type' || typeNode.type === 'reference_type') {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
// Skip mutable_specifier for Rust &mut references — firstNamedChild would be
|
|
230
|
+
// `mutable_specifier` not the actual type. Walk named children to find the type.
|
|
231
|
+
for (let i = 0; i < typeNode.namedChildCount; i++) {
|
|
232
|
+
const child = typeNode.namedChild(i);
|
|
233
|
+
if (child && child.type !== 'mutable_specifier') {
|
|
234
|
+
return extractSimpleTypeName(child, depth + 1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
232
237
|
}
|
|
233
238
|
// Primitive/predefined types: string, int, float, bool, number, unknown, any
|
|
234
239
|
// PHP: primitive_type; TS/JS: predefined_type
|
|
235
|
-
|
|
240
|
+
// Java: integral_type (int/long/short/byte), floating_point_type (float/double),
|
|
241
|
+
// boolean_type (boolean), void_type (void)
|
|
242
|
+
if (typeNode.type === 'primitive_type' || typeNode.type === 'predefined_type'
|
|
243
|
+
|| typeNode.type === 'integral_type' || typeNode.type === 'floating_point_type'
|
|
244
|
+
|| typeNode.type === 'boolean_type' || typeNode.type === 'void_type') {
|
|
236
245
|
return typeNode.text;
|
|
237
246
|
}
|
|
238
247
|
// PHP named_type / optional_type
|
|
@@ -449,15 +458,6 @@ export const extractCalleeName = (callNode) => {
|
|
|
449
458
|
return undefined;
|
|
450
459
|
return extractSimpleTypeName(func);
|
|
451
460
|
};
|
|
452
|
-
/** Find the first named child with the given node type */
|
|
453
|
-
export const findChildByType = (node, type) => {
|
|
454
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
455
|
-
const child = node.namedChild(i);
|
|
456
|
-
if (child?.type === type)
|
|
457
|
-
return child;
|
|
458
|
-
}
|
|
459
|
-
return null;
|
|
460
|
-
};
|
|
461
461
|
// Internal helper: extract the first comma-separated argument from a string,
|
|
462
462
|
// respecting nested angle-bracket and square-bracket depth.
|
|
463
463
|
function extractFirstArg(args) {
|
|
@@ -701,3 +701,105 @@ export const extractReturnTypeName = (raw, depth = 0) => {
|
|
|
701
701
|
return undefined;
|
|
702
702
|
return text;
|
|
703
703
|
};
|
|
704
|
+
// ── Property declared-type extraction ────────────────────────────────────
|
|
705
|
+
// Shared between parse-worker (worker path) and parsing-processor (sequential path).
|
|
706
|
+
/**
|
|
707
|
+
* Extract the declared type of a property/field from its AST definition node.
|
|
708
|
+
* Handles cross-language patterns:
|
|
709
|
+
* - TypeScript: `name: Type` → type_annotation child
|
|
710
|
+
* - Java: `Type name` → type child on field_declaration
|
|
711
|
+
* - C#: `Type Name { get; set; }` → type child on property_declaration
|
|
712
|
+
* - Go: `Name Type` → type child on field_declaration
|
|
713
|
+
* - Kotlin: `var name: Type` → variable_declaration child with type field
|
|
714
|
+
*
|
|
715
|
+
* Returns the normalized type name, or undefined if no type can be extracted.
|
|
716
|
+
*/
|
|
717
|
+
export const extractPropertyDeclaredType = (definitionNode) => {
|
|
718
|
+
if (!definitionNode)
|
|
719
|
+
return undefined;
|
|
720
|
+
// Strategy 1: Look for a `type` or `type_annotation` named field
|
|
721
|
+
const typeNode = definitionNode.childForFieldName?.('type');
|
|
722
|
+
if (typeNode) {
|
|
723
|
+
const typeName = extractSimpleTypeName(typeNode);
|
|
724
|
+
if (typeName)
|
|
725
|
+
return typeName;
|
|
726
|
+
// Fallback: use the raw text (for complex types like User[] or List<User>)
|
|
727
|
+
const text = typeNode.text?.trim();
|
|
728
|
+
if (text && text.length < 100)
|
|
729
|
+
return text;
|
|
730
|
+
}
|
|
731
|
+
// Strategy 2: Walk children looking for type_annotation (TypeScript pattern)
|
|
732
|
+
for (let i = 0; i < definitionNode.childCount; i++) {
|
|
733
|
+
const child = definitionNode.child(i);
|
|
734
|
+
if (!child)
|
|
735
|
+
continue;
|
|
736
|
+
if (child.type === 'type_annotation') {
|
|
737
|
+
// Type annotation has the actual type as a child
|
|
738
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
739
|
+
const typeChild = child.child(j);
|
|
740
|
+
if (typeChild && typeChild.type !== ':') {
|
|
741
|
+
const typeName = extractSimpleTypeName(typeChild);
|
|
742
|
+
if (typeName)
|
|
743
|
+
return typeName;
|
|
744
|
+
const text = typeChild.text?.trim();
|
|
745
|
+
if (text && text.length < 100)
|
|
746
|
+
return text;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
// Strategy 3: For Java field_declaration, the type is a sibling of variable_declarator
|
|
752
|
+
// AST: (field_declaration type: (type_identifier) declarator: (variable_declarator ...))
|
|
753
|
+
const parentDecl = definitionNode.parent;
|
|
754
|
+
if (parentDecl) {
|
|
755
|
+
const parentType = parentDecl.childForFieldName?.('type');
|
|
756
|
+
if (parentType) {
|
|
757
|
+
const typeName = extractSimpleTypeName(parentType);
|
|
758
|
+
if (typeName)
|
|
759
|
+
return typeName;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
// Strategy 4: Kotlin property_declaration — type is nested inside variable_declaration child
|
|
763
|
+
// AST: (property_declaration (variable_declaration (simple_identifier) ":" (user_type (type_identifier))))
|
|
764
|
+
// Kotlin's variable_declaration has NO named 'type' field — children are all positional.
|
|
765
|
+
for (let i = 0; i < definitionNode.childCount; i++) {
|
|
766
|
+
const child = definitionNode.child(i);
|
|
767
|
+
if (child?.type === 'variable_declaration') {
|
|
768
|
+
// Try named field first (works for other languages sharing this strategy)
|
|
769
|
+
const varType = child.childForFieldName?.('type');
|
|
770
|
+
if (varType) {
|
|
771
|
+
const typeName = extractSimpleTypeName(varType);
|
|
772
|
+
if (typeName)
|
|
773
|
+
return typeName;
|
|
774
|
+
const text = varType.text?.trim();
|
|
775
|
+
if (text && text.length < 100)
|
|
776
|
+
return text;
|
|
777
|
+
}
|
|
778
|
+
// Fallback: walk unnamed children for user_type / type_identifier (Kotlin)
|
|
779
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
780
|
+
const varChild = child.namedChild(j);
|
|
781
|
+
if (varChild && (varChild.type === 'user_type' || varChild.type === 'type_identifier'
|
|
782
|
+
|| varChild.type === 'nullable_type' || varChild.type === 'generic_type')) {
|
|
783
|
+
const typeName = extractSimpleTypeName(varChild);
|
|
784
|
+
if (typeName)
|
|
785
|
+
return typeName;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
// Strategy 5: PHP @var PHPDoc — look for preceding comment with @var Type
|
|
791
|
+
// Handles pre-PHP-7.4 code: /** @var Address */ public $address;
|
|
792
|
+
const prevSibling = definitionNode.previousNamedSibling ?? definitionNode.parent?.previousNamedSibling;
|
|
793
|
+
if (prevSibling?.type === 'comment') {
|
|
794
|
+
const commentText = prevSibling.text;
|
|
795
|
+
const varMatch = commentText?.match(/@var\s+([A-Z][\w\\]*)/);
|
|
796
|
+
if (varMatch) {
|
|
797
|
+
// Strip namespace prefix: \App\Models\User → User
|
|
798
|
+
const raw = varMatch[1];
|
|
799
|
+
const base = raw.includes('\\') ? raw.split('\\').pop() : raw;
|
|
800
|
+
if (base && /^[A-Z]\w*$/.test(base))
|
|
801
|
+
return base;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return undefined;
|
|
805
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { extractSimpleTypeName, extractVarName,
|
|
1
|
+
import { extractSimpleTypeName, extractVarName, hasTypeAnnotation } from './shared.js';
|
|
2
|
+
import { findChild } from '../resolvers/utils.js';
|
|
2
3
|
const DECLARATION_NODE_TYPES = new Set([
|
|
3
4
|
'property_declaration',
|
|
4
5
|
]);
|
|
@@ -6,9 +7,9 @@ const DECLARATION_NODE_TYPES = new Set([
|
|
|
6
7
|
const extractDeclaration = (node, env) => {
|
|
7
8
|
// Swift property_declaration has pattern and type_annotation
|
|
8
9
|
const pattern = node.childForFieldName('pattern')
|
|
9
|
-
??
|
|
10
|
+
?? findChild(node, 'pattern');
|
|
10
11
|
const typeAnnotation = node.childForFieldName('type')
|
|
11
|
-
??
|
|
12
|
+
?? findChild(node, 'type_annotation');
|
|
12
13
|
if (!pattern || !typeAnnotation)
|
|
13
14
|
return;
|
|
14
15
|
const varName = extractVarName(pattern) ?? pattern.text;
|
|
@@ -43,17 +44,17 @@ const extractInitializer = (node, env, classNames) => {
|
|
|
43
44
|
if (node.type !== 'property_declaration')
|
|
44
45
|
return;
|
|
45
46
|
// Skip if has type annotation — extractDeclaration handled it
|
|
46
|
-
if (node.childForFieldName('type') ||
|
|
47
|
+
if (node.childForFieldName('type') || findChild(node, 'type_annotation'))
|
|
47
48
|
return;
|
|
48
49
|
// Find pattern (variable name)
|
|
49
|
-
const pattern = node.childForFieldName('pattern') ??
|
|
50
|
+
const pattern = node.childForFieldName('pattern') ?? findChild(node, 'pattern');
|
|
50
51
|
if (!pattern)
|
|
51
52
|
return;
|
|
52
53
|
const varName = extractVarName(pattern) ?? pattern.text;
|
|
53
54
|
if (!varName || env.has(varName))
|
|
54
55
|
return;
|
|
55
56
|
// Find call_expression in the value
|
|
56
|
-
const callExpr =
|
|
57
|
+
const callExpr = findChild(node, 'call_expression');
|
|
57
58
|
if (!callExpr)
|
|
58
59
|
return;
|
|
59
60
|
const callee = callExpr.firstNamedChild;
|
|
@@ -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). */
|
|
@@ -49,8 +63,10 @@ export interface ForLoopExtractorContext {
|
|
|
49
63
|
/** Extracts loop variable type binding from a for-each statement. */
|
|
50
64
|
export type ForLoopExtractor = (node: SyntaxNode, ctx: ForLoopExtractorContext) => void;
|
|
51
65
|
/** Discriminated union for pending Tier-2 propagation items.
|
|
52
|
-
* - `copy`
|
|
53
|
-
* - `callResult`
|
|
66
|
+
* - `copy` — `const b = a` (identifier alias, propagate a's type to b)
|
|
67
|
+
* - `callResult` — `const b = foo()` (bind b to foo's declared return type)
|
|
68
|
+
* - `fieldAccess` — `const b = a.field` (bind b to field's declaredType on a's type)
|
|
69
|
+
* - `methodCallResult` — `const b = a.method()` (bind b to method's returnType on a's type) */
|
|
54
70
|
export type PendingAssignment = {
|
|
55
71
|
kind: 'copy';
|
|
56
72
|
lhs: string;
|
|
@@ -59,15 +75,42 @@ export type PendingAssignment = {
|
|
|
59
75
|
kind: 'callResult';
|
|
60
76
|
lhs: string;
|
|
61
77
|
callee: string;
|
|
78
|
+
} | {
|
|
79
|
+
kind: 'fieldAccess';
|
|
80
|
+
lhs: string;
|
|
81
|
+
receiver: string;
|
|
82
|
+
field: string;
|
|
83
|
+
} | {
|
|
84
|
+
kind: 'methodCallResult';
|
|
85
|
+
lhs: string;
|
|
86
|
+
receiver: string;
|
|
87
|
+
method: string;
|
|
62
88
|
};
|
|
63
89
|
/** Extracts a pending assignment for Tier 2 propagation.
|
|
64
|
-
* Returns a PendingAssignment when the RHS is a bare identifier (`copy`)
|
|
65
|
-
* call expression (`callResult`)
|
|
90
|
+
* Returns a PendingAssignment when the RHS is a bare identifier (`copy`), a
|
|
91
|
+
* call expression (`callResult`), a field access (`fieldAccess`), or a
|
|
92
|
+
* method call with receiver (`methodCallResult`) and the LHS has no resolved type yet.
|
|
93
|
+
* May return an array of PendingAssignment items for destructuring patterns
|
|
94
|
+
* (e.g., `const { a, b } = obj` emits N fieldAccess items).
|
|
66
95
|
* Returns undefined if the node is not a matching assignment. */
|
|
67
|
-
export type PendingAssignmentExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>) => PendingAssignment | undefined;
|
|
96
|
+
export type PendingAssignmentExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>) => PendingAssignment | PendingAssignment[] | undefined;
|
|
97
|
+
/** Result of a pattern binding extraction. */
|
|
98
|
+
export interface PatternBindingResult {
|
|
99
|
+
varName: string;
|
|
100
|
+
typeName: string;
|
|
101
|
+
/** Optional: AST node whose position range should be used for the patternOverride.
|
|
102
|
+
* When present, the override uses this node's range instead of the auto-detected
|
|
103
|
+
* branch scope. Used by null-check narrowing to target the if-body specifically. */
|
|
104
|
+
narrowingRange?: {
|
|
105
|
+
startIndex: number;
|
|
106
|
+
endIndex: number;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
68
109
|
/** Extracts a typed variable binding from a pattern-matching construct.
|
|
69
|
-
* Returns { varName, typeName } for patterns that introduce NEW variables
|
|
70
|
-
*
|
|
110
|
+
* Returns { varName, typeName } for patterns that introduce NEW variables
|
|
111
|
+
* or narrow existing variables (null-check narrowing).
|
|
112
|
+
* Examples: `if let Some(user) = opt` (Rust), `x instanceof User user` (Java),
|
|
113
|
+
* `if (x != null)` (null-check narrowing in TS/Kotlin/C#).
|
|
71
114
|
* Conservative: returns undefined when the source variable's type is unknown.
|
|
72
115
|
*
|
|
73
116
|
* @param scopeEnv Read-only view of already-resolved type bindings in the current scope.
|
|
@@ -75,10 +118,7 @@ export type PendingAssignmentExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMa
|
|
|
75
118
|
* annotation AST node. Allows extracting generic type arguments (e.g., T from Result<T,E>)
|
|
76
119
|
* that are stripped during normal TypeEnv extraction.
|
|
77
120
|
* @param scope Current scope key (e.g. `"process@42"`) for declarationTypeNodes lookups. */
|
|
78
|
-
export type PatternBindingExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>, declarationTypeNodes: ReadonlyMap<string, SyntaxNode>, scope: string) =>
|
|
79
|
-
varName: string;
|
|
80
|
-
typeName: string;
|
|
81
|
-
} | undefined;
|
|
121
|
+
export type PatternBindingExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>, declarationTypeNodes: ReadonlyMap<string, SyntaxNode>, scope: string) => PatternBindingResult | undefined;
|
|
82
122
|
/** Per-language type extraction configuration */
|
|
83
123
|
export interface LanguageTypeConfig {
|
|
84
124
|
/** Allow pattern binding to overwrite existing scopeEnv entries.
|
|
@@ -124,4 +164,7 @@ export interface LanguageTypeConfig {
|
|
|
124
164
|
* The extractor receives the current scope's resolved bindings (read-only) to look up the
|
|
125
165
|
* source variable's type. Returns undefined for non-matching nodes or unknown source types. */
|
|
126
166
|
extractPatternBinding?: PatternBindingExtractor;
|
|
167
|
+
inferLiteralType?: LiteralTypeInferrer;
|
|
168
|
+
detectConstructorType?: ConstructorTypeDetector;
|
|
169
|
+
unwrapDeclaredType?: DeclaredTypeUnwrapper;
|
|
127
170
|
}
|