@getmikk/core 2.0.13 → 2.0.15
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 +4 -4
- package/package.json +2 -1
- package/src/analysis/index.ts +9 -0
- package/src/analysis/taint-analysis.ts +419 -0
- package/src/analysis/type-flow.ts +247 -0
- package/src/cache/incremental-cache.ts +278 -0
- package/src/cache/index.ts +1 -0
- package/src/contract/contract-generator.ts +31 -3
- package/src/contract/contract-reader.ts +1 -0
- package/src/contract/lock-compiler.ts +125 -12
- package/src/contract/schema.ts +4 -0
- package/src/error-handler.ts +2 -1
- package/src/graph/cluster-detector.ts +2 -4
- package/src/graph/dead-code-detector.ts +303 -117
- package/src/graph/graph-builder.ts +21 -161
- package/src/graph/impact-analyzer.ts +1 -0
- package/src/graph/index.ts +2 -0
- package/src/graph/rich-function-index.ts +1080 -0
- package/src/graph/symbol-table.ts +252 -0
- package/src/hash/hash-store.ts +1 -0
- package/src/index.ts +4 -0
- package/src/parser/base-extractor.ts +19 -0
- package/src/parser/boundary-checker.ts +31 -12
- package/src/parser/error-recovery.ts +647 -0
- package/src/parser/function-body-extractor.ts +248 -0
- package/src/parser/go/go-extractor.ts +249 -676
- package/src/parser/index.ts +138 -295
- package/src/parser/language-registry.ts +57 -0
- package/src/parser/oxc-parser.ts +166 -28
- package/src/parser/oxc-resolver.ts +179 -11
- package/src/parser/parser-constants.ts +1 -0
- package/src/parser/rust/rust-extractor.ts +109 -0
- package/src/parser/tree-sitter/parser.ts +400 -66
- package/src/parser/tree-sitter/queries.ts +106 -10
- package/src/parser/types.ts +20 -1
- package/src/search/bm25.ts +21 -8
- package/src/search/direct-search.ts +472 -0
- package/src/search/embedding-provider.ts +249 -0
- package/src/search/index.ts +12 -0
- package/src/search/semantic-search.ts +435 -0
- package/src/security/index.ts +1 -0
- package/src/security/scanner.ts +342 -0
- package/src/utils/artifact-transaction.ts +1 -0
- package/src/utils/atomic-write.ts +1 -0
- package/src/utils/errors.ts +89 -4
- package/src/utils/fs.ts +150 -65
- package/src/utils/json.ts +1 -0
- package/src/utils/language-registry.ts +96 -5
- package/src/utils/minimatch.ts +49 -6
- package/src/utils/path.ts +26 -0
- package/tests/dead-code.test.ts +3 -2
- package/tests/direct-search.test.ts +435 -0
- package/tests/error-recovery.test.ts +143 -0
- package/tests/fixtures/simple-api/src/index.ts +1 -1
- package/tests/go-parser.test.ts +19 -335
- package/tests/js-parser.test.ts +18 -1089
- package/tests/language-registry-all.test.ts +276 -0
- package/tests/language-registry.test.ts +6 -4
- package/tests/parse-diagnostics.test.ts +9 -96
- package/tests/parser.test.ts +42 -771
- package/tests/polyglot-parser.test.ts +117 -0
- package/tests/rich-function-index.test.ts +703 -0
- package/tests/tree-sitter-parser.test.ts +108 -80
- package/tests/ts-parser.test.ts +8 -8
- package/tests/verification.test.ts +175 -0
- package/src/parser/base-parser.ts +0 -16
- package/src/parser/go/go-parser.ts +0 -43
- package/src/parser/javascript/js-extractor.ts +0 -278
- package/src/parser/javascript/js-parser.ts +0 -101
- package/src/parser/typescript/ts-extractor.ts +0 -447
- package/src/parser/typescript/ts-parser.ts +0 -36
|
@@ -1,731 +1,304 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { LanguageRegistry } from '../language-registry.js';
|
|
3
|
+
import { BaseExtractor } from '../base-extractor.js';
|
|
4
|
+
import { hashContent } from '../../hash/file-hasher.js';
|
|
2
5
|
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'iota', 'string', 'int', 'int8', 'int16', 'int32', 'int64', 'uint',
|
|
15
|
-
'uint8', 'uint16', 'uint32', 'uint64', 'uintptr', 'float32', 'float64',
|
|
16
|
-
'complex64', 'complex128', 'bool', 'byte', 'rune', 'error', 'any',
|
|
17
|
-
])
|
|
18
|
-
|
|
19
|
-
// --- Route detection patterns (Gin, Echo, Chi, Mux, net/http, Fiber) --------
|
|
20
|
-
type RoutePattern = { re: RegExp; methodGroup: number; pathGroup: number; handlerGroup: number; fixedMethod?: string }
|
|
21
|
-
|
|
22
|
-
const ROUTE_PATTERNS: RoutePattern[] = [
|
|
23
|
-
// Gin / Echo / Chi: r.GET("/path", handler)
|
|
24
|
-
{
|
|
25
|
-
re: /\b(?:router|r|e|app|v\d*|api|g|group|server)\.(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|Any|Use)\s*\(\s*"([^"]+)"\s*,\s*([\w.]+)/,
|
|
26
|
-
methodGroup: 1, pathGroup: 2, handlerGroup: 3,
|
|
27
|
-
},
|
|
28
|
-
// Gorilla Mux: r.HandleFunc("/path", handler).Methods("GET")
|
|
29
|
-
{
|
|
30
|
-
re: /\b(?:r|router|mux)\.HandleFunc\s*\(\s*"([^"]+)"\s*,\s*([\w.]+).*?\.Methods\s*\(\s*"(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD)"/,
|
|
31
|
-
methodGroup: 3, pathGroup: 1, handlerGroup: 2,
|
|
32
|
-
},
|
|
33
|
-
// Gorilla Mux (no Methods): r.HandleFunc("/path", handler)
|
|
34
|
-
{
|
|
35
|
-
re: /\b(?:r|router|mux)\.HandleFunc\s*\(\s*"([^"]+)"\s*,\s*([\w.]+)/,
|
|
36
|
-
methodGroup: -1, pathGroup: 1, handlerGroup: 2, fixedMethod: 'ANY',
|
|
37
|
-
},
|
|
38
|
-
// net/http: http.HandleFunc("/path", handler)
|
|
39
|
-
{
|
|
40
|
-
re: /http\.HandleFunc\s*\(\s*"([^"]+)"\s*,\s*([\w.]+)/,
|
|
41
|
-
methodGroup: -1, pathGroup: 1, handlerGroup: 2, fixedMethod: 'ANY',
|
|
42
|
-
},
|
|
43
|
-
// Fiber / lowercase Chi: app.Get("/path", handler)
|
|
44
|
-
{
|
|
45
|
-
re: /\b(?:app|server)\.(Get|Post|Put|Delete|Patch|Options|Head|Use)\s*\(\s*"([^"]+)"\s*,\s*([\w.]+)/,
|
|
46
|
-
methodGroup: 1, pathGroup: 2, handlerGroup: 3,
|
|
47
|
-
},
|
|
48
|
-
]
|
|
6
|
+
ParsedFile,
|
|
7
|
+
ParsedFunction,
|
|
8
|
+
ParsedClass,
|
|
9
|
+
ParsedImport,
|
|
10
|
+
ParsedExport,
|
|
11
|
+
ParsedParam,
|
|
12
|
+
ParsedGeneric,
|
|
13
|
+
ParsedRoute,
|
|
14
|
+
CallExpression,
|
|
15
|
+
ParsedVariable
|
|
16
|
+
} from '../types.js';
|
|
49
17
|
|
|
50
18
|
/**
|
|
51
|
-
* GoExtractor
|
|
52
|
-
*
|
|
53
|
-
* without any external Go AST dependency.
|
|
19
|
+
* GoExtractor: robust regex-based extractor for Go source.
|
|
20
|
+
* Handles structs, interfaces, functions, and common routing patterns.
|
|
54
21
|
*/
|
|
55
|
-
export class GoExtractor {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
22
|
+
export class GoExtractor extends BaseExtractor {
|
|
23
|
+
async extract(filePath: string, content: string): Promise<ParsedFile> {
|
|
24
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
25
|
+
const worker = new GoExtractorWorker(normalizedPath, content);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
path: normalizedPath,
|
|
29
|
+
language: 'go',
|
|
30
|
+
functions: worker.extractFunctions(),
|
|
31
|
+
classes: worker.extractClasses(),
|
|
32
|
+
variables: worker.extractVariables(),
|
|
33
|
+
generics: worker.extractGenerics(),
|
|
34
|
+
imports: worker.extractImports(),
|
|
35
|
+
exports: worker.extractExports(),
|
|
36
|
+
routes: worker.extractRoutes(),
|
|
37
|
+
calls: worker.extractModuleCalls(),
|
|
38
|
+
hash: hashContent(content),
|
|
39
|
+
parsedAt: Date.now()
|
|
40
|
+
};
|
|
64
41
|
}
|
|
65
42
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
extractFunctions(): ParsedFunction[] {
|
|
70
|
-
return this.scanFunctions().filter(f => !f.receiverType)
|
|
71
|
-
.map(f => this.buildParsedFunction(f))
|
|
43
|
+
async resolveImports(files: ParsedFile[], _projectRoot: string): Promise<ParsedFile[]> {
|
|
44
|
+
// Go import resolution is usually handled by the Resolver
|
|
45
|
+
return files;
|
|
72
46
|
}
|
|
47
|
+
}
|
|
73
48
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const allMethods = this.scanFunctions().filter(f => !!f.receiverType)
|
|
77
|
-
|
|
78
|
-
// Group methods by receiver type
|
|
79
|
-
const byReceiver = new Map<string, ReturnType<typeof this.scanFunctions>[number][]>()
|
|
80
|
-
for (const m of allMethods) {
|
|
81
|
-
const arr = byReceiver.get(m.receiverType!) ?? []
|
|
82
|
-
arr.push(m)
|
|
83
|
-
byReceiver.set(m.receiverType!, arr)
|
|
84
|
-
}
|
|
49
|
+
class GoExtractorWorker {
|
|
50
|
+
private nameCounter = new Map<string, number>();
|
|
85
51
|
|
|
86
|
-
|
|
52
|
+
constructor(
|
|
53
|
+
private readonly filePath: string,
|
|
54
|
+
private readonly content: string
|
|
55
|
+
) {}
|
|
56
|
+
|
|
57
|
+
private allocateId(prefix: string, name: string): string {
|
|
58
|
+
const count = (this.nameCounter.get(name) ?? 0) + 1;
|
|
59
|
+
this.nameCounter.set(name, count);
|
|
60
|
+
const suffix = count === 1 ? '' : `#${count}`;
|
|
61
|
+
return `${prefix}:${this.filePath}:${name}${suffix}`.toLowerCase();
|
|
62
|
+
}
|
|
87
63
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
64
|
+
extractFunctions(): ParsedFunction[] {
|
|
65
|
+
const functions: ParsedFunction[] = [];
|
|
66
|
+
// Pattern: func Name(args) ret { or func (r Receiver) Name(args) ret {
|
|
67
|
+
const funcRegex = /func\s+(?:\(\s*[^)]+\s*\)\s*)?(\w+)\s*\(([^)]*)\)\s*([^{]*)\{/g;
|
|
68
|
+
let match;
|
|
69
|
+
while ((match = funcRegex.exec(this.content)) !== null) {
|
|
70
|
+
const name = match[1];
|
|
71
|
+
const params = this.parseParams(match[2]);
|
|
72
|
+
const returnType = match[3].trim() || 'void';
|
|
73
|
+
const startLine = this.getLineNumber(match.index);
|
|
74
|
+
const body = this.extractBracedContent(match.index + match[0].length - 1);
|
|
75
|
+
|
|
76
|
+
functions.push({
|
|
77
|
+
id: this.allocateId('fn', name),
|
|
78
|
+
name,
|
|
94
79
|
file: this.filePath,
|
|
95
|
-
startLine
|
|
96
|
-
endLine:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
80
|
+
startLine,
|
|
81
|
+
endLine: this.getLineNumber(match.index + match[0].length + body.length),
|
|
82
|
+
params,
|
|
83
|
+
returnType,
|
|
84
|
+
isExported: /^[A-Z]/.test(name),
|
|
85
|
+
isAsync: false, // Go uses goroutines, not async keywords
|
|
86
|
+
calls: this.extractCalls(body),
|
|
87
|
+
hash: hashContent(body),
|
|
88
|
+
purpose: this.extractDocComment(match.index),
|
|
89
|
+
edgeCasesHandled: [],
|
|
90
|
+
errorHandling: [],
|
|
91
|
+
detailedLines: []
|
|
92
|
+
});
|
|
104
93
|
}
|
|
94
|
+
return functions;
|
|
95
|
+
}
|
|
105
96
|
|
|
106
|
-
|
|
107
|
-
|
|
97
|
+
extractClasses(): ParsedClass[] {
|
|
98
|
+
const classes: ParsedClass[] = [];
|
|
99
|
+
// Pattern: type Name struct {
|
|
100
|
+
const structRegex = /type\s+(\w+)\s+struct\s+\{/g;
|
|
101
|
+
let match;
|
|
102
|
+
while ((match = structRegex.exec(this.content)) !== null) {
|
|
103
|
+
const name = match[1];
|
|
104
|
+
const startLine = this.getLineNumber(match.index);
|
|
105
|
+
const body = this.extractBracedContent(match.index + match[0].length - 1);
|
|
106
|
+
|
|
108
107
|
classes.push({
|
|
109
|
-
id:
|
|
110
|
-
name
|
|
108
|
+
id: this.allocateId('class', name),
|
|
109
|
+
name,
|
|
111
110
|
file: this.filePath,
|
|
112
|
-
startLine
|
|
113
|
-
endLine:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
hash:
|
|
118
|
-
purpose:
|
|
119
|
-
})
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return classes
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
extractImports(): ParsedImport[] {
|
|
126
|
-
return this.parseImports()
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
extractExports(): ParsedExport[] {
|
|
130
|
-
const fns = this.scanFunctions()
|
|
131
|
-
const types = this.scanTypeDeclarations()
|
|
132
|
-
const exports: ParsedExport[] = []
|
|
133
|
-
|
|
134
|
-
for (const fn of fns) {
|
|
135
|
-
if (isExported(fn.name)) {
|
|
136
|
-
exports.push({ name: fn.name, type: 'function', file: this.filePath })
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
for (const t of types) {
|
|
140
|
-
if (isExported(t.name)) {
|
|
141
|
-
exports.push({ name: t.name, type: t.kind === 'interface' ? 'interface' : 'class', file: this.filePath })
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return exports
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
extractRoutes(): ParsedRoute[] {
|
|
149
|
-
const routes: ParsedRoute[] = []
|
|
150
|
-
for (let i = 0; i < this.lines.length; i++) {
|
|
151
|
-
const line = this.lines[i]
|
|
152
|
-
for (const pat of ROUTE_PATTERNS) {
|
|
153
|
-
const m = pat.re.exec(line)
|
|
154
|
-
if (!m) continue
|
|
155
|
-
const method = pat.fixedMethod ?? (pat.methodGroup > 0 ? m[pat.methodGroup].toUpperCase() : 'ANY')
|
|
156
|
-
routes.push({
|
|
157
|
-
method,
|
|
158
|
-
path: m[pat.pathGroup],
|
|
159
|
-
handler: m[pat.handlerGroup],
|
|
160
|
-
middlewares: [],
|
|
161
|
-
file: this.filePath,
|
|
162
|
-
line: i + 1,
|
|
163
|
-
})
|
|
164
|
-
}
|
|
111
|
+
startLine,
|
|
112
|
+
endLine: this.getLineNumber(match.index + match[0].length + body.length),
|
|
113
|
+
methods: [], // Methods are extracted as functions in Go
|
|
114
|
+
properties: this.parseStructFields(body),
|
|
115
|
+
isExported: /^[A-Z]/.test(name),
|
|
116
|
+
hash: hashContent(body),
|
|
117
|
+
purpose: this.extractDocComment(match.index)
|
|
118
|
+
});
|
|
165
119
|
}
|
|
166
|
-
return
|
|
120
|
+
return classes;
|
|
167
121
|
}
|
|
168
122
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
bodyStart: number
|
|
180
|
-
endLine: number
|
|
181
|
-
purpose: string
|
|
182
|
-
}> {
|
|
183
|
-
if (this.cachedFunctions) return this.cachedFunctions
|
|
184
|
-
const results: Array<{
|
|
185
|
-
name: string
|
|
186
|
-
receiverType?: string
|
|
187
|
-
receiverVar?: string
|
|
188
|
-
paramStr: string
|
|
189
|
-
returnStr: string
|
|
190
|
-
startLine: number
|
|
191
|
-
bodyStart: number
|
|
192
|
-
endLine: number
|
|
193
|
-
purpose: string
|
|
194
|
-
}> = []
|
|
195
|
-
|
|
196
|
-
let i = 0
|
|
197
|
-
while (i < this.lines.length) {
|
|
198
|
-
const trimmed = this.lines[i].trimStart()
|
|
199
|
-
|
|
200
|
-
// Only lines starting with `func`
|
|
201
|
-
if (!trimmed.startsWith('func ') && !trimmed.startsWith('func\t')) {
|
|
202
|
-
i++
|
|
203
|
-
continue
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const funcLineStart = i
|
|
207
|
-
|
|
208
|
-
// Collect signature lines until we find the opening `{`
|
|
209
|
-
const sigLines: string[] = []
|
|
210
|
-
let j = i
|
|
211
|
-
let foundOpen = false
|
|
212
|
-
while (j < this.lines.length) {
|
|
213
|
-
sigLines.push(this.lines[j])
|
|
214
|
-
if (this.lines[j].includes('{')) {
|
|
215
|
-
foundOpen = true
|
|
216
|
-
break
|
|
217
|
-
}
|
|
218
|
-
j++
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (!foundOpen) { i++; continue }
|
|
222
|
-
|
|
223
|
-
const sigRaw = sigLines.join(' ').replace(/\s+/g, ' ').trim()
|
|
224
|
-
|
|
225
|
-
// Parse signature
|
|
226
|
-
const parsed = parseGoFuncSignature(sigRaw)
|
|
227
|
-
if (!parsed) { i++; continue }
|
|
228
|
-
|
|
229
|
-
// Find body bounds
|
|
230
|
-
const { bodyEnd } = findBodyBounds(this.lines, j)
|
|
231
|
-
|
|
232
|
-
// Extract purpose from leading comment
|
|
233
|
-
const purpose = extractLeadingComment(this.lines, funcLineStart)
|
|
234
|
-
|
|
235
|
-
results.push({
|
|
236
|
-
...parsed,
|
|
237
|
-
startLine: funcLineStart + 1,
|
|
238
|
-
bodyStart: j + 1,
|
|
239
|
-
endLine: bodyEnd + 1,
|
|
240
|
-
purpose,
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
// Skip to after this function
|
|
244
|
-
i = bodyEnd + 1
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
this.cachedFunctions = results
|
|
248
|
-
return results
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** Build ParsedFunction from scanned raw data */
|
|
252
|
-
private buildParsedFunction(raw: ReturnType<typeof this.scanFunctions>[number]): ParsedFunction {
|
|
253
|
-
const name = raw.receiverType ? `${raw.receiverType}.${raw.name}` : raw.name
|
|
254
|
-
const id = `fn:${this.filePath}:${name}`
|
|
255
|
-
|
|
256
|
-
const bodyLines = this.lines.slice(raw.bodyStart - 1, raw.endLine)
|
|
257
|
-
const hash = hashContent(bodyLines.join('\n'))
|
|
258
|
-
const calls = extractCallsFromBody(bodyLines, raw.bodyStart)
|
|
259
|
-
const edgeCases = extractEdgeCases(bodyLines)
|
|
260
|
-
const errorHandling = extractErrorHandling(bodyLines, raw.bodyStart)
|
|
261
|
-
|
|
262
|
-
return {
|
|
263
|
-
id,
|
|
264
|
-
name,
|
|
265
|
-
file: this.filePath,
|
|
266
|
-
startLine: raw.startLine,
|
|
267
|
-
endLine: raw.endLine,
|
|
268
|
-
params: parseGoParams(raw.paramStr),
|
|
269
|
-
returnType: cleanReturnType(raw.returnStr),
|
|
270
|
-
isExported: isExported(raw.name),
|
|
271
|
-
isAsync: false, // Go has goroutines but no async keyword
|
|
272
|
-
calls,
|
|
273
|
-
hash,
|
|
274
|
-
purpose: raw.purpose,
|
|
275
|
-
edgeCasesHandled: edgeCases,
|
|
276
|
-
errorHandling,
|
|
277
|
-
detailedLines: [],
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/** Scan for type struct / type interface declarations */
|
|
282
|
-
private scanTypeDeclarations(): Array<{
|
|
283
|
-
name: string
|
|
284
|
-
kind: 'struct' | 'interface'
|
|
285
|
-
startLine: number
|
|
286
|
-
endLine: number
|
|
287
|
-
purpose: string
|
|
288
|
-
}> {
|
|
289
|
-
const results: Array<{
|
|
290
|
-
name: string
|
|
291
|
-
kind: 'struct' | 'interface'
|
|
292
|
-
startLine: number
|
|
293
|
-
endLine: number
|
|
294
|
-
purpose: string
|
|
295
|
-
}> = []
|
|
296
|
-
|
|
297
|
-
for (let i = 0; i < this.lines.length; i++) {
|
|
298
|
-
const trimmed = this.lines[i].trim()
|
|
299
|
-
const m = /^type\s+(\w+)\s+(struct|interface)\s*\{?/.exec(trimmed)
|
|
300
|
-
if (!m) continue
|
|
301
|
-
|
|
302
|
-
const name = m[1]
|
|
303
|
-
const kind = m[2] as 'struct' | 'interface'
|
|
304
|
-
const purpose = extractLeadingComment(this.lines, i)
|
|
305
|
-
|
|
306
|
-
// Find end of type block
|
|
307
|
-
const { bodyEnd } = findBodyBounds(this.lines, i)
|
|
308
|
-
results.push({
|
|
123
|
+
extractGenerics(): ParsedGeneric[] {
|
|
124
|
+
const generics: ParsedGeneric[] = [];
|
|
125
|
+
// Pattern: type Name interface {
|
|
126
|
+
const interfaceRegex = /type\s+(\w+)\s+interface\s+\{/g;
|
|
127
|
+
let match;
|
|
128
|
+
while ((match = interfaceRegex.exec(this.content)) !== null) {
|
|
129
|
+
const name = match[1];
|
|
130
|
+
const body = this.extractBracedContent(match.index + match[0].length - 1);
|
|
131
|
+
generics.push({
|
|
132
|
+
id: this.allocateId('type', name),
|
|
309
133
|
name,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
134
|
+
type: 'interface',
|
|
135
|
+
file: this.filePath,
|
|
136
|
+
startLine: this.getLineNumber(match.index),
|
|
137
|
+
endLine: this.getLineNumber(match.index + match[0].length + body.length),
|
|
138
|
+
isExported: /^[A-Z]/.test(name),
|
|
139
|
+
typeParameters: [],
|
|
140
|
+
hash: hashContent(body),
|
|
141
|
+
purpose: this.extractDocComment(match.index)
|
|
142
|
+
});
|
|
316
143
|
}
|
|
317
|
-
|
|
318
|
-
return results
|
|
144
|
+
return generics;
|
|
319
145
|
}
|
|
320
146
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
let
|
|
325
|
-
while (
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
147
|
+
extractImports(): ParsedImport[] {
|
|
148
|
+
const imports: ParsedImport[] = [];
|
|
149
|
+
const importRegex = /import\s+(?:\(\s*([^)]+)\s*\)|"([^"]+)")/g;
|
|
150
|
+
let match;
|
|
151
|
+
while ((match = importRegex.exec(this.content)) !== null) {
|
|
152
|
+
if (match[1]) {
|
|
153
|
+
const lines = match[1].split('\n');
|
|
154
|
+
for (const line of lines) {
|
|
155
|
+
const parts = line.trim().match(/(?:(\w+)\s+)??"([^"]+)"/);
|
|
156
|
+
if (parts) {
|
|
157
|
+
imports.push({
|
|
158
|
+
source: parts[2],
|
|
159
|
+
resolvedPath: '',
|
|
160
|
+
names: parts[1] ? [parts[1]] : [],
|
|
161
|
+
isDefault: !parts[1],
|
|
162
|
+
isDynamic: false
|
|
163
|
+
});
|
|
164
|
+
}
|
|
337
165
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
166
|
+
} else {
|
|
167
|
+
imports.push({
|
|
168
|
+
source: match[2],
|
|
169
|
+
resolvedPath: '',
|
|
170
|
+
names: [],
|
|
171
|
+
isDefault: true,
|
|
172
|
+
isDynamic: false
|
|
173
|
+
});
|
|
346
174
|
}
|
|
347
|
-
|
|
348
|
-
i++
|
|
349
|
-
}
|
|
350
|
-
return imports
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// --- Signature parsing --------------------------------------------------------
|
|
355
|
-
|
|
356
|
-
interface GoFuncSignature {
|
|
357
|
-
name: string
|
|
358
|
-
receiverType?: string
|
|
359
|
-
receiverVar?: string
|
|
360
|
-
paramStr: string
|
|
361
|
-
returnStr: string
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Find balanced (...) starting from `fromIdx` in `s`.
|
|
366
|
-
* Returns the content between parens and the index of the closing paren.
|
|
367
|
-
* Handles nested parens correctly, so `fn func(int) bool` extracts properly.
|
|
368
|
-
*/
|
|
369
|
-
function extractBalancedParens(s: string, fromIdx: number): { content: string; end: number } | null {
|
|
370
|
-
const start = s.indexOf('(', fromIdx)
|
|
371
|
-
if (start === -1) return null
|
|
372
|
-
let depth = 0
|
|
373
|
-
for (let i = start; i < s.length; i++) {
|
|
374
|
-
if (s[i] === '(') depth++
|
|
375
|
-
else if (s[i] === ')') {
|
|
376
|
-
depth--
|
|
377
|
-
if (depth === 0) return { content: s.slice(start + 1, i), end: i }
|
|
378
175
|
}
|
|
176
|
+
return imports;
|
|
379
177
|
}
|
|
380
|
-
return null
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function parseGoFuncSignature(sig: string): GoFuncSignature | null {
|
|
384
|
-
// Strip leading 'func ' prefix
|
|
385
|
-
let rest = sig.replace(/^func\s+/, '')
|
|
386
|
-
let receiverVar: string | undefined
|
|
387
|
-
let receiverType: string | undefined
|
|
388
178
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
const recvMatch = /^(\w+)\s+\*?(\w+)/.exec(recv.content.trim())
|
|
394
|
-
if (recvMatch) {
|
|
395
|
-
receiverVar = recvMatch[1]
|
|
396
|
-
// Strip generic brackets: Stack[T] → Stack
|
|
397
|
-
receiverType = recvMatch[2].replace(/\[.*$/, '')
|
|
398
|
-
}
|
|
399
|
-
rest = rest.slice(recv.end + 1).trimStart()
|
|
179
|
+
extractExports(): ParsedExport[] {
|
|
180
|
+
// In Go, any name starting with UpperCase is exported
|
|
181
|
+
const exports: ParsedExport[] = [];
|
|
182
|
+
return exports;
|
|
400
183
|
}
|
|
401
184
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
return { name, receiverType, receiverVar, paramStr, returnStr }
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// ─── Parameter parsing ────────────────────────────────────────────────────────
|
|
420
|
-
|
|
421
|
-
function parseGoParams(paramStr: string): ParsedParam[] {
|
|
422
|
-
if (!paramStr.trim()) return []
|
|
423
|
-
|
|
424
|
-
const params: Array<{ name: string; type: string }> = []
|
|
425
|
-
const parts = splitTopLevel(paramStr, ',').map(p => p.trim()).filter(Boolean)
|
|
426
|
-
|
|
427
|
-
for (const part of parts) {
|
|
428
|
-
// Variadic: name ...Type
|
|
429
|
-
const variadicRe = /^(\w+)\s+\.\.\.(.+)$/.exec(part)
|
|
430
|
-
if (variadicRe) {
|
|
431
|
-
params.push({ name: variadicRe[1], type: '...' + variadicRe[2].trim() })
|
|
432
|
-
continue
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Named with type: name Type (space-separated, e.g. "ctx context.Context")
|
|
436
|
-
const namedRe = /^(\w+)\s+(\S.*)$/.exec(part)
|
|
437
|
-
if (namedRe) {
|
|
438
|
-
params.push({ name: namedRe[1], type: namedRe[2].trim() })
|
|
439
|
-
continue
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// Single token — could be a grouped name (e.g. "first" in "first, last string")
|
|
443
|
-
// or an unnamed type (e.g. "int" in "(int, string)").
|
|
444
|
-
// Heuristic: Go builtin types, pointer/slice/qualified types → unnamed; else grouped name.
|
|
445
|
-
if (looksLikeGoType(part)) {
|
|
446
|
-
params.push({ name: '_', type: part })
|
|
447
|
-
} else {
|
|
448
|
-
params.push({ name: part, type: '' }) // grouped name — back-filled below
|
|
185
|
+
extractRoutes(): ParsedRoute[] {
|
|
186
|
+
const routes: ParsedRoute[] = [];
|
|
187
|
+
// Simplified route detection for common Go frameworks (Gin, Echo, Chi)
|
|
188
|
+
const routeRegex = /\.(GET|POST|PUT|DELETE|PATCH)\s*\(\s*"([^"]+)"\s*,\s*([\w.]+)/g;
|
|
189
|
+
let match;
|
|
190
|
+
while ((match = routeRegex.exec(this.content)) !== null) {
|
|
191
|
+
routes.push({
|
|
192
|
+
method: match[1] as any,
|
|
193
|
+
path: match[2],
|
|
194
|
+
handler: match[3],
|
|
195
|
+
middlewares: [],
|
|
196
|
+
file: this.filePath,
|
|
197
|
+
line: this.getLineNumber(match.index)
|
|
198
|
+
});
|
|
449
199
|
}
|
|
200
|
+
return routes;
|
|
450
201
|
}
|
|
451
202
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
for (let i = params.length - 2; i >= 0; i--) {
|
|
455
|
-
if (params[i].type === '' && params[i + 1].type !== '') {
|
|
456
|
-
params[i].type = params[i + 1].type
|
|
457
|
-
}
|
|
203
|
+
extractVariables(): ParsedVariable[] {
|
|
204
|
+
return [];
|
|
458
205
|
}
|
|
459
206
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
type: p.type || 'any',
|
|
463
|
-
optional: false,
|
|
464
|
-
}))
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Returns true when a bare single-token parameter is a type annotation rather
|
|
469
|
-
* than a parameter name in a grouped declaration like `(first, last string)`.
|
|
470
|
-
*/
|
|
471
|
-
function looksLikeGoType(token: string): boolean {
|
|
472
|
-
if (!token) return false
|
|
473
|
-
const ch = token[0]
|
|
474
|
-
// Pointer (*int), slice ([]byte), or channel (chan)
|
|
475
|
-
if (ch === '*' || ch === '[' || ch === '<') return true
|
|
476
|
-
// Go builtin types and keywords
|
|
477
|
-
if (GO_BUILTINS.has(token)) return true
|
|
478
|
-
// Exported named type (e.g. Context, ResponseWriter)
|
|
479
|
-
if (ch >= 'A' && ch <= 'Z') return true
|
|
480
|
-
// Qualified type (e.g. context.Context, http.Request)
|
|
481
|
-
if (token.includes('.')) return true
|
|
482
|
-
return false
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function cleanReturnType(ret: string): string {
|
|
486
|
-
// Strip trailing `{`
|
|
487
|
-
ret = ret.replace(/\s*\{.*$/, '').trim()
|
|
488
|
-
// Clean up multiple return: (Type1, Type2) → keep as-is for readability
|
|
489
|
-
return ret
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// --- Import line parsing ------------------------------------------------------
|
|
493
|
-
|
|
494
|
-
function parseImportLine(line: string): ParsedImport | null {
|
|
495
|
-
const trimmed = line.trim()
|
|
496
|
-
if (!trimmed || trimmed.startsWith('//')) return null
|
|
497
|
-
|
|
498
|
-
// Blank import: _ "pkg"
|
|
499
|
-
if (/^_\s+"/.test(trimmed)) return null
|
|
500
|
-
|
|
501
|
-
// Aliased: alias "pkg"
|
|
502
|
-
const aliasRe = /^(\w+)\s+"([^"]+)"/.exec(trimmed)
|
|
503
|
-
if (aliasRe) {
|
|
504
|
-
const [, alias, pkg] = aliasRe
|
|
505
|
-
return { source: pkg, resolvedPath: '', names: [alias], isDefault: false, isDynamic: false }
|
|
207
|
+
extractModuleCalls(): CallExpression[] {
|
|
208
|
+
return [];
|
|
506
209
|
}
|
|
507
210
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
211
|
+
private parseParams(paramsStr: string): ParsedParam[] {
|
|
212
|
+
if (!paramsStr.trim()) return [];
|
|
213
|
+
return paramsStr.split(',').map(p => {
|
|
214
|
+
const parts = p.trim().split(/\s+/);
|
|
215
|
+
return {
|
|
216
|
+
name: parts[0],
|
|
217
|
+
type: parts[1] || 'any',
|
|
218
|
+
optional: false
|
|
219
|
+
};
|
|
220
|
+
});
|
|
515
221
|
}
|
|
516
222
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
let stringChar = ''
|
|
533
|
-
let inBlockComment = false
|
|
534
|
-
|
|
535
|
-
for (let i = startLine; i < lines.length; i++) {
|
|
536
|
-
const line = lines[i]
|
|
537
|
-
let inLineComment = false
|
|
538
|
-
|
|
539
|
-
for (let j = 0; j < line.length; j++) {
|
|
540
|
-
const ch = line[j]
|
|
541
|
-
const next = line[j + 1]
|
|
542
|
-
|
|
543
|
-
if (inLineComment) break
|
|
544
|
-
|
|
545
|
-
if (inBlockComment) {
|
|
546
|
-
if (ch === '*' && next === '/') { inBlockComment = false; j++ }
|
|
547
|
-
continue
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
if (inString) {
|
|
551
|
-
if (ch === '\\') { j++; continue } // escape sequence
|
|
552
|
-
if (ch === stringChar) inString = false
|
|
553
|
-
continue
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
if (ch === '/' && next === '/') { inLineComment = true; break }
|
|
557
|
-
if (ch === '/' && next === '*') { inBlockComment = true; j++; continue }
|
|
558
|
-
if (ch === '"' || ch === '`') {
|
|
559
|
-
inString = true
|
|
560
|
-
stringChar = ch
|
|
561
|
-
continue
|
|
562
|
-
}
|
|
563
|
-
if (ch === '\'') {
|
|
564
|
-
// Go rune literal: consume exactly one character (or escape) then close
|
|
565
|
-
if (next === '\\') j += 3 // '\n' or '\x00' etc.
|
|
566
|
-
else j += 2 // 'a'
|
|
567
|
-
continue
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
if (ch === '{') {
|
|
571
|
-
if (bodyStart === -1) bodyStart = i
|
|
572
|
-
braceDepth++
|
|
573
|
-
} else if (ch === '}') {
|
|
574
|
-
braceDepth--
|
|
575
|
-
if (braceDepth === 0 && bodyStart !== -1) {
|
|
576
|
-
return { bodyStart, bodyEnd: i }
|
|
577
|
-
}
|
|
223
|
+
private parseStructFields(body: string): ParsedVariable[] {
|
|
224
|
+
const fields: ParsedVariable[] = [];
|
|
225
|
+
const lines = body.split('\n');
|
|
226
|
+
for (let i = 0; i < lines.length; i++) {
|
|
227
|
+
const line = lines[i].trim();
|
|
228
|
+
const parts = line.split(/\s+/);
|
|
229
|
+
if (parts.length >= 2 && !line.startsWith('//')) {
|
|
230
|
+
fields.push({
|
|
231
|
+
id: this.allocateId('var', parts[0]),
|
|
232
|
+
name: parts[0],
|
|
233
|
+
type: parts[1],
|
|
234
|
+
file: this.filePath,
|
|
235
|
+
line: i + 1, // Approximate
|
|
236
|
+
isExported: /^[A-Z]/.test(parts[0])
|
|
237
|
+
});
|
|
578
238
|
}
|
|
579
239
|
}
|
|
240
|
+
return fields;
|
|
580
241
|
}
|
|
581
242
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
while (i < code.length) {
|
|
593
|
-
const ch = code[i]
|
|
594
|
-
const next = code[i + 1]
|
|
595
|
-
|
|
596
|
-
if (inBlockComment) {
|
|
597
|
-
if (ch === '*' && next === '/') { inBlockComment = false; i += 2 }
|
|
598
|
-
else i++
|
|
599
|
-
continue
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
if (inString) {
|
|
603
|
-
if (ch === '\\') { i += 2; continue }
|
|
604
|
-
if (ch === stringChar) { inString = false }
|
|
605
|
-
i++
|
|
606
|
-
continue
|
|
243
|
+
private extractCalls(body: string): CallExpression[] {
|
|
244
|
+
const calls: CallExpression[] = [];
|
|
245
|
+
const callRegex = /([\w.]+)\s*\(/g;
|
|
246
|
+
let match;
|
|
247
|
+
while ((match = callRegex.exec(body)) !== null) {
|
|
248
|
+
calls.push({
|
|
249
|
+
name: match[1],
|
|
250
|
+
line: this.getLineNumber(match.index),
|
|
251
|
+
type: match[1].includes('.') ? 'method' : 'function'
|
|
252
|
+
});
|
|
607
253
|
}
|
|
608
|
-
|
|
609
|
-
if (ch === '/' && next === '/') {
|
|
610
|
-
// Skip to end of line
|
|
611
|
-
while (i < code.length && code[i] !== '\n') i++
|
|
612
|
-
continue
|
|
613
|
-
}
|
|
614
|
-
if (ch === '/' && next === '*') { inBlockComment = true; i += 2; continue }
|
|
615
|
-
if (ch === '"' || ch === '`' || ch === '\'') {
|
|
616
|
-
inString = true
|
|
617
|
-
stringChar = ch
|
|
618
|
-
i++
|
|
619
|
-
continue
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
out += ch
|
|
623
|
-
i++
|
|
254
|
+
return calls;
|
|
624
255
|
}
|
|
625
|
-
return out
|
|
626
|
-
}
|
|
627
256
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
const name = m[1]
|
|
638
|
-
if (!GO_BUILTINS.has(name)) {
|
|
639
|
-
// Heuristic for line number: find the line in bodyLines
|
|
640
|
-
// This is a rough estimation but good enough for static analysis
|
|
641
|
-
calls.push({ name, line: baseLine, type: 'function' })
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// Method calls: receiver.Method(
|
|
646
|
-
const methodRe = /\b([A-Za-z_]\w+\.[A-Za-z_]\w*)\s*\(/g
|
|
647
|
-
while ((m = methodRe.exec(stripped)) !== null) {
|
|
648
|
-
calls.push({ name: m[1], line: baseLine, type: 'method' })
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
return calls
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function extractEdgeCases(bodyLines: string[]): string[] {
|
|
655
|
-
const edgeCases: string[] = []
|
|
656
|
-
for (let i = 0; i < bodyLines.length; i++) {
|
|
657
|
-
const trimmed = bodyLines[i].trim()
|
|
658
|
-
// if condition followed by return/panic/error
|
|
659
|
-
const m = /^if\s+(.+?)\s*\{?\s*$/.exec(trimmed)
|
|
660
|
-
if (m && bodyLines[i + 1]?.trim().match(/^(return|panic|log\.|fmt\.)/)) {
|
|
661
|
-
edgeCases.push(m[1].trim())
|
|
257
|
+
private extractBracedContent(startIndex: number): string {
|
|
258
|
+
let depth = 0;
|
|
259
|
+
let result = '';
|
|
260
|
+
for (let i = startIndex; i < this.content.length; i++) {
|
|
261
|
+
const char = this.content[i];
|
|
262
|
+
if (char === '{') depth++;
|
|
263
|
+
if (char === '}') depth--;
|
|
264
|
+
result += char;
|
|
265
|
+
if (depth === 0) break;
|
|
662
266
|
}
|
|
267
|
+
return result;
|
|
663
268
|
}
|
|
664
|
-
return edgeCases
|
|
665
|
-
}
|
|
666
269
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
for (let i = 0; i < bodyLines.length; i++) {
|
|
670
|
-
const trimmed = bodyLines[i].trim()
|
|
671
|
-
// if err != nil { return ..., err }
|
|
672
|
-
if (/^if\s+\w*err\w*\s*!=\s*nil/.test(trimmed)) {
|
|
673
|
-
errors.push({ line: baseLineNumber + i, type: 'try-catch', detail: trimmed })
|
|
674
|
-
}
|
|
675
|
-
// panic(...)
|
|
676
|
-
if (/^panic\(/.test(trimmed)) {
|
|
677
|
-
errors.push({ line: baseLineNumber + i, type: 'throw', detail: trimmed })
|
|
678
|
-
}
|
|
270
|
+
private getLineNumber(offset: number): number {
|
|
271
|
+
return this.content.substring(0, offset).split('\n').length;
|
|
679
272
|
}
|
|
680
|
-
return errors
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// --- Comment extraction -------------------------------------------------------
|
|
684
273
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
274
|
+
private extractDocComment(offset: number): string {
|
|
275
|
+
const lines = this.content.substring(0, offset).split('\n');
|
|
276
|
+
const comments = [];
|
|
277
|
+
for (let i = lines.length - 2; i >= 0; i--) {
|
|
278
|
+
const line = lines[i].trim();
|
|
279
|
+
if (line.startsWith('//')) {
|
|
280
|
+
comments.unshift(line.replace('//', '').trim());
|
|
281
|
+
} else if (line === '') {
|
|
282
|
+
continue;
|
|
283
|
+
} else {
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
697
286
|
}
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
// First meaningful non-divider line
|
|
701
|
-
for (const line of commentLines) {
|
|
702
|
-
if (/^[-=*─]{3,}$/.test(line)) continue
|
|
703
|
-
return line.trim()
|
|
287
|
+
return comments.join(' ');
|
|
704
288
|
}
|
|
705
|
-
return ''
|
|
706
289
|
}
|
|
707
290
|
|
|
708
|
-
//
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
if (ch === '(' || ch === '[' || ch === '{') depth++
|
|
721
|
-
else if (ch === ')' || ch === ']' || ch === '}') depth--
|
|
722
|
-
else if (ch === delimiter && depth === 0) {
|
|
723
|
-
parts.push(current)
|
|
724
|
-
current = ''
|
|
725
|
-
continue
|
|
726
|
-
}
|
|
727
|
-
current += ch
|
|
291
|
+
// Register in the global registry
|
|
292
|
+
LanguageRegistry.getInstance().register({
|
|
293
|
+
name: 'go',
|
|
294
|
+
extensions: ['.go'],
|
|
295
|
+
treeSitterGrammar: '',
|
|
296
|
+
extractor: new GoExtractor(),
|
|
297
|
+
semanticFeatures: {
|
|
298
|
+
hasTypeSystem: true,
|
|
299
|
+
hasGenerics: true,
|
|
300
|
+
hasMacros: false,
|
|
301
|
+
hasAnnotations: false,
|
|
302
|
+
hasPatternMatching: false
|
|
728
303
|
}
|
|
729
|
-
|
|
730
|
-
return parts
|
|
731
|
-
}
|
|
304
|
+
});
|