@duytransipher/gitnexus 1.4.6-sipher.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +73 -0
- package/README.md +261 -0
- package/dist/cli/ai-context.d.ts +23 -0
- package/dist/cli/ai-context.js +265 -0
- package/dist/cli/analyze.d.ts +12 -0
- package/dist/cli/analyze.js +345 -0
- package/dist/cli/augment.d.ts +13 -0
- package/dist/cli/augment.js +33 -0
- package/dist/cli/clean.d.ts +10 -0
- package/dist/cli/clean.js +60 -0
- package/dist/cli/eval-server.d.ts +37 -0
- package/dist/cli/eval-server.js +389 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +137 -0
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/cli/list.d.ts +6 -0
- package/dist/cli/list.js +30 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +36 -0
- package/dist/cli/serve.d.ts +4 -0
- package/dist/cli/serve.js +6 -0
- package/dist/cli/setup.d.ts +8 -0
- package/dist/cli/setup.js +367 -0
- package/dist/cli/sipher-patched.d.ts +2 -0
- package/dist/cli/sipher-patched.js +77 -0
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.js +36 -0
- package/dist/cli/tool.d.ts +60 -0
- package/dist/cli/tool.js +180 -0
- package/dist/cli/wiki.d.ts +15 -0
- package/dist/cli/wiki.js +365 -0
- package/dist/config/ignore-service.d.ts +26 -0
- package/dist/config/ignore-service.js +284 -0
- package/dist/config/supported-languages.d.ts +15 -0
- package/dist/config/supported-languages.js +16 -0
- package/dist/core/augmentation/engine.d.ts +26 -0
- package/dist/core/augmentation/engine.js +240 -0
- package/dist/core/embeddings/embedder.d.ts +60 -0
- package/dist/core/embeddings/embedder.js +251 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +51 -0
- package/dist/core/embeddings/embedding-pipeline.js +356 -0
- package/dist/core/embeddings/index.d.ts +9 -0
- package/dist/core/embeddings/index.js +9 -0
- package/dist/core/embeddings/text-generator.d.ts +24 -0
- package/dist/core/embeddings/text-generator.js +182 -0
- package/dist/core/embeddings/types.d.ts +87 -0
- package/dist/core/embeddings/types.js +32 -0
- package/dist/core/graph/graph.d.ts +2 -0
- package/dist/core/graph/graph.js +66 -0
- package/dist/core/graph/types.d.ts +66 -0
- package/dist/core/graph/types.js +1 -0
- package/dist/core/ingestion/ast-cache.d.ts +11 -0
- package/dist/core/ingestion/ast-cache.js +35 -0
- package/dist/core/ingestion/call-processor.d.ts +23 -0
- package/dist/core/ingestion/call-processor.js +793 -0
- package/dist/core/ingestion/call-routing.d.ts +68 -0
- package/dist/core/ingestion/call-routing.js +129 -0
- package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
- package/dist/core/ingestion/cluster-enricher.js +170 -0
- package/dist/core/ingestion/community-processor.d.ts +39 -0
- package/dist/core/ingestion/community-processor.js +312 -0
- package/dist/core/ingestion/constants.d.ts +16 -0
- package/dist/core/ingestion/constants.js +16 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +40 -0
- package/dist/core/ingestion/entry-point-scoring.js +353 -0
- package/dist/core/ingestion/export-detection.d.ts +18 -0
- package/dist/core/ingestion/export-detection.js +231 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +28 -0
- package/dist/core/ingestion/filesystem-walker.js +81 -0
- package/dist/core/ingestion/framework-detection.d.ts +54 -0
- package/dist/core/ingestion/framework-detection.js +411 -0
- package/dist/core/ingestion/heritage-processor.d.ts +28 -0
- package/dist/core/ingestion/heritage-processor.js +251 -0
- package/dist/core/ingestion/import-processor.d.ts +34 -0
- package/dist/core/ingestion/import-processor.js +398 -0
- package/dist/core/ingestion/language-config.d.ts +46 -0
- package/dist/core/ingestion/language-config.js +167 -0
- package/dist/core/ingestion/mro-processor.d.ts +45 -0
- package/dist/core/ingestion/mro-processor.js +369 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
- package/dist/core/ingestion/named-binding-extraction.js +363 -0
- package/dist/core/ingestion/parsing-processor.d.ts +19 -0
- package/dist/core/ingestion/parsing-processor.js +315 -0
- package/dist/core/ingestion/pipeline.d.ts +6 -0
- package/dist/core/ingestion/pipeline.js +401 -0
- package/dist/core/ingestion/process-processor.d.ts +51 -0
- package/dist/core/ingestion/process-processor.js +315 -0
- package/dist/core/ingestion/resolution-context.d.ts +53 -0
- package/dist/core/ingestion/resolution-context.js +132 -0
- package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
- package/dist/core/ingestion/resolvers/csharp.js +109 -0
- package/dist/core/ingestion/resolvers/go.d.ts +19 -0
- package/dist/core/ingestion/resolvers/go.js +42 -0
- package/dist/core/ingestion/resolvers/index.d.ts +18 -0
- package/dist/core/ingestion/resolvers/index.js +13 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
- package/dist/core/ingestion/resolvers/jvm.js +87 -0
- package/dist/core/ingestion/resolvers/php.d.ts +15 -0
- package/dist/core/ingestion/resolvers/php.js +35 -0
- package/dist/core/ingestion/resolvers/python.d.ts +19 -0
- package/dist/core/ingestion/resolvers/python.js +52 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
- package/dist/core/ingestion/resolvers/ruby.js +15 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
- package/dist/core/ingestion/resolvers/rust.js +73 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
- package/dist/core/ingestion/resolvers/standard.js +123 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
- package/dist/core/ingestion/resolvers/utils.js +122 -0
- package/dist/core/ingestion/structure-processor.d.ts +2 -0
- package/dist/core/ingestion/structure-processor.js +36 -0
- package/dist/core/ingestion/symbol-table.d.ts +63 -0
- package/dist/core/ingestion/symbol-table.js +85 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +15 -0
- package/dist/core/ingestion/tree-sitter-queries.js +888 -0
- package/dist/core/ingestion/type-env.d.ts +49 -0
- package/dist/core/ingestion/type-env.js +613 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/csharp.js +383 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/go.js +467 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
- package/dist/core/ingestion/type-extractors/index.js +31 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
- package/dist/core/ingestion/type-extractors/jvm.js +681 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/php.js +549 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/python.js +455 -0
- package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/ruby.js +389 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/rust.js +456 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +145 -0
- package/dist/core/ingestion/type-extractors/shared.js +810 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/swift.js +137 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
- package/dist/core/ingestion/type-extractors/types.js +1 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/typescript.js +494 -0
- package/dist/core/ingestion/utils.d.ts +138 -0
- package/dist/core/ingestion/utils.js +1290 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +122 -0
- package/dist/core/ingestion/workers/parse-worker.js +1126 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
- package/dist/core/ingestion/workers/worker-pool.js +128 -0
- package/dist/core/lbug/csv-generator.d.ts +33 -0
- package/dist/core/lbug/csv-generator.js +366 -0
- package/dist/core/lbug/lbug-adapter.d.ts +103 -0
- package/dist/core/lbug/lbug-adapter.js +769 -0
- package/dist/core/lbug/schema.d.ts +53 -0
- package/dist/core/lbug/schema.js +430 -0
- package/dist/core/search/bm25-index.d.ts +23 -0
- package/dist/core/search/bm25-index.js +96 -0
- package/dist/core/search/hybrid-search.d.ts +49 -0
- package/dist/core/search/hybrid-search.js +118 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +5 -0
- package/dist/core/tree-sitter/parser-loader.js +63 -0
- package/dist/core/wiki/generator.d.ts +120 -0
- package/dist/core/wiki/generator.js +939 -0
- package/dist/core/wiki/graph-queries.d.ts +80 -0
- package/dist/core/wiki/graph-queries.js +238 -0
- package/dist/core/wiki/html-viewer.d.ts +10 -0
- package/dist/core/wiki/html-viewer.js +297 -0
- package/dist/core/wiki/llm-client.d.ts +43 -0
- package/dist/core/wiki/llm-client.js +186 -0
- package/dist/core/wiki/prompts.d.ts +53 -0
- package/dist/core/wiki/prompts.js +174 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +3 -0
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/core/embedder.d.ts +27 -0
- package/dist/mcp/core/embedder.js +108 -0
- package/dist/mcp/core/lbug-adapter.d.ts +57 -0
- package/dist/mcp/core/lbug-adapter.js +455 -0
- package/dist/mcp/local/local-backend.d.ts +181 -0
- package/dist/mcp/local/local-backend.js +1722 -0
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +411 -0
- package/dist/mcp/server.d.ts +23 -0
- package/dist/mcp/server.js +296 -0
- package/dist/mcp/staleness.d.ts +15 -0
- package/dist/mcp/staleness.js +29 -0
- package/dist/mcp/tools.d.ts +24 -0
- package/dist/mcp/tools.js +292 -0
- package/dist/server/api.d.ts +10 -0
- package/dist/server/api.js +344 -0
- package/dist/server/mcp-http.d.ts +13 -0
- package/dist/server/mcp-http.js +100 -0
- package/dist/storage/git.d.ts +6 -0
- package/dist/storage/git.js +35 -0
- package/dist/storage/repo-manager.d.ts +138 -0
- package/dist/storage/repo-manager.js +299 -0
- package/dist/types/pipeline.d.ts +32 -0
- package/dist/types/pipeline.js +18 -0
- package/dist/unreal/bridge.d.ts +4 -0
- package/dist/unreal/bridge.js +113 -0
- package/dist/unreal/config.d.ts +6 -0
- package/dist/unreal/config.js +55 -0
- package/dist/unreal/types.d.ts +105 -0
- package/dist/unreal/types.js +1 -0
- package/hooks/claude/gitnexus-hook.cjs +238 -0
- package/hooks/claude/pre-tool-use.sh +79 -0
- package/hooks/claude/session-start.sh +42 -0
- package/package.json +100 -0
- package/scripts/ensure-cli-executable.cjs +21 -0
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/scripts/setup-unreal-gitnexus.ps1 +191 -0
- package/skills/gitnexus-cli.md +82 -0
- package/skills/gitnexus-debugging.md +89 -0
- package/skills/gitnexus-exploring.md +78 -0
- package/skills/gitnexus-guide.md +64 -0
- package/skills/gitnexus-impact-analysis.md +97 -0
- package/skills/gitnexus-pr-review.md +163 -0
- package/skills/gitnexus-refactoring.md +121 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
/** Empty set for containers that have no key-yielding methods */
|
|
2
|
+
const NO_KEYS = new Set();
|
|
3
|
+
/** Standard key-yielding methods across languages */
|
|
4
|
+
const STD_KEY_METHODS = new Set(['keys']);
|
|
5
|
+
const JAVA_KEY_METHODS = new Set(['keySet']);
|
|
6
|
+
const CSHARP_KEY_METHODS = new Set(['Keys']);
|
|
7
|
+
/** Standard value-yielding methods across languages */
|
|
8
|
+
const STD_VALUE_METHODS = new Set(['values', 'get', 'pop', 'remove']);
|
|
9
|
+
const CSHARP_VALUE_METHODS = new Set(['Values', 'TryGetValue']);
|
|
10
|
+
const SINGLE_ELEMENT_METHODS = new Set([
|
|
11
|
+
'iter', 'into_iter', 'iterator', 'get', 'first', 'last', 'pop',
|
|
12
|
+
'peek', 'poll', 'find', 'filter', 'map',
|
|
13
|
+
]);
|
|
14
|
+
const CONTAINER_DESCRIPTORS = new Map([
|
|
15
|
+
// --- Map / Dict types (arity 2: key + value) ---
|
|
16
|
+
['Map', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
|
|
17
|
+
['WeakMap', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
|
|
18
|
+
['HashMap', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
|
|
19
|
+
['BTreeMap', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
|
|
20
|
+
['LinkedHashMap', { arity: 2, keyMethods: JAVA_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
|
|
21
|
+
['TreeMap', { arity: 2, keyMethods: JAVA_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
|
|
22
|
+
['dict', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
|
|
23
|
+
['Dict', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
|
|
24
|
+
['Dictionary', { arity: 2, keyMethods: CSHARP_KEY_METHODS, valueMethods: CSHARP_VALUE_METHODS }],
|
|
25
|
+
['SortedDictionary', { arity: 2, keyMethods: CSHARP_KEY_METHODS, valueMethods: CSHARP_VALUE_METHODS }],
|
|
26
|
+
['Record', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
|
|
27
|
+
['OrderedDict', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
|
|
28
|
+
['ConcurrentHashMap', { arity: 2, keyMethods: JAVA_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
|
|
29
|
+
['ConcurrentDictionary', { arity: 2, keyMethods: CSHARP_KEY_METHODS, valueMethods: CSHARP_VALUE_METHODS }],
|
|
30
|
+
// --- Single-element containers (arity 1) ---
|
|
31
|
+
['Array', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
32
|
+
['List', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
33
|
+
['ArrayList', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
34
|
+
['LinkedList', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
35
|
+
['Vec', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
36
|
+
['VecDeque', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
37
|
+
['Set', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
38
|
+
['HashSet', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
39
|
+
['BTreeSet', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
40
|
+
['TreeSet', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
41
|
+
['Queue', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
42
|
+
['Deque', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
43
|
+
['Stack', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
44
|
+
['Sequence', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
45
|
+
['Iterable', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
46
|
+
['Iterator', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
47
|
+
['IEnumerable', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
48
|
+
['IList', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
49
|
+
['ICollection', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
50
|
+
['Collection', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
51
|
+
['ObservableCollection', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
52
|
+
['IEnumerator', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
53
|
+
['SortedSet', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
54
|
+
['Stream', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
55
|
+
['MutableList', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
56
|
+
['MutableSet', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
57
|
+
['LinkedHashSet', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
58
|
+
['ArrayDeque', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
59
|
+
['PriorityQueue', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
60
|
+
['MutableMap', { arity: 2, keyMethods: STD_KEY_METHODS, valueMethods: STD_VALUE_METHODS }],
|
|
61
|
+
['list', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
62
|
+
['set', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
63
|
+
['tuple', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
64
|
+
['frozenset', { arity: 1, keyMethods: NO_KEYS, valueMethods: SINGLE_ELEMENT_METHODS }],
|
|
65
|
+
]);
|
|
66
|
+
/** Determine which type arg to extract based on container type name and access method.
|
|
67
|
+
*
|
|
68
|
+
* Resolution order:
|
|
69
|
+
* 1. If container is known and method is in keyMethods → 'first'
|
|
70
|
+
* 2. If container is known with arity 1 → 'last' (same as 'first' for single-arg)
|
|
71
|
+
* 3. If container is unknown → fall back to method name heuristic
|
|
72
|
+
* 4. Default: 'last' (value type)
|
|
73
|
+
*/
|
|
74
|
+
export function methodToTypeArgPosition(methodName, containerTypeName) {
|
|
75
|
+
if (containerTypeName) {
|
|
76
|
+
const desc = CONTAINER_DESCRIPTORS.get(containerTypeName);
|
|
77
|
+
if (desc) {
|
|
78
|
+
// Single-element container: always 'last' (= only arg)
|
|
79
|
+
if (desc.arity === 1)
|
|
80
|
+
return 'last';
|
|
81
|
+
// Multi-element: check if method yields key type
|
|
82
|
+
if (methodName && desc.keyMethods.has(methodName))
|
|
83
|
+
return 'first';
|
|
84
|
+
// Default for multi-element: value type
|
|
85
|
+
return 'last';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Fallback for unknown containers: simple method name heuristic
|
|
89
|
+
if (methodName && (methodName === 'keys' || methodName === 'keySet' || methodName === 'Keys')) {
|
|
90
|
+
return 'first';
|
|
91
|
+
}
|
|
92
|
+
return 'last';
|
|
93
|
+
}
|
|
94
|
+
/** Look up the container descriptor for a type name. Exported for heritage-chain lookups. */
|
|
95
|
+
export function getContainerDescriptor(typeName) {
|
|
96
|
+
return CONTAINER_DESCRIPTORS.get(typeName);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Shared 3-strategy fallback for resolving the element type of a container variable.
|
|
100
|
+
* Used by all for-loop extractors to resolve the loop variable's type from the iterable.
|
|
101
|
+
*
|
|
102
|
+
* Strategy 1: declarationTypeNodes — raw AST type annotation node (handles container types
|
|
103
|
+
* where extractSimpleTypeName returned undefined, e.g., User[], List[User])
|
|
104
|
+
* Strategy 2: scopeEnv string — extractElementTypeFromString on the stored type string
|
|
105
|
+
* Strategy 3: AST walk — language-specific upward walk to enclosing function parameters
|
|
106
|
+
*
|
|
107
|
+
* @param extractFromTypeNode Language-specific function to extract element type from AST node
|
|
108
|
+
* @param findParamElementType Optional language-specific AST walk to find parameter type
|
|
109
|
+
* @param typeArgPos Which generic type arg to extract: 'first' for keys, 'last' for values (default)
|
|
110
|
+
*/
|
|
111
|
+
export function resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractFromTypeNode, findParamElementType, typeArgPos = 'last') {
|
|
112
|
+
// Strategy 1: declarationTypeNodes AST node (check current scope, then file scope)
|
|
113
|
+
const typeNode = declarationTypeNodes.get(`${scope}\0${iterableName}`)
|
|
114
|
+
?? (scope !== '' ? declarationTypeNodes.get(`\0${iterableName}`) : undefined);
|
|
115
|
+
if (typeNode) {
|
|
116
|
+
const t = extractFromTypeNode(typeNode, typeArgPos);
|
|
117
|
+
if (t)
|
|
118
|
+
return t;
|
|
119
|
+
}
|
|
120
|
+
// Strategy 2: scopeEnv string → extractElementTypeFromString
|
|
121
|
+
const iterableType = scopeEnv.get(iterableName);
|
|
122
|
+
if (iterableType) {
|
|
123
|
+
const el = extractElementTypeFromString(iterableType, typeArgPos);
|
|
124
|
+
if (el)
|
|
125
|
+
return el;
|
|
126
|
+
}
|
|
127
|
+
// Strategy 3: AST walk to function parameters
|
|
128
|
+
if (findParamElementType)
|
|
129
|
+
return findParamElementType(iterableName, node, typeArgPos);
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
/** Known single-arg nullable wrapper types that unwrap to their inner type
|
|
133
|
+
* for receiver resolution. Optional<User> → "User", Option<User> → "User".
|
|
134
|
+
* Only nullable wrappers — NOT containers (List, Vec) or async wrappers (Promise, Future).
|
|
135
|
+
* See WRAPPER_GENERICS below for the full set used in return-type inference. */
|
|
136
|
+
const NULLABLE_WRAPPER_TYPES = new Set([
|
|
137
|
+
'Optional', // Java
|
|
138
|
+
'Option', // Rust, Scala
|
|
139
|
+
'Maybe', // Haskell-style, Kotlin Arrow
|
|
140
|
+
]);
|
|
141
|
+
/**
|
|
142
|
+
* Extract the simple type name from a type AST node.
|
|
143
|
+
* Handles generic types (e.g., List<User> → List), qualified names
|
|
144
|
+
* (e.g., models.User → User), and nullable types (e.g., User? → User).
|
|
145
|
+
* Returns undefined for complex types (unions, intersections, function types).
|
|
146
|
+
*/
|
|
147
|
+
export const extractSimpleTypeName = (typeNode, depth = 0) => {
|
|
148
|
+
if (depth > 50 || typeNode.text.length > 2048)
|
|
149
|
+
return undefined;
|
|
150
|
+
// Direct type identifier (includes Ruby 'constant' for class names)
|
|
151
|
+
if (typeNode.type === 'type_identifier' || typeNode.type === 'identifier'
|
|
152
|
+
|| typeNode.type === 'simple_identifier' || typeNode.type === 'constant') {
|
|
153
|
+
return typeNode.text;
|
|
154
|
+
}
|
|
155
|
+
// Qualified/scoped names: take the last segment (e.g., models.User → User, Models::User → User)
|
|
156
|
+
if (typeNode.type === 'scoped_identifier' || typeNode.type === 'qualified_identifier'
|
|
157
|
+
|| typeNode.type === 'scoped_type_identifier' || typeNode.type === 'qualified_name'
|
|
158
|
+
|| typeNode.type === 'qualified_type'
|
|
159
|
+
|| typeNode.type === 'member_expression' || typeNode.type === 'member_access_expression'
|
|
160
|
+
|| typeNode.type === 'attribute'
|
|
161
|
+
|| typeNode.type === 'scope_resolution'
|
|
162
|
+
|| typeNode.type === 'selector_expression') {
|
|
163
|
+
const last = typeNode.lastNamedChild;
|
|
164
|
+
if (last && (last.type === 'type_identifier' || last.type === 'identifier'
|
|
165
|
+
|| last.type === 'simple_identifier' || last.type === 'name'
|
|
166
|
+
|| last.type === 'constant' || last.type === 'property_identifier'
|
|
167
|
+
|| last.type === 'field_identifier')) {
|
|
168
|
+
return last.text;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// C++ template_type (e.g., vector<User>, map<string, User>): extract base name
|
|
172
|
+
if (typeNode.type === 'template_type') {
|
|
173
|
+
const base = typeNode.childForFieldName('name') ?? typeNode.firstNamedChild;
|
|
174
|
+
if (base)
|
|
175
|
+
return extractSimpleTypeName(base, depth + 1);
|
|
176
|
+
}
|
|
177
|
+
// Generic types: extract the base type (e.g., List<User> → List)
|
|
178
|
+
// For nullable wrappers (Optional<User>, Option<User>), unwrap to inner type.
|
|
179
|
+
if (typeNode.type === 'generic_type' || typeNode.type === 'parameterized_type'
|
|
180
|
+
|| typeNode.type === 'generic_name') {
|
|
181
|
+
const base = typeNode.childForFieldName('name')
|
|
182
|
+
?? typeNode.childForFieldName('type')
|
|
183
|
+
?? typeNode.firstNamedChild;
|
|
184
|
+
if (!base)
|
|
185
|
+
return undefined;
|
|
186
|
+
const baseName = extractSimpleTypeName(base, depth + 1);
|
|
187
|
+
// Unwrap known nullable wrappers: Optional<User> → User, Option<User> → User
|
|
188
|
+
if (baseName && NULLABLE_WRAPPER_TYPES.has(baseName)) {
|
|
189
|
+
const args = extractGenericTypeArgs(typeNode);
|
|
190
|
+
if (args.length >= 1)
|
|
191
|
+
return args[0];
|
|
192
|
+
}
|
|
193
|
+
return baseName;
|
|
194
|
+
}
|
|
195
|
+
// Nullable types (Kotlin User?, C# User?)
|
|
196
|
+
if (typeNode.type === 'nullable_type') {
|
|
197
|
+
const inner = typeNode.firstNamedChild;
|
|
198
|
+
if (inner)
|
|
199
|
+
return extractSimpleTypeName(inner, depth + 1);
|
|
200
|
+
}
|
|
201
|
+
// Nullable union types (TS/JS: User | null, User | undefined, User | null | undefined)
|
|
202
|
+
// Extract the single non-null/undefined type from the union.
|
|
203
|
+
if (typeNode.type === 'union_type') {
|
|
204
|
+
const nonNullTypes = [];
|
|
205
|
+
for (let i = 0; i < typeNode.namedChildCount; i++) {
|
|
206
|
+
const child = typeNode.namedChild(i);
|
|
207
|
+
if (!child)
|
|
208
|
+
continue;
|
|
209
|
+
// Skip null/undefined/void literal types
|
|
210
|
+
const text = child.text;
|
|
211
|
+
if (text === 'null' || text === 'undefined' || text === 'void')
|
|
212
|
+
continue;
|
|
213
|
+
nonNullTypes.push(child);
|
|
214
|
+
}
|
|
215
|
+
// Only unwrap if exactly one meaningful type remains
|
|
216
|
+
if (nonNullTypes.length === 1) {
|
|
217
|
+
return extractSimpleTypeName(nonNullTypes[0], depth + 1);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Type annotations that wrap the actual type (TS/Python: `: Foo`, Kotlin: user_type)
|
|
221
|
+
if (typeNode.type === 'type_annotation' || typeNode.type === 'type'
|
|
222
|
+
|| typeNode.type === 'user_type') {
|
|
223
|
+
const inner = typeNode.firstNamedChild;
|
|
224
|
+
if (inner)
|
|
225
|
+
return extractSimpleTypeName(inner, depth + 1);
|
|
226
|
+
}
|
|
227
|
+
// Pointer/reference types (C++, Rust): User*, &User, &mut User
|
|
228
|
+
if (typeNode.type === 'pointer_type' || typeNode.type === 'reference_type') {
|
|
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
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Primitive/predefined types: string, int, float, bool, number, unknown, any
|
|
239
|
+
// PHP: primitive_type; TS/JS: predefined_type
|
|
240
|
+
if (typeNode.type === 'primitive_type' || typeNode.type === 'predefined_type') {
|
|
241
|
+
return typeNode.text;
|
|
242
|
+
}
|
|
243
|
+
// PHP named_type / optional_type
|
|
244
|
+
if (typeNode.type === 'named_type' || typeNode.type === 'optional_type') {
|
|
245
|
+
const inner = typeNode.childForFieldName('name') ?? typeNode.firstNamedChild;
|
|
246
|
+
if (inner)
|
|
247
|
+
return extractSimpleTypeName(inner, depth + 1);
|
|
248
|
+
}
|
|
249
|
+
// Name node (PHP)
|
|
250
|
+
if (typeNode.type === 'name') {
|
|
251
|
+
return typeNode.text;
|
|
252
|
+
}
|
|
253
|
+
return undefined;
|
|
254
|
+
};
|
|
255
|
+
/**
|
|
256
|
+
* Extract variable name from a declarator or pattern node.
|
|
257
|
+
* Returns the simple identifier text, or undefined for destructuring/complex patterns.
|
|
258
|
+
*/
|
|
259
|
+
export const extractVarName = (node) => {
|
|
260
|
+
if (node.type === 'identifier' || node.type === 'simple_identifier'
|
|
261
|
+
|| node.type === 'variable_name' || node.type === 'name'
|
|
262
|
+
|| node.type === 'constant' || node.type === 'property_identifier') {
|
|
263
|
+
return node.text;
|
|
264
|
+
}
|
|
265
|
+
// variable_declarator (Java/C#): has a 'name' field
|
|
266
|
+
if (node.type === 'variable_declarator') {
|
|
267
|
+
const nameChild = node.childForFieldName('name');
|
|
268
|
+
if (nameChild)
|
|
269
|
+
return extractVarName(nameChild);
|
|
270
|
+
}
|
|
271
|
+
// Rust: let mut x = ... — mut_pattern wraps an identifier
|
|
272
|
+
if (node.type === 'mut_pattern') {
|
|
273
|
+
const inner = node.firstNamedChild;
|
|
274
|
+
if (inner)
|
|
275
|
+
return extractVarName(inner);
|
|
276
|
+
}
|
|
277
|
+
return undefined;
|
|
278
|
+
};
|
|
279
|
+
/** Node types for function/method parameters with type annotations */
|
|
280
|
+
export const TYPED_PARAMETER_TYPES = new Set([
|
|
281
|
+
'required_parameter', // TS: (x: Foo)
|
|
282
|
+
'optional_parameter', // TS: (x?: Foo)
|
|
283
|
+
'formal_parameter', // Java/Kotlin
|
|
284
|
+
'parameter', // C#/Rust/Go/Python/Swift
|
|
285
|
+
'typed_parameter', // Python: def f(x: Foo) — distinct from 'parameter' in tree-sitter-python
|
|
286
|
+
'parameter_declaration', // C/C++ void f(Type name)
|
|
287
|
+
'simple_parameter', // PHP function(Foo $x)
|
|
288
|
+
'property_promotion_parameter', // PHP 8.0+ constructor promotion: __construct(private Foo $x)
|
|
289
|
+
'closure_parameter', // Rust: |user: User| — typed closure parameters
|
|
290
|
+
]);
|
|
291
|
+
/**
|
|
292
|
+
* Extract type arguments from a generic type node.
|
|
293
|
+
* e.g., List<User, String> → ['User', 'String'], Vec<User> → ['User']
|
|
294
|
+
*
|
|
295
|
+
* Used by extractSimpleTypeName to unwrap nullable wrappers (Optional<User> → User).
|
|
296
|
+
*
|
|
297
|
+
* Handles language-specific AST structures:
|
|
298
|
+
* - TS/Java/Rust/Go: generic_type > type_arguments > type nodes
|
|
299
|
+
* - C#: generic_type > type_argument_list > type nodes
|
|
300
|
+
* - Kotlin: generic_type > type_arguments > type_projection > type nodes
|
|
301
|
+
*
|
|
302
|
+
* Note: Go slices/maps use slice_type/map_type, not generic_type — those are
|
|
303
|
+
* NOT handled here. Use language-specific extractors for Go container types.
|
|
304
|
+
*
|
|
305
|
+
* @param typeNode A generic_type or parameterized_type AST node (or any node —
|
|
306
|
+
* returns [] for non-generic types).
|
|
307
|
+
* @returns Array of resolved type argument names. Unresolvable arguments are omitted.
|
|
308
|
+
*/
|
|
309
|
+
export const extractGenericTypeArgs = (typeNode, depth = 0) => {
|
|
310
|
+
if (depth > 50)
|
|
311
|
+
return [];
|
|
312
|
+
// Unwrap wrapper nodes that may sit above the generic_type
|
|
313
|
+
if (typeNode.type === 'type_annotation' || typeNode.type === 'type'
|
|
314
|
+
|| typeNode.type === 'user_type' || typeNode.type === 'nullable_type'
|
|
315
|
+
|| typeNode.type === 'optional_type') {
|
|
316
|
+
const inner = typeNode.firstNamedChild;
|
|
317
|
+
if (inner)
|
|
318
|
+
return extractGenericTypeArgs(inner, depth + 1);
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
// Only process generic/parameterized type nodes (includes C#'s generic_name)
|
|
322
|
+
if (typeNode.type !== 'generic_type' && typeNode.type !== 'parameterized_type'
|
|
323
|
+
&& typeNode.type !== 'generic_name') {
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
// Find the type_arguments / type_argument_list child
|
|
327
|
+
let argsNode = null;
|
|
328
|
+
for (let i = 0; i < typeNode.namedChildCount; i++) {
|
|
329
|
+
const child = typeNode.namedChild(i);
|
|
330
|
+
if (child && (child.type === 'type_arguments' || child.type === 'type_argument_list')) {
|
|
331
|
+
argsNode = child;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (!argsNode)
|
|
336
|
+
return [];
|
|
337
|
+
const result = [];
|
|
338
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
339
|
+
let argNode = argsNode.namedChild(i);
|
|
340
|
+
if (!argNode)
|
|
341
|
+
continue;
|
|
342
|
+
// Kotlin: type_arguments > type_projection > user_type > type_identifier
|
|
343
|
+
if (argNode.type === 'type_projection') {
|
|
344
|
+
argNode = argNode.firstNamedChild;
|
|
345
|
+
if (!argNode)
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
const name = extractSimpleTypeName(argNode);
|
|
349
|
+
if (name)
|
|
350
|
+
result.push(name);
|
|
351
|
+
}
|
|
352
|
+
return result;
|
|
353
|
+
};
|
|
354
|
+
/**
|
|
355
|
+
* Match Ruby constructor assignment: `user = User.new` or `service = Models::User.new`.
|
|
356
|
+
* Returns { varName, calleeName } or undefined if the node is not a Ruby constructor assignment.
|
|
357
|
+
* Handles both simple constants and scope_resolution (namespaced) receivers.
|
|
358
|
+
*/
|
|
359
|
+
export const extractRubyConstructorAssignment = (node) => {
|
|
360
|
+
if (node.type !== 'assignment')
|
|
361
|
+
return undefined;
|
|
362
|
+
const left = node.childForFieldName('left');
|
|
363
|
+
const right = node.childForFieldName('right');
|
|
364
|
+
if (!left || !right)
|
|
365
|
+
return undefined;
|
|
366
|
+
if (left.type !== 'identifier' && left.type !== 'constant')
|
|
367
|
+
return undefined;
|
|
368
|
+
if (right.type !== 'call')
|
|
369
|
+
return undefined;
|
|
370
|
+
const method = right.childForFieldName('method');
|
|
371
|
+
if (!method || method.text !== 'new')
|
|
372
|
+
return undefined;
|
|
373
|
+
const receiver = right.childForFieldName('receiver');
|
|
374
|
+
if (!receiver)
|
|
375
|
+
return undefined;
|
|
376
|
+
let calleeName;
|
|
377
|
+
if (receiver.type === 'constant') {
|
|
378
|
+
calleeName = receiver.text;
|
|
379
|
+
}
|
|
380
|
+
else if (receiver.type === 'scope_resolution') {
|
|
381
|
+
// Models::User → extract last segment "User"
|
|
382
|
+
const last = receiver.lastNamedChild;
|
|
383
|
+
if (!last || last.type !== 'constant')
|
|
384
|
+
return undefined;
|
|
385
|
+
calleeName = last.text;
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
390
|
+
return { varName: left.text, calleeName };
|
|
391
|
+
};
|
|
392
|
+
/**
|
|
393
|
+
* Check if an AST node has an explicit type annotation.
|
|
394
|
+
* Checks both named fields ('type') and child nodes ('type_annotation').
|
|
395
|
+
* Used by constructor binding scanners to skip annotated declarations.
|
|
396
|
+
*/
|
|
397
|
+
export const hasTypeAnnotation = (node) => {
|
|
398
|
+
if (node.childForFieldName('type'))
|
|
399
|
+
return true;
|
|
400
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
401
|
+
if (node.child(i)?.type === 'type_annotation')
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
return false;
|
|
405
|
+
};
|
|
406
|
+
/** Bare nullable keywords that should not produce a receiver binding. */
|
|
407
|
+
const NULLABLE_KEYWORDS = new Set(['null', 'undefined', 'void', 'None', 'nil']);
|
|
408
|
+
/**
|
|
409
|
+
* Strip nullable wrappers from a type name string.
|
|
410
|
+
* Used by both lookupInEnv (TypeEnv annotations) and extractReturnTypeName
|
|
411
|
+
* (return-type text) to normalize types before receiver lookup.
|
|
412
|
+
*
|
|
413
|
+
* "User | null" → "User"
|
|
414
|
+
* "User | undefined" → "User"
|
|
415
|
+
* "User | null | undefined" → "User"
|
|
416
|
+
* "User?" → "User"
|
|
417
|
+
* "User | Repo" → undefined (genuine union — refuse)
|
|
418
|
+
* "null" → undefined
|
|
419
|
+
*/
|
|
420
|
+
export const stripNullable = (typeName) => {
|
|
421
|
+
let text = typeName.trim();
|
|
422
|
+
if (!text)
|
|
423
|
+
return undefined;
|
|
424
|
+
if (NULLABLE_KEYWORDS.has(text))
|
|
425
|
+
return undefined;
|
|
426
|
+
// Strip nullable suffix: User? → User
|
|
427
|
+
if (text.endsWith('?'))
|
|
428
|
+
text = text.slice(0, -1).trim();
|
|
429
|
+
// Strip union with null/undefined/None/nil/void
|
|
430
|
+
if (text.includes('|')) {
|
|
431
|
+
const parts = text.split('|').map(p => p.trim()).filter(p => p !== '' && !NULLABLE_KEYWORDS.has(p));
|
|
432
|
+
if (parts.length === 1)
|
|
433
|
+
return parts[0];
|
|
434
|
+
return undefined; // genuine union or all-nullable — refuse
|
|
435
|
+
}
|
|
436
|
+
return text || undefined;
|
|
437
|
+
};
|
|
438
|
+
/**
|
|
439
|
+
* Unwrap an await_expression to get the inner value.
|
|
440
|
+
* Returns the node itself if not an await_expression, or null if input is null.
|
|
441
|
+
*/
|
|
442
|
+
export const unwrapAwait = (node) => {
|
|
443
|
+
if (!node)
|
|
444
|
+
return null;
|
|
445
|
+
return node.type === 'await_expression' ? node.firstNamedChild : node;
|
|
446
|
+
};
|
|
447
|
+
/**
|
|
448
|
+
* Extract the callee name from a call_expression node.
|
|
449
|
+
* Navigates to the 'function' field (or first named child) and extracts a simple type name.
|
|
450
|
+
*/
|
|
451
|
+
export const extractCalleeName = (callNode) => {
|
|
452
|
+
const func = callNode.childForFieldName('function') ?? callNode.firstNamedChild;
|
|
453
|
+
if (!func)
|
|
454
|
+
return undefined;
|
|
455
|
+
return extractSimpleTypeName(func);
|
|
456
|
+
};
|
|
457
|
+
/** Find the first named child with the given node type */
|
|
458
|
+
export const findChildByType = (node, type) => {
|
|
459
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
460
|
+
const child = node.namedChild(i);
|
|
461
|
+
if (child?.type === type)
|
|
462
|
+
return child;
|
|
463
|
+
}
|
|
464
|
+
return null;
|
|
465
|
+
};
|
|
466
|
+
// Internal helper: extract the first comma-separated argument from a string,
|
|
467
|
+
// respecting nested angle-bracket and square-bracket depth.
|
|
468
|
+
function extractFirstArg(args) {
|
|
469
|
+
let depth = 0;
|
|
470
|
+
for (let i = 0; i < args.length; i++) {
|
|
471
|
+
const ch = args[i];
|
|
472
|
+
if (ch === '<' || ch === '[')
|
|
473
|
+
depth++;
|
|
474
|
+
else if (ch === '>' || ch === ']')
|
|
475
|
+
depth--;
|
|
476
|
+
else if (ch === ',' && depth === 0)
|
|
477
|
+
return args.slice(0, i).trim();
|
|
478
|
+
}
|
|
479
|
+
return args.trim();
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Extract element type from a container type string.
|
|
483
|
+
* Uses bracket-balanced parsing (no regex) for generic argument extraction.
|
|
484
|
+
* Returns undefined for ambiguous or unparseable strings.
|
|
485
|
+
*
|
|
486
|
+
* Handles:
|
|
487
|
+
* - Array<User> → User (generic angle brackets)
|
|
488
|
+
* - User[] → User (array suffix)
|
|
489
|
+
* - []User → User (Go slice prefix)
|
|
490
|
+
* - List[User] → User (Python subscript)
|
|
491
|
+
* - [User] → User (Swift array sugar)
|
|
492
|
+
* - vector<User> → User (C++ container)
|
|
493
|
+
* - Vec<User> → User (Rust container)
|
|
494
|
+
*
|
|
495
|
+
* For multi-argument generics (Map<K, V>), returns the first or last type arg
|
|
496
|
+
* based on `pos` ('first' for keys, 'last' for values — default 'last').
|
|
497
|
+
* Returns undefined when the extracted type is not a simple word.
|
|
498
|
+
*/
|
|
499
|
+
export function extractElementTypeFromString(typeStr, pos = 'last') {
|
|
500
|
+
if (!typeStr || typeStr.length === 0 || typeStr.length > 2048)
|
|
501
|
+
return undefined;
|
|
502
|
+
// 1. Array suffix: User[] → User
|
|
503
|
+
if (typeStr.endsWith('[]')) {
|
|
504
|
+
const base = typeStr.slice(0, -2).trim();
|
|
505
|
+
return base && /^\w+$/.test(base) ? base : undefined;
|
|
506
|
+
}
|
|
507
|
+
// 2. Go slice prefix: []User → User
|
|
508
|
+
if (typeStr.startsWith('[]')) {
|
|
509
|
+
const element = typeStr.slice(2).trim();
|
|
510
|
+
return element && /^\w+$/.test(element) ? element : undefined;
|
|
511
|
+
}
|
|
512
|
+
// 3. Swift array sugar: [User] → User
|
|
513
|
+
// Must start with '[', end with ']', and contain no angle brackets
|
|
514
|
+
// (to avoid confusing with List[User] handled below).
|
|
515
|
+
if (typeStr.startsWith('[') && typeStr.endsWith(']') && !typeStr.includes('<')) {
|
|
516
|
+
const element = typeStr.slice(1, -1).trim();
|
|
517
|
+
return element && /^\w+$/.test(element) ? element : undefined;
|
|
518
|
+
}
|
|
519
|
+
// 4. Generic bracket-balanced extraction: Array<User> / List[User] / Vec<User>
|
|
520
|
+
// Find the first opening bracket (< or [) and pick the one that appears first.
|
|
521
|
+
const openAngle = typeStr.indexOf('<');
|
|
522
|
+
const openSquare = typeStr.indexOf('[');
|
|
523
|
+
let openIdx = -1;
|
|
524
|
+
let openChar = '';
|
|
525
|
+
let closeChar = '';
|
|
526
|
+
if (openAngle >= 0 && (openSquare < 0 || openAngle < openSquare)) {
|
|
527
|
+
openIdx = openAngle;
|
|
528
|
+
openChar = '<';
|
|
529
|
+
closeChar = '>';
|
|
530
|
+
}
|
|
531
|
+
else if (openSquare >= 0) {
|
|
532
|
+
openIdx = openSquare;
|
|
533
|
+
openChar = '[';
|
|
534
|
+
closeChar = ']';
|
|
535
|
+
}
|
|
536
|
+
if (openIdx < 0)
|
|
537
|
+
return undefined;
|
|
538
|
+
// Walk bracket-balanced from the character after the opening bracket to find
|
|
539
|
+
// the matching close bracket, tracking depth for nested brackets.
|
|
540
|
+
// All bracket types (<, >, [, ]) contribute to depth uniformly, but only the
|
|
541
|
+
// selected closeChar can match at depth 0 (prevents cross-bracket miscounting).
|
|
542
|
+
let depth = 0;
|
|
543
|
+
const start = openIdx + 1;
|
|
544
|
+
let lastCommaIdx = -1; // Track last top-level comma for 'last' position
|
|
545
|
+
for (let i = start; i < typeStr.length; i++) {
|
|
546
|
+
const ch = typeStr[i];
|
|
547
|
+
if (ch === '<' || ch === '[') {
|
|
548
|
+
depth++;
|
|
549
|
+
}
|
|
550
|
+
else if (ch === '>' || ch === ']') {
|
|
551
|
+
if (depth === 0) {
|
|
552
|
+
// At depth 0 — only match if it is our selected close bracket.
|
|
553
|
+
if (ch !== closeChar)
|
|
554
|
+
return undefined; // mismatched bracket = malformed
|
|
555
|
+
if (pos === 'last' && lastCommaIdx >= 0) {
|
|
556
|
+
// Return last arg (text after last comma)
|
|
557
|
+
const lastArg = typeStr.slice(lastCommaIdx + 1, i).trim();
|
|
558
|
+
return lastArg && /^\w+$/.test(lastArg) ? lastArg : undefined;
|
|
559
|
+
}
|
|
560
|
+
const inner = typeStr.slice(start, i).trim();
|
|
561
|
+
const firstArg = extractFirstArg(inner);
|
|
562
|
+
return firstArg && /^\w+$/.test(firstArg) ? firstArg : undefined;
|
|
563
|
+
}
|
|
564
|
+
depth--;
|
|
565
|
+
}
|
|
566
|
+
else if (ch === ',' && depth === 0) {
|
|
567
|
+
if (pos === 'first') {
|
|
568
|
+
// Return first arg (text before first comma)
|
|
569
|
+
const arg = typeStr.slice(start, i).trim();
|
|
570
|
+
return arg && /^\w+$/.test(arg) ? arg : undefined;
|
|
571
|
+
}
|
|
572
|
+
lastCommaIdx = i;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
// ── Return type text helpers ─────────────────────────────────────────────
|
|
578
|
+
// extractReturnTypeName works on raw return-type text already stored in
|
|
579
|
+
// SymbolDefinition (e.g. "User", "Promise<User>", "User | null", "*User").
|
|
580
|
+
// Extracts the base user-defined type name.
|
|
581
|
+
/** Primitive / built-in types that should NOT produce a receiver binding. */
|
|
582
|
+
const PRIMITIVE_TYPES = new Set([
|
|
583
|
+
'string', 'number', 'boolean', 'void', 'int', 'float', 'double', 'long',
|
|
584
|
+
'short', 'byte', 'char', 'bool', 'str', 'i8', 'i16', 'i32', 'i64',
|
|
585
|
+
'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'usize', 'isize',
|
|
586
|
+
'undefined', 'null', 'None', 'nil',
|
|
587
|
+
]);
|
|
588
|
+
/**
|
|
589
|
+
* Extract a simple type name from raw return-type text.
|
|
590
|
+
* Handles common patterns:
|
|
591
|
+
* "User" → "User"
|
|
592
|
+
* "Promise<User>" → "User" (unwrap wrapper generics)
|
|
593
|
+
* "Option<User>" → "User"
|
|
594
|
+
* "Result<User, Error>" → "User" (first type arg)
|
|
595
|
+
* "User | null" → "User" (strip nullable union)
|
|
596
|
+
* "User?" → "User" (strip nullable suffix)
|
|
597
|
+
* "*User" → "User" (Go pointer)
|
|
598
|
+
* "&User" → "User" (Rust reference)
|
|
599
|
+
* Returns undefined for complex types or primitives.
|
|
600
|
+
*/
|
|
601
|
+
const WRAPPER_GENERICS = new Set([
|
|
602
|
+
'Promise', 'Observable', 'Future', 'CompletableFuture', 'Task', 'ValueTask', // async wrappers
|
|
603
|
+
'Option', 'Some', 'Optional', 'Maybe', // nullable wrappers
|
|
604
|
+
'Result', 'Either', // result wrappers
|
|
605
|
+
// Rust smart pointers (Deref to inner type)
|
|
606
|
+
'Rc', 'Arc', 'Weak', // pointer types
|
|
607
|
+
'MutexGuard', 'RwLockReadGuard', 'RwLockWriteGuard', // guard types
|
|
608
|
+
'Ref', 'RefMut', // RefCell guards
|
|
609
|
+
'Cow', // copy-on-write
|
|
610
|
+
// Containers (List, Array, Vec, Set, etc.) are intentionally excluded —
|
|
611
|
+
// methods are called on the container, not the element type.
|
|
612
|
+
// Non-wrapper generics return the base type (e.g., List) via the else branch.
|
|
613
|
+
]);
|
|
614
|
+
/**
|
|
615
|
+
* Extracts the first type argument from a comma-separated generic argument string,
|
|
616
|
+
* respecting nested angle brackets. For example:
|
|
617
|
+
* "Result<User, Error>" → "Result<User, Error>" (no top-level comma)
|
|
618
|
+
* "User, Error" → "User"
|
|
619
|
+
* "Map<K, V>, string" → "Map<K, V>"
|
|
620
|
+
*/
|
|
621
|
+
function extractFirstGenericArg(args) {
|
|
622
|
+
let depth = 0;
|
|
623
|
+
for (let i = 0; i < args.length; i++) {
|
|
624
|
+
if (args[i] === '<')
|
|
625
|
+
depth++;
|
|
626
|
+
else if (args[i] === '>')
|
|
627
|
+
depth--;
|
|
628
|
+
else if (args[i] === ',' && depth === 0)
|
|
629
|
+
return args.slice(0, i).trim();
|
|
630
|
+
}
|
|
631
|
+
return args.trim();
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Extract the first non-lifetime type argument from a generic argument string.
|
|
635
|
+
* Skips Rust lifetime parameters (e.g., `'a`, `'_`) to find the actual type.
|
|
636
|
+
* "'_, User" → "User"
|
|
637
|
+
* "'a, User" → "User"
|
|
638
|
+
* "User, Error" → "User" (no lifetime — delegates to extractFirstGenericArg)
|
|
639
|
+
*/
|
|
640
|
+
function extractFirstTypeArg(args) {
|
|
641
|
+
let remaining = args;
|
|
642
|
+
while (remaining) {
|
|
643
|
+
const first = extractFirstGenericArg(remaining);
|
|
644
|
+
if (!first.startsWith("'"))
|
|
645
|
+
return first;
|
|
646
|
+
// Skip past this lifetime arg + the comma separator
|
|
647
|
+
const commaIdx = remaining.indexOf(',', first.length);
|
|
648
|
+
if (commaIdx < 0)
|
|
649
|
+
return first; // only lifetimes — fall through
|
|
650
|
+
remaining = remaining.slice(commaIdx + 1).trim();
|
|
651
|
+
}
|
|
652
|
+
return args.trim();
|
|
653
|
+
}
|
|
654
|
+
const MAX_RETURN_TYPE_INPUT_LENGTH = 2048;
|
|
655
|
+
const MAX_RETURN_TYPE_LENGTH = 512;
|
|
656
|
+
export const extractReturnTypeName = (raw, depth = 0) => {
|
|
657
|
+
if (depth > 10)
|
|
658
|
+
return undefined;
|
|
659
|
+
if (raw.length > MAX_RETURN_TYPE_INPUT_LENGTH)
|
|
660
|
+
return undefined;
|
|
661
|
+
let text = raw.trim();
|
|
662
|
+
if (!text)
|
|
663
|
+
return undefined;
|
|
664
|
+
// Strip pointer/reference prefixes: *User, &User, &mut User
|
|
665
|
+
text = text.replace(/^[&*]+\s*(mut\s+)?/, '');
|
|
666
|
+
// Strip nullable suffix: User?
|
|
667
|
+
text = text.replace(/\?$/, '');
|
|
668
|
+
// Handle union types: "User | null" → "User"
|
|
669
|
+
if (text.includes('|')) {
|
|
670
|
+
const parts = text.split('|').map(p => p.trim()).filter(p => p !== 'null' && p !== 'undefined' && p !== 'void' && p !== 'None' && p !== 'nil');
|
|
671
|
+
if (parts.length === 1)
|
|
672
|
+
text = parts[0];
|
|
673
|
+
else
|
|
674
|
+
return undefined; // genuine union — too complex
|
|
675
|
+
}
|
|
676
|
+
// Handle generics: Promise<User> → unwrap if wrapper, else take base
|
|
677
|
+
const genericMatch = text.match(/^(\w+)\s*<(.+)>$/);
|
|
678
|
+
if (genericMatch) {
|
|
679
|
+
const [, base, args] = genericMatch;
|
|
680
|
+
if (WRAPPER_GENERICS.has(base)) {
|
|
681
|
+
// Take the first non-lifetime type argument, using bracket-balanced splitting
|
|
682
|
+
// so that nested generics like Result<User, Error> are not split at the inner
|
|
683
|
+
// comma. Lifetime parameters (Rust 'a, '_) are skipped.
|
|
684
|
+
const firstArg = extractFirstTypeArg(args);
|
|
685
|
+
return extractReturnTypeName(firstArg, depth + 1);
|
|
686
|
+
}
|
|
687
|
+
// Non-wrapper generic: return the base type (e.g., Map<K,V> → Map)
|
|
688
|
+
return PRIMITIVE_TYPES.has(base.toLowerCase()) ? undefined : base;
|
|
689
|
+
}
|
|
690
|
+
// Bare wrapper type without generic argument (e.g. Task, Promise, Option)
|
|
691
|
+
// should not produce a binding — these are meaningless without a type parameter
|
|
692
|
+
if (WRAPPER_GENERICS.has(text))
|
|
693
|
+
return undefined;
|
|
694
|
+
// Handle qualified names: models.User → User, Models::User → User, \App\Models\User → User
|
|
695
|
+
if (text.includes('::') || text.includes('.') || text.includes('\\')) {
|
|
696
|
+
text = text.split(/::|[.\\]/).pop();
|
|
697
|
+
}
|
|
698
|
+
// Final check: skip primitives
|
|
699
|
+
if (PRIMITIVE_TYPES.has(text) || PRIMITIVE_TYPES.has(text.toLowerCase()))
|
|
700
|
+
return undefined;
|
|
701
|
+
// Must start with uppercase (class/type convention) or be a valid identifier
|
|
702
|
+
if (!/^[A-Z_]\w*$/.test(text))
|
|
703
|
+
return undefined;
|
|
704
|
+
// If the final extracted type name is too long, reject it
|
|
705
|
+
if (text.length > MAX_RETURN_TYPE_LENGTH)
|
|
706
|
+
return undefined;
|
|
707
|
+
return text;
|
|
708
|
+
};
|
|
709
|
+
// ── Property declared-type extraction ────────────────────────────────────
|
|
710
|
+
// Shared between parse-worker (worker path) and parsing-processor (sequential path).
|
|
711
|
+
/**
|
|
712
|
+
* Extract the declared type of a property/field from its AST definition node.
|
|
713
|
+
* Handles cross-language patterns:
|
|
714
|
+
* - TypeScript: `name: Type` → type_annotation child
|
|
715
|
+
* - Java: `Type name` → type child on field_declaration
|
|
716
|
+
* - C#: `Type Name { get; set; }` → type child on property_declaration
|
|
717
|
+
* - Go: `Name Type` → type child on field_declaration
|
|
718
|
+
* - Kotlin: `var name: Type` → variable_declaration child with type field
|
|
719
|
+
*
|
|
720
|
+
* Returns the normalized type name, or undefined if no type can be extracted.
|
|
721
|
+
*/
|
|
722
|
+
export const extractPropertyDeclaredType = (definitionNode) => {
|
|
723
|
+
if (!definitionNode)
|
|
724
|
+
return undefined;
|
|
725
|
+
// Strategy 1: Look for a `type` or `type_annotation` named field
|
|
726
|
+
const typeNode = definitionNode.childForFieldName?.('type');
|
|
727
|
+
if (typeNode) {
|
|
728
|
+
const typeName = extractSimpleTypeName(typeNode);
|
|
729
|
+
if (typeName)
|
|
730
|
+
return typeName;
|
|
731
|
+
// Fallback: use the raw text (for complex types like User[] or List<User>)
|
|
732
|
+
const text = typeNode.text?.trim();
|
|
733
|
+
if (text && text.length < 100)
|
|
734
|
+
return text;
|
|
735
|
+
}
|
|
736
|
+
// Strategy 2: Walk children looking for type_annotation (TypeScript pattern)
|
|
737
|
+
for (let i = 0; i < definitionNode.childCount; i++) {
|
|
738
|
+
const child = definitionNode.child(i);
|
|
739
|
+
if (!child)
|
|
740
|
+
continue;
|
|
741
|
+
if (child.type === 'type_annotation') {
|
|
742
|
+
// Type annotation has the actual type as a child
|
|
743
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
744
|
+
const typeChild = child.child(j);
|
|
745
|
+
if (typeChild && typeChild.type !== ':') {
|
|
746
|
+
const typeName = extractSimpleTypeName(typeChild);
|
|
747
|
+
if (typeName)
|
|
748
|
+
return typeName;
|
|
749
|
+
const text = typeChild.text?.trim();
|
|
750
|
+
if (text && text.length < 100)
|
|
751
|
+
return text;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
// Strategy 3: For Java field_declaration, the type is a sibling of variable_declarator
|
|
757
|
+
// AST: (field_declaration type: (type_identifier) declarator: (variable_declarator ...))
|
|
758
|
+
const parentDecl = definitionNode.parent;
|
|
759
|
+
if (parentDecl) {
|
|
760
|
+
const parentType = parentDecl.childForFieldName?.('type');
|
|
761
|
+
if (parentType) {
|
|
762
|
+
const typeName = extractSimpleTypeName(parentType);
|
|
763
|
+
if (typeName)
|
|
764
|
+
return typeName;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
// Strategy 4: Kotlin property_declaration — type is nested inside variable_declaration child
|
|
768
|
+
// AST: (property_declaration (variable_declaration (simple_identifier) ":" (user_type (type_identifier))))
|
|
769
|
+
// Kotlin's variable_declaration has NO named 'type' field — children are all positional.
|
|
770
|
+
for (let i = 0; i < definitionNode.childCount; i++) {
|
|
771
|
+
const child = definitionNode.child(i);
|
|
772
|
+
if (child?.type === 'variable_declaration') {
|
|
773
|
+
// Try named field first (works for other languages sharing this strategy)
|
|
774
|
+
const varType = child.childForFieldName?.('type');
|
|
775
|
+
if (varType) {
|
|
776
|
+
const typeName = extractSimpleTypeName(varType);
|
|
777
|
+
if (typeName)
|
|
778
|
+
return typeName;
|
|
779
|
+
const text = varType.text?.trim();
|
|
780
|
+
if (text && text.length < 100)
|
|
781
|
+
return text;
|
|
782
|
+
}
|
|
783
|
+
// Fallback: walk unnamed children for user_type / type_identifier (Kotlin)
|
|
784
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
785
|
+
const varChild = child.namedChild(j);
|
|
786
|
+
if (varChild && (varChild.type === 'user_type' || varChild.type === 'type_identifier'
|
|
787
|
+
|| varChild.type === 'nullable_type' || varChild.type === 'generic_type')) {
|
|
788
|
+
const typeName = extractSimpleTypeName(varChild);
|
|
789
|
+
if (typeName)
|
|
790
|
+
return typeName;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// Strategy 5: PHP @var PHPDoc — look for preceding comment with @var Type
|
|
796
|
+
// Handles pre-PHP-7.4 code: /** @var Address */ public $address;
|
|
797
|
+
const prevSibling = definitionNode.previousNamedSibling ?? definitionNode.parent?.previousNamedSibling;
|
|
798
|
+
if (prevSibling?.type === 'comment') {
|
|
799
|
+
const commentText = prevSibling.text;
|
|
800
|
+
const varMatch = commentText?.match(/@var\s+([A-Z][\w\\]*)/);
|
|
801
|
+
if (varMatch) {
|
|
802
|
+
// Strip namespace prefix: \App\Models\User → User
|
|
803
|
+
const raw = varMatch[1];
|
|
804
|
+
const base = raw.includes('\\') ? raw.split('\\').pop() : raw;
|
|
805
|
+
if (base && /^[A-Z]\w*$/.test(base))
|
|
806
|
+
return base;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return undefined;
|
|
810
|
+
};
|