@zuvia-software-solutions/code-mapper 1.4.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/README.md +215 -0
- package/dist/cli/ai-context.d.ts +19 -0
- package/dist/cli/ai-context.js +168 -0
- package/dist/cli/analyze.d.ts +7 -0
- package/dist/cli/analyze.js +325 -0
- package/dist/cli/augment.d.ts +7 -0
- package/dist/cli/augment.js +27 -0
- package/dist/cli/clean.d.ts +5 -0
- package/dist/cli/clean.js +56 -0
- package/dist/cli/eval-server.d.ts +25 -0
- package/dist/cli/eval-server.js +365 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +102 -0
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +19 -0
- package/dist/cli/list.d.ts +2 -0
- package/dist/cli/list.js +27 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +35 -0
- package/dist/cli/refresh.d.ts +12 -0
- package/dist/cli/refresh.js +165 -0
- package/dist/cli/serve.d.ts +5 -0
- package/dist/cli/serve.js +8 -0
- package/dist/cli/setup.d.ts +6 -0
- package/dist/cli/setup.js +218 -0
- package/dist/cli/status.d.ts +2 -0
- package/dist/cli/status.js +33 -0
- package/dist/cli/tool.d.ts +28 -0
- package/dist/cli/tool.js +87 -0
- package/dist/config/ignore-service.d.ts +32 -0
- package/dist/config/ignore-service.js +282 -0
- package/dist/config/supported-languages.d.ts +23 -0
- package/dist/config/supported-languages.js +52 -0
- package/dist/core/augmentation/engine.d.ts +22 -0
- package/dist/core/augmentation/engine.js +232 -0
- package/dist/core/embeddings/embedder.d.ts +35 -0
- package/dist/core/embeddings/embedder.js +171 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +41 -0
- package/dist/core/embeddings/embedding-pipeline.js +402 -0
- package/dist/core/embeddings/index.d.ts +5 -0
- package/dist/core/embeddings/index.js +6 -0
- package/dist/core/embeddings/text-generator.d.ts +20 -0
- package/dist/core/embeddings/text-generator.js +159 -0
- package/dist/core/embeddings/types.d.ts +60 -0
- package/dist/core/embeddings/types.js +23 -0
- package/dist/core/graph/graph.d.ts +4 -0
- package/dist/core/graph/graph.js +65 -0
- package/dist/core/graph/types.d.ts +69 -0
- package/dist/core/graph/types.js +3 -0
- package/dist/core/incremental/child-process.d.ts +8 -0
- package/dist/core/incremental/child-process.js +649 -0
- package/dist/core/incremental/refresh-coordinator.d.ts +32 -0
- package/dist/core/incremental/refresh-coordinator.js +147 -0
- package/dist/core/incremental/types.d.ts +78 -0
- package/dist/core/incremental/types.js +153 -0
- package/dist/core/incremental/watcher.d.ts +63 -0
- package/dist/core/incremental/watcher.js +338 -0
- package/dist/core/ingestion/ast-cache.d.ts +12 -0
- package/dist/core/ingestion/ast-cache.js +34 -0
- package/dist/core/ingestion/call-processor.d.ts +34 -0
- package/dist/core/ingestion/call-processor.js +937 -0
- package/dist/core/ingestion/call-routing.d.ts +40 -0
- package/dist/core/ingestion/call-routing.js +97 -0
- package/dist/core/ingestion/cluster-enricher.d.ts +30 -0
- package/dist/core/ingestion/cluster-enricher.js +151 -0
- package/dist/core/ingestion/community-processor.d.ts +26 -0
- package/dist/core/ingestion/community-processor.js +272 -0
- package/dist/core/ingestion/constants.d.ts +5 -0
- package/dist/core/ingestion/constants.js +8 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +23 -0
- package/dist/core/ingestion/entry-point-scoring.js +317 -0
- package/dist/core/ingestion/export-detection.d.ts +11 -0
- package/dist/core/ingestion/export-detection.js +203 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +18 -0
- package/dist/core/ingestion/filesystem-walker.js +64 -0
- package/dist/core/ingestion/framework-detection.d.ts +42 -0
- package/dist/core/ingestion/framework-detection.js +405 -0
- package/dist/core/ingestion/heritage-processor.d.ts +15 -0
- package/dist/core/ingestion/heritage-processor.js +237 -0
- package/dist/core/ingestion/import-processor.d.ts +31 -0
- package/dist/core/ingestion/import-processor.js +416 -0
- package/dist/core/ingestion/language-config.d.ts +32 -0
- package/dist/core/ingestion/language-config.js +161 -0
- package/dist/core/ingestion/mro-processor.d.ts +32 -0
- package/dist/core/ingestion/mro-processor.js +343 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +51 -0
- package/dist/core/ingestion/named-binding-extraction.js +343 -0
- package/dist/core/ingestion/parsing-processor.d.ts +20 -0
- package/dist/core/ingestion/parsing-processor.js +282 -0
- package/dist/core/ingestion/pipeline.d.ts +3 -0
- package/dist/core/ingestion/pipeline.js +416 -0
- package/dist/core/ingestion/process-processor.d.ts +42 -0
- package/dist/core/ingestion/process-processor.js +357 -0
- package/dist/core/ingestion/resolution-context.d.ts +40 -0
- package/dist/core/ingestion/resolution-context.js +171 -0
- package/dist/core/ingestion/resolvers/csharp.d.ts +10 -0
- package/dist/core/ingestion/resolvers/csharp.js +101 -0
- package/dist/core/ingestion/resolvers/go.d.ts +8 -0
- package/dist/core/ingestion/resolvers/go.js +33 -0
- package/dist/core/ingestion/resolvers/index.d.ts +14 -0
- package/dist/core/ingestion/resolvers/index.js +10 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +9 -0
- package/dist/core/ingestion/resolvers/jvm.js +74 -0
- package/dist/core/ingestion/resolvers/php.d.ts +7 -0
- package/dist/core/ingestion/resolvers/php.js +30 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +9 -0
- package/dist/core/ingestion/resolvers/ruby.js +13 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +5 -0
- package/dist/core/ingestion/resolvers/rust.js +62 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +16 -0
- package/dist/core/ingestion/resolvers/standard.js +144 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +18 -0
- package/dist/core/ingestion/resolvers/utils.js +113 -0
- package/dist/core/ingestion/structure-processor.d.ts +4 -0
- package/dist/core/ingestion/structure-processor.js +39 -0
- package/dist/core/ingestion/symbol-table.d.ts +34 -0
- package/dist/core/ingestion/symbol-table.js +48 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +20 -0
- package/dist/core/ingestion/tree-sitter-queries.js +691 -0
- package/dist/core/ingestion/type-env.d.ts +52 -0
- package/dist/core/ingestion/type-env.js +349 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +214 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/csharp.js +224 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/go.js +261 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +20 -0
- package/dist/core/ingestion/type-extractors/index.js +30 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +5 -0
- package/dist/core/ingestion/type-extractors/jvm.js +386 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/php.js +280 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/python.js +175 -0
- package/dist/core/ingestion/type-extractors/ruby.d.ts +12 -0
- package/dist/core/ingestion/type-extractors/ruby.js +218 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/rust.js +290 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +81 -0
- package/dist/core/ingestion/type-extractors/shared.js +322 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/swift.js +140 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +111 -0
- package/dist/core/ingestion/type-extractors/types.js +4 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +4 -0
- package/dist/core/ingestion/type-extractors/typescript.js +227 -0
- package/dist/core/ingestion/utils.d.ts +73 -0
- package/dist/core/ingestion/utils.js +992 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +99 -0
- package/dist/core/ingestion/workers/parse-worker.js +1055 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
- package/dist/core/ingestion/workers/worker-pool.js +123 -0
- package/dist/core/lbug/csv-generator.d.ts +28 -0
- package/dist/core/lbug/csv-generator.js +355 -0
- package/dist/core/lbug/lbug-adapter.d.ts +96 -0
- package/dist/core/lbug/lbug-adapter.js +753 -0
- package/dist/core/lbug/schema.d.ts +46 -0
- package/dist/core/lbug/schema.js +402 -0
- package/dist/core/search/bm25-index.d.ts +20 -0
- package/dist/core/search/bm25-index.js +123 -0
- package/dist/core/search/hybrid-search.d.ts +32 -0
- package/dist/core/search/hybrid-search.js +131 -0
- package/dist/core/search/query-cache.d.ts +18 -0
- package/dist/core/search/query-cache.js +47 -0
- package/dist/core/search/query-expansion.d.ts +19 -0
- package/dist/core/search/query-expansion.js +75 -0
- package/dist/core/search/reranker.d.ts +29 -0
- package/dist/core/search/reranker.js +122 -0
- package/dist/core/search/types.d.ts +154 -0
- package/dist/core/search/types.js +51 -0
- package/dist/core/semantic/tsgo-service.d.ts +67 -0
- package/dist/core/semantic/tsgo-service.js +355 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +12 -0
- package/dist/core/tree-sitter/parser-loader.js +71 -0
- package/dist/lib/memory-guard.d.ts +35 -0
- package/dist/lib/memory-guard.js +70 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.js +6 -0
- package/dist/mcp/compatible-stdio-transport.d.ts +32 -0
- package/dist/mcp/compatible-stdio-transport.js +209 -0
- package/dist/mcp/core/embedder.d.ts +24 -0
- package/dist/mcp/core/embedder.js +168 -0
- package/dist/mcp/core/lbug-adapter.d.ts +29 -0
- package/dist/mcp/core/lbug-adapter.js +330 -0
- package/dist/mcp/local/local-backend.d.ts +188 -0
- package/dist/mcp/local/local-backend.js +2759 -0
- package/dist/mcp/resources.d.ts +22 -0
- package/dist/mcp/resources.js +379 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +217 -0
- package/dist/mcp/staleness.d.ts +10 -0
- package/dist/mcp/staleness.js +25 -0
- package/dist/mcp/tools.d.ts +21 -0
- package/dist/mcp/tools.js +202 -0
- package/dist/server/api.d.ts +5 -0
- package/dist/server/api.js +340 -0
- package/dist/server/mcp-http.d.ts +7 -0
- package/dist/server/mcp-http.js +95 -0
- package/dist/storage/git.d.ts +6 -0
- package/dist/storage/git.js +35 -0
- package/dist/storage/repo-manager.d.ts +87 -0
- package/dist/storage/repo-manager.js +249 -0
- package/dist/types/pipeline.d.ts +35 -0
- package/dist/types/pipeline.js +20 -0
- package/hooks/claude/code-mapper-hook.cjs +238 -0
- package/hooks/claude/pre-tool-use.sh +79 -0
- package/hooks/claude/session-start.sh +42 -0
- package/models/mlx-embedder.py +185 -0
- package/package.json +100 -0
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// code-mapper/src/core/ingestion/type-extractors/python.ts
|
|
2
|
+
/** @file python.ts
|
|
3
|
+
* @description Type extraction for Python (PEP 484/526 annotations and constructor inference) */
|
|
4
|
+
import { extractSimpleTypeName, extractVarName } from './shared.js';
|
|
5
|
+
const DECLARATION_NODE_TYPES = new Set([
|
|
6
|
+
'assignment',
|
|
7
|
+
'named_expression',
|
|
8
|
+
'expression_statement',
|
|
9
|
+
]);
|
|
10
|
+
// Python: x: Foo = ... (PEP 484 annotated assignment) or x: Foo (standalone annotation)
|
|
11
|
+
//
|
|
12
|
+
// tree-sitter-python grammar produces two distinct shapes:
|
|
13
|
+
// 1. Annotated assignment with value: `name: str = ""`
|
|
14
|
+
// Node type: assignment, Fields: left=identifier, type=identifier/type, right=value
|
|
15
|
+
// 2. Standalone annotation (no value): `name: str`
|
|
16
|
+
// Node type: expression_statement, Child: type { name=identifier, type=identifier/type }
|
|
17
|
+
// Both appear at file scope and inside class bodies (PEP 526 class variable annotations)
|
|
18
|
+
const extractDeclaration = (node, env) => {
|
|
19
|
+
if (node.type === 'expression_statement') {
|
|
20
|
+
// Standalone annotation: expression_statement > type { name: identifier, type: identifier }
|
|
21
|
+
const typeChild = node.firstNamedChild;
|
|
22
|
+
if (!typeChild || typeChild.type !== 'type')
|
|
23
|
+
return;
|
|
24
|
+
const nameNode = typeChild.childForFieldName('name');
|
|
25
|
+
const typeNode = typeChild.childForFieldName('type');
|
|
26
|
+
if (!nameNode || !typeNode)
|
|
27
|
+
return;
|
|
28
|
+
const varName = extractVarName(nameNode);
|
|
29
|
+
const inner = typeNode.type === 'type' ? (typeNode.firstNamedChild ?? typeNode) : typeNode;
|
|
30
|
+
const typeName = extractSimpleTypeName(inner) ?? inner.text;
|
|
31
|
+
if (varName && typeName)
|
|
32
|
+
env.set(varName, typeName);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Annotated assignment: left : type = value
|
|
36
|
+
const left = node.childForFieldName('left');
|
|
37
|
+
const typeNode = node.childForFieldName('type');
|
|
38
|
+
if (!left || !typeNode)
|
|
39
|
+
return;
|
|
40
|
+
const varName = extractVarName(left);
|
|
41
|
+
// extractSimpleTypeName handles identifiers and qualified names
|
|
42
|
+
// Python 3.10+ union syntax `User | None` is parsed as binary_operator,
|
|
43
|
+
// which extractSimpleTypeName doesn't handle -- fall back to raw text so
|
|
44
|
+
// stripNullable can process it at lookup time (e.g. "User | None" -> "User")
|
|
45
|
+
const inner = typeNode.type === 'type' ? (typeNode.firstNamedChild ?? typeNode) : typeNode;
|
|
46
|
+
const typeName = extractSimpleTypeName(inner) ?? inner.text;
|
|
47
|
+
if (varName && typeName)
|
|
48
|
+
env.set(varName, typeName);
|
|
49
|
+
};
|
|
50
|
+
// Python: parameter with type annotation
|
|
51
|
+
const extractParameter = (node, env) => {
|
|
52
|
+
let nameNode = null;
|
|
53
|
+
let typeNode = null;
|
|
54
|
+
if (node.type === 'parameter') {
|
|
55
|
+
nameNode = node.childForFieldName('name');
|
|
56
|
+
typeNode = node.childForFieldName('type');
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
nameNode = node.childForFieldName('name') ?? node.childForFieldName('pattern');
|
|
60
|
+
typeNode = node.childForFieldName('type');
|
|
61
|
+
}
|
|
62
|
+
if (!nameNode || !typeNode)
|
|
63
|
+
return;
|
|
64
|
+
const varName = extractVarName(nameNode);
|
|
65
|
+
const typeName = extractSimpleTypeName(typeNode);
|
|
66
|
+
if (varName && typeName)
|
|
67
|
+
env.set(varName, typeName);
|
|
68
|
+
};
|
|
69
|
+
// Python: user = User("alice") -- infer type from call when callee is a known class
|
|
70
|
+
// Python constructors are syntactically identical to function calls, so we verify
|
|
71
|
+
// against classNames (which may include cross-file SymbolTable lookups)
|
|
72
|
+
// Also handles walrus operator: if (user := User("alice")):
|
|
73
|
+
const extractInitializer = (node, env, classNames) => {
|
|
74
|
+
let left;
|
|
75
|
+
let right;
|
|
76
|
+
if (node.type === 'named_expression') {
|
|
77
|
+
// Walrus operator: (user := User("alice"))
|
|
78
|
+
// tree-sitter-python: named_expression has 'name' and 'value' fields
|
|
79
|
+
left = node.childForFieldName('name');
|
|
80
|
+
right = node.childForFieldName('value');
|
|
81
|
+
}
|
|
82
|
+
else if (node.type === 'assignment') {
|
|
83
|
+
left = node.childForFieldName('left');
|
|
84
|
+
right = node.childForFieldName('right');
|
|
85
|
+
// Skip if already has type annotation -- extractDeclaration handled it
|
|
86
|
+
if (node.childForFieldName('type'))
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!left || !right)
|
|
93
|
+
return;
|
|
94
|
+
const varName = extractVarName(left);
|
|
95
|
+
if (!varName || env.has(varName))
|
|
96
|
+
return;
|
|
97
|
+
if (right.type !== 'call')
|
|
98
|
+
return;
|
|
99
|
+
const func = right.childForFieldName('function');
|
|
100
|
+
if (!func)
|
|
101
|
+
return;
|
|
102
|
+
// Support both direct calls (User()) and qualified calls (models.User())
|
|
103
|
+
// tree-sitter-python: direct -> identifier, qualified -> attribute
|
|
104
|
+
const calleeName = extractSimpleTypeName(func);
|
|
105
|
+
if (!calleeName)
|
|
106
|
+
return;
|
|
107
|
+
if (classNames.has(calleeName)) {
|
|
108
|
+
env.set(varName, calleeName);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
// Python: user = User("alice") -- scan assignment/walrus for constructor-like calls
|
|
112
|
+
// Returns {varName, calleeName} without checking classNames (caller validates)
|
|
113
|
+
const scanConstructorBinding = (node) => {
|
|
114
|
+
let left;
|
|
115
|
+
let right;
|
|
116
|
+
if (node.type === 'named_expression') {
|
|
117
|
+
left = node.childForFieldName('name');
|
|
118
|
+
right = node.childForFieldName('value');
|
|
119
|
+
}
|
|
120
|
+
else if (node.type === 'assignment') {
|
|
121
|
+
left = node.childForFieldName('left');
|
|
122
|
+
right = node.childForFieldName('right');
|
|
123
|
+
if (node.childForFieldName('type'))
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
if (!left || !right)
|
|
130
|
+
return undefined;
|
|
131
|
+
if (left.type !== 'identifier')
|
|
132
|
+
return undefined;
|
|
133
|
+
if (right.type !== 'call')
|
|
134
|
+
return undefined;
|
|
135
|
+
const func = right.childForFieldName('function');
|
|
136
|
+
if (!func)
|
|
137
|
+
return undefined;
|
|
138
|
+
const calleeName = extractSimpleTypeName(func);
|
|
139
|
+
if (!calleeName)
|
|
140
|
+
return undefined;
|
|
141
|
+
return { varName: left.text, calleeName };
|
|
142
|
+
};
|
|
143
|
+
// Python: alias = u -> assignment with left/right fields
|
|
144
|
+
// Also handles walrus operator: alias := u -> named_expression with name/value fields
|
|
145
|
+
const extractPendingAssignment = (node, scopeEnv) => {
|
|
146
|
+
let left;
|
|
147
|
+
let right;
|
|
148
|
+
if (node.type === 'assignment') {
|
|
149
|
+
left = node.childForFieldName('left');
|
|
150
|
+
right = node.childForFieldName('right');
|
|
151
|
+
}
|
|
152
|
+
else if (node.type === 'named_expression') {
|
|
153
|
+
left = node.childForFieldName('name');
|
|
154
|
+
right = node.childForFieldName('value');
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
if (!left || !right)
|
|
160
|
+
return undefined;
|
|
161
|
+
const lhs = left.type === 'identifier' ? left.text : undefined;
|
|
162
|
+
if (!lhs || scopeEnv.has(lhs))
|
|
163
|
+
return undefined;
|
|
164
|
+
if (right.type === 'identifier')
|
|
165
|
+
return { lhs, rhs: right.text };
|
|
166
|
+
return undefined;
|
|
167
|
+
};
|
|
168
|
+
export const typeConfig = {
|
|
169
|
+
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
|
170
|
+
extractDeclaration,
|
|
171
|
+
extractParameter,
|
|
172
|
+
extractInitializer,
|
|
173
|
+
scanConstructorBinding,
|
|
174
|
+
extractPendingAssignment,
|
|
175
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** @file ruby.ts
|
|
2
|
+
* @description Type extraction for Ruby via YARD annotation parsing and constructor inference
|
|
3
|
+
*
|
|
4
|
+
* Ruby has no static type system, but YARD provides de facto type annotations via comments:
|
|
5
|
+
* `# @param name [String]`, `# @return [User]`
|
|
6
|
+
*
|
|
7
|
+
* Resolution tiers:
|
|
8
|
+
* - Tier 0: YARD @param annotations (extractDeclaration pre-populates env)
|
|
9
|
+
* - Tier 1: Constructor inference via `user = User.new` (scanConstructorBinding)
|
|
10
|
+
*/
|
|
11
|
+
import type { LanguageTypeConfig } from './types.js';
|
|
12
|
+
export declare const typeConfig: LanguageTypeConfig;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// code-mapper/src/core/ingestion/type-extractors/ruby.ts
|
|
2
|
+
/** @file ruby.ts
|
|
3
|
+
* @description Type extraction for Ruby via YARD annotation parsing and constructor inference
|
|
4
|
+
*
|
|
5
|
+
* Ruby has no static type system, but YARD provides de facto type annotations via comments:
|
|
6
|
+
* `# @param name [String]`, `# @return [User]`
|
|
7
|
+
*
|
|
8
|
+
* Resolution tiers:
|
|
9
|
+
* - Tier 0: YARD @param annotations (extractDeclaration pre-populates env)
|
|
10
|
+
* - Tier 1: Constructor inference via `user = User.new` (scanConstructorBinding)
|
|
11
|
+
*/
|
|
12
|
+
import { extractRubyConstructorAssignment, extractSimpleTypeName } from './shared.js';
|
|
13
|
+
// Regex to extract @param annotations: `@param name [Type]`
|
|
14
|
+
const YARD_PARAM_RE = /@param\s+(\w+)\s+\[([^\]]+)\]/g;
|
|
15
|
+
// Alternate YARD order: `@param [Type] name`
|
|
16
|
+
const YARD_PARAM_ALT_RE = /@param\s+\[([^\]]+)\]\s+(\w+)/g;
|
|
17
|
+
// Regex to extract @return annotations: `@return [Type]`
|
|
18
|
+
const YARD_RETURN_RE = /@return\s+\[([^\]]+)\]/;
|
|
19
|
+
/**
|
|
20
|
+
* Extract the simple type name from a YARD type string
|
|
21
|
+
*
|
|
22
|
+
* Handles simple types ("String"), qualified ("Models::User" -> "User"),
|
|
23
|
+
* generic ("Array<User>" -> "Array"), nullable ("String, nil" -> "String"),
|
|
24
|
+
* and rejects ambiguous unions ("String, Integer" -> undefined)
|
|
25
|
+
*/
|
|
26
|
+
const extractYardTypeName = (yardType) => {
|
|
27
|
+
const trimmed = yardType.trim();
|
|
28
|
+
// Handle nullable: "Type, nil" or "nil, Type"
|
|
29
|
+
// Use bracket-balanced split to avoid breaking on commas inside generics (e.g. Hash<Symbol, User>)
|
|
30
|
+
const parts = [];
|
|
31
|
+
let depth = 0, start = 0;
|
|
32
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
33
|
+
if (trimmed[i] === '<')
|
|
34
|
+
depth++;
|
|
35
|
+
else if (trimmed[i] === '>')
|
|
36
|
+
depth--;
|
|
37
|
+
else if (trimmed[i] === ',' && depth === 0) {
|
|
38
|
+
parts.push(trimmed.slice(start, i).trim());
|
|
39
|
+
start = i + 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
parts.push(trimmed.slice(start).trim());
|
|
43
|
+
const filtered = parts.filter(p => p !== '' && p !== 'nil');
|
|
44
|
+
if (filtered.length !== 1)
|
|
45
|
+
return undefined; // ambiguous union
|
|
46
|
+
const typePart = filtered[0];
|
|
47
|
+
// Handle qualified: "Models::User" -> "User"
|
|
48
|
+
const segments = typePart.split('::');
|
|
49
|
+
const last = segments[segments.length - 1];
|
|
50
|
+
// Handle generic: "Array<User>" -> "Array"
|
|
51
|
+
const genericMatch = last.match(/^(\w+)\s*[<{(]/);
|
|
52
|
+
if (genericMatch)
|
|
53
|
+
return genericMatch[1];
|
|
54
|
+
// Simple identifier check
|
|
55
|
+
if (/^\w+$/.test(last))
|
|
56
|
+
return last;
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Collect YARD @param annotations from comment nodes preceding a method definition
|
|
61
|
+
*
|
|
62
|
+
* In tree-sitter-ruby, comments are sibling nodes before the method node
|
|
63
|
+
* Walks backwards through preceding siblings collecting consecutive comment nodes
|
|
64
|
+
* @returns Map of paramName -> typeName
|
|
65
|
+
*/
|
|
66
|
+
const collectYardParams = (methodNode) => {
|
|
67
|
+
const params = new Map();
|
|
68
|
+
// In tree-sitter-ruby, YARD comments preceding a method inside a class body are
|
|
69
|
+
// placed as children of the `class` node, NOT as siblings of the `method` inside
|
|
70
|
+
// `body_statement`. For top-level methods, comments ARE direct siblings
|
|
71
|
+
// We handle both: if method has no preceding comment siblings, check parent
|
|
72
|
+
// (body_statement) siblings instead
|
|
73
|
+
const commentTexts = [];
|
|
74
|
+
const collectComments = (startNode) => {
|
|
75
|
+
let sibling = startNode.previousSibling;
|
|
76
|
+
while (sibling) {
|
|
77
|
+
if (sibling.type === 'comment') {
|
|
78
|
+
commentTexts.unshift(sibling.text);
|
|
79
|
+
}
|
|
80
|
+
else if (sibling.isNamed) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
sibling = sibling.previousSibling;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
// Try method's own siblings first (top-level methods)
|
|
87
|
+
collectComments(methodNode);
|
|
88
|
+
// If no comments found and parent is body_statement, check parent's siblings
|
|
89
|
+
if (commentTexts.length === 0 && methodNode.parent?.type === 'body_statement') {
|
|
90
|
+
collectComments(methodNode.parent);
|
|
91
|
+
}
|
|
92
|
+
// Parse all comment lines for @param annotations
|
|
93
|
+
const commentBlock = commentTexts.join('\n');
|
|
94
|
+
let match;
|
|
95
|
+
// Reset regex state
|
|
96
|
+
YARD_PARAM_RE.lastIndex = 0;
|
|
97
|
+
while ((match = YARD_PARAM_RE.exec(commentBlock)) !== null) {
|
|
98
|
+
const paramName = match[1];
|
|
99
|
+
const rawType = match[2];
|
|
100
|
+
const typeName = extractYardTypeName(rawType);
|
|
101
|
+
if (typeName) {
|
|
102
|
+
params.set(paramName, typeName);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Also check alternate YARD order: @param [Type] name
|
|
106
|
+
YARD_PARAM_ALT_RE.lastIndex = 0;
|
|
107
|
+
while ((match = YARD_PARAM_ALT_RE.exec(commentBlock)) !== null) {
|
|
108
|
+
const rawType = match[1];
|
|
109
|
+
const paramName = match[2];
|
|
110
|
+
if (params.has(paramName))
|
|
111
|
+
continue; // standard format takes priority
|
|
112
|
+
const typeName = extractYardTypeName(rawType);
|
|
113
|
+
if (typeName) {
|
|
114
|
+
params.set(paramName, typeName);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return params;
|
|
118
|
+
};
|
|
119
|
+
// Ruby node types that may carry type bindings:
|
|
120
|
+
// - method/singleton_method: YARD @param annotations (via extractDeclaration)
|
|
121
|
+
// - assignment: Constructor inference like `user = User.new` (via extractInitializer)
|
|
122
|
+
const DECLARATION_NODE_TYPES = new Set([
|
|
123
|
+
'method',
|
|
124
|
+
'singleton_method',
|
|
125
|
+
'assignment',
|
|
126
|
+
]);
|
|
127
|
+
// Extract YARD annotations from method definitions
|
|
128
|
+
// Pre-populates the scope env with parameter types before the standard parameter walk
|
|
129
|
+
const extractDeclaration = (node, env) => {
|
|
130
|
+
if (node.type !== 'method' && node.type !== 'singleton_method')
|
|
131
|
+
return;
|
|
132
|
+
const yardParams = collectYardParams(node);
|
|
133
|
+
if (yardParams.size === 0)
|
|
134
|
+
return;
|
|
135
|
+
// Pre-populate env with YARD type bindings for each parameter
|
|
136
|
+
for (const [paramName, typeName] of yardParams) {
|
|
137
|
+
env.set(paramName, typeName);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
// Ruby parameters have no inline type annotations -- YARD types are already populated
|
|
141
|
+
// by extractDeclaration. No-op to satisfy the LanguageTypeConfig contract
|
|
142
|
+
const extractParameter = (_node, _env) => {
|
|
143
|
+
// no-op: YARD types are pre-populated by extractDeclaration
|
|
144
|
+
};
|
|
145
|
+
// Ruby constructor inference: user = User.new or service = Models::User.new
|
|
146
|
+
// Uses the shared extractRubyConstructorAssignment helper, then resolves against known class names
|
|
147
|
+
const extractInitializer = (node, env, classNames) => {
|
|
148
|
+
const result = extractRubyConstructorAssignment(node);
|
|
149
|
+
if (!result)
|
|
150
|
+
return;
|
|
151
|
+
if (env.has(result.varName))
|
|
152
|
+
return;
|
|
153
|
+
if (classNames.has(result.calleeName)) {
|
|
154
|
+
env.set(result.varName, result.calleeName);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
// Extract return type from YARD `@return [Type]` annotation preceding a method
|
|
158
|
+
// Same comment-walking strategy as collectYardParams: try direct siblings first,
|
|
159
|
+
// fall back to parent (body_statement) siblings for class methods
|
|
160
|
+
const extractReturnType = (node) => {
|
|
161
|
+
const search = (startNode) => {
|
|
162
|
+
let sibling = startNode.previousSibling;
|
|
163
|
+
while (sibling) {
|
|
164
|
+
if (sibling.type === 'comment') {
|
|
165
|
+
const match = YARD_RETURN_RE.exec(sibling.text);
|
|
166
|
+
if (match)
|
|
167
|
+
return extractYardTypeName(match[1]);
|
|
168
|
+
}
|
|
169
|
+
else if (sibling.isNamed) {
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
sibling = sibling.previousSibling;
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
};
|
|
176
|
+
const result = search(node);
|
|
177
|
+
if (result)
|
|
178
|
+
return result;
|
|
179
|
+
if (node.parent?.type === 'body_statement') {
|
|
180
|
+
return search(node.parent);
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
};
|
|
184
|
+
// Ruby constructor binding scanner: captures both `user = User.new` and plain call
|
|
185
|
+
// assignments like `user = get_user()`. The `.new` pattern returns the class name
|
|
186
|
+
// directly; plain calls return the callee name for return-type inference
|
|
187
|
+
const scanConstructorBinding = (node) => {
|
|
188
|
+
// Try the .new pattern first (returns class name directly)
|
|
189
|
+
const newResult = extractRubyConstructorAssignment(node);
|
|
190
|
+
if (newResult)
|
|
191
|
+
return newResult;
|
|
192
|
+
// Plain call assignment: user = get_user() / user = Models.create()
|
|
193
|
+
if (node.type !== 'assignment')
|
|
194
|
+
return undefined;
|
|
195
|
+
const left = node.childForFieldName('left');
|
|
196
|
+
const right = node.childForFieldName('right');
|
|
197
|
+
if (!left || !right)
|
|
198
|
+
return undefined;
|
|
199
|
+
if (left.type !== 'identifier' && left.type !== 'constant')
|
|
200
|
+
return undefined;
|
|
201
|
+
if (right.type !== 'call')
|
|
202
|
+
return undefined;
|
|
203
|
+
const method = right.childForFieldName('method');
|
|
204
|
+
if (!method)
|
|
205
|
+
return undefined;
|
|
206
|
+
const calleeName = extractSimpleTypeName(method);
|
|
207
|
+
if (!calleeName)
|
|
208
|
+
return undefined;
|
|
209
|
+
return { varName: left.text, calleeName };
|
|
210
|
+
};
|
|
211
|
+
export const typeConfig = {
|
|
212
|
+
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
|
213
|
+
extractDeclaration,
|
|
214
|
+
extractParameter,
|
|
215
|
+
extractInitializer,
|
|
216
|
+
scanConstructorBinding,
|
|
217
|
+
extractReturnType,
|
|
218
|
+
};
|