ucn 3.8.11 → 3.8.13
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/.github/workflows/ci.yml +33 -0
- package/.github/workflows/publish.yml +79 -0
- package/README.md +5 -2
- package/core/project.js +30 -9
- package/languages/go.js +249 -216
- package/languages/java.js +303 -250
- package/languages/javascript.js +463 -412
- package/languages/python.js +189 -148
- package/languages/rust.js +394 -337
- package/languages/utils.js +89 -10
- package/mcp/server.js +43 -33
- package/package.json +1 -1
- package/.claude/scheduled_tasks.lock +0 -1
package/languages/go.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
traverseTree,
|
|
10
|
+
traverseTreeCached,
|
|
10
11
|
nodeToLocation,
|
|
11
12
|
parseStructuredParams,
|
|
12
13
|
extractGoDocstring
|
|
@@ -54,88 +55,242 @@ function extractReceiver(receiverNode) {
|
|
|
54
55
|
return text.replace(/^\(|\)$/g, '').trim();
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
// --- Single-pass helpers: extracted from find* callbacks ---
|
|
59
|
+
|
|
57
60
|
/**
|
|
58
|
-
*
|
|
61
|
+
* Process a node for function extraction (single-pass helper)
|
|
62
|
+
* Returns true if node was matched, false otherwise
|
|
59
63
|
*/
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
function _processFunction(node, functions, processedRanges, lines) {
|
|
65
|
+
if (node.type === 'function_declaration') {
|
|
66
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
67
|
+
if (processedRanges.has(rangeKey)) return false;
|
|
68
|
+
processedRanges.add(rangeKey);
|
|
64
69
|
|
|
65
|
-
|
|
70
|
+
const nameNode = node.childForFieldName('name');
|
|
71
|
+
const paramsNode = node.childForFieldName('parameters');
|
|
72
|
+
|
|
73
|
+
if (nameNode) {
|
|
74
|
+
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
75
|
+
const returnType = extractReturnType(node);
|
|
76
|
+
const docstring = extractGoDocstring(lines, startLine);
|
|
77
|
+
const typeParams = extractTypeParams(node);
|
|
78
|
+
const isExported = /^[A-Z]/.test(nameNode.text);
|
|
79
|
+
|
|
80
|
+
functions.push({
|
|
81
|
+
name: nameNode.text,
|
|
82
|
+
params: extractGoParams(paramsNode),
|
|
83
|
+
paramsStructured: parseStructuredParams(paramsNode, 'go'),
|
|
84
|
+
startLine,
|
|
85
|
+
endLine,
|
|
86
|
+
indent,
|
|
87
|
+
modifiers: isExported ? ['export'] : [],
|
|
88
|
+
...(returnType && { returnType }),
|
|
89
|
+
...(docstring && { docstring }),
|
|
90
|
+
...(typeParams && { generics: typeParams })
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (node.type === 'method_declaration') {
|
|
66
97
|
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
98
|
+
if (processedRanges.has(rangeKey)) return false;
|
|
99
|
+
processedRanges.add(rangeKey);
|
|
67
100
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
101
|
+
const nameNode = node.childForFieldName('name');
|
|
102
|
+
const paramsNode = node.childForFieldName('parameters');
|
|
103
|
+
const receiverNode = node.childForFieldName('receiver');
|
|
104
|
+
|
|
105
|
+
if (nameNode) {
|
|
106
|
+
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
107
|
+
const receiver = extractReceiver(receiverNode);
|
|
108
|
+
const returnType = extractReturnType(node);
|
|
109
|
+
const docstring = extractGoDocstring(lines, startLine);
|
|
110
|
+
const isExported = /^[A-Z]/.test(nameNode.text);
|
|
111
|
+
|
|
112
|
+
functions.push({
|
|
113
|
+
name: nameNode.text,
|
|
114
|
+
params: extractGoParams(paramsNode),
|
|
115
|
+
paramsStructured: parseStructuredParams(paramsNode, 'go'),
|
|
116
|
+
startLine,
|
|
117
|
+
endLine,
|
|
118
|
+
indent,
|
|
119
|
+
isMethod: true,
|
|
120
|
+
receiver,
|
|
121
|
+
modifiers: isExported ? ['export'] : [],
|
|
122
|
+
...(returnType && { returnType }),
|
|
123
|
+
...(docstring && { docstring })
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
72
128
|
|
|
73
|
-
|
|
74
|
-
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
75
131
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Process a node for type/class extraction (single-pass helper)
|
|
134
|
+
* Returns true if node was matched, false otherwise
|
|
135
|
+
*/
|
|
136
|
+
function _processClass(node, types, processedRanges, lines) {
|
|
137
|
+
if (node.type !== 'type_declaration') return false;
|
|
138
|
+
|
|
139
|
+
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
140
|
+
if (processedRanges.has(rangeKey)) return false;
|
|
141
|
+
processedRanges.add(rangeKey);
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
144
|
+
const spec = node.namedChild(i);
|
|
145
|
+
if (spec.type === 'type_spec') {
|
|
146
|
+
const nameNode = spec.childForFieldName('name');
|
|
147
|
+
const typeNode = spec.childForFieldName('type');
|
|
148
|
+
|
|
149
|
+
if (nameNode && typeNode) {
|
|
150
|
+
const { startLine, endLine } = nodeToLocation(node, lines);
|
|
151
|
+
const name = nameNode.text;
|
|
152
|
+
const docstring = extractGoDocstring(lines, startLine);
|
|
153
|
+
const typeParams = extractTypeParams(spec);
|
|
154
|
+
|
|
155
|
+
let typeKind = 'type';
|
|
156
|
+
if (typeNode.type === 'struct_type') {
|
|
157
|
+
typeKind = 'struct';
|
|
158
|
+
} else if (typeNode.type === 'interface_type') {
|
|
159
|
+
typeKind = 'interface';
|
|
160
|
+
}
|
|
81
161
|
|
|
82
|
-
|
|
83
|
-
const isExported = /^[A-Z]/.test(nameNode.text);
|
|
162
|
+
const isExported = /^[A-Z]/.test(name);
|
|
84
163
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
164
|
+
const members = typeKind === 'struct' ? extractStructFields(typeNode, lines)
|
|
165
|
+
: typeKind === 'interface' ? extractInterfaceMembers(typeNode, lines)
|
|
166
|
+
: [];
|
|
167
|
+
|
|
168
|
+
const embeddedBases = members
|
|
169
|
+
.filter(m => m.embedded)
|
|
170
|
+
.map(m => m.name);
|
|
171
|
+
|
|
172
|
+
types.push({
|
|
173
|
+
name,
|
|
89
174
|
startLine,
|
|
90
175
|
endLine,
|
|
91
|
-
|
|
176
|
+
type: typeKind,
|
|
177
|
+
members,
|
|
92
178
|
modifiers: isExported ? ['export'] : [],
|
|
93
|
-
...(returnType && { returnType }),
|
|
94
179
|
...(docstring && { docstring }),
|
|
95
|
-
...(typeParams && { generics: typeParams })
|
|
180
|
+
...(typeParams && { generics: typeParams }),
|
|
181
|
+
...(embeddedBases.length > 0 && { extends: embeddedBases.join(', ') })
|
|
96
182
|
});
|
|
97
183
|
}
|
|
98
|
-
return true;
|
|
99
184
|
}
|
|
185
|
+
}
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
100
188
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (processedRanges.has(rangeKey)) return true;
|
|
104
|
-
processedRanges.add(rangeKey);
|
|
189
|
+
// Module-level state detection helpers
|
|
190
|
+
const GO_STATE_PATTERN = /^(CONFIG|SETTINGS|[A-Z][A-Z0-9_]+|Default[A-Z][a-zA-Z]*|[A-Z][a-zA-Z]*(?:Config|Settings|Options))$/;
|
|
105
191
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
192
|
+
function _isGoExportedName(name) {
|
|
193
|
+
return /^[A-Z]/.test(name);
|
|
194
|
+
}
|
|
109
195
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
196
|
+
function _isCompositeLiteral(valueNode) {
|
|
197
|
+
if (!valueNode) return false;
|
|
198
|
+
if (valueNode.type === 'composite_literal') return true;
|
|
199
|
+
for (let i = 0; i < valueNode.namedChildCount; i++) {
|
|
200
|
+
if (valueNode.namedChild(i).type === 'composite_literal') return true;
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
115
204
|
|
|
116
|
-
|
|
117
|
-
|
|
205
|
+
function _blockHasIota(constDecl) {
|
|
206
|
+
for (let i = 0; i < constDecl.namedChildCount; i++) {
|
|
207
|
+
const spec = constDecl.namedChild(i);
|
|
208
|
+
if (spec.type === 'const_spec') {
|
|
209
|
+
const valueNode = spec.childForFieldName('value');
|
|
210
|
+
if (valueNode) {
|
|
211
|
+
const checkIota = (n) => {
|
|
212
|
+
if (n.type === 'iota') return true;
|
|
213
|
+
for (let j = 0; j < n.childCount; j++) {
|
|
214
|
+
if (checkIota(n.child(j))) return true;
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
};
|
|
218
|
+
if (checkIota(valueNode)) return true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
118
224
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Process a node for state object extraction (single-pass helper)
|
|
227
|
+
* Returns true if node was matched, false otherwise
|
|
228
|
+
*/
|
|
229
|
+
function _processState(node, objects, lines) {
|
|
230
|
+
if (node.type === 'const_declaration') {
|
|
231
|
+
const isIotaBlock = _blockHasIota(node);
|
|
232
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
233
|
+
const spec = node.namedChild(i);
|
|
234
|
+
if (spec.type === 'const_spec') {
|
|
235
|
+
const nameNode = spec.childForFieldName('name');
|
|
236
|
+
const valueNode = spec.childForFieldName('value');
|
|
237
|
+
if (!nameNode) continue;
|
|
238
|
+
const name = nameNode.text;
|
|
239
|
+
|
|
240
|
+
if (valueNode && _isCompositeLiteral(valueNode) && GO_STATE_PATTERN.test(name)) {
|
|
241
|
+
const { startLine, endLine } = nodeToLocation(spec, lines);
|
|
242
|
+
objects.push({ name, startLine, endLine });
|
|
243
|
+
} else if (isIotaBlock && /^[A-Z]/.test(name)) {
|
|
244
|
+
const { startLine, endLine } = nodeToLocation(spec, lines);
|
|
245
|
+
objects.push({ name, startLine, endLine, isConst: true });
|
|
246
|
+
} else if (_isGoExportedName(name)) {
|
|
247
|
+
const { startLine, endLine } = nodeToLocation(spec, lines);
|
|
248
|
+
objects.push({ name, startLine, endLine, isConst: true });
|
|
249
|
+
}
|
|
132
250
|
}
|
|
133
|
-
return true;
|
|
134
251
|
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (node.type === 'var_declaration') {
|
|
256
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
257
|
+
const spec = node.namedChild(i);
|
|
258
|
+
if (spec.type === 'var_spec') {
|
|
259
|
+
const nameNode = spec.childForFieldName('name');
|
|
260
|
+
const valueNode = spec.childForFieldName('value');
|
|
135
261
|
|
|
262
|
+
if (nameNode) {
|
|
263
|
+
const name = nameNode.text;
|
|
264
|
+
if (valueNode && _isCompositeLiteral(valueNode) && GO_STATE_PATTERN.test(name)) {
|
|
265
|
+
const { startLine, endLine } = nodeToLocation(spec, lines);
|
|
266
|
+
objects.push({ name, startLine, endLine });
|
|
267
|
+
} else if (_isGoExportedName(name)) {
|
|
268
|
+
const { startLine, endLine } = nodeToLocation(spec, lines);
|
|
269
|
+
objects.push({ name, startLine, endLine });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
136
274
|
return true;
|
|
137
|
-
}
|
|
275
|
+
}
|
|
138
276
|
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// --- End single-pass helpers ---
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Find all functions in Go code using tree-sitter
|
|
284
|
+
*/
|
|
285
|
+
function findFunctions(code, parser) {
|
|
286
|
+
const tree = parseTree(parser, code);
|
|
287
|
+
const lines = code.split('\n');
|
|
288
|
+
const functions = [];
|
|
289
|
+
const processedRanges = new Set();
|
|
290
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
291
|
+
_processFunction(node, functions, processedRanges, lines);
|
|
292
|
+
return true;
|
|
293
|
+
});
|
|
139
294
|
functions.sort((a, b) => a.startLine - b.startLine);
|
|
140
295
|
return functions;
|
|
141
296
|
}
|
|
@@ -156,67 +311,13 @@ function extractTypeParams(node) {
|
|
|
156
311
|
*/
|
|
157
312
|
function findClasses(code, parser) {
|
|
158
313
|
const tree = parseTree(parser, code);
|
|
314
|
+
const lines = code.split('\n');
|
|
159
315
|
const types = [];
|
|
160
316
|
const processedRanges = new Set();
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const rangeKey = `${node.startIndex}-${node.endIndex}`;
|
|
164
|
-
|
|
165
|
-
if (node.type === 'type_declaration') {
|
|
166
|
-
if (processedRanges.has(rangeKey)) return true;
|
|
167
|
-
processedRanges.add(rangeKey);
|
|
168
|
-
|
|
169
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
170
|
-
const spec = node.namedChild(i);
|
|
171
|
-
if (spec.type === 'type_spec') {
|
|
172
|
-
const nameNode = spec.childForFieldName('name');
|
|
173
|
-
const typeNode = spec.childForFieldName('type');
|
|
174
|
-
|
|
175
|
-
if (nameNode && typeNode) {
|
|
176
|
-
const { startLine, endLine } = nodeToLocation(node, code);
|
|
177
|
-
const name = nameNode.text;
|
|
178
|
-
const docstring = extractGoDocstring(code, startLine);
|
|
179
|
-
const typeParams = extractTypeParams(spec);
|
|
180
|
-
|
|
181
|
-
let typeKind = 'type';
|
|
182
|
-
if (typeNode.type === 'struct_type') {
|
|
183
|
-
typeKind = 'struct';
|
|
184
|
-
} else if (typeNode.type === 'interface_type') {
|
|
185
|
-
typeKind = 'interface';
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Check if exported
|
|
189
|
-
const isExported = /^[A-Z]/.test(name);
|
|
190
|
-
|
|
191
|
-
const members = typeKind === 'struct' ? extractStructFields(typeNode, code)
|
|
192
|
-
: typeKind === 'interface' ? extractInterfaceMembers(typeNode, code)
|
|
193
|
-
: [];
|
|
194
|
-
|
|
195
|
-
// Extract embedded field names as extends (Go composition)
|
|
196
|
-
const embeddedBases = members
|
|
197
|
-
.filter(m => m.embedded)
|
|
198
|
-
.map(m => m.name);
|
|
199
|
-
|
|
200
|
-
types.push({
|
|
201
|
-
name,
|
|
202
|
-
startLine,
|
|
203
|
-
endLine,
|
|
204
|
-
type: typeKind,
|
|
205
|
-
members,
|
|
206
|
-
modifiers: isExported ? ['export'] : [],
|
|
207
|
-
...(docstring && { docstring }),
|
|
208
|
-
...(typeParams && { generics: typeParams }),
|
|
209
|
-
...(embeddedBases.length > 0 && { extends: embeddedBases.join(', ') })
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
|
|
317
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
318
|
+
_processClass(node, types, processedRanges, lines);
|
|
217
319
|
return true;
|
|
218
320
|
});
|
|
219
|
-
|
|
220
321
|
types.sort((a, b) => a.startLine - b.startLine);
|
|
221
322
|
return types;
|
|
222
323
|
}
|
|
@@ -224,7 +325,7 @@ function findClasses(code, parser) {
|
|
|
224
325
|
/**
|
|
225
326
|
* Extract struct fields
|
|
226
327
|
*/
|
|
227
|
-
function extractStructFields(structNode,
|
|
328
|
+
function extractStructFields(structNode, codeOrLines) {
|
|
228
329
|
const fields = [];
|
|
229
330
|
// struct_type contains a field_declaration_list child (not a 'body' field)
|
|
230
331
|
let fieldListNode = structNode.childForFieldName('body');
|
|
@@ -241,7 +342,7 @@ function extractStructFields(structNode, code) {
|
|
|
241
342
|
for (let i = 0; i < fieldListNode.namedChildCount; i++) {
|
|
242
343
|
const field = fieldListNode.namedChild(i);
|
|
243
344
|
if (field.type === 'field_declaration') {
|
|
244
|
-
const { startLine, endLine } = nodeToLocation(field,
|
|
345
|
+
const { startLine, endLine } = nodeToLocation(field, codeOrLines);
|
|
245
346
|
const nameNode = field.childForFieldName('name');
|
|
246
347
|
const typeNode = field.childForFieldName('type');
|
|
247
348
|
|
|
@@ -280,13 +381,13 @@ function extractStructFields(structNode, code) {
|
|
|
280
381
|
/**
|
|
281
382
|
* Extract interface method signatures
|
|
282
383
|
*/
|
|
283
|
-
function extractInterfaceMembers(interfaceNode,
|
|
384
|
+
function extractInterfaceMembers(interfaceNode, codeOrLines) {
|
|
284
385
|
const members = [];
|
|
285
386
|
for (let i = 0; i < interfaceNode.namedChildCount; i++) {
|
|
286
387
|
const child = interfaceNode.namedChild(i);
|
|
287
388
|
// tree-sitter Go uses method_elem (or method_spec in older versions)
|
|
288
389
|
if (child.type === 'method_elem' || child.type === 'method_spec') {
|
|
289
|
-
const { startLine, endLine } = nodeToLocation(child,
|
|
390
|
+
const { startLine, endLine } = nodeToLocation(child, codeOrLines);
|
|
290
391
|
// Name is in a field_identifier child
|
|
291
392
|
let nameText = null;
|
|
292
393
|
let paramsText = null;
|
|
@@ -355,7 +456,7 @@ function extractInterfaceMembers(interfaceNode, code) {
|
|
|
355
456
|
}
|
|
356
457
|
} else if (child.type === 'type_identifier' || child.type === 'qualified_type') {
|
|
357
458
|
// Standalone type identifier inside interface body — embedded interface
|
|
358
|
-
const { startLine, endLine } = nodeToLocation(child,
|
|
459
|
+
const { startLine, endLine } = nodeToLocation(child, codeOrLines);
|
|
359
460
|
let embName = child.text;
|
|
360
461
|
const dotIdx = embName.indexOf('.');
|
|
361
462
|
if (dotIdx >= 0) embName = embName.slice(dotIdx + 1);
|
|
@@ -373,7 +474,7 @@ function extractInterfaceMembers(interfaceNode, code) {
|
|
|
373
474
|
for (let j = 0; j < child.namedChildCount; j++) {
|
|
374
475
|
const sub = child.namedChild(j);
|
|
375
476
|
if (sub.type === 'type_identifier' || sub.type === 'qualified_type') {
|
|
376
|
-
const { startLine, endLine } = nodeToLocation(sub,
|
|
477
|
+
const { startLine, endLine } = nodeToLocation(sub, codeOrLines);
|
|
377
478
|
let embName = sub.text;
|
|
378
479
|
const dotIdx = embName.indexOf('.');
|
|
379
480
|
if (dotIdx >= 0) embName = embName.slice(dotIdx + 1);
|
|
@@ -397,99 +498,12 @@ function extractInterfaceMembers(interfaceNode, code) {
|
|
|
397
498
|
*/
|
|
398
499
|
function findStateObjects(code, parser) {
|
|
399
500
|
const tree = parseTree(parser, code);
|
|
501
|
+
const lines = code.split('\n');
|
|
400
502
|
const objects = [];
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
// All exported (^[A-Z]) package-level const/var are indexed as state objects
|
|
404
|
-
const isExportedName = (name) => /^[A-Z]/.test(name);
|
|
405
|
-
|
|
406
|
-
// Check if a value node is a composite literal
|
|
407
|
-
function isCompositeLiteral(valueNode) {
|
|
408
|
-
if (!valueNode) return false;
|
|
409
|
-
if (valueNode.type === 'composite_literal') return true;
|
|
410
|
-
for (let i = 0; i < valueNode.namedChildCount; i++) {
|
|
411
|
-
if (valueNode.namedChild(i).type === 'composite_literal') return true;
|
|
412
|
-
}
|
|
413
|
-
return false;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Check if a const block uses iota (enum-like pattern)
|
|
417
|
-
function blockHasIota(constDecl) {
|
|
418
|
-
for (let i = 0; i < constDecl.namedChildCount; i++) {
|
|
419
|
-
const spec = constDecl.namedChild(i);
|
|
420
|
-
if (spec.type === 'const_spec') {
|
|
421
|
-
const valueNode = spec.childForFieldName('value');
|
|
422
|
-
if (valueNode) {
|
|
423
|
-
// Check if any child is 'iota'
|
|
424
|
-
const checkIota = (n) => {
|
|
425
|
-
if (n.type === 'iota') return true;
|
|
426
|
-
for (let j = 0; j < n.childCount; j++) {
|
|
427
|
-
if (checkIota(n.child(j))) return true;
|
|
428
|
-
}
|
|
429
|
-
return false;
|
|
430
|
-
};
|
|
431
|
-
if (checkIota(valueNode)) return true;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
return false;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
traverseTree(tree.rootNode, (node) => {
|
|
439
|
-
// Handle const declarations
|
|
440
|
-
if (node.type === 'const_declaration') {
|
|
441
|
-
const isIotaBlock = blockHasIota(node);
|
|
442
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
443
|
-
const spec = node.namedChild(i);
|
|
444
|
-
if (spec.type === 'const_spec') {
|
|
445
|
-
const nameNode = spec.childForFieldName('name');
|
|
446
|
-
const valueNode = spec.childForFieldName('value');
|
|
447
|
-
if (!nameNode) continue;
|
|
448
|
-
const name = nameNode.text;
|
|
449
|
-
|
|
450
|
-
// Include if: composite literal matching state pattern, OR exported const in iota block,
|
|
451
|
-
// OR any exported (^[A-Z]) package-level const
|
|
452
|
-
if (valueNode && isCompositeLiteral(valueNode) && statePattern.test(name)) {
|
|
453
|
-
const { startLine, endLine } = nodeToLocation(spec, code);
|
|
454
|
-
objects.push({ name, startLine, endLine });
|
|
455
|
-
} else if (isIotaBlock && /^[A-Z]/.test(name)) {
|
|
456
|
-
const { startLine, endLine } = nodeToLocation(spec, code);
|
|
457
|
-
objects.push({ name, startLine, endLine, isConst: true });
|
|
458
|
-
} else if (isExportedName(name)) {
|
|
459
|
-
const { startLine, endLine } = nodeToLocation(spec, code);
|
|
460
|
-
objects.push({ name, startLine, endLine, isConst: true });
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
return true;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Handle var declarations
|
|
468
|
-
if (node.type === 'var_declaration') {
|
|
469
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
470
|
-
const spec = node.namedChild(i);
|
|
471
|
-
if (spec.type === 'var_spec') {
|
|
472
|
-
const nameNode = spec.childForFieldName('name');
|
|
473
|
-
const valueNode = spec.childForFieldName('value');
|
|
474
|
-
|
|
475
|
-
if (nameNode) {
|
|
476
|
-
const name = nameNode.text;
|
|
477
|
-
if (valueNode && isCompositeLiteral(valueNode) && statePattern.test(name)) {
|
|
478
|
-
const { startLine, endLine } = nodeToLocation(spec, code);
|
|
479
|
-
objects.push({ name, startLine, endLine });
|
|
480
|
-
} else if (isExportedName(name)) {
|
|
481
|
-
const { startLine, endLine } = nodeToLocation(spec, code);
|
|
482
|
-
objects.push({ name, startLine, endLine });
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
return true;
|
|
488
|
-
}
|
|
489
|
-
|
|
503
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
504
|
+
_processState(node, objects, lines);
|
|
490
505
|
return true;
|
|
491
506
|
});
|
|
492
|
-
|
|
493
507
|
objects.sort((a, b) => a.startLine - b.startLine);
|
|
494
508
|
return objects;
|
|
495
509
|
}
|
|
@@ -498,12 +512,31 @@ function findStateObjects(code, parser) {
|
|
|
498
512
|
* Parse a Go file completely
|
|
499
513
|
*/
|
|
500
514
|
function parse(code, parser) {
|
|
515
|
+
const tree = parseTree(parser, code);
|
|
516
|
+
const lines = code.split('\n');
|
|
517
|
+
const functions = [];
|
|
518
|
+
const classes = [];
|
|
519
|
+
const stateObjects = [];
|
|
520
|
+
const processedFn = new Set();
|
|
521
|
+
const processedCls = new Set();
|
|
522
|
+
|
|
523
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
524
|
+
_processFunction(node, functions, processedFn, lines);
|
|
525
|
+
_processClass(node, classes, processedCls, lines);
|
|
526
|
+
_processState(node, stateObjects, lines);
|
|
527
|
+
return true;
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
functions.sort((a, b) => a.startLine - b.startLine);
|
|
531
|
+
classes.sort((a, b) => a.startLine - b.startLine);
|
|
532
|
+
stateObjects.sort((a, b) => a.startLine - b.startLine);
|
|
533
|
+
|
|
501
534
|
return {
|
|
502
535
|
language: 'go',
|
|
503
|
-
totalLines:
|
|
504
|
-
functions
|
|
505
|
-
classes
|
|
506
|
-
stateObjects
|
|
536
|
+
totalLines: lines.length,
|
|
537
|
+
functions,
|
|
538
|
+
classes,
|
|
539
|
+
stateObjects,
|
|
507
540
|
imports: [],
|
|
508
541
|
exports: []
|
|
509
542
|
};
|
|
@@ -1103,7 +1136,7 @@ function findImportsInCode(code, parser) {
|
|
|
1103
1136
|
}
|
|
1104
1137
|
}
|
|
1105
1138
|
|
|
1106
|
-
|
|
1139
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
1107
1140
|
if (node.type === 'import_declaration') {
|
|
1108
1141
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
1109
1142
|
const child = node.namedChild(i);
|
|
@@ -1138,7 +1171,7 @@ function findExportsInCode(code, parser) {
|
|
|
1138
1171
|
const tree = parseTree(parser, code);
|
|
1139
1172
|
const exports = [];
|
|
1140
1173
|
|
|
1141
|
-
|
|
1174
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
1142
1175
|
// Exported functions
|
|
1143
1176
|
if (node.type === 'function_declaration') {
|
|
1144
1177
|
const nameNode = node.childForFieldName('name');
|
|
@@ -1218,7 +1251,7 @@ function findUsagesInCode(code, name, parser) {
|
|
|
1218
1251
|
const tree = parseTree(parser, code);
|
|
1219
1252
|
const usages = [];
|
|
1220
1253
|
|
|
1221
|
-
|
|
1254
|
+
traverseTreeCached(tree.rootNode, (node) => {
|
|
1222
1255
|
// Look for identifier, field_identifier (method names in selector expressions),
|
|
1223
1256
|
// and type_identifier (type references in params, return types, composite literals, etc.)
|
|
1224
1257
|
const isIdentifier = node.type === 'identifier' || node.type === 'field_identifier' || node.type === 'type_identifier';
|