gitnexus 1.6.4-rc.97 → 1.6.4-rc.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/ingestion/field-extractors/configs/csharp.js +8 -3
- package/dist/core/ingestion/languages/csharp/captures.js +93 -0
- package/dist/core/ingestion/languages/csharp/query.js +6 -0
- package/dist/core/lbug/csv-generator.js +5 -1
- package/dist/core/lbug/lbug-adapter.js +15 -0
- package/dist/core/lbug/schema.d.ts +1 -1
- package/dist/core/lbug/schema.js +12 -1
- package/dist/mcp/local/local-backend.js +73 -7
- package/package.json +1 -1
|
@@ -3,6 +3,11 @@ import { SupportedLanguages } from '../../../../_shared/index.js';
|
|
|
3
3
|
import { findVisibility, hasKeyword, hasModifier, collectModifierTexts } from './helpers.js';
|
|
4
4
|
import { extractSimpleTypeName } from '../../type-extractors/shared.js';
|
|
5
5
|
const CSHARP_VIS = new Set(['public', 'private', 'protected', 'internal']);
|
|
6
|
+
const extractCsharpDeclaredType = (typeNode) => {
|
|
7
|
+
if (typeNode.type === 'generic_name')
|
|
8
|
+
return typeNode.text.trim();
|
|
9
|
+
return extractSimpleTypeName(typeNode) ?? typeNode.text?.trim();
|
|
10
|
+
};
|
|
6
11
|
/**
|
|
7
12
|
* C# field extraction config.
|
|
8
13
|
*
|
|
@@ -47,18 +52,18 @@ export const csharpConfig = {
|
|
|
47
52
|
if (child?.type === 'variable_declaration') {
|
|
48
53
|
const typeNode = child.childForFieldName('type');
|
|
49
54
|
if (typeNode)
|
|
50
|
-
return
|
|
55
|
+
return extractCsharpDeclaredType(typeNode);
|
|
51
56
|
// fallback: first child that is a type
|
|
52
57
|
const first = child.firstNamedChild;
|
|
53
58
|
if (first && first.type !== 'variable_declarator') {
|
|
54
|
-
return
|
|
59
|
+
return extractCsharpDeclaredType(first);
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
// property_declaration: type is first named child
|
|
59
64
|
const typeNode = node.childForFieldName('type');
|
|
60
65
|
if (typeNode)
|
|
61
|
-
return
|
|
66
|
+
return extractCsharpDeclaredType(typeNode);
|
|
62
67
|
return undefined;
|
|
63
68
|
},
|
|
64
69
|
extractVisibility(node) {
|
|
@@ -37,6 +37,37 @@ const FUNCTION_NODE_TYPES = [
|
|
|
37
37
|
'conversion_operator_declaration',
|
|
38
38
|
'local_function_statement',
|
|
39
39
|
];
|
|
40
|
+
const BUILTIN_TYPE_NAMES = new Set([
|
|
41
|
+
'bool',
|
|
42
|
+
'byte',
|
|
43
|
+
'char',
|
|
44
|
+
'decimal',
|
|
45
|
+
'double',
|
|
46
|
+
'float',
|
|
47
|
+
'int',
|
|
48
|
+
'long',
|
|
49
|
+
'object',
|
|
50
|
+
'sbyte',
|
|
51
|
+
'short',
|
|
52
|
+
'string',
|
|
53
|
+
'uint',
|
|
54
|
+
'ulong',
|
|
55
|
+
'ushort',
|
|
56
|
+
'void',
|
|
57
|
+
]);
|
|
58
|
+
function shouldEmitReadMember(memberNode) {
|
|
59
|
+
const parent = memberNode.parent;
|
|
60
|
+
if (parent === null)
|
|
61
|
+
return true;
|
|
62
|
+
switch (parent.type) {
|
|
63
|
+
case 'invocation_expression':
|
|
64
|
+
return parent.childForFieldName('function')?.id !== memberNode.id;
|
|
65
|
+
case 'assignment_expression':
|
|
66
|
+
return parent.childForFieldName('left')?.id !== memberNode.id;
|
|
67
|
+
default:
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
40
71
|
export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
41
72
|
// Skip the parse when the caller (parse phase's scopeTreeCache)
|
|
42
73
|
// already produced a Tree for this source. Cache miss = re-parse,
|
|
@@ -83,6 +114,13 @@ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
83
114
|
out.push(grouped);
|
|
84
115
|
continue;
|
|
85
116
|
}
|
|
117
|
+
if (grouped['@reference.read.member'] !== undefined) {
|
|
118
|
+
const anchor = grouped['@reference.read.member'];
|
|
119
|
+
const memberNode = findNodeAtRange(tree.rootNode, anchor.range, 'member_access_expression');
|
|
120
|
+
if (memberNode === null || !shouldEmitReadMember(memberNode)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
86
124
|
// Synthesize `this` / `base` receiver type-bindings on every
|
|
87
125
|
// instance method-like. Tree-sitter can't cleanly express "the
|
|
88
126
|
// implicit receiver of a non-static member of a class/struct/
|
|
@@ -166,8 +204,63 @@ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
|
166
204
|
}
|
|
167
205
|
}
|
|
168
206
|
}
|
|
207
|
+
out.push(...synthesizeGenericTypeArgumentReferences(tree.rootNode));
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
210
|
+
function synthesizeGenericTypeArgumentReferences(root) {
|
|
211
|
+
const out = [];
|
|
212
|
+
// Treat all generic type arguments as static type references, including
|
|
213
|
+
// declaration signatures and call-site generic instantiations.
|
|
214
|
+
visit(root, (node) => {
|
|
215
|
+
if (node.type !== 'generic_name')
|
|
216
|
+
return;
|
|
217
|
+
const args = findNamedChild(node, 'type_argument_list');
|
|
218
|
+
if (args === null)
|
|
219
|
+
return;
|
|
220
|
+
for (const arg of args.namedChildren) {
|
|
221
|
+
if (arg === null)
|
|
222
|
+
continue;
|
|
223
|
+
const nameNode = terminalTypeNameNode(arg);
|
|
224
|
+
if (nameNode === null)
|
|
225
|
+
continue;
|
|
226
|
+
if (BUILTIN_TYPE_NAMES.has(nameNode.text))
|
|
227
|
+
continue;
|
|
228
|
+
out.push({
|
|
229
|
+
'@reference.type': nodeToCapture('@reference.type', nameNode),
|
|
230
|
+
'@reference.name': nodeToCapture('@reference.name', nameNode),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
});
|
|
169
234
|
return out;
|
|
170
235
|
}
|
|
236
|
+
function terminalTypeNameNode(node) {
|
|
237
|
+
switch (node.type) {
|
|
238
|
+
case 'identifier':
|
|
239
|
+
return node;
|
|
240
|
+
case 'nullable_type':
|
|
241
|
+
return node.firstNamedChild === null ? null : terminalTypeNameNode(node.firstNamedChild);
|
|
242
|
+
case 'qualified_name':
|
|
243
|
+
return node.lastNamedChild;
|
|
244
|
+
case 'generic_name':
|
|
245
|
+
return node.childForFieldName('name') ?? node.firstNamedChild;
|
|
246
|
+
default:
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function findNamedChild(node, type) {
|
|
251
|
+
for (const child of node.namedChildren) {
|
|
252
|
+
if (child !== null && child.type === type)
|
|
253
|
+
return child;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
function visit(node, cb) {
|
|
258
|
+
cb(node);
|
|
259
|
+
for (const child of node.namedChildren) {
|
|
260
|
+
if (child !== null)
|
|
261
|
+
visit(child, cb);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
171
264
|
/** C# 12 primary constructor: `class X(a, b) { }` / `record X(a, b)`.
|
|
172
265
|
* The parameters are a bare `parameter_list` named child of the type
|
|
173
266
|
* declaration (no `constructor_declaration` node). Emit a synthetic
|
|
@@ -497,6 +497,12 @@ const CSHARP_SCOPE_QUERY = `
|
|
|
497
497
|
left: (member_access_expression
|
|
498
498
|
expression: "base" @reference.receiver
|
|
499
499
|
name: (identifier) @reference.name)) @reference.write.member
|
|
500
|
+
|
|
501
|
+
;; References — field/property reads: \`obj.Name\`
|
|
502
|
+
;; Emit-side filtering drops call targets and assignment left-hand sides.
|
|
503
|
+
(member_access_expression
|
|
504
|
+
expression: (_) @reference.receiver
|
|
505
|
+
name: (identifier) @reference.name) @reference.read.member
|
|
500
506
|
`;
|
|
501
507
|
let _parser = null;
|
|
502
508
|
let _query = null;
|
|
@@ -237,9 +237,10 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir) => {
|
|
|
237
237
|
'Template',
|
|
238
238
|
'Module',
|
|
239
239
|
];
|
|
240
|
+
const propertyHeader = 'id,name,filePath,startLine,endLine,content,description,declaredType';
|
|
240
241
|
const multiLangWriters = new Map();
|
|
241
242
|
for (const t of MULTI_LANG_TYPES) {
|
|
242
|
-
multiLangWriters.set(t, new BufferedCSVWriter(path.join(csvDir, `${t.toLowerCase()}.csv`), multiLangHeader));
|
|
243
|
+
multiLangWriters.set(t, new BufferedCSVWriter(path.join(csvDir, `${t.toLowerCase()}.csv`), t === 'Property' ? propertyHeader : multiLangHeader));
|
|
243
244
|
}
|
|
244
245
|
const codeWriterMap = {
|
|
245
246
|
Function: functionWriter,
|
|
@@ -390,6 +391,9 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir) => {
|
|
|
390
391
|
escapeCSVNumber(node.properties.endLine, -1),
|
|
391
392
|
escapeCSVField(content),
|
|
392
393
|
escapeCSVField(node.properties.description || ''),
|
|
394
|
+
...(node.label === 'Property'
|
|
395
|
+
? [escapeCSVField(node.properties.declaredType || '')]
|
|
396
|
+
: []),
|
|
393
397
|
].join(','));
|
|
394
398
|
}
|
|
395
399
|
}
|
|
@@ -520,6 +520,9 @@ const getCopyQuery = (table, filePath) => {
|
|
|
520
520
|
if (table === 'Method') {
|
|
521
521
|
return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description, parameterCount, returnType) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
|
522
522
|
}
|
|
523
|
+
if (table === 'Property') {
|
|
524
|
+
return `COPY ${t}(id, name, filePath, startLine, endLine, content, description, declaredType) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
|
525
|
+
}
|
|
523
526
|
// TypeScript/JS code element tables have isExported; multi-language tables do not
|
|
524
527
|
if (TABLES_WITH_EXPORTED.has(table)) {
|
|
525
528
|
return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description) FROM "${filePath}" ${COPY_CSV_OPTS}`;
|
|
@@ -569,6 +572,12 @@ export const insertNodeToLbug = async (label, properties, dbPath) => {
|
|
|
569
572
|
: '';
|
|
570
573
|
query = `CREATE (n:${t} {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, isExported: ${!!properties.isExported}, content: ${escapeValue(properties.content || '')}${descPart}})`;
|
|
571
574
|
}
|
|
575
|
+
else if (label === 'Property') {
|
|
576
|
+
const descPart = properties.description
|
|
577
|
+
? `, description: ${escapeValue(properties.description)}`
|
|
578
|
+
: '';
|
|
579
|
+
query = `CREATE (n:${t} {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, content: ${escapeValue(properties.content || '')}${descPart}, declaredType: ${escapeValue(properties.declaredType || '')}})`;
|
|
580
|
+
}
|
|
572
581
|
else {
|
|
573
582
|
// Multi-language tables (Struct, Impl, Trait, Macro, etc.) — no isExported
|
|
574
583
|
const descPart = properties.description
|
|
@@ -646,6 +655,12 @@ export const batchInsertNodesToLbug = async (nodes, dbPath) => {
|
|
|
646
655
|
: '';
|
|
647
656
|
query = `MERGE (n:${t} {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.isExported = ${!!properties.isExported}, n.content = ${escapeValue(properties.content || '')}${descPart}`;
|
|
648
657
|
}
|
|
658
|
+
else if (label === 'Property') {
|
|
659
|
+
const descPart = properties.description
|
|
660
|
+
? `, n.description = ${escapeValue(properties.description)}`
|
|
661
|
+
: '';
|
|
662
|
+
query = `MERGE (n:${t} {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.content = ${escapeValue(properties.content || '')}${descPart}, n.declaredType = ${escapeValue(properties.declaredType || '')}`;
|
|
663
|
+
}
|
|
649
664
|
else {
|
|
650
665
|
const descPart = properties.description
|
|
651
666
|
? `, n.description = ${escapeValue(properties.description)}`
|
|
@@ -32,7 +32,7 @@ export declare const TYPE_ALIAS_SCHEMA: string;
|
|
|
32
32
|
export declare const CONST_SCHEMA: string;
|
|
33
33
|
export declare const STATIC_SCHEMA: string;
|
|
34
34
|
export declare const VARIABLE_SCHEMA: string;
|
|
35
|
-
export declare const PROPERTY_SCHEMA
|
|
35
|
+
export declare const PROPERTY_SCHEMA = "\nCREATE NODE TABLE `Property` (\n id STRING,\n name STRING,\n filePath STRING,\n startLine INT64,\n endLine INT64,\n content STRING,\n description STRING,\n declaredType STRING,\n PRIMARY KEY (id)\n)";
|
|
36
36
|
export declare const RECORD_SCHEMA: string;
|
|
37
37
|
export declare const DELEGATE_SCHEMA: string;
|
|
38
38
|
export declare const ANNOTATION_SCHEMA: string;
|
package/dist/core/lbug/schema.js
CHANGED
|
@@ -150,7 +150,18 @@ export const TYPE_ALIAS_SCHEMA = CODE_ELEMENT_BASE('TypeAlias');
|
|
|
150
150
|
export const CONST_SCHEMA = CODE_ELEMENT_BASE('Const');
|
|
151
151
|
export const STATIC_SCHEMA = CODE_ELEMENT_BASE('Static');
|
|
152
152
|
export const VARIABLE_SCHEMA = CODE_ELEMENT_BASE('Variable');
|
|
153
|
-
export const PROPERTY_SCHEMA =
|
|
153
|
+
export const PROPERTY_SCHEMA = `
|
|
154
|
+
CREATE NODE TABLE \`Property\` (
|
|
155
|
+
id STRING,
|
|
156
|
+
name STRING,
|
|
157
|
+
filePath STRING,
|
|
158
|
+
startLine INT64,
|
|
159
|
+
endLine INT64,
|
|
160
|
+
content STRING,
|
|
161
|
+
description STRING,
|
|
162
|
+
declaredType STRING,
|
|
163
|
+
PRIMARY KEY (id)
|
|
164
|
+
)`;
|
|
154
165
|
export const RECORD_SCHEMA = CODE_ELEMENT_BASE('Record');
|
|
155
166
|
export const DELEGATE_SCHEMA = CODE_ELEMENT_BASE('Delegate');
|
|
156
167
|
export const ANNOTATION_SCHEMA = CODE_ELEMENT_BASE('Annotation');
|
|
@@ -1443,10 +1443,11 @@ export class LocalBackend {
|
|
|
1443
1443
|
// Categorized incoming refs
|
|
1444
1444
|
const incomingRows = await executeParameterized(repo.id, `
|
|
1445
1445
|
MATCH (caller)-[r:CodeRelation]->(n {id: $symId})
|
|
1446
|
-
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
|
|
1446
|
+
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'USES', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
|
|
1447
1447
|
RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
|
|
1448
1448
|
LIMIT 30
|
|
1449
1449
|
`, { symId });
|
|
1450
|
+
let typedPropertyRows = [];
|
|
1450
1451
|
// Fix #480: Class/Interface nodes have no direct CALLS/IMPORTS edges —
|
|
1451
1452
|
// those point to Constructor and File nodes respectively. Fetch those
|
|
1452
1453
|
// extra incoming refs and merge them in so context() shows real callers.
|
|
@@ -1476,13 +1477,13 @@ export class LocalBackend {
|
|
|
1476
1477
|
}
|
|
1477
1478
|
if (isClassLike) {
|
|
1478
1479
|
try {
|
|
1479
|
-
// Run
|
|
1480
|
-
const [ctorIncoming, fileIncoming] = await Promise.all([
|
|
1480
|
+
// Run incoming-ref queries in parallel — they are independent.
|
|
1481
|
+
const [ctorIncoming, fileIncoming, typedPropertyIncoming, typedProperties] = await Promise.all([
|
|
1481
1482
|
executeParameterized(repo.id, `
|
|
1482
1483
|
MATCH (n)-[hm:CodeRelation]->(ctor:Constructor)
|
|
1483
1484
|
WHERE n.id = $symId AND hm.type = 'HAS_METHOD'
|
|
1484
1485
|
MATCH (caller)-[r:CodeRelation]->(ctor)
|
|
1485
|
-
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'ACCESSES']
|
|
1486
|
+
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'USES', 'ACCESSES']
|
|
1486
1487
|
RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
|
|
1487
1488
|
LIMIT 30
|
|
1488
1489
|
`, { symId }),
|
|
@@ -1494,12 +1495,40 @@ export class LocalBackend {
|
|
|
1494
1495
|
RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
|
|
1495
1496
|
LIMIT 30
|
|
1496
1497
|
`, { symId }),
|
|
1498
|
+
executeParameterized(repo.id, `
|
|
1499
|
+
MATCH (p:\`Property\`)
|
|
1500
|
+
WHERE p.declaredType = $name
|
|
1501
|
+
OR p.declaredType STARTS WITH $genericPrefix
|
|
1502
|
+
OR p.declaredType CONTAINS $genericArg
|
|
1503
|
+
MATCH (caller)-[r:CodeRelation]->(p)
|
|
1504
|
+
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'USES', 'ACCESSES']
|
|
1505
|
+
RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
|
|
1506
|
+
LIMIT 30
|
|
1507
|
+
`, {
|
|
1508
|
+
name: sym.name,
|
|
1509
|
+
genericPrefix: `${sym.name}<`,
|
|
1510
|
+
genericArg: `<${sym.name}>`,
|
|
1511
|
+
}),
|
|
1512
|
+
executeParameterized(repo.id, `
|
|
1513
|
+
MATCH (p:\`Property\`)
|
|
1514
|
+
WHERE p.declaredType = $name
|
|
1515
|
+
OR p.declaredType STARTS WITH $genericPrefix
|
|
1516
|
+
OR p.declaredType CONTAINS $genericArg
|
|
1517
|
+
RETURN p.id AS uid, p.name AS name, p.filePath AS filePath, labels(p)[0] AS kind,
|
|
1518
|
+
p.declaredType AS declaredType
|
|
1519
|
+
LIMIT 30
|
|
1520
|
+
`, {
|
|
1521
|
+
name: sym.name,
|
|
1522
|
+
genericPrefix: `${sym.name}<`,
|
|
1523
|
+
genericArg: `<${sym.name}>`,
|
|
1524
|
+
}),
|
|
1497
1525
|
]);
|
|
1526
|
+
typedPropertyRows = typedProperties;
|
|
1498
1527
|
// Deduplicate by (relType, uid) — a caller can have multiple relation
|
|
1499
1528
|
// types to the same target (e.g. both IMPORTS and CALLS), and each
|
|
1500
1529
|
// must be preserved so every category appears in the output.
|
|
1501
1530
|
const seenKeys = new Set(incomingRows.map((r) => `${r.relType || r[0]}:${r.uid || r[1]}`));
|
|
1502
|
-
for (const r of [...ctorIncoming, ...fileIncoming]) {
|
|
1531
|
+
for (const r of [...ctorIncoming, ...fileIncoming, ...typedPropertyIncoming]) {
|
|
1503
1532
|
const key = `${r.relType || r[0]}:${r.uid || r[1]}`;
|
|
1504
1533
|
if (!seenKeys.has(key)) {
|
|
1505
1534
|
seenKeys.add(key);
|
|
@@ -1514,7 +1543,7 @@ export class LocalBackend {
|
|
|
1514
1543
|
// Categorized outgoing refs
|
|
1515
1544
|
const outgoingRows = await executeParameterized(repo.id, `
|
|
1516
1545
|
MATCH (n {id: $symId})-[r:CodeRelation]->(target)
|
|
1517
|
-
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
|
|
1546
|
+
WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'USES', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
|
|
1518
1547
|
RETURN r.type AS relType, target.id AS uid, target.name AS name, target.filePath AS filePath, labels(target)[0] AS kind
|
|
1519
1548
|
LIMIT 30
|
|
1520
1549
|
`, { symId });
|
|
@@ -1593,6 +1622,17 @@ export class LocalBackend {
|
|
|
1593
1622
|
},
|
|
1594
1623
|
incoming: categorize(incomingRows),
|
|
1595
1624
|
outgoing: categorize(outgoingRows),
|
|
1625
|
+
...(typedPropertyRows.length > 0
|
|
1626
|
+
? {
|
|
1627
|
+
typed_properties: typedPropertyRows.map((r) => ({
|
|
1628
|
+
uid: r.uid || r[0],
|
|
1629
|
+
name: r.name || r[1],
|
|
1630
|
+
filePath: r.filePath || r[2],
|
|
1631
|
+
kind: r.kind || r[3],
|
|
1632
|
+
declaredType: r.declaredType || r[4],
|
|
1633
|
+
})),
|
|
1634
|
+
}
|
|
1635
|
+
: {}),
|
|
1596
1636
|
processes: processRows.map((r) => ({
|
|
1597
1637
|
id: r.pid || r[0],
|
|
1598
1638
|
name: r.label || r[1],
|
|
@@ -2024,6 +2064,7 @@ export class LocalBackend {
|
|
|
2024
2064
|
const maxDepth = params.maxDepth || 3;
|
|
2025
2065
|
// Map legacy relation type names before filtering (backward compat for OVERRIDES → METHOD_OVERRIDES)
|
|
2026
2066
|
const mappedRelTypes = params.relationTypes?.flatMap((t) => t === 'OVERRIDES' ? ['OVERRIDES', 'METHOD_OVERRIDES'] : [t]);
|
|
2067
|
+
const hasExplicitRelationTypes = mappedRelTypes !== undefined && mappedRelTypes.length > 0;
|
|
2027
2068
|
const rawRelTypes = mappedRelTypes && mappedRelTypes.length > 0
|
|
2028
2069
|
? mappedRelTypes.filter((t) => VALID_RELATION_TYPES.has(t))
|
|
2029
2070
|
: [
|
|
@@ -2031,6 +2072,7 @@ export class LocalBackend {
|
|
|
2031
2072
|
'IMPORTS',
|
|
2032
2073
|
'EXTENDS',
|
|
2033
2074
|
'IMPLEMENTS',
|
|
2075
|
+
'USES',
|
|
2034
2076
|
'METHOD_OVERRIDES',
|
|
2035
2077
|
'OVERRIDES',
|
|
2036
2078
|
'METHOD_IMPLEMENTS',
|
|
@@ -2042,6 +2084,7 @@ export class LocalBackend {
|
|
|
2042
2084
|
'IMPORTS',
|
|
2043
2085
|
'EXTENDS',
|
|
2044
2086
|
'IMPLEMENTS',
|
|
2087
|
+
'USES',
|
|
2045
2088
|
'METHOD_OVERRIDES',
|
|
2046
2089
|
'OVERRIDES',
|
|
2047
2090
|
'METHOD_IMPLEMENTS',
|
|
@@ -2092,9 +2135,14 @@ export class LocalBackend {
|
|
|
2092
2135
|
filePath: outcome.symbol.filePath,
|
|
2093
2136
|
};
|
|
2094
2137
|
const symType = outcome.resolvedLabel || outcome.symbol.type || '';
|
|
2138
|
+
const effectiveRelationTypes = (symType === 'Class' || symType === 'Interface') &&
|
|
2139
|
+
!hasExplicitRelationTypes &&
|
|
2140
|
+
!relationTypes.includes('ACCESSES')
|
|
2141
|
+
? [...relationTypes, 'ACCESSES']
|
|
2142
|
+
: relationTypes;
|
|
2095
2143
|
return this._runImpactBFS(repo, sym, symType, direction, {
|
|
2096
2144
|
maxDepth,
|
|
2097
|
-
relationTypes,
|
|
2145
|
+
relationTypes: effectiveRelationTypes,
|
|
2098
2146
|
includeTests,
|
|
2099
2147
|
minConfidence,
|
|
2100
2148
|
});
|
|
@@ -2149,6 +2197,24 @@ export class LocalBackend {
|
|
|
2149
2197
|
frontier.push(rid);
|
|
2150
2198
|
}
|
|
2151
2199
|
}
|
|
2200
|
+
const typedPropertyRows = await executeParameterized(repo.id, `
|
|
2201
|
+
MATCH (p:\`Property\`)
|
|
2202
|
+
WHERE p.declaredType = $name
|
|
2203
|
+
OR p.declaredType STARTS WITH $genericPrefix
|
|
2204
|
+
OR p.declaredType CONTAINS $genericArg
|
|
2205
|
+
RETURN p.id AS id, p.name AS name, labels(p)[0] AS type, p.filePath AS filePath
|
|
2206
|
+
`, {
|
|
2207
|
+
name: sym.name,
|
|
2208
|
+
genericPrefix: `${sym.name}<`,
|
|
2209
|
+
genericArg: `<${sym.name}>`,
|
|
2210
|
+
});
|
|
2211
|
+
for (const r of typedPropertyRows) {
|
|
2212
|
+
const rid = r.id || r[0];
|
|
2213
|
+
if (rid && !visited.has(rid)) {
|
|
2214
|
+
visited.add(rid);
|
|
2215
|
+
frontier.push(rid);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2152
2218
|
}
|
|
2153
2219
|
catch (e) {
|
|
2154
2220
|
logQueryError('impact:class-node-expansion', e);
|
package/package.json
CHANGED