@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,389 @@
|
|
|
1
|
+
import { extractRubyConstructorAssignment, extractSimpleTypeName, extractElementTypeFromString, extractVarName, resolveIterableElementType } from './shared.js';
|
|
2
|
+
/**
|
|
3
|
+
* Ruby type extractor — YARD annotation parsing.
|
|
4
|
+
*
|
|
5
|
+
* Ruby has no static type system, but the YARD documentation convention
|
|
6
|
+
* provides de facto type annotations via comments:
|
|
7
|
+
*
|
|
8
|
+
* # @param name [String] the user's name
|
|
9
|
+
* # @param repo [UserRepo] the repository
|
|
10
|
+
* # @return [User]
|
|
11
|
+
* def create(name, repo)
|
|
12
|
+
* repo.save
|
|
13
|
+
* end
|
|
14
|
+
*
|
|
15
|
+
* This extractor parses `@param name [Type]` patterns from comment nodes
|
|
16
|
+
* preceding method definitions and binds parameter names to their types.
|
|
17
|
+
*
|
|
18
|
+
* Resolution tiers:
|
|
19
|
+
* - Tier 0: YARD @param annotations (extractDeclaration pre-populates env)
|
|
20
|
+
* - Tier 1: Constructor inference via `user = User.new` (handled by scanConstructorBinding in typeConfig)
|
|
21
|
+
*/
|
|
22
|
+
/** Regex to extract @param annotations: `@param name [Type]` */
|
|
23
|
+
const YARD_PARAM_RE = /@param\s+(\w+)\s+\[([^\]]+)\]/g;
|
|
24
|
+
/** Alternate YARD order: `@param [Type] name` */
|
|
25
|
+
const YARD_PARAM_ALT_RE = /@param\s+\[([^\]]+)\]\s+(\w+)/g;
|
|
26
|
+
/** Regex to extract @return annotations: `@return [Type]` */
|
|
27
|
+
const YARD_RETURN_RE = /@return\s+\[([^\]]+)\]/;
|
|
28
|
+
/**
|
|
29
|
+
* Extract the simple type name from a YARD type string.
|
|
30
|
+
* Handles:
|
|
31
|
+
* - Simple types: "String" → "String"
|
|
32
|
+
* - Qualified types: "Models::User" → "User"
|
|
33
|
+
* - Generic types: "Array<User>" → "Array"
|
|
34
|
+
* - Nullable types: "String, nil" → "String"
|
|
35
|
+
* - Union types: "String, Integer" → undefined (ambiguous)
|
|
36
|
+
*/
|
|
37
|
+
const extractYardTypeName = (yardType) => {
|
|
38
|
+
const trimmed = yardType.trim();
|
|
39
|
+
// Handle nullable: "Type, nil" or "nil, Type"
|
|
40
|
+
// Use bracket-balanced split to avoid breaking on commas inside generics like Hash<Symbol, User>
|
|
41
|
+
const parts = [];
|
|
42
|
+
let depth = 0, start = 0;
|
|
43
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
44
|
+
if (trimmed[i] === '<')
|
|
45
|
+
depth++;
|
|
46
|
+
else if (trimmed[i] === '>')
|
|
47
|
+
depth--;
|
|
48
|
+
else if (trimmed[i] === ',' && depth === 0) {
|
|
49
|
+
parts.push(trimmed.slice(start, i).trim());
|
|
50
|
+
start = i + 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
parts.push(trimmed.slice(start).trim());
|
|
54
|
+
const filtered = parts.filter(p => p !== '' && p !== 'nil');
|
|
55
|
+
if (filtered.length !== 1)
|
|
56
|
+
return undefined; // ambiguous union
|
|
57
|
+
const typePart = filtered[0];
|
|
58
|
+
// Handle qualified: "Models::User" → "User"
|
|
59
|
+
const segments = typePart.split('::');
|
|
60
|
+
const last = segments[segments.length - 1];
|
|
61
|
+
// Handle generic: "Array<User>" → "Array"
|
|
62
|
+
const genericMatch = last.match(/^(\w+)\s*[<{(]/);
|
|
63
|
+
if (genericMatch)
|
|
64
|
+
return genericMatch[1];
|
|
65
|
+
// Simple identifier check
|
|
66
|
+
if (/^\w+$/.test(last))
|
|
67
|
+
return last;
|
|
68
|
+
return undefined;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Collect YARD @param annotations from comment nodes preceding a method definition.
|
|
72
|
+
* Returns a map of paramName → typeName.
|
|
73
|
+
*
|
|
74
|
+
* In tree-sitter-ruby, comments are sibling nodes that appear before the method node.
|
|
75
|
+
* We walk backwards through preceding siblings collecting consecutive comment nodes.
|
|
76
|
+
*/
|
|
77
|
+
const collectYardParams = (methodNode) => {
|
|
78
|
+
const params = new Map();
|
|
79
|
+
// In tree-sitter-ruby, YARD comments preceding a method inside a class body
|
|
80
|
+
// are placed as children of the `class` node, NOT as siblings of the `method`
|
|
81
|
+
// inside `body_statement`. The AST structure is:
|
|
82
|
+
//
|
|
83
|
+
// class
|
|
84
|
+
// constant = "ClassName"
|
|
85
|
+
// comment = "# @param ..." ← sibling of body_statement
|
|
86
|
+
// comment = "# @param ..." ← sibling of body_statement
|
|
87
|
+
// body_statement
|
|
88
|
+
// method ← method is here, no preceding siblings
|
|
89
|
+
//
|
|
90
|
+
// For top-level methods (outside classes), comments ARE direct siblings.
|
|
91
|
+
// We handle both by checking: if method has no preceding comment siblings,
|
|
92
|
+
// look at parent (body_statement) siblings instead.
|
|
93
|
+
const commentTexts = [];
|
|
94
|
+
const collectComments = (startNode) => {
|
|
95
|
+
let sibling = startNode.previousSibling;
|
|
96
|
+
while (sibling) {
|
|
97
|
+
if (sibling.type === 'comment') {
|
|
98
|
+
commentTexts.unshift(sibling.text);
|
|
99
|
+
}
|
|
100
|
+
else if (sibling.isNamed) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
sibling = sibling.previousSibling;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
// Try method's own siblings first (top-level methods)
|
|
107
|
+
collectComments(methodNode);
|
|
108
|
+
// If no comments found and parent is body_statement, check parent's siblings
|
|
109
|
+
if (commentTexts.length === 0 && methodNode.parent?.type === 'body_statement') {
|
|
110
|
+
collectComments(methodNode.parent);
|
|
111
|
+
}
|
|
112
|
+
// Parse all comment lines for @param annotations
|
|
113
|
+
const commentBlock = commentTexts.join('\n');
|
|
114
|
+
let match;
|
|
115
|
+
// Reset regex state
|
|
116
|
+
YARD_PARAM_RE.lastIndex = 0;
|
|
117
|
+
while ((match = YARD_PARAM_RE.exec(commentBlock)) !== null) {
|
|
118
|
+
const paramName = match[1];
|
|
119
|
+
const rawType = match[2];
|
|
120
|
+
const typeName = extractYardTypeName(rawType);
|
|
121
|
+
if (typeName) {
|
|
122
|
+
params.set(paramName, typeName);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Also check alternate YARD order: @param [Type] name
|
|
126
|
+
YARD_PARAM_ALT_RE.lastIndex = 0;
|
|
127
|
+
while ((match = YARD_PARAM_ALT_RE.exec(commentBlock)) !== null) {
|
|
128
|
+
const rawType = match[1];
|
|
129
|
+
const paramName = match[2];
|
|
130
|
+
if (params.has(paramName))
|
|
131
|
+
continue; // standard format takes priority
|
|
132
|
+
const typeName = extractYardTypeName(rawType);
|
|
133
|
+
if (typeName) {
|
|
134
|
+
params.set(paramName, typeName);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return params;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Ruby node types that may carry type bindings.
|
|
141
|
+
* - `method`/`singleton_method`: YARD @param annotations (via extractDeclaration)
|
|
142
|
+
* - `assignment`: Constructor inference like `user = User.new` (via extractInitializer;
|
|
143
|
+
* extractDeclaration returns early for these nodes)
|
|
144
|
+
*/
|
|
145
|
+
const DECLARATION_NODE_TYPES = new Set([
|
|
146
|
+
'method',
|
|
147
|
+
'singleton_method',
|
|
148
|
+
'assignment',
|
|
149
|
+
]);
|
|
150
|
+
/**
|
|
151
|
+
* Extract YARD annotations from method definitions.
|
|
152
|
+
* Pre-populates the scope env with parameter types before the
|
|
153
|
+
* standard parameter walk (which won't find types since Ruby has none).
|
|
154
|
+
*/
|
|
155
|
+
const extractDeclaration = (node, env) => {
|
|
156
|
+
if (node.type !== 'method' && node.type !== 'singleton_method')
|
|
157
|
+
return;
|
|
158
|
+
const yardParams = collectYardParams(node);
|
|
159
|
+
if (yardParams.size === 0)
|
|
160
|
+
return;
|
|
161
|
+
// Pre-populate env with YARD type bindings for each parameter
|
|
162
|
+
for (const [paramName, typeName] of yardParams) {
|
|
163
|
+
env.set(paramName, typeName);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Ruby parameter extraction.
|
|
168
|
+
* Ruby parameters (identifiers inside method_parameters) have no inline
|
|
169
|
+
* type annotations. YARD types are already populated by extractDeclaration,
|
|
170
|
+
* so this is a no-op — the bindings are already in the env.
|
|
171
|
+
*
|
|
172
|
+
* We still register this to maintain the LanguageTypeConfig contract.
|
|
173
|
+
*/
|
|
174
|
+
const extractParameter = (_node, _env) => {
|
|
175
|
+
// Ruby parameters have no type annotations.
|
|
176
|
+
// YARD types are pre-populated by extractDeclaration.
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Ruby constructor inference: user = User.new or service = Models::User.new
|
|
180
|
+
* Uses the shared extractRubyConstructorAssignment helper for AST matching,
|
|
181
|
+
* then resolves against locally-known class names.
|
|
182
|
+
*/
|
|
183
|
+
const extractInitializer = (node, env, classNames) => {
|
|
184
|
+
const result = extractRubyConstructorAssignment(node);
|
|
185
|
+
if (!result)
|
|
186
|
+
return;
|
|
187
|
+
if (env.has(result.varName))
|
|
188
|
+
return;
|
|
189
|
+
if (classNames.has(result.calleeName)) {
|
|
190
|
+
env.set(result.varName, result.calleeName);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Extract return type from YARD `@return [Type]` annotation preceding a method.
|
|
195
|
+
* Reuses the same comment-walking strategy as collectYardParams: try direct
|
|
196
|
+
* siblings first, fall back to parent (body_statement) siblings for class methods.
|
|
197
|
+
*/
|
|
198
|
+
const extractReturnType = (node) => {
|
|
199
|
+
const search = (startNode) => {
|
|
200
|
+
let sibling = startNode.previousSibling;
|
|
201
|
+
while (sibling) {
|
|
202
|
+
if (sibling.type === 'comment') {
|
|
203
|
+
const match = YARD_RETURN_RE.exec(sibling.text);
|
|
204
|
+
if (match)
|
|
205
|
+
return extractYardTypeName(match[1]);
|
|
206
|
+
}
|
|
207
|
+
else if (sibling.isNamed) {
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
sibling = sibling.previousSibling;
|
|
211
|
+
}
|
|
212
|
+
return undefined;
|
|
213
|
+
};
|
|
214
|
+
const result = search(node);
|
|
215
|
+
if (result)
|
|
216
|
+
return result;
|
|
217
|
+
if (node.parent?.type === 'body_statement') {
|
|
218
|
+
return search(node.parent);
|
|
219
|
+
}
|
|
220
|
+
return undefined;
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Ruby constructor binding scanner: captures both `user = User.new` and
|
|
224
|
+
* plain call assignments like `user = get_user()`.
|
|
225
|
+
* The `.new` pattern returns the class name directly; plain calls return the
|
|
226
|
+
* callee name for return-type inference via SymbolTable lookup.
|
|
227
|
+
*/
|
|
228
|
+
const scanConstructorBinding = (node) => {
|
|
229
|
+
// Try the .new pattern first (returns class name directly)
|
|
230
|
+
const newResult = extractRubyConstructorAssignment(node);
|
|
231
|
+
if (newResult)
|
|
232
|
+
return newResult;
|
|
233
|
+
// Plain call assignment: user = get_user() / user = Models.create()
|
|
234
|
+
if (node.type !== 'assignment')
|
|
235
|
+
return undefined;
|
|
236
|
+
const left = node.childForFieldName('left');
|
|
237
|
+
const right = node.childForFieldName('right');
|
|
238
|
+
if (!left || !right)
|
|
239
|
+
return undefined;
|
|
240
|
+
if (left.type !== 'identifier' && left.type !== 'constant')
|
|
241
|
+
return undefined;
|
|
242
|
+
if (right.type !== 'call')
|
|
243
|
+
return undefined;
|
|
244
|
+
const method = right.childForFieldName('method');
|
|
245
|
+
if (!method)
|
|
246
|
+
return undefined;
|
|
247
|
+
const calleeName = extractSimpleTypeName(method);
|
|
248
|
+
if (!calleeName)
|
|
249
|
+
return undefined;
|
|
250
|
+
return { varName: left.text, calleeName };
|
|
251
|
+
};
|
|
252
|
+
/** Ruby method node types that carry a parameter list. */
|
|
253
|
+
const RUBY_METHOD_NODE_TYPES = new Set(['method', 'singleton_method']);
|
|
254
|
+
const FOR_LOOP_NODE_TYPES = new Set(['for']);
|
|
255
|
+
/**
|
|
256
|
+
* Collect raw YARD @param type strings from comment nodes preceding a method.
|
|
257
|
+
* Unlike collectYardParams which returns simplified type names, this returns the
|
|
258
|
+
* raw bracket content (e.g., "Array<User>" not "Array") for element type extraction.
|
|
259
|
+
*/
|
|
260
|
+
const collectYardRawParams = (methodNode) => {
|
|
261
|
+
const params = new Map();
|
|
262
|
+
const commentTexts = [];
|
|
263
|
+
const collectComments = (startNode) => {
|
|
264
|
+
let sibling = startNode.previousSibling;
|
|
265
|
+
while (sibling) {
|
|
266
|
+
if (sibling.type === 'comment') {
|
|
267
|
+
commentTexts.unshift(sibling.text);
|
|
268
|
+
}
|
|
269
|
+
else if (sibling.isNamed) {
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
sibling = sibling.previousSibling;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
collectComments(methodNode);
|
|
276
|
+
if (commentTexts.length === 0 && methodNode.parent?.type === 'body_statement') {
|
|
277
|
+
collectComments(methodNode.parent);
|
|
278
|
+
}
|
|
279
|
+
const commentBlock = commentTexts.join('\n');
|
|
280
|
+
let match;
|
|
281
|
+
YARD_PARAM_RE.lastIndex = 0;
|
|
282
|
+
while ((match = YARD_PARAM_RE.exec(commentBlock)) !== null) {
|
|
283
|
+
params.set(match[1], match[2]);
|
|
284
|
+
}
|
|
285
|
+
YARD_PARAM_ALT_RE.lastIndex = 0;
|
|
286
|
+
while ((match = YARD_PARAM_ALT_RE.exec(commentBlock)) !== null) {
|
|
287
|
+
if (!params.has(match[2]))
|
|
288
|
+
params.set(match[2], match[1]);
|
|
289
|
+
}
|
|
290
|
+
return params;
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* Walk up the AST from a for-statement to find the enclosing method,
|
|
294
|
+
* then search its YARD @param annotations for one named `iterableName`.
|
|
295
|
+
* Returns the element type extracted from the raw YARD type string.
|
|
296
|
+
*
|
|
297
|
+
* Example: `@param users [Array<User>]` → extracts "User" from "Array<User>".
|
|
298
|
+
*/
|
|
299
|
+
const findRubyParamElementType = (iterableName, startNode) => {
|
|
300
|
+
let current = startNode.parent;
|
|
301
|
+
while (current) {
|
|
302
|
+
if (RUBY_METHOD_NODE_TYPES.has(current.type)) {
|
|
303
|
+
const rawParams = collectYardRawParams(current);
|
|
304
|
+
const rawType = rawParams.get(iterableName);
|
|
305
|
+
if (rawType)
|
|
306
|
+
return extractElementTypeFromString(rawType);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
current = current.parent;
|
|
310
|
+
}
|
|
311
|
+
return undefined;
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
314
|
+
* Ruby: for user in users ... end
|
|
315
|
+
*
|
|
316
|
+
* tree-sitter-ruby `for` node structure:
|
|
317
|
+
* pattern field: the loop variable (identifier)
|
|
318
|
+
* value field: `in` node whose child is the iterable expression
|
|
319
|
+
*
|
|
320
|
+
* Tier 1c: resolves the element type via:
|
|
321
|
+
* 1. scopeEnv string — extractElementTypeFromString on the stored type
|
|
322
|
+
* 2. AST walk — walks up to the enclosing method's YARD @param to read Array<User> directly
|
|
323
|
+
*
|
|
324
|
+
* Ruby has no static types on loop variables, so this mainly works when the
|
|
325
|
+
* iterable has a YARD-annotated container type (e.g., `@param users [Array<User>]`).
|
|
326
|
+
*/
|
|
327
|
+
const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope }) => {
|
|
328
|
+
if (node.type !== 'for')
|
|
329
|
+
return;
|
|
330
|
+
// The loop variable is the `pattern` field (identifier).
|
|
331
|
+
const patternNode = node.childForFieldName('pattern');
|
|
332
|
+
if (!patternNode)
|
|
333
|
+
return;
|
|
334
|
+
const loopVarName = extractVarName(patternNode);
|
|
335
|
+
if (!loopVarName)
|
|
336
|
+
return;
|
|
337
|
+
// The iterable is inside the `value` field which is an `in` node wrapping the expression.
|
|
338
|
+
const inNode = node.childForFieldName('value');
|
|
339
|
+
if (!inNode)
|
|
340
|
+
return;
|
|
341
|
+
const iterableNode = inNode.firstNamedChild;
|
|
342
|
+
let iterableName;
|
|
343
|
+
if (iterableNode?.type === 'identifier') {
|
|
344
|
+
iterableName = iterableNode.text;
|
|
345
|
+
}
|
|
346
|
+
else if (iterableNode?.type === 'call') {
|
|
347
|
+
const method = iterableNode.childForFieldName('method');
|
|
348
|
+
if (method)
|
|
349
|
+
iterableName = method.text;
|
|
350
|
+
}
|
|
351
|
+
if (!iterableName)
|
|
352
|
+
return;
|
|
353
|
+
// Ruby has no extractFromTypeNode (no AST type annotations), pass a no-op.
|
|
354
|
+
const noopExtractFromTypeNode = () => undefined;
|
|
355
|
+
const elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, noopExtractFromTypeNode, findRubyParamElementType, undefined);
|
|
356
|
+
if (!elementType)
|
|
357
|
+
return;
|
|
358
|
+
scopeEnv.set(loopVarName, elementType);
|
|
359
|
+
};
|
|
360
|
+
/**
|
|
361
|
+
* Ruby: alias_user = user → assignment with left/right identifier fields.
|
|
362
|
+
* Only handles plain identifier RHS (not calls, not literals).
|
|
363
|
+
* Skips if LHS already has a resolved type in scopeEnv.
|
|
364
|
+
*/
|
|
365
|
+
const extractPendingAssignment = (node, scopeEnv) => {
|
|
366
|
+
if (node.type !== 'assignment')
|
|
367
|
+
return undefined;
|
|
368
|
+
const lhsNode = node.childForFieldName('left');
|
|
369
|
+
if (!lhsNode || lhsNode.type !== 'identifier')
|
|
370
|
+
return undefined;
|
|
371
|
+
const varName = lhsNode.text;
|
|
372
|
+
if (scopeEnv.has(varName))
|
|
373
|
+
return undefined;
|
|
374
|
+
const rhsNode = node.childForFieldName('right');
|
|
375
|
+
if (!rhsNode || rhsNode.type !== 'identifier')
|
|
376
|
+
return undefined;
|
|
377
|
+
return { kind: 'copy', lhs: varName, rhs: rhsNode.text };
|
|
378
|
+
};
|
|
379
|
+
export const typeConfig = {
|
|
380
|
+
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
|
381
|
+
forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
|
|
382
|
+
extractDeclaration,
|
|
383
|
+
extractParameter,
|
|
384
|
+
extractInitializer,
|
|
385
|
+
scanConstructorBinding,
|
|
386
|
+
extractReturnType,
|
|
387
|
+
extractForLoopBinding,
|
|
388
|
+
extractPendingAssignment,
|
|
389
|
+
};
|