legacyver 2.1.0 → 2.1.2
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/.agent/skills/openspec-apply-change/SKILL.md +156 -0
- package/.agent/skills/openspec-archive-change/SKILL.md +114 -0
- package/.agent/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.agent/skills/openspec-continue-change/SKILL.md +118 -0
- package/.agent/skills/openspec-explore/SKILL.md +290 -0
- package/.agent/skills/openspec-ff-change/SKILL.md +101 -0
- package/.agent/skills/openspec-new-change/SKILL.md +74 -0
- package/.agent/skills/openspec-onboard/SKILL.md +529 -0
- package/.agent/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.agent/skills/openspec-verify-change/SKILL.md +168 -0
- package/.agent/workflows/opsx-apply.md +149 -0
- package/.agent/workflows/opsx-archive.md +154 -0
- package/.agent/workflows/opsx-bulk-archive.md +239 -0
- package/.agent/workflows/opsx-continue.md +111 -0
- package/.agent/workflows/opsx-explore.md +171 -0
- package/.agent/workflows/opsx-ff.md +91 -0
- package/.agent/workflows/opsx-new.md +66 -0
- package/.agent/workflows/opsx-onboard.md +522 -0
- package/.agent/workflows/opsx-sync.md +131 -0
- package/.agent/workflows/opsx-verify.md +161 -0
- package/.github/prompts/opsx-apply.prompt.md +149 -0
- package/.github/prompts/opsx-archive.prompt.md +154 -0
- package/.github/prompts/opsx-bulk-archive.prompt.md +239 -0
- package/.github/prompts/opsx-continue.prompt.md +111 -0
- package/.github/prompts/opsx-explore.prompt.md +171 -0
- package/.github/prompts/opsx-ff.prompt.md +91 -0
- package/.github/prompts/opsx-new.prompt.md +66 -0
- package/.github/prompts/opsx-onboard.prompt.md +522 -0
- package/.github/prompts/opsx-sync.prompt.md +131 -0
- package/.github/prompts/opsx-verify.prompt.md +161 -0
- package/.github/skills/openspec-apply-change/SKILL.md +156 -0
- package/.github/skills/openspec-archive-change/SKILL.md +114 -0
- package/.github/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.github/skills/openspec-continue-change/SKILL.md +118 -0
- package/.github/skills/openspec-explore/SKILL.md +290 -0
- package/.github/skills/openspec-ff-change/SKILL.md +101 -0
- package/.github/skills/openspec-new-change/SKILL.md +74 -0
- package/.github/skills/openspec-onboard/SKILL.md +529 -0
- package/.github/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.github/skills/openspec-verify-change/SKILL.md +168 -0
- package/.legacyverignore.example +43 -0
- package/.legacyverrc +7 -0
- package/.opencode/command/opsx-apply.md +149 -0
- package/.opencode/command/opsx-archive.md +154 -0
- package/.opencode/command/opsx-bulk-archive.md +239 -0
- package/.opencode/command/opsx-continue.md +111 -0
- package/.opencode/command/opsx-explore.md +171 -0
- package/.opencode/command/opsx-ff.md +91 -0
- package/.opencode/command/opsx-new.md +66 -0
- package/.opencode/command/opsx-onboard.md +522 -0
- package/.opencode/command/opsx-sync.md +131 -0
- package/.opencode/command/opsx-verify.md +161 -0
- package/.opencode/skills/openspec-apply-change/SKILL.md +156 -0
- package/.opencode/skills/openspec-archive-change/SKILL.md +114 -0
- package/.opencode/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.opencode/skills/openspec-continue-change/SKILL.md +118 -0
- package/.opencode/skills/openspec-explore/SKILL.md +290 -0
- package/.opencode/skills/openspec-ff-change/SKILL.md +101 -0
- package/.opencode/skills/openspec-new-change/SKILL.md +74 -0
- package/.opencode/skills/openspec-onboard/SKILL.md +529 -0
- package/.opencode/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.opencode/skills/openspec-verify-change/SKILL.md +168 -0
- package/LICENSE +1 -1
- package/README.md +128 -83
- package/bin/legacyver.js +48 -25
- package/legacyver-docs/SUMMARY.md +3 -0
- package/legacyver-docs/components.md +57 -0
- package/legacyver-docs/index.md +15 -0
- package/nul +2 -0
- package/package.json +23 -25
- package/src/cache/hash.js +9 -10
- package/src/cache/index.js +43 -65
- package/src/cli/commands/analyze.js +212 -190
- package/src/cli/commands/cache.js +15 -35
- package/src/cli/commands/init.js +63 -107
- package/src/cli/commands/providers.js +56 -81
- package/src/cli/commands/version.js +7 -10
- package/src/cli/ui.js +58 -77
- package/src/crawler/filters.js +41 -40
- package/src/crawler/index.js +52 -36
- package/src/crawler/manifest.js +31 -43
- package/src/crawler/walk.js +32 -38
- package/src/llm/chunker.js +34 -56
- package/src/llm/cost-estimator.js +68 -51
- package/src/llm/free-model.js +67 -0
- package/src/llm/index.js +22 -43
- package/src/llm/prompts.js +45 -33
- package/src/llm/providers/gemini.js +94 -0
- package/src/llm/providers/groq.js +55 -40
- package/src/llm/providers/ollama.js +38 -65
- package/src/llm/providers/openrouter.js +67 -0
- package/src/llm/queue.js +59 -88
- package/src/llm/re-prompter.js +41 -0
- package/src/llm/validator.js +72 -0
- package/src/parser/ast/generic.js +45 -222
- package/src/parser/ast/go.js +86 -205
- package/src/parser/ast/java.js +76 -146
- package/src/parser/ast/javascript.js +173 -241
- package/src/parser/ast/laravel/blade.js +56 -0
- package/src/parser/ast/laravel/classifier.js +30 -0
- package/src/parser/ast/laravel/controller.js +35 -0
- package/src/parser/ast/laravel/index.js +54 -0
- package/src/parser/ast/laravel/model.js +41 -0
- package/src/parser/ast/laravel/provider.js +28 -0
- package/src/parser/ast/laravel/routes.js +45 -0
- package/src/parser/ast/php.js +129 -0
- package/src/parser/ast/python.js +76 -199
- package/src/parser/ast/typescript.js +10 -244
- package/src/parser/body-extractor.js +40 -0
- package/src/parser/call-graph.js +50 -67
- package/src/parser/complexity-scorer.js +59 -0
- package/src/parser/index.js +61 -86
- package/src/parser/pattern-detector.js +71 -0
- package/src/parser/pkg-builder.js +36 -83
- package/src/renderer/html.js +63 -135
- package/src/renderer/index.js +23 -35
- package/src/renderer/json.js +17 -35
- package/src/renderer/markdown.js +83 -117
- package/src/utils/config.js +52 -53
- package/src/utils/errors.js +26 -41
- package/src/utils/logger.js +32 -53
- package/src/cli/flags.js +0 -87
- package/src/llm/providers/anthropic.js +0 -57
- package/src/llm/providers/google.js +0 -65
- package/src/llm/providers/openai.js +0 -52
- package/src/parser/ast/tree-sitter-init.js +0 -80
package/src/parser/ast/go.js
CHANGED
|
@@ -1,236 +1,117 @@
|
|
|
1
|
-
|
|
2
|
-
* Go AST parser using web-tree-sitter.
|
|
3
|
-
* Extracts functions, structs, interfaces, import blocks.
|
|
4
|
-
*/
|
|
1
|
+
'use strict';
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
let goParser = null;
|
|
10
|
-
let goInitialized = false;
|
|
11
|
-
|
|
12
|
-
async function ensureParser() {
|
|
13
|
-
if (goInitialized) return goParser;
|
|
14
|
-
goInitialized = true;
|
|
15
|
-
|
|
16
|
-
const Parser = await initTreeSitter();
|
|
17
|
-
if (!Parser) return null;
|
|
18
|
-
|
|
19
|
-
const language = await loadLanguage('go');
|
|
20
|
-
if (!language) return null;
|
|
21
|
-
|
|
22
|
-
goParser = createParser(language);
|
|
23
|
-
return goParser;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Parse a Go file and extract structural facts.
|
|
28
|
-
* @param {string} source - Raw file content
|
|
29
|
-
* @param {string} relativePath - Relative file path
|
|
30
|
-
* @returns {Promise<import('../../parser/index.js').FileFacts>}
|
|
31
|
-
*/
|
|
32
|
-
export async function parseGo(source, relativePath) {
|
|
33
|
-
const parser = await ensureParser();
|
|
34
|
-
|
|
35
|
-
if (!parser) {
|
|
36
|
-
return parseGeneric(source, relativePath, 'go');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const tree = parser.parse(source);
|
|
40
|
-
const rootNode = tree.rootNode;
|
|
3
|
+
const { scoreComplexity } = require('../complexity-scorer');
|
|
4
|
+
const { extractBodySnippet } = require('../body-extractor');
|
|
41
5
|
|
|
6
|
+
function parse(sourceText, relativePath) {
|
|
7
|
+
const lines = sourceText.split('\n');
|
|
42
8
|
const functions = [];
|
|
43
9
|
const classes = []; // structs/interfaces in Go
|
|
44
10
|
const imports = [];
|
|
45
11
|
const exports = [];
|
|
46
|
-
const lines = source.split('\n');
|
|
47
|
-
const linesOfCode = lines.filter((l) => l.trim().length > 0).length;
|
|
48
12
|
|
|
49
|
-
|
|
13
|
+
for (let i = 0; i < lines.length; i++) {
|
|
14
|
+
const line = lines[i];
|
|
15
|
+
const trimmed = line.trim();
|
|
16
|
+
|
|
17
|
+
// Imports
|
|
18
|
+
let m = trimmed.match(/^import\s+"([^"]+)"/);
|
|
19
|
+
if (m) { imports.push({ module: m[1], specifiers: [] }); continue; }
|
|
20
|
+
|
|
21
|
+
// Import blocks
|
|
22
|
+
if (trimmed === 'import (') {
|
|
23
|
+
i++;
|
|
24
|
+
while (i < lines.length && lines[i].trim() !== ')') {
|
|
25
|
+
const imp = lines[i].trim().replace(/['"]/g, '').split(/\s+/).pop();
|
|
26
|
+
if (imp) imports.push({ module: imp, specifiers: [] });
|
|
27
|
+
i++;
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
50
31
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
32
|
+
// Struct / Interface
|
|
33
|
+
m = trimmed.match(/^type\s+(\w+)\s+(struct|interface)\s*\{/);
|
|
34
|
+
if (m) {
|
|
35
|
+
classes.push({ name: m[1], methods: [], extends: null });
|
|
36
|
+
continue;
|
|
56
37
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
38
|
+
|
|
39
|
+
// Functions and methods
|
|
40
|
+
m = trimmed.match(/^func\s+(?:\((\w+)\s+\*?(\w+)\)\s+)?(\w+)\s*\(([^)]*)\)(?:\s+(?:\(([^)]*)\)|(\w+)))?\s*\{/);
|
|
41
|
+
if (m) {
|
|
42
|
+
const receiver = m[2] || null;
|
|
43
|
+
const name = m[3];
|
|
44
|
+
const paramsStr = m[4] || '';
|
|
45
|
+
const params = paramsStr.split(',').map(p => {
|
|
46
|
+
const parts = p.trim().split(/\s+/);
|
|
47
|
+
return { name: parts[0] || '', type: parts[1] || null };
|
|
48
|
+
}).filter(p => p.name);
|
|
49
|
+
const returnType = m[5] || m[6] || null;
|
|
50
|
+
const isExported = /^[A-Z]/.test(name); // Go export convention
|
|
51
|
+
const lineEnd = findBlockEnd(lines, i);
|
|
52
|
+
const body = lines.slice(i, lineEnd).join('\n');
|
|
53
|
+
const complexity = scoreComplexity(body);
|
|
54
|
+
const snippet = extractBodySnippet(sourceText, i + 1, lineEnd, complexity.complexityScore);
|
|
55
|
+
functions.push({
|
|
56
|
+
name,
|
|
57
|
+
params,
|
|
58
|
+
returnType,
|
|
59
|
+
isExported,
|
|
60
|
+
isAsync: false,
|
|
61
|
+
receiver,
|
|
62
|
+
lineStart: i + 1,
|
|
63
|
+
lineEnd,
|
|
64
|
+
calls: extractCalls(body),
|
|
65
|
+
...complexity,
|
|
66
|
+
bodySnippet: snippet.bodySnippet,
|
|
67
|
+
bodySnippetTruncated: snippet.bodySnippetTruncated,
|
|
68
|
+
});
|
|
69
|
+
if (receiver) {
|
|
70
|
+
const cls = classes.find(c => c.name === receiver);
|
|
71
|
+
if (cls) cls.methods.push(name);
|
|
72
|
+
}
|
|
61
73
|
}
|
|
62
74
|
}
|
|
63
75
|
|
|
76
|
+
const exportedFns = functions.filter(f => f.isExported).map(f => f.name);
|
|
77
|
+
|
|
64
78
|
return {
|
|
65
79
|
relativePath,
|
|
66
80
|
language: 'go',
|
|
67
|
-
linesOfCode,
|
|
68
|
-
parserType: 'tree-sitter',
|
|
81
|
+
linesOfCode: lines.length,
|
|
69
82
|
functions,
|
|
70
83
|
classes,
|
|
71
84
|
imports,
|
|
72
|
-
exports,
|
|
85
|
+
exports: exportedFns,
|
|
73
86
|
callsTo: [],
|
|
74
87
|
calledBy: [],
|
|
75
|
-
hash:
|
|
88
|
+
hash: null,
|
|
89
|
+
parserType: 'ast',
|
|
76
90
|
};
|
|
77
91
|
}
|
|
78
92
|
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
case 'type_declaration':
|
|
88
|
-
extractTypeDecl(node, ctx);
|
|
89
|
-
break;
|
|
90
|
-
case 'import_declaration':
|
|
91
|
-
extractImport(node, ctx);
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
96
|
-
walkNode(node.child(i), ctx);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function extractFunction(node, ctx) {
|
|
101
|
-
const nameNode = node.childForFieldName('name');
|
|
102
|
-
const paramsNode = node.childForFieldName('parameters');
|
|
103
|
-
const resultNode = node.childForFieldName('result');
|
|
104
|
-
|
|
105
|
-
if (nameNode) {
|
|
106
|
-
ctx.functions.push({
|
|
107
|
-
name: nameNode.text,
|
|
108
|
-
params: extractParams(paramsNode),
|
|
109
|
-
returnType: resultNode ? resultNode.text : null,
|
|
110
|
-
isExported: false, // determined later by capitalization
|
|
111
|
-
isAsync: false,
|
|
112
|
-
lineStart: node.startPosition.row + 1,
|
|
113
|
-
lineEnd: node.endPosition.row + 1,
|
|
114
|
-
calls: [],
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function extractMethod(node, ctx) {
|
|
120
|
-
const nameNode = node.childForFieldName('name');
|
|
121
|
-
const paramsNode = node.childForFieldName('parameters');
|
|
122
|
-
const resultNode = node.childForFieldName('result');
|
|
123
|
-
|
|
124
|
-
if (nameNode) {
|
|
125
|
-
// Get receiver type
|
|
126
|
-
let receiver = null;
|
|
127
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
128
|
-
const child = node.child(i);
|
|
129
|
-
if (child.type === 'parameter_list' && child !== paramsNode) {
|
|
130
|
-
receiver = child.text;
|
|
131
|
-
break;
|
|
93
|
+
function findBlockEnd(lines, startIdx) {
|
|
94
|
+
let depth = 0;
|
|
95
|
+
for (let i = startIdx; i < lines.length; i++) {
|
|
96
|
+
for (const ch of lines[i]) {
|
|
97
|
+
if (ch === '{') depth++;
|
|
98
|
+
else if (ch === '}') {
|
|
99
|
+
depth--;
|
|
100
|
+
if (depth === 0) return i + 1;
|
|
132
101
|
}
|
|
133
102
|
}
|
|
134
|
-
|
|
135
|
-
ctx.functions.push({
|
|
136
|
-
name: nameNode.text,
|
|
137
|
-
params: extractParams(paramsNode),
|
|
138
|
-
returnType: resultNode ? resultNode.text : null,
|
|
139
|
-
isExported: false,
|
|
140
|
-
isAsync: false,
|
|
141
|
-
lineStart: node.startPosition.row + 1,
|
|
142
|
-
lineEnd: node.endPosition.row + 1,
|
|
143
|
-
calls: [],
|
|
144
|
-
receiver,
|
|
145
|
-
});
|
|
146
103
|
}
|
|
104
|
+
return lines.length;
|
|
147
105
|
}
|
|
148
106
|
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (nameNode && typeNode) {
|
|
157
|
-
if (typeNode.type === 'struct_type') {
|
|
158
|
-
const methods = [];
|
|
159
|
-
ctx.classes.push({
|
|
160
|
-
name: nameNode.text,
|
|
161
|
-
methods,
|
|
162
|
-
extends: null,
|
|
163
|
-
kind: 'struct',
|
|
164
|
-
});
|
|
165
|
-
} else if (typeNode.type === 'interface_type') {
|
|
166
|
-
const methods = [];
|
|
167
|
-
for (let j = 0; j < typeNode.childCount; j++) {
|
|
168
|
-
const member = typeNode.child(j);
|
|
169
|
-
if (member.type === 'method_spec') {
|
|
170
|
-
const methodName = member.childForFieldName('name');
|
|
171
|
-
if (methodName) methods.push(methodName.text);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
ctx.classes.push({
|
|
175
|
-
name: nameNode.text,
|
|
176
|
-
methods,
|
|
177
|
-
extends: null,
|
|
178
|
-
kind: 'interface',
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
107
|
+
function extractCalls(body) {
|
|
108
|
+
const calls = new Set();
|
|
109
|
+
const keywords = new Set(['if', 'for', 'range', 'switch', 'case', 'return', 'defer', 'go', 'select', 'func', 'var', 'const', 'type', 'struct', 'interface', 'map', 'chan', 'make', 'new', 'len', 'cap', 'append', 'copy', 'delete', 'close', 'panic', 'recover', 'print', 'println', 'error']);
|
|
110
|
+
for (const match of body.matchAll(/\b(\w+)\s*\(/g)) {
|
|
111
|
+
const name = match[1];
|
|
112
|
+
if (!keywords.has(name) && name.length > 1) calls.add(name);
|
|
183
113
|
}
|
|
114
|
+
return [...calls];
|
|
184
115
|
}
|
|
185
116
|
|
|
186
|
-
|
|
187
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
188
|
-
const child = node.child(i);
|
|
189
|
-
if (child.type === 'import_spec_list') {
|
|
190
|
-
for (let j = 0; j < child.childCount; j++) {
|
|
191
|
-
const spec = child.child(j);
|
|
192
|
-
if (spec.type === 'import_spec') {
|
|
193
|
-
const pathNode = spec.childForFieldName('path');
|
|
194
|
-
if (pathNode) {
|
|
195
|
-
ctx.imports.push({
|
|
196
|
-
module: pathNode.text.replace(/"/g, ''),
|
|
197
|
-
specifiers: [],
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
} else if (child.type === 'import_spec') {
|
|
203
|
-
const pathNode = child.childForFieldName('path');
|
|
204
|
-
if (pathNode) {
|
|
205
|
-
ctx.imports.push({
|
|
206
|
-
module: pathNode.text.replace(/"/g, ''),
|
|
207
|
-
specifiers: [],
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
} else if (child.type === 'interpreted_string_literal') {
|
|
211
|
-
ctx.imports.push({
|
|
212
|
-
module: child.text.replace(/"/g, ''),
|
|
213
|
-
specifiers: [],
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function extractParams(paramsNode) {
|
|
220
|
-
if (!paramsNode) return [];
|
|
221
|
-
const params = [];
|
|
222
|
-
for (let i = 0; i < paramsNode.childCount; i++) {
|
|
223
|
-
const child = paramsNode.child(i);
|
|
224
|
-
if (child.type === 'parameter_declaration') {
|
|
225
|
-
const nameNode = child.childForFieldName('name');
|
|
226
|
-
const typeNode = child.childForFieldName('type');
|
|
227
|
-
if (nameNode) {
|
|
228
|
-
params.push({
|
|
229
|
-
name: nameNode.text,
|
|
230
|
-
type: typeNode ? typeNode.text : null,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return params;
|
|
236
|
-
}
|
|
117
|
+
module.exports = { parse };
|
package/src/parser/ast/java.js
CHANGED
|
@@ -1,173 +1,103 @@
|
|
|
1
|
-
|
|
2
|
-
* Java AST parser using web-tree-sitter.
|
|
3
|
-
* Extracts methods, class hierarchy, import statements.
|
|
4
|
-
*/
|
|
1
|
+
'use strict';
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
let javaParser = null;
|
|
10
|
-
let javaInitialized = false;
|
|
11
|
-
|
|
12
|
-
async function ensureParser() {
|
|
13
|
-
if (javaInitialized) return javaParser;
|
|
14
|
-
javaInitialized = true;
|
|
15
|
-
|
|
16
|
-
const Parser = await initTreeSitter();
|
|
17
|
-
if (!Parser) return null;
|
|
18
|
-
|
|
19
|
-
const language = await loadLanguage('java');
|
|
20
|
-
if (!language) return null;
|
|
21
|
-
|
|
22
|
-
javaParser = createParser(language);
|
|
23
|
-
return javaParser;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Parse a Java file and extract structural facts.
|
|
28
|
-
* @param {string} source - Raw file content
|
|
29
|
-
* @param {string} relativePath - Relative file path
|
|
30
|
-
* @returns {Promise<import('../../parser/index.js').FileFacts>}
|
|
31
|
-
*/
|
|
32
|
-
export async function parseJava(source, relativePath) {
|
|
33
|
-
const parser = await ensureParser();
|
|
34
|
-
|
|
35
|
-
if (!parser) {
|
|
36
|
-
return parseGeneric(source, relativePath, 'java');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const tree = parser.parse(source);
|
|
40
|
-
const rootNode = tree.rootNode;
|
|
3
|
+
const { scoreComplexity } = require('../complexity-scorer');
|
|
4
|
+
const { extractBodySnippet } = require('../body-extractor');
|
|
41
5
|
|
|
6
|
+
function parse(sourceText, relativePath) {
|
|
7
|
+
const lines = sourceText.split('\n');
|
|
42
8
|
const functions = [];
|
|
43
9
|
const classes = [];
|
|
44
10
|
const imports = [];
|
|
45
11
|
const exports = [];
|
|
46
|
-
const lines = source.split('\n');
|
|
47
|
-
const linesOfCode = lines.filter((l) => l.trim().length > 0).length;
|
|
48
12
|
|
|
49
|
-
|
|
13
|
+
for (let i = 0; i < lines.length; i++) {
|
|
14
|
+
const line = lines[i];
|
|
15
|
+
const trimmed = line.trim();
|
|
16
|
+
|
|
17
|
+
// Imports
|
|
18
|
+
let m = trimmed.match(/^import\s+([\w.]+(?:\s*,\s*[\w.]+)*)/);
|
|
19
|
+
if (m) { m[1].split(',').forEach(imp => imports.push({ module: imp.trim(), specifiers: [] })); continue; }
|
|
20
|
+
m = trimmed.match(/^import\s+[\w.]+\.\{([^}]+)\}/);
|
|
21
|
+
if (m) { imports.push({ module: trimmed.replace(/^import\s+/, '').split('.')[0], specifiers: m[1].split(',').map(s => s.trim()) }); continue; }
|
|
22
|
+
|
|
23
|
+
// Class
|
|
24
|
+
m = trimmed.match(/^(?:public\s+|private\s+|abstract\s+)*class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([\w,\s]+))?/);
|
|
25
|
+
if (m) {
|
|
26
|
+
classes.push({ name: m[1], methods: [], extends: m[2] || null });
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Methods
|
|
31
|
+
m = trimmed.match(/^(?:(public|private|protected|static|final|abstract|synchronized)\s+)*(?:(\w+(?:<[^>]+>)?)\s+)?(\w+)\s*\(([^)]*)\)\s*(?:throws\s+[\w,\s]+)?\s*\{/);
|
|
32
|
+
if (m && m[3] && m[3] !== 'if' && m[3] !== 'for' && m[3] !== 'while' && m[3] !== 'switch' && m[3] !== 'catch') {
|
|
33
|
+
const name = m[3];
|
|
34
|
+
const visibility = m[1] || 'package';
|
|
35
|
+
const returnType = m[2] || 'void';
|
|
36
|
+
const paramsStr = m[4] || '';
|
|
37
|
+
const params = paramsStr.split(',').map(p => {
|
|
38
|
+
const parts = p.trim().split(/\s+/);
|
|
39
|
+
return { name: parts[parts.length - 1] || '', type: parts[parts.length - 2] || null };
|
|
40
|
+
}).filter(p => p.name);
|
|
41
|
+
const lineEnd = findBlockEnd(lines, i);
|
|
42
|
+
const body = lines.slice(i, lineEnd).join('\n');
|
|
43
|
+
const complexity = scoreComplexity(body);
|
|
44
|
+
const snippet = extractBodySnippet(sourceText, i + 1, lineEnd, complexity.complexityScore);
|
|
45
|
+
functions.push({
|
|
46
|
+
name,
|
|
47
|
+
params,
|
|
48
|
+
returnType,
|
|
49
|
+
isExported: visibility === 'public',
|
|
50
|
+
isAsync: false,
|
|
51
|
+
lineStart: i + 1,
|
|
52
|
+
lineEnd,
|
|
53
|
+
calls: extractCalls(body),
|
|
54
|
+
...complexity,
|
|
55
|
+
bodySnippet: snippet.bodySnippet,
|
|
56
|
+
bodySnippetTruncated: snippet.bodySnippetTruncated,
|
|
57
|
+
});
|
|
58
|
+
if (classes.length > 0) classes[classes.length - 1].methods.push(name);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const publicFns = functions.filter(f => f.isExported).map(f => f.name);
|
|
50
63
|
|
|
51
64
|
return {
|
|
52
65
|
relativePath,
|
|
53
66
|
language: 'java',
|
|
54
|
-
linesOfCode,
|
|
55
|
-
parserType: 'tree-sitter',
|
|
67
|
+
linesOfCode: lines.length,
|
|
56
68
|
functions,
|
|
57
69
|
classes,
|
|
58
70
|
imports,
|
|
59
|
-
exports:
|
|
71
|
+
exports: publicFns,
|
|
60
72
|
callsTo: [],
|
|
61
73
|
calledBy: [],
|
|
62
|
-
hash:
|
|
74
|
+
hash: null,
|
|
75
|
+
parserType: 'ast',
|
|
63
76
|
};
|
|
64
77
|
}
|
|
65
78
|
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
77
|
-
walkNode(node.child(i), ctx);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function extractClass(node, ctx) {
|
|
82
|
-
const nameNode = node.childForFieldName('name');
|
|
83
|
-
const superclassNode = node.childForFieldName('superclass');
|
|
84
|
-
const bodyNode = node.childForFieldName('body');
|
|
85
|
-
|
|
86
|
-
if (!nameNode) return;
|
|
87
|
-
|
|
88
|
-
const methods = [];
|
|
89
|
-
const className = nameNode.text;
|
|
90
|
-
let superclass = superclassNode ? superclassNode.text : null;
|
|
91
|
-
|
|
92
|
-
if (bodyNode) {
|
|
93
|
-
for (let i = 0; i < bodyNode.childCount; i++) {
|
|
94
|
-
const child = bodyNode.child(i);
|
|
95
|
-
if (child.type === 'method_declaration' || child.type === 'constructor_declaration') {
|
|
96
|
-
const method = extractMethod(child, className);
|
|
97
|
-
if (method) {
|
|
98
|
-
methods.push(method.name);
|
|
99
|
-
ctx.functions.push(method);
|
|
100
|
-
}
|
|
79
|
+
function findBlockEnd(lines, startIdx) {
|
|
80
|
+
let depth = 0;
|
|
81
|
+
for (let i = startIdx; i < lines.length; i++) {
|
|
82
|
+
for (const ch of lines[i]) {
|
|
83
|
+
if (ch === '{') depth++;
|
|
84
|
+
else if (ch === '}') {
|
|
85
|
+
depth--;
|
|
86
|
+
if (depth === 0) return i + 1;
|
|
101
87
|
}
|
|
102
88
|
}
|
|
103
89
|
}
|
|
104
|
-
|
|
105
|
-
ctx.classes.push({
|
|
106
|
-
name: className,
|
|
107
|
-
methods,
|
|
108
|
-
extends: superclass,
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function extractMethod(node, className) {
|
|
113
|
-
const nameNode = node.childForFieldName('name');
|
|
114
|
-
const paramsNode = node.childForFieldName('parameters');
|
|
115
|
-
const typeNode = node.childForFieldName('type');
|
|
116
|
-
|
|
117
|
-
// Detect visibility
|
|
118
|
-
let visibility = 'package';
|
|
119
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
120
|
-
const child = node.child(i);
|
|
121
|
-
if (child.type === 'modifiers') {
|
|
122
|
-
if (child.text.includes('public')) visibility = 'public';
|
|
123
|
-
else if (child.text.includes('private')) visibility = 'private';
|
|
124
|
-
else if (child.text.includes('protected')) visibility = 'protected';
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const name = nameNode ? nameNode.text : (node.type === 'constructor_declaration' ? className : null);
|
|
129
|
-
if (!name) return null;
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
name,
|
|
133
|
-
params: extractParams(paramsNode),
|
|
134
|
-
returnType: typeNode ? typeNode.text : (node.type === 'constructor_declaration' ? className : 'void'),
|
|
135
|
-
isExported: visibility === 'public',
|
|
136
|
-
isAsync: false,
|
|
137
|
-
lineStart: node.startPosition.row + 1,
|
|
138
|
-
lineEnd: node.endPosition.row + 1,
|
|
139
|
-
calls: [],
|
|
140
|
-
visibility,
|
|
141
|
-
};
|
|
90
|
+
return lines.length;
|
|
142
91
|
}
|
|
143
92
|
|
|
144
|
-
function
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
module: match[1],
|
|
151
|
-
specifiers: [parts[parts.length - 1]],
|
|
152
|
-
});
|
|
93
|
+
function extractCalls(body) {
|
|
94
|
+
const calls = new Set();
|
|
95
|
+
const keywords = new Set(['if', 'for', 'while', 'switch', 'catch', 'new', 'return', 'throw', 'instanceof', 'class', 'void', 'int', 'long', 'double', 'float', 'boolean', 'String', 'System', 'else', 'try', 'finally']);
|
|
96
|
+
for (const match of body.matchAll(/\b(\w+)\s*\(/g)) {
|
|
97
|
+
const name = match[1];
|
|
98
|
+
if (!keywords.has(name) && name.length > 1) calls.add(name);
|
|
153
99
|
}
|
|
100
|
+
return [...calls];
|
|
154
101
|
}
|
|
155
102
|
|
|
156
|
-
|
|
157
|
-
if (!paramsNode) return [];
|
|
158
|
-
const params = [];
|
|
159
|
-
for (let i = 0; i < paramsNode.childCount; i++) {
|
|
160
|
-
const child = paramsNode.child(i);
|
|
161
|
-
if (child.type === 'formal_parameter') {
|
|
162
|
-
const nameNode = child.childForFieldName('name');
|
|
163
|
-
const typeNode = child.childForFieldName('type');
|
|
164
|
-
if (nameNode) {
|
|
165
|
-
params.push({
|
|
166
|
-
name: nameNode.text,
|
|
167
|
-
type: typeNode ? typeNode.text : null,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return params;
|
|
173
|
-
}
|
|
103
|
+
module.exports = { parse };
|