gitnexus 1.4.8 → 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 +7 -0
- package/dist/cli/index-repo.d.ts +15 -0
- package/dist/cli/index-repo.js +115 -0
- package/dist/cli/index.js +11 -2
- package/dist/cli/setup.js +12 -9
- package/dist/cli/wiki.d.ts +4 -0
- package/dist/cli/wiki.js +174 -53
- package/dist/config/supported-languages.d.ts +7 -5
- package/dist/config/supported-languages.js +6 -4
- package/dist/core/graph/graph.js +9 -1
- package/dist/core/graph/types.d.ts +10 -2
- package/dist/core/ingestion/call-processor.d.ts +18 -1
- package/dist/core/ingestion/call-processor.js +297 -38
- package/dist/core/ingestion/call-routing.d.ts +3 -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 +18 -4
- 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 +87 -0
- package/dist/core/ingestion/framework-detection.js +65 -2
- package/dist/core/ingestion/heritage-processor.js +15 -17
- package/dist/core/ingestion/import-processor.d.ts +9 -10
- package/dist/core/ingestion/import-processor.js +59 -14
- 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 +9 -1
- package/dist/core/ingestion/{resolvers → import-resolvers}/jvm.js +56 -0
- package/dist/core/ingestion/{resolvers → import-resolvers}/php.d.ts +6 -10
- package/dist/core/ingestion/{resolvers → import-resolvers}/php.js +7 -2
- 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 +0 -3
- package/dist/core/ingestion/{resolvers → import-resolvers}/utils.js +0 -9
- package/dist/core/ingestion/language-config.d.ts +4 -1
- 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/mro-processor.js +14 -15
- package/dist/core/ingestion/{named-binding-extraction.d.ts → named-binding-processor.d.ts} +0 -9
- 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 +5 -1
- package/dist/core/ingestion/parsing-processor.js +115 -16
- package/dist/core/ingestion/pipeline.js +925 -424
- package/dist/core/ingestion/resolution-context.js +1 -1
- 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/tree-sitter-queries.d.ts +8 -7
- package/dist/core/ingestion/tree-sitter-queries.js +231 -9
- package/dist/core/ingestion/type-env.d.ts +14 -17
- package/dist/core/ingestion/type-env.js +66 -14
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +1 -1
- package/dist/core/ingestion/type-extractors/csharp.js +1 -1
- 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 +1 -1
- package/dist/core/ingestion/type-extractors/shared.d.ts +1 -13
- package/dist/core/ingestion/type-extractors/shared.js +9 -102
- package/dist/core/ingestion/type-extractors/swift.js +334 -4
- package/dist/core/ingestion/type-extractors/types.d.ts +3 -1
- package/dist/core/ingestion/{ast-helpers.d.ts → utils/ast-helpers.d.ts} +16 -13
- package/dist/core/ingestion/{ast-helpers.js → utils/ast-helpers.js} +111 -32
- package/dist/core/ingestion/{call-analysis.js → utils/call-analysis.js} +37 -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 +43 -2
- package/dist/core/ingestion/workers/parse-worker.js +361 -150
- package/dist/core/lbug/csv-generator.js +34 -1
- package/dist/core/lbug/lbug-adapter.js +6 -0
- package/dist/core/lbug/schema.d.ts +5 -3
- package/dist/core/lbug/schema.js +39 -2
- 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/lbug-adapter.d.ts +5 -0
- package/dist/mcp/core/lbug-adapter.js +11 -1
- package/dist/mcp/local/local-backend.d.ts +16 -5
- package/dist/mcp/local/local-backend.js +711 -74
- package/dist/mcp/tools.js +71 -2
- package/dist/storage/repo-manager.d.ts +3 -0
- package/package.json +17 -16
- package/dist/core/ingestion/import-resolution.d.ts +0 -101
- package/dist/core/ingestion/import-resolution.js +0 -251
- package/dist/core/ingestion/named-binding-extraction.js +0 -373
- package/dist/core/ingestion/resolvers/index.d.ts +0 -18
- package/dist/core/ingestion/resolvers/index.js +0 -13
- 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 -20
- package/dist/core/ingestion/utils.js +0 -242
- package/scripts/patch-tree-sitter-swift.cjs +0 -74
- /package/dist/core/ingestion/{call-analysis.d.ts → utils/call-analysis.d.ts} +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swift Language Provider
|
|
3
|
+
*
|
|
4
|
+
* Assembles all Swift-specific ingestion capabilities into a single
|
|
5
|
+
* LanguageProvider, following the Strategy pattern used by the pipeline.
|
|
6
|
+
*
|
|
7
|
+
* Key Swift traits:
|
|
8
|
+
* - importSemantics: 'wildcard' (Swift imports entire modules)
|
|
9
|
+
* - heritageDefaultEdge: 'IMPLEMENTS' (protocols are more common than class inheritance)
|
|
10
|
+
* - implicitImportWirer: all files in the same SPM target see each other
|
|
11
|
+
*/
|
|
12
|
+
import { SupportedLanguages } from '../../../config/supported-languages.js';
|
|
13
|
+
import { defineLanguage } from '../language-provider.js';
|
|
14
|
+
import { typeConfig as swiftConfig } from '../type-extractors/swift.js';
|
|
15
|
+
import { swiftExportChecker } from '../export-detection.js';
|
|
16
|
+
import { resolveSwiftImport } from '../import-resolvers/swift.js';
|
|
17
|
+
import { SWIFT_QUERIES } from '../tree-sitter-queries.js';
|
|
18
|
+
import { createFieldExtractor } from '../field-extractors/generic.js';
|
|
19
|
+
import { swiftConfig as swiftFieldConfig } from '../field-extractors/configs/swift.js';
|
|
20
|
+
/**
|
|
21
|
+
* Group Swift files by SPM target for implicit module visibility.
|
|
22
|
+
* If SwiftPackageConfig is available, use target -> directory mappings.
|
|
23
|
+
* Otherwise, group all Swift files under a single "default" target
|
|
24
|
+
* (assumes a single-module Xcode project).
|
|
25
|
+
*/
|
|
26
|
+
function groupSwiftFilesByTarget(swiftFiles, swiftPackageConfig) {
|
|
27
|
+
// No SPM config -> single target (common for Xcode projects)
|
|
28
|
+
if (!swiftPackageConfig || swiftPackageConfig.targets.size === 0) {
|
|
29
|
+
return new Map([['__default__', swiftFiles]]);
|
|
30
|
+
}
|
|
31
|
+
// Pre-convert target dirs to normalized prefix format once
|
|
32
|
+
const targets = [...swiftPackageConfig.targets.entries()].map(([name, dir]) => ({ name, prefix: dir.replace(/\\/g, '/') + '/' }));
|
|
33
|
+
const groups = new Map();
|
|
34
|
+
const defaultGroup = [];
|
|
35
|
+
for (const file of swiftFiles) {
|
|
36
|
+
const normalized = file.includes('\\') ? file.replace(/\\/g, '/') : file;
|
|
37
|
+
let assigned = false;
|
|
38
|
+
for (const { name, prefix } of targets) {
|
|
39
|
+
const idx = normalized.indexOf(prefix);
|
|
40
|
+
if (idx === 0 || (idx > 0 && normalized[idx - 1] === '/')) {
|
|
41
|
+
let group = groups.get(name);
|
|
42
|
+
if (!group) {
|
|
43
|
+
group = [];
|
|
44
|
+
groups.set(name, group);
|
|
45
|
+
}
|
|
46
|
+
group.push(file);
|
|
47
|
+
assigned = true;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!assigned)
|
|
52
|
+
defaultGroup.push(file);
|
|
53
|
+
}
|
|
54
|
+
if (defaultGroup.length > 0)
|
|
55
|
+
groups.set('__default__', defaultGroup);
|
|
56
|
+
return groups;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Wire implicit inter-file imports for Swift.
|
|
60
|
+
* All files in the same SPM target see each other (full module visibility).
|
|
61
|
+
* Two fast paths avoid unnecessary work:
|
|
62
|
+
* 1. No existing imports for src -> emit all (m-1) edges without Set.has checks
|
|
63
|
+
* 2. Existing imports present -> skip already-connected pairs
|
|
64
|
+
*/
|
|
65
|
+
function wireSwiftImplicitImports(swiftFiles, importMap, addImportEdge, projectConfig) {
|
|
66
|
+
const configs = projectConfig;
|
|
67
|
+
const targetGroups = groupSwiftFilesByTarget(swiftFiles, configs?.swiftPackageConfig ?? null);
|
|
68
|
+
for (const group of targetGroups.values()) {
|
|
69
|
+
const m = group.length;
|
|
70
|
+
if (m <= 1)
|
|
71
|
+
continue;
|
|
72
|
+
// All-pairs implicit edges: O(m²) is inherent for full module visibility.
|
|
73
|
+
for (let i = 0; i < m; i++) {
|
|
74
|
+
const src = group[i];
|
|
75
|
+
const existing = importMap.get(src);
|
|
76
|
+
if (!existing || existing.size === 0) {
|
|
77
|
+
// Fast path: no prior imports — emit all peers unconditionally
|
|
78
|
+
for (let j = 0; j < m; j++) {
|
|
79
|
+
if (i !== j)
|
|
80
|
+
addImportEdge(src, group[j]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Dedup path: skip already-connected pairs
|
|
85
|
+
for (let j = 0; j < m; j++) {
|
|
86
|
+
if (i !== j && !existing.has(group[j])) {
|
|
87
|
+
addImportEdge(src, group[j]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const BUILT_INS = new Set([
|
|
95
|
+
'print', 'debugPrint', 'dump', 'fatalError', 'precondition', 'preconditionFailure',
|
|
96
|
+
'assert', 'assertionFailure', 'NSLog',
|
|
97
|
+
'abs', 'min', 'max', 'zip', 'stride', 'sequence', 'repeatElement',
|
|
98
|
+
'swap', 'withUnsafePointer', 'withUnsafeMutablePointer', 'withUnsafeBytes',
|
|
99
|
+
'autoreleasepool', 'unsafeBitCast', 'unsafeDowncast', 'numericCast',
|
|
100
|
+
'type', 'MemoryLayout',
|
|
101
|
+
'map', 'flatMap', 'compactMap', 'filter', 'reduce', 'forEach', 'contains',
|
|
102
|
+
'first', 'last', 'prefix', 'suffix', 'dropFirst', 'dropLast',
|
|
103
|
+
'sorted', 'reversed', 'enumerated', 'joined', 'split',
|
|
104
|
+
'append', 'insert', 'remove', 'removeAll', 'removeFirst', 'removeLast',
|
|
105
|
+
'isEmpty', 'count', 'index', 'startIndex', 'endIndex',
|
|
106
|
+
'addSubview', 'removeFromSuperview', 'layoutSubviews', 'setNeedsLayout',
|
|
107
|
+
'layoutIfNeeded', 'setNeedsDisplay', 'invalidateIntrinsicContentSize',
|
|
108
|
+
'addTarget', 'removeTarget', 'addGestureRecognizer',
|
|
109
|
+
'addConstraint', 'addConstraints', 'removeConstraint', 'removeConstraints',
|
|
110
|
+
'NSLocalizedString', 'Bundle',
|
|
111
|
+
'reloadData', 'reloadSections', 'reloadRows', 'performBatchUpdates',
|
|
112
|
+
'register', 'dequeueReusableCell', 'dequeueReusableSupplementaryView',
|
|
113
|
+
'beginUpdates', 'endUpdates', 'insertRows', 'deleteRows', 'insertSections', 'deleteSections',
|
|
114
|
+
'present', 'dismiss', 'pushViewController', 'popViewController', 'popToRootViewController',
|
|
115
|
+
'performSegue', 'prepare',
|
|
116
|
+
'DispatchQueue', 'async', 'sync', 'asyncAfter',
|
|
117
|
+
'Task', 'withCheckedContinuation', 'withCheckedThrowingContinuation',
|
|
118
|
+
'sink', 'store', 'assign', 'receive', 'subscribe',
|
|
119
|
+
'addObserver', 'removeObserver', 'post', 'NotificationCenter',
|
|
120
|
+
]);
|
|
121
|
+
export const swiftProvider = defineLanguage({
|
|
122
|
+
id: SupportedLanguages.Swift,
|
|
123
|
+
extensions: ['.swift'],
|
|
124
|
+
treeSitterQueries: SWIFT_QUERIES,
|
|
125
|
+
typeConfig: swiftConfig,
|
|
126
|
+
exportChecker: swiftExportChecker,
|
|
127
|
+
importResolver: resolveSwiftImport,
|
|
128
|
+
importSemantics: 'wildcard',
|
|
129
|
+
heritageDefaultEdge: 'IMPLEMENTS',
|
|
130
|
+
fieldExtractor: createFieldExtractor(swiftFieldConfig),
|
|
131
|
+
implicitImportWirer: wireSwiftImplicitImports,
|
|
132
|
+
builtInNames: BUILT_INS,
|
|
133
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript and JavaScript language providers.
|
|
3
|
+
*
|
|
4
|
+
* Both languages share the same type extraction config (typescriptConfig),
|
|
5
|
+
* export checker (tsExportChecker), and named binding extractor
|
|
6
|
+
* (extractTsNamedBindings). They differ in file extensions, tree-sitter
|
|
7
|
+
* queries (TypeScript grammar has interface/type nodes), and language ID.
|
|
8
|
+
*/
|
|
9
|
+
export declare const typescriptProvider: import("../language-provider.js").LanguageProvider;
|
|
10
|
+
export declare const javascriptProvider: import("../language-provider.js").LanguageProvider;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript and JavaScript language providers.
|
|
3
|
+
*
|
|
4
|
+
* Both languages share the same type extraction config (typescriptConfig),
|
|
5
|
+
* export checker (tsExportChecker), and named binding extractor
|
|
6
|
+
* (extractTsNamedBindings). They differ in file extensions, tree-sitter
|
|
7
|
+
* queries (TypeScript grammar has interface/type nodes), and language ID.
|
|
8
|
+
*/
|
|
9
|
+
import { SupportedLanguages } from '../../../config/supported-languages.js';
|
|
10
|
+
import { defineLanguage } from '../language-provider.js';
|
|
11
|
+
import { typeConfig as typescriptConfig } from '../type-extractors/typescript.js';
|
|
12
|
+
import { tsExportChecker } from '../export-detection.js';
|
|
13
|
+
import { resolveTypescriptImport, resolveJavascriptImport } from '../import-resolvers/standard.js';
|
|
14
|
+
import { extractTsNamedBindings } from '../named-bindings/typescript.js';
|
|
15
|
+
import { TYPESCRIPT_QUERIES, JAVASCRIPT_QUERIES } from '../tree-sitter-queries.js';
|
|
16
|
+
import { typescriptFieldExtractor } from '../field-extractors/typescript.js';
|
|
17
|
+
import { createFieldExtractor } from '../field-extractors/generic.js';
|
|
18
|
+
import { javascriptConfig } from '../field-extractors/configs/typescript-javascript.js';
|
|
19
|
+
const BUILT_INS = new Set([
|
|
20
|
+
'console', 'log', 'warn', 'error', 'info', 'debug',
|
|
21
|
+
'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
|
|
22
|
+
'parseInt', 'parseFloat', 'isNaN', 'isFinite',
|
|
23
|
+
'encodeURI', 'decodeURI', 'encodeURIComponent', 'decodeURIComponent',
|
|
24
|
+
'JSON', 'parse', 'stringify',
|
|
25
|
+
'Object', 'Array', 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
|
|
26
|
+
'Map', 'Set', 'WeakMap', 'WeakSet',
|
|
27
|
+
'Promise', 'resolve', 'reject', 'then', 'catch', 'finally',
|
|
28
|
+
'Math', 'Date', 'RegExp', 'Error',
|
|
29
|
+
'require', 'import', 'export', 'fetch', 'Response', 'Request',
|
|
30
|
+
'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef', 'useContext',
|
|
31
|
+
'useReducer', 'useLayoutEffect', 'useImperativeHandle', 'useDebugValue',
|
|
32
|
+
'createElement', 'createContext', 'createRef', 'forwardRef', 'memo', 'lazy',
|
|
33
|
+
'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex', 'some', 'every',
|
|
34
|
+
'includes', 'indexOf', 'slice', 'splice', 'concat', 'join', 'split',
|
|
35
|
+
'push', 'pop', 'shift', 'unshift', 'sort', 'reverse',
|
|
36
|
+
'keys', 'values', 'entries', 'assign', 'freeze', 'seal',
|
|
37
|
+
'hasOwnProperty', 'toString', 'valueOf',
|
|
38
|
+
]);
|
|
39
|
+
export const typescriptProvider = defineLanguage({
|
|
40
|
+
id: SupportedLanguages.TypeScript,
|
|
41
|
+
extensions: ['.ts', '.tsx'],
|
|
42
|
+
treeSitterQueries: TYPESCRIPT_QUERIES,
|
|
43
|
+
typeConfig: typescriptConfig,
|
|
44
|
+
exportChecker: tsExportChecker,
|
|
45
|
+
importResolver: resolveTypescriptImport,
|
|
46
|
+
namedBindingExtractor: extractTsNamedBindings,
|
|
47
|
+
fieldExtractor: typescriptFieldExtractor,
|
|
48
|
+
builtInNames: BUILT_INS,
|
|
49
|
+
});
|
|
50
|
+
export const javascriptProvider = defineLanguage({
|
|
51
|
+
id: SupportedLanguages.JavaScript,
|
|
52
|
+
extensions: ['.js', '.jsx'],
|
|
53
|
+
treeSitterQueries: JAVASCRIPT_QUERIES,
|
|
54
|
+
typeConfig: typescriptConfig,
|
|
55
|
+
exportChecker: tsExportChecker,
|
|
56
|
+
importResolver: resolveJavascriptImport,
|
|
57
|
+
namedBindingExtractor: extractTsNamedBindings,
|
|
58
|
+
fieldExtractor: createFieldExtractor(javascriptConfig),
|
|
59
|
+
builtInNames: BUILT_INS,
|
|
60
|
+
});
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Cypher: MATCH (c:Class)-[r:CodeRelation {type: 'OVERRIDES'}]->(m:Method)
|
|
20
20
|
*/
|
|
21
21
|
import { generateId } from '../../lib/utils.js';
|
|
22
|
-
import {
|
|
22
|
+
import { getProvider } from './languages/index.js';
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
24
|
// Internal helpers
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
@@ -219,9 +219,10 @@ export function computeMRO(graph) {
|
|
|
219
219
|
if (!language)
|
|
220
220
|
continue;
|
|
221
221
|
const className = classNode.properties.name;
|
|
222
|
-
// Compute linearized MRO depending on language
|
|
222
|
+
// Compute linearized MRO depending on language strategy
|
|
223
|
+
const provider = getProvider(language);
|
|
223
224
|
let mroOrder;
|
|
224
|
-
if (
|
|
225
|
+
if (provider.mroStrategy === 'c3') {
|
|
225
226
|
const c3Result = c3Linearize(classId, parentMap, c3Cache);
|
|
226
227
|
mroOrder = c3Result ?? gatherAncestors(classId, parentMap);
|
|
227
228
|
}
|
|
@@ -264,8 +265,8 @@ export function computeMRO(graph) {
|
|
|
264
265
|
}
|
|
265
266
|
// Detect collisions: methods defined in 2+ different ancestors
|
|
266
267
|
const ambiguities = [];
|
|
267
|
-
// Compute transitive edge types once per class (only needed for
|
|
268
|
-
const needsEdgeTypes =
|
|
268
|
+
// Compute transitive edge types once per class (only needed for implements-split languages)
|
|
269
|
+
const needsEdgeTypes = provider.mroStrategy === 'implements-split';
|
|
269
270
|
const classEdgeTypes = needsEdgeTypes
|
|
270
271
|
? buildTransitiveEdgeTypes(classId, parentMap, parentEdgeType)
|
|
271
272
|
: undefined;
|
|
@@ -281,22 +282,20 @@ export function computeMRO(graph) {
|
|
|
281
282
|
if (ownDefinesIt)
|
|
282
283
|
continue;
|
|
283
284
|
let resolution;
|
|
284
|
-
switch (
|
|
285
|
-
case
|
|
286
|
-
resolution = resolveByMroOrder(methodName, defs, mroOrder, '
|
|
285
|
+
switch (provider.mroStrategy) {
|
|
286
|
+
case 'leftmost-base':
|
|
287
|
+
resolution = resolveByMroOrder(methodName, defs, mroOrder, 'leftmost base');
|
|
287
288
|
break;
|
|
288
|
-
case
|
|
289
|
-
case SupportedLanguages.Java:
|
|
290
|
-
case SupportedLanguages.Kotlin:
|
|
289
|
+
case 'implements-split':
|
|
291
290
|
resolution = resolveCsharpJava(methodName, defs, classEdgeTypes);
|
|
292
291
|
break;
|
|
293
|
-
case
|
|
294
|
-
resolution = resolveByMroOrder(methodName, defs, mroOrder, '
|
|
292
|
+
case 'c3':
|
|
293
|
+
resolution = resolveByMroOrder(methodName, defs, mroOrder, 'C3 MRO');
|
|
295
294
|
break;
|
|
296
|
-
case
|
|
295
|
+
case 'qualified-syntax':
|
|
297
296
|
resolution = {
|
|
298
297
|
resolvedTo: null,
|
|
299
|
-
reason: `
|
|
298
|
+
reason: `requires qualified syntax: <Type as Trait>::${methodName}()`,
|
|
300
299
|
confidence: 0.5,
|
|
301
300
|
};
|
|
302
301
|
break;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { SymbolTable, SymbolDefinition } from './symbol-table.js';
|
|
2
2
|
import type { NamedImportMap } from './import-processor.js';
|
|
3
|
-
import type { NamedBinding } from './import-resolution.js';
|
|
4
|
-
import type { SyntaxNode } from './utils.js';
|
|
5
3
|
/**
|
|
6
4
|
* Walk a named-binding re-export chain through NamedImportMap.
|
|
7
5
|
*
|
|
@@ -18,10 +16,3 @@ import type { SyntaxNode } from './utils.js';
|
|
|
18
16
|
* silent misses at depth=0 for non-aliased bindings.
|
|
19
17
|
*/
|
|
20
18
|
export declare function walkBindingChain(name: string, currentFilePath: string, symbolTable: SymbolTable, namedImportMap: NamedImportMap, allDefs: SymbolDefinition[]): SymbolDefinition[] | null;
|
|
21
|
-
export declare function extractTsNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
|
|
22
|
-
export declare function extractPythonNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
|
|
23
|
-
export declare function extractKotlinNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
|
|
24
|
-
export declare function extractRustNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
|
|
25
|
-
export declare function extractPhpNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
|
|
26
|
-
export declare function extractCsharpNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
|
|
27
|
-
export declare function extractJavaNamedBindings(importNode: SyntaxNode): NamedBinding[] | undefined;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk a named-binding re-export chain through NamedImportMap.
|
|
3
|
+
*
|
|
4
|
+
* When file A imports { User } from B, and B re-exports { User } from C,
|
|
5
|
+
* the NamedImportMap for A points to B, but B has no User definition.
|
|
6
|
+
* This function follows the chain: A→B→C until a definition is found.
|
|
7
|
+
*
|
|
8
|
+
* Returns the definitions found at the end of the chain, or null if the
|
|
9
|
+
* chain breaks (missing binding, circular reference, or depth exceeded).
|
|
10
|
+
* Max depth 5 to prevent infinite loops.
|
|
11
|
+
*
|
|
12
|
+
* @param allDefs Pre-computed `symbolTable.lookupFuzzy(name)` result — must be the
|
|
13
|
+
* complete unfiltered result. Passing a file-filtered subset will cause
|
|
14
|
+
* silent misses at depth=0 for non-aliased bindings.
|
|
15
|
+
*/
|
|
16
|
+
export function walkBindingChain(name, currentFilePath, symbolTable, namedImportMap, allDefs) {
|
|
17
|
+
let lookupFile = currentFilePath;
|
|
18
|
+
let lookupName = name;
|
|
19
|
+
const visited = new Set();
|
|
20
|
+
for (let depth = 0; depth < 5; depth++) {
|
|
21
|
+
const bindings = namedImportMap.get(lookupFile);
|
|
22
|
+
if (!bindings)
|
|
23
|
+
return null;
|
|
24
|
+
const binding = bindings.get(lookupName);
|
|
25
|
+
if (!binding)
|
|
26
|
+
return null;
|
|
27
|
+
const key = `${binding.sourcePath}:${binding.exportedName}`;
|
|
28
|
+
if (visited.has(key))
|
|
29
|
+
return null; // circular
|
|
30
|
+
visited.add(key);
|
|
31
|
+
const targetName = binding.exportedName;
|
|
32
|
+
const resolvedDefs = targetName !== lookupName || depth > 0
|
|
33
|
+
? symbolTable.lookupFuzzy(targetName).filter(def => def.filePath === binding.sourcePath)
|
|
34
|
+
: allDefs.filter(def => def.filePath === binding.sourcePath);
|
|
35
|
+
if (resolvedDefs.length > 0)
|
|
36
|
+
return resolvedDefs;
|
|
37
|
+
// No definition in source file → follow re-export chain
|
|
38
|
+
lookupFile = binding.sourcePath;
|
|
39
|
+
lookupName = targetName;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export function extractCSharpNamedBindings(importNode) {
|
|
2
|
+
// using_directive — three forms:
|
|
3
|
+
// using Alias = NS.Type; → aliasIdent + qualifiedName
|
|
4
|
+
// using static NS.Type; → static + qualifiedName (no alias)
|
|
5
|
+
// using NS; → qualifiedName only (namespace, not capturable)
|
|
6
|
+
if (importNode.type !== 'using_directive')
|
|
7
|
+
return undefined;
|
|
8
|
+
let aliasIdent = null;
|
|
9
|
+
let qualifiedName = null;
|
|
10
|
+
let isStatic = false;
|
|
11
|
+
for (let i = 0; i < importNode.childCount; i++) {
|
|
12
|
+
const child = importNode.child(i);
|
|
13
|
+
if (child?.text === 'static')
|
|
14
|
+
isStatic = true;
|
|
15
|
+
}
|
|
16
|
+
for (let i = 0; i < importNode.namedChildCount; i++) {
|
|
17
|
+
const child = importNode.namedChild(i);
|
|
18
|
+
if (child?.type === 'identifier' && !aliasIdent)
|
|
19
|
+
aliasIdent = child;
|
|
20
|
+
else if (child?.type === 'qualified_name')
|
|
21
|
+
qualifiedName = child;
|
|
22
|
+
}
|
|
23
|
+
// Form 1: using Alias = NS.Type;
|
|
24
|
+
if (aliasIdent && qualifiedName) {
|
|
25
|
+
const fullText = qualifiedName.text;
|
|
26
|
+
const exportedName = fullText.includes('.') ? fullText.split('.').pop() : fullText;
|
|
27
|
+
return [{ local: aliasIdent.text, exported: exportedName }];
|
|
28
|
+
}
|
|
29
|
+
// Form 2: using static NS.Type; — last segment is the class name
|
|
30
|
+
if (isStatic && qualifiedName) {
|
|
31
|
+
const fullText = qualifiedName.text;
|
|
32
|
+
const lastSegment = fullText.includes('.') ? fullText.split('.').pop() : fullText;
|
|
33
|
+
return [{ local: lastSegment, exported: lastSegment }];
|
|
34
|
+
}
|
|
35
|
+
// Form 3: using NS; — namespace import, can't resolve to per-symbol bindings
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { findChild } from '../utils/ast-helpers.js';
|
|
2
|
+
export function extractJavaNamedBindings(importNode) {
|
|
3
|
+
// import_declaration > scoped_identifier "com.example.models.User"
|
|
4
|
+
// Wildcard imports (.*) don't produce named bindings
|
|
5
|
+
if (importNode.type !== 'import_declaration')
|
|
6
|
+
return undefined;
|
|
7
|
+
// Check for asterisk (wildcard import) and static modifier
|
|
8
|
+
let isStatic = false;
|
|
9
|
+
for (let i = 0; i < importNode.childCount; i++) {
|
|
10
|
+
const child = importNode.child(i);
|
|
11
|
+
if (child?.type === 'asterisk')
|
|
12
|
+
return undefined;
|
|
13
|
+
if (child?.text === 'static')
|
|
14
|
+
isStatic = true;
|
|
15
|
+
}
|
|
16
|
+
const scopedId = findChild(importNode, 'scoped_identifier');
|
|
17
|
+
if (!scopedId)
|
|
18
|
+
return undefined;
|
|
19
|
+
const fullText = scopedId.text;
|
|
20
|
+
const lastDot = fullText.lastIndexOf('.');
|
|
21
|
+
if (lastDot === -1)
|
|
22
|
+
return undefined;
|
|
23
|
+
const name = fullText.slice(lastDot + 1);
|
|
24
|
+
// Non-static: skip lowercase names — those are package imports, not class imports.
|
|
25
|
+
// Static: allow lowercase — `import static models.UserFactory.getUser` imports a method.
|
|
26
|
+
if (!isStatic && name[0] && name[0] === name[0].toLowerCase())
|
|
27
|
+
return undefined;
|
|
28
|
+
return [{ local: name, exported: name }];
|
|
29
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { findChild } from '../utils/ast-helpers.js';
|
|
2
|
+
export function extractKotlinNamedBindings(importNode) {
|
|
3
|
+
// import_header > identifier + import_alias > simple_identifier
|
|
4
|
+
if (importNode.type !== 'import_header')
|
|
5
|
+
return undefined;
|
|
6
|
+
const fullIdent = findChild(importNode, 'identifier');
|
|
7
|
+
if (!fullIdent)
|
|
8
|
+
return undefined;
|
|
9
|
+
const fullText = fullIdent.text;
|
|
10
|
+
const exportedName = fullText.includes('.') ? fullText.split('.').pop() : fullText;
|
|
11
|
+
const importAlias = findChild(importNode, 'import_alias');
|
|
12
|
+
if (importAlias) {
|
|
13
|
+
// Aliased: import com.example.User as U
|
|
14
|
+
const aliasIdent = findChild(importAlias, 'simple_identifier');
|
|
15
|
+
if (!aliasIdent)
|
|
16
|
+
return undefined;
|
|
17
|
+
return [{ local: aliasIdent.text, exported: exportedName }];
|
|
18
|
+
}
|
|
19
|
+
// Non-aliased: import com.example.User → local="User", exported="User"
|
|
20
|
+
// Also handles top-level function imports: import models.getUser → local="getUser"
|
|
21
|
+
// Skip wildcard imports (ending in *)
|
|
22
|
+
if (fullText.endsWith('.*') || fullText.endsWith('*'))
|
|
23
|
+
return undefined;
|
|
24
|
+
// Skip class-member imports (e.g., import util.OneArg.writeAudit) where the
|
|
25
|
+
// second-to-last segment is PascalCase (a class name). Multiple member imports
|
|
26
|
+
// with the same function name would collide in NamedImportMap, breaking
|
|
27
|
+
// arity-based disambiguation. Top-level function imports (import models.getUser)
|
|
28
|
+
// and class imports (import models.User) have package-only prefixes.
|
|
29
|
+
const segments = fullText.split('.');
|
|
30
|
+
if (segments.length >= 3) {
|
|
31
|
+
const parentSegment = segments[segments.length - 2];
|
|
32
|
+
if (parentSegment[0] && parentSegment[0] === parentSegment[0].toUpperCase())
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
return [{ local: exportedName, exported: exportedName }];
|
|
36
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function extractPhpNamedBindings(importNode) {
|
|
2
|
+
// namespace_use_declaration > namespace_use_clause* (flat)
|
|
3
|
+
// namespace_use_declaration > namespace_use_group > namespace_use_clause* (grouped)
|
|
4
|
+
if (importNode.type !== 'namespace_use_declaration')
|
|
5
|
+
return undefined;
|
|
6
|
+
// Skip 'use function' and 'use const' declarations — these import callables/constants,
|
|
7
|
+
// not class types, and should not be added to namedImportMap as type bindings.
|
|
8
|
+
const useTypeNode = importNode.childForFieldName?.('type');
|
|
9
|
+
if (useTypeNode && (useTypeNode.text === 'function' || useTypeNode.text === 'const')) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
const bindings = [];
|
|
13
|
+
// Collect all clauses — from direct children AND from namespace_use_group
|
|
14
|
+
const clauses = [];
|
|
15
|
+
for (let i = 0; i < importNode.namedChildCount; i++) {
|
|
16
|
+
const child = importNode.namedChild(i);
|
|
17
|
+
if (child?.type === 'namespace_use_clause') {
|
|
18
|
+
clauses.push(child);
|
|
19
|
+
}
|
|
20
|
+
else if (child?.type === 'namespace_use_group') {
|
|
21
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
22
|
+
const groupChild = child.namedChild(j);
|
|
23
|
+
if (groupChild?.type === 'namespace_use_clause')
|
|
24
|
+
clauses.push(groupChild);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const clause of clauses) {
|
|
29
|
+
// Flat imports: qualified_name + name (alias)
|
|
30
|
+
let qualifiedName = null;
|
|
31
|
+
const names = [];
|
|
32
|
+
for (let j = 0; j < clause.namedChildCount; j++) {
|
|
33
|
+
const child = clause.namedChild(j);
|
|
34
|
+
if (child?.type === 'qualified_name')
|
|
35
|
+
qualifiedName = child;
|
|
36
|
+
else if (child?.type === 'name')
|
|
37
|
+
names.push(child);
|
|
38
|
+
}
|
|
39
|
+
if (qualifiedName && names.length > 0) {
|
|
40
|
+
// Flat aliased import: use App\Models\Repo as R;
|
|
41
|
+
const fullText = qualifiedName.text;
|
|
42
|
+
const exportedName = fullText.includes('\\') ? fullText.split('\\').pop() : fullText;
|
|
43
|
+
bindings.push({ local: names[0].text, exported: exportedName });
|
|
44
|
+
}
|
|
45
|
+
else if (qualifiedName && names.length === 0) {
|
|
46
|
+
// Flat non-aliased import: use App\Models\User;
|
|
47
|
+
const fullText = qualifiedName.text;
|
|
48
|
+
const lastSegment = fullText.includes('\\') ? fullText.split('\\').pop() : fullText;
|
|
49
|
+
bindings.push({ local: lastSegment, exported: lastSegment });
|
|
50
|
+
}
|
|
51
|
+
else if (!qualifiedName && names.length >= 2) {
|
|
52
|
+
// Grouped aliased import: {Repo as R} — first name = exported, second = alias
|
|
53
|
+
bindings.push({ local: names[1].text, exported: names[0].text });
|
|
54
|
+
}
|
|
55
|
+
else if (!qualifiedName && names.length === 1) {
|
|
56
|
+
// Grouped non-aliased import: {User} in use App\Models\{User, Repo as R}
|
|
57
|
+
bindings.push({ local: names[0].text, exported: names[0].text });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return bindings.length > 0 ? bindings : undefined;
|
|
61
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { findChild } from '../utils/ast-helpers.js';
|
|
2
|
+
export function extractPythonNamedBindings(importNode) {
|
|
3
|
+
// Handle: from x import User, Repo as R
|
|
4
|
+
if (importNode.type === 'import_from_statement') {
|
|
5
|
+
const bindings = [];
|
|
6
|
+
for (let i = 0; i < importNode.namedChildCount; i++) {
|
|
7
|
+
const child = importNode.namedChild(i);
|
|
8
|
+
if (!child)
|
|
9
|
+
continue;
|
|
10
|
+
if (child.type === 'dotted_name') {
|
|
11
|
+
// Skip the module_name (first dotted_name is the source module)
|
|
12
|
+
const fieldName = importNode.childForFieldName?.('module_name');
|
|
13
|
+
if (fieldName && child.startIndex === fieldName.startIndex)
|
|
14
|
+
continue;
|
|
15
|
+
// This is an imported name: from x import User
|
|
16
|
+
const name = child.text;
|
|
17
|
+
if (name)
|
|
18
|
+
bindings.push({ local: name, exported: name });
|
|
19
|
+
}
|
|
20
|
+
if (child.type === 'aliased_import') {
|
|
21
|
+
// from x import Repo as R
|
|
22
|
+
const dottedName = findChild(child, 'dotted_name');
|
|
23
|
+
const aliasIdent = findChild(child, 'identifier');
|
|
24
|
+
if (dottedName && aliasIdent) {
|
|
25
|
+
bindings.push({ local: aliasIdent.text, exported: dottedName.text });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return bindings.length > 0 ? bindings : undefined;
|
|
30
|
+
}
|
|
31
|
+
// Handle: import numpy as np (import_statement with aliased_import child)
|
|
32
|
+
// Tagged with isModuleAlias so applyImportResult routes these directly to
|
|
33
|
+
// moduleAliasMap (e.g. "np" → "numpy.py") instead of namedImportMap.
|
|
34
|
+
if (importNode.type === 'import_statement') {
|
|
35
|
+
const bindings = [];
|
|
36
|
+
for (let i = 0; i < importNode.namedChildCount; i++) {
|
|
37
|
+
const child = importNode.namedChild(i);
|
|
38
|
+
if (!child || child.type !== 'aliased_import')
|
|
39
|
+
continue;
|
|
40
|
+
const dottedName = findChild(child, 'dotted_name');
|
|
41
|
+
const aliasIdent = findChild(child, 'identifier');
|
|
42
|
+
if (dottedName && aliasIdent) {
|
|
43
|
+
bindings.push({ local: aliasIdent.text, exported: dottedName.text, isModuleAlias: true });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return bindings.length > 0 ? bindings : undefined;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export function extractRustNamedBindings(importNode) {
|
|
2
|
+
// use_declaration may contain use_as_clause at any depth
|
|
3
|
+
if (importNode.type !== 'use_declaration')
|
|
4
|
+
return undefined;
|
|
5
|
+
const bindings = [];
|
|
6
|
+
collectRustBindings(importNode, bindings);
|
|
7
|
+
return bindings.length > 0 ? bindings : undefined;
|
|
8
|
+
}
|
|
9
|
+
function collectRustBindings(node, bindings) {
|
|
10
|
+
if (node.type === 'use_as_clause') {
|
|
11
|
+
// First identifier = exported name, second identifier = local alias
|
|
12
|
+
const idents = [];
|
|
13
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
14
|
+
const child = node.namedChild(i);
|
|
15
|
+
if (child?.type === 'identifier')
|
|
16
|
+
idents.push(child.text);
|
|
17
|
+
// For scoped_identifier, extract the last segment
|
|
18
|
+
if (child?.type === 'scoped_identifier') {
|
|
19
|
+
const nameNode = child.childForFieldName?.('name');
|
|
20
|
+
if (nameNode)
|
|
21
|
+
idents.push(nameNode.text);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (idents.length === 2) {
|
|
25
|
+
bindings.push({ local: idents[1], exported: idents[0] });
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Terminal identifier in a use_list: use crate::models::{User, Repo}
|
|
30
|
+
if (node.type === 'identifier' && node.parent?.type === 'use_list') {
|
|
31
|
+
bindings.push({ local: node.text, exported: node.text });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Skip scoped_identifier that serves as path prefix in scoped_use_list
|
|
35
|
+
// e.g. use crate::models::{User, Repo} — the path node "crate::models" is not an importable symbol
|
|
36
|
+
if (node.type === 'scoped_identifier' && node.parent?.type === 'scoped_use_list') {
|
|
37
|
+
return; // path prefix — the use_list sibling handles the actual symbols
|
|
38
|
+
}
|
|
39
|
+
// Terminal scoped_identifier: use crate::models::User;
|
|
40
|
+
// Only extract if this is a leaf (no deeper use_list/use_as_clause/scoped_use_list)
|
|
41
|
+
if (node.type === 'scoped_identifier') {
|
|
42
|
+
let hasDeeper = false;
|
|
43
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
44
|
+
const child = node.namedChild(i);
|
|
45
|
+
if (child?.type === 'use_list' || child?.type === 'use_as_clause' || child?.type === 'scoped_use_list') {
|
|
46
|
+
hasDeeper = true;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!hasDeeper) {
|
|
51
|
+
const nameNode = node.childForFieldName?.('name');
|
|
52
|
+
if (nameNode) {
|
|
53
|
+
bindings.push({ local: nameNode.text, exported: nameNode.text });
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Recurse into children
|
|
59
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
60
|
+
const child = node.namedChild(i);
|
|
61
|
+
if (child)
|
|
62
|
+
collectRustBindings(child, bindings);
|
|
63
|
+
}
|
|
64
|
+
}
|