septum 0.1.4 → 0.1.6
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 +21 -21
- package/README.md +105 -105
- package/bin/septum.ts +4 -4
- package/package.json +63 -63
- package/src/cli/commands/check.ts +161 -161
- package/src/cli/commands/hook.ts +216 -188
- package/src/cli/commands/ingest.ts +29 -29
- package/src/cli/commands/init.ts +213 -211
- package/src/cli/commands/locate.ts +61 -61
- package/src/cli/commands/query.ts +104 -64
- package/src/cli/commands/serve.ts +5 -5
- package/src/cli/commands/slice.ts +50 -48
- package/src/cli/commands/sync.ts +31 -31
- package/src/cli/index.ts +220 -210
- package/src/cli/templates/hooks.ts +75 -75
- package/src/cli/templates/rules.ts +50 -38
- package/src/cli/templates/skill.ts +65 -69
- package/src/core/boundary/evaluator.ts +429 -210
- package/src/core/config/loader.ts +117 -97
- package/src/core/config/schema.ts +39 -39
- package/src/core/database/client.ts +44 -44
- package/src/core/database/repositories/dependency.repository.ts +552 -349
- package/src/core/database/repositories/domain.repository.ts +275 -198
- package/src/core/database/repositories/file.repository.ts +117 -106
- package/src/core/database/repositories/meta.repository.ts +33 -33
- package/src/core/database/repositories/symbol.repository.ts +482 -443
- package/src/core/database/repository.ts +281 -260
- package/src/core/database/schema.ts +154 -153
- package/src/core/discovery/topology-detector.ts +486 -486
- package/src/core/ingestion/hasher.ts +5 -5
- package/src/core/ingestion/pipeline.ts +606 -535
- package/src/core/parser/boundary-tracker.ts +433 -288
- package/src/core/parser/extractors/base.ts +62 -62
- package/src/core/parser/extractors/blade.ts +158 -158
- package/src/core/parser/extractors/frontend.ts +147 -194
- package/src/core/parser/extractors/go.ts +408 -180
- package/src/core/parser/extractors/laravel-semantic.ts +1045 -901
- package/src/core/parser/extractors/mcp-cli-semantic.ts +328 -199
- package/src/core/parser/extractors/nestjs-semantic.ts +134 -119
- package/src/core/parser/extractors/php.ts +415 -153
- package/src/core/parser/extractors/python.ts +460 -228
- package/src/core/parser/extractors/registry.ts +70 -70
- package/src/core/parser/extractors/semantic-extractor.interface.ts +21 -21
- package/src/core/parser/extractors/typescript.ts +668 -181
- package/src/core/parser/tree-sitter.ts +41 -41
- package/src/core/parser/wasm-loader.ts +271 -0
- package/src/core/resolver/call-graph-tracer.ts +698 -668
- package/src/core/resolver/module-resolver.ts +479 -342
- package/src/core/resolver/path-resolver.ts +147 -147
- package/src/core/resolver/symbol-locator.ts +543 -543
- package/src/core/resolver/vertical-slice-tracer.ts +323 -285
- package/src/core/session/session-manager.ts +56 -56
- package/src/core/telemetry/telemetry.ts +86 -86
- package/src/core/topology/environment-detector.ts +109 -6
- package/src/index.ts +21 -21
- package/src/mcp/schemas.ts +695 -286
- package/src/mcp/server.ts +927 -716
- package/src/mcp/tools/check-boundary.ts +307 -152
- package/src/mcp/tools/get-domain-catalog.ts +155 -121
- package/src/mcp/tools/get-environment-topology.ts +3 -6
- package/src/mcp/tools/get-feature-context.ts +227 -116
- package/src/mcp/tools/get-symbol-hotspots.ts +54 -59
- package/src/mcp/tools/get-symbol-impact.ts +28 -26
- package/src/mcp/tools/get-symbol.ts +65 -63
- package/src/mcp/tools/locate-symbol.ts +169 -190
- package/src/mcp/tools/register-domain.ts +59 -64
- package/src/mcp/tools/register-feature.ts +91 -0
- package/src/mcp/tools/trace-vertical-slice.ts +74 -48
- package/src/types/index.ts +366 -315
- package/src/version.ts +3 -3
|
@@ -1,180 +1,408 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { ExtractedDependency, ExtractedSymbol, ParsedFileAST, Visibility } from "../../../types/index.ts";
|
|
4
|
+
import { findBraceBlockEnd, calculateControlFlowNesting } from "../boundary-tracker.ts";
|
|
5
|
+
import type { CodeExtractor } from "./base.ts";
|
|
6
|
+
|
|
7
|
+
const GO_STDLIB_PREFIXES = new Set([
|
|
8
|
+
"bufio",
|
|
9
|
+
"bytes",
|
|
10
|
+
"context",
|
|
11
|
+
"crypto",
|
|
12
|
+
"database",
|
|
13
|
+
"encoding",
|
|
14
|
+
"errors",
|
|
15
|
+
"flag",
|
|
16
|
+
"fmt",
|
|
17
|
+
"html",
|
|
18
|
+
"image",
|
|
19
|
+
"io",
|
|
20
|
+
"log",
|
|
21
|
+
"math",
|
|
22
|
+
"mime",
|
|
23
|
+
"net",
|
|
24
|
+
"os",
|
|
25
|
+
"path",
|
|
26
|
+
"plugin",
|
|
27
|
+
"reflect",
|
|
28
|
+
"regexp",
|
|
29
|
+
"runtime",
|
|
30
|
+
"sort",
|
|
31
|
+
"strconv",
|
|
32
|
+
"strings",
|
|
33
|
+
"sync",
|
|
34
|
+
"syscall",
|
|
35
|
+
"testing",
|
|
36
|
+
"text",
|
|
37
|
+
"time",
|
|
38
|
+
"unicode",
|
|
39
|
+
"unsafe",
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
import type Parser from "web-tree-sitter";
|
|
43
|
+
import { blankCommentsAndStrings } from "../boundary-tracker.ts";
|
|
44
|
+
import { calculateAstControlFlowNesting, parseWithTreeSitter } from "../wasm-loader.ts";
|
|
45
|
+
|
|
46
|
+
const GO_CONTROL_FLOW_NODES = new Set([
|
|
47
|
+
"if_statement",
|
|
48
|
+
"for_statement",
|
|
49
|
+
"expression_switch_statement",
|
|
50
|
+
"type_switch_statement",
|
|
51
|
+
"select_statement",
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
const GO_FUNCTION_BOUNDARY_NODES = new Set([
|
|
55
|
+
"function_declaration",
|
|
56
|
+
"method_declaration",
|
|
57
|
+
"func_literal",
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
export class GoExtractor implements CodeExtractor {
|
|
61
|
+
public canHandle(filePath: string): boolean {
|
|
62
|
+
return /\.go$/i.test(filePath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public async extract(filePath: string, content: string): Promise<ParsedFileAST> {
|
|
66
|
+
const astResult = await parseWithTreeSitter("go", content, (rootNode) =>
|
|
67
|
+
this.extractFromAst(rootNode, filePath, content)
|
|
68
|
+
);
|
|
69
|
+
if (astResult) {
|
|
70
|
+
return astResult;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return this.extractFallback(filePath, content);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private extractFallback(filePath: string, content: string): ParsedFileAST {
|
|
77
|
+
const symbols: ExtractedSymbol[] = [];
|
|
78
|
+
const dependencies: ExtractedDependency[] = [];
|
|
79
|
+
|
|
80
|
+
const lines = content.split("\n");
|
|
81
|
+
const cleanLines = blankCommentsAndStrings(content).split("\n");
|
|
82
|
+
|
|
83
|
+
// 1. Extract Go Imports
|
|
84
|
+
let inImportBlock = false;
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < lines.length; i++) {
|
|
87
|
+
const line = lines[i];
|
|
88
|
+
const trimmed = line.trim();
|
|
89
|
+
const cleanTrimmed = cleanLines[i].trim();
|
|
90
|
+
const lineNum = i + 1;
|
|
91
|
+
|
|
92
|
+
if (!cleanTrimmed || trimmed.startsWith("//")) continue;
|
|
93
|
+
|
|
94
|
+
if (cleanTrimmed.startsWith("import (")) {
|
|
95
|
+
inImportBlock = true;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (inImportBlock) {
|
|
100
|
+
if (cleanTrimmed === ")") {
|
|
101
|
+
inImportBlock = false;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const blockMatch = /^(?:([\w.]+)\s+)?["'`]([^"'`]+)["'`]/.exec(trimmed);
|
|
106
|
+
if (blockMatch) {
|
|
107
|
+
const alias = blockMatch[1];
|
|
108
|
+
const importPath = blockMatch[2];
|
|
109
|
+
const isExternal = this.isExternalImport(importPath, filePath);
|
|
110
|
+
const pkgSymbol = alias && alias !== "." && alias !== "_" ? alias : importPath.split("/").pop() || importPath;
|
|
111
|
+
dependencies.push({
|
|
112
|
+
target: importPath,
|
|
113
|
+
statement: trimmed,
|
|
114
|
+
line_number: lineNum,
|
|
115
|
+
is_external: isExternal,
|
|
116
|
+
imported_symbols: [pkgSymbol],
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const singleMatch = /^import\s+(?:([\w.]+)\s+)?["'`]([^"'`]+)["'`]/.exec(trimmed);
|
|
123
|
+
if (singleMatch) {
|
|
124
|
+
const alias = singleMatch[1];
|
|
125
|
+
const importPath = singleMatch[2];
|
|
126
|
+
const isExternal = this.isExternalImport(importPath, filePath);
|
|
127
|
+
const pkgSymbol = alias && alias !== "." && alias !== "_" ? alias : importPath.split("/").pop() || importPath;
|
|
128
|
+
dependencies.push({
|
|
129
|
+
target: importPath,
|
|
130
|
+
statement: trimmed,
|
|
131
|
+
line_number: lineNum,
|
|
132
|
+
is_external: isExternal,
|
|
133
|
+
imported_symbols: [pkgSymbol],
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 2. Extract Go Types, Structs, Interfaces, and Functions
|
|
139
|
+
for (let i = 0; i < cleanLines.length; i++) {
|
|
140
|
+
const trimmed = cleanLines[i].trim();
|
|
141
|
+
const rawTrimmed = lines[i].trim();
|
|
142
|
+
const lineNum = i + 1;
|
|
143
|
+
|
|
144
|
+
if (!trimmed) continue;
|
|
145
|
+
|
|
146
|
+
const typeMatch = /^type\s+([A-Za-z0-9_]+)\s+(struct|interface)\b/.exec(trimmed);
|
|
147
|
+
if (typeMatch) {
|
|
148
|
+
const typeName = typeMatch[1];
|
|
149
|
+
const kind = typeMatch[2] === "interface" ? "interface" : "class";
|
|
150
|
+
const visibility: Visibility = /^[A-Z]/.test(typeName) ? "public" : "private";
|
|
151
|
+
const endLine = findBraceBlockEnd(cleanLines, i);
|
|
152
|
+
|
|
153
|
+
symbols.push({
|
|
154
|
+
name: typeName,
|
|
155
|
+
kind,
|
|
156
|
+
signature: rawTrimmed.replace(/\{$/, "").trim(),
|
|
157
|
+
visibility,
|
|
158
|
+
line_start: lineNum,
|
|
159
|
+
line_end: endLine,
|
|
160
|
+
line_count: Math.max(1, endLine - lineNum + 1),
|
|
161
|
+
});
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const methodMatch = /^func\s*\([^)]+\)\s*([A-Za-z0-9_]+)\s*\([^)]*\)/.exec(trimmed);
|
|
166
|
+
if (methodMatch) {
|
|
167
|
+
const methodName = methodMatch[1];
|
|
168
|
+
const visibility: Visibility = /^[A-Z]/.test(methodName) ? "public" : "private";
|
|
169
|
+
const endLine = findBraceBlockEnd(cleanLines, i);
|
|
170
|
+
const nestingDepth = calculateControlFlowNesting(lines, i, endLine - 1);
|
|
171
|
+
|
|
172
|
+
symbols.push({
|
|
173
|
+
name: methodName,
|
|
174
|
+
kind: "method",
|
|
175
|
+
signature: rawTrimmed.replace(/\{$/, "").trim(),
|
|
176
|
+
visibility,
|
|
177
|
+
line_start: lineNum,
|
|
178
|
+
line_end: endLine,
|
|
179
|
+
line_count: Math.max(1, endLine - lineNum + 1),
|
|
180
|
+
nesting_depth: nestingDepth,
|
|
181
|
+
});
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const funcMatch = /^func\s+([A-Za-z0-9_]+)\s*\([^)]*\)/.exec(trimmed);
|
|
186
|
+
if (funcMatch) {
|
|
187
|
+
const funcName = funcMatch[1];
|
|
188
|
+
const visibility: Visibility = /^[A-Z]/.test(funcName) ? "public" : "private";
|
|
189
|
+
const endLine = findBraceBlockEnd(cleanLines, i);
|
|
190
|
+
const nestingDepth = calculateControlFlowNesting(lines, i, endLine - 1);
|
|
191
|
+
|
|
192
|
+
symbols.push({
|
|
193
|
+
name: funcName,
|
|
194
|
+
kind: "function",
|
|
195
|
+
signature: rawTrimmed.replace(/\{$/, "").trim(),
|
|
196
|
+
visibility,
|
|
197
|
+
line_start: lineNum,
|
|
198
|
+
line_end: endLine,
|
|
199
|
+
line_count: Math.max(1, endLine - lineNum + 1),
|
|
200
|
+
nesting_depth: nestingDepth,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
symbols,
|
|
207
|
+
dependencies,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
private isExternalImport(importPath: string, filePath?: string): boolean {
|
|
213
|
+
if (this.isGoStdlib(importPath)) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
if (importPath.startsWith(".") || importPath.startsWith("/")) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
const modName = this.findGoModuleName(filePath);
|
|
220
|
+
if (modName) {
|
|
221
|
+
if (importPath === modName || importPath.startsWith(`${modName}/`)) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (
|
|
228
|
+
importPath.includes("/domains/") ||
|
|
229
|
+
importPath.includes("/internal/") ||
|
|
230
|
+
importPath.includes("/pkg/")
|
|
231
|
+
) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (importPath.includes(".")) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private findGoModuleName(filePath?: string): string | null {
|
|
243
|
+
if (!filePath) return null;
|
|
244
|
+
try {
|
|
245
|
+
let currentDir = path.dirname(path.resolve(filePath));
|
|
246
|
+
while (currentDir && currentDir !== path.dirname(currentDir)) {
|
|
247
|
+
const goModPath = path.join(currentDir, "go.mod");
|
|
248
|
+
if (fs.existsSync(goModPath)) {
|
|
249
|
+
const content = fs.readFileSync(goModPath, "utf-8");
|
|
250
|
+
const match = /^module\s+([^\s\r\n]+)/m.exec(content);
|
|
251
|
+
if (match) return match[1];
|
|
252
|
+
}
|
|
253
|
+
currentDir = path.dirname(currentDir);
|
|
254
|
+
}
|
|
255
|
+
} catch {}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private isGoStdlib(importPath: string): boolean {
|
|
260
|
+
const rootSegment = importPath.split("/")[0];
|
|
261
|
+
return GO_STDLIB_PREFIXES.has(rootSegment) || !importPath.includes(".");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private extractFromAst(
|
|
265
|
+
rootNode: Parser.SyntaxNode,
|
|
266
|
+
filePath: string,
|
|
267
|
+
content: string
|
|
268
|
+
): ParsedFileAST {
|
|
269
|
+
const symbols: ExtractedSymbol[] = [];
|
|
270
|
+
const dependencies: ExtractedDependency[] = [];
|
|
271
|
+
const lines = content.split("\n");
|
|
272
|
+
|
|
273
|
+
const addImportSpec = (specNode: Parser.SyntaxNode): void => {
|
|
274
|
+
const pathNode =
|
|
275
|
+
specNode.childForFieldName("path") ??
|
|
276
|
+
specNode.namedChildren.find(
|
|
277
|
+
(c) => c.type === "interpreted_string_literal" || c.type === "raw_string_literal"
|
|
278
|
+
);
|
|
279
|
+
if (!pathNode) return;
|
|
280
|
+
|
|
281
|
+
const importPath = pathNode.text.replace(/^["'`]|["'`]$/g, "").trim();
|
|
282
|
+
if (!importPath) return;
|
|
283
|
+
|
|
284
|
+
const nameNode =
|
|
285
|
+
specNode.childForFieldName("name") ??
|
|
286
|
+
specNode.namedChildren.find((c) => c.type === "package_identifier" || c.type === "dot" || c.type === "blank_identifier");
|
|
287
|
+
const alias = nameNode?.text?.trim();
|
|
288
|
+
const pkgSymbol =
|
|
289
|
+
alias && alias !== "." && alias !== "_" ? alias : importPath.split("/").pop() || importPath;
|
|
290
|
+
|
|
291
|
+
dependencies.push({
|
|
292
|
+
target: importPath,
|
|
293
|
+
statement: (lines[specNode.startPosition.row] ?? specNode.text).trim(),
|
|
294
|
+
line_number: specNode.startPosition.row + 1,
|
|
295
|
+
is_external: this.isExternalImport(importPath, filePath),
|
|
296
|
+
imported_symbols: [pkgSymbol],
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
for (const node of rootNode.namedChildren) {
|
|
301
|
+
if (node.type === "import_declaration") {
|
|
302
|
+
for (const child of node.namedChildren) {
|
|
303
|
+
if (child.type === "import_spec") {
|
|
304
|
+
addImportSpec(child);
|
|
305
|
+
} else if (child.type === "import_spec_list") {
|
|
306
|
+
for (const spec of child.namedChildren) {
|
|
307
|
+
if (spec.type === "import_spec") {
|
|
308
|
+
addImportSpec(spec);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (node.type === "type_declaration") {
|
|
317
|
+
for (const spec of node.namedChildren) {
|
|
318
|
+
if (spec.type !== "type_spec" && spec.type !== "type_alias") continue;
|
|
319
|
+
const nameNode =
|
|
320
|
+
spec.childForFieldName("name") ??
|
|
321
|
+
spec.namedChildren.find((c) => c.type === "type_identifier");
|
|
322
|
+
if (!nameNode) continue;
|
|
323
|
+
|
|
324
|
+
const typeName = nameNode.text;
|
|
325
|
+
const typeVal = spec.childForFieldName("type") ?? spec.namedChildren[1];
|
|
326
|
+
const kind = typeVal?.type === "interface_type" ? "interface" : "class";
|
|
327
|
+
const visibility: Visibility = /^[A-Z]/.test(typeName) ? "public" : "private";
|
|
328
|
+
const lineStart = spec.startPosition.row + 1;
|
|
329
|
+
const lineEnd = Math.max(lineStart, spec.endPosition.row + 1);
|
|
330
|
+
const headerLine = (lines[spec.startPosition.row] ?? "").trim().replace(/\{$/, "").trim();
|
|
331
|
+
|
|
332
|
+
symbols.push({
|
|
333
|
+
name: typeName,
|
|
334
|
+
kind,
|
|
335
|
+
signature: headerLine || `type ${typeName}`,
|
|
336
|
+
visibility,
|
|
337
|
+
line_start: lineStart,
|
|
338
|
+
line_end: lineEnd,
|
|
339
|
+
line_count: Math.max(1, lineEnd - lineStart + 1),
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (node.type === "method_declaration") {
|
|
346
|
+
const nameNode =
|
|
347
|
+
node.childForFieldName("name") ??
|
|
348
|
+
node.namedChildren.find((c) => c.type === "field_identifier" || c.type === "identifier");
|
|
349
|
+
if (!nameNode) continue;
|
|
350
|
+
|
|
351
|
+
const methodName = nameNode.text;
|
|
352
|
+
const visibility: Visibility = /^[A-Z]/.test(methodName) ? "public" : "private";
|
|
353
|
+
const lineStart = node.startPosition.row + 1;
|
|
354
|
+
const lineEnd = Math.max(lineStart, node.endPosition.row + 1);
|
|
355
|
+
const headerLine = (lines[node.startPosition.row] ?? "").trim().replace(/\{$/, "").trim();
|
|
356
|
+
const nestingDepth = calculateAstControlFlowNesting(
|
|
357
|
+
node,
|
|
358
|
+
GO_CONTROL_FLOW_NODES,
|
|
359
|
+
GO_FUNCTION_BOUNDARY_NODES
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
symbols.push({
|
|
363
|
+
name: methodName,
|
|
364
|
+
kind: "method",
|
|
365
|
+
signature: headerLine || `func ${methodName}()`,
|
|
366
|
+
visibility,
|
|
367
|
+
line_start: lineStart,
|
|
368
|
+
line_end: lineEnd,
|
|
369
|
+
line_count: Math.max(1, lineEnd - lineStart + 1),
|
|
370
|
+
nesting_depth: nestingDepth,
|
|
371
|
+
});
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (node.type === "function_declaration") {
|
|
376
|
+
const nameNode =
|
|
377
|
+
node.childForFieldName("name") ??
|
|
378
|
+
node.namedChildren.find((c) => c.type === "identifier");
|
|
379
|
+
if (!nameNode) continue;
|
|
380
|
+
|
|
381
|
+
const funcName = nameNode.text;
|
|
382
|
+
const visibility: Visibility = /^[A-Z]/.test(funcName) ? "public" : "private";
|
|
383
|
+
const lineStart = node.startPosition.row + 1;
|
|
384
|
+
const lineEnd = Math.max(lineStart, node.endPosition.row + 1);
|
|
385
|
+
const headerLine = (lines[node.startPosition.row] ?? "").trim().replace(/\{$/, "").trim();
|
|
386
|
+
const nestingDepth = calculateAstControlFlowNesting(
|
|
387
|
+
node,
|
|
388
|
+
GO_CONTROL_FLOW_NODES,
|
|
389
|
+
GO_FUNCTION_BOUNDARY_NODES
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
symbols.push({
|
|
393
|
+
name: funcName,
|
|
394
|
+
kind: "function",
|
|
395
|
+
signature: headerLine || `func ${funcName}()`,
|
|
396
|
+
visibility,
|
|
397
|
+
line_start: lineStart,
|
|
398
|
+
line_end: lineEnd,
|
|
399
|
+
line_count: Math.max(1, lineEnd - lineStart + 1),
|
|
400
|
+
nesting_depth: nestingDepth,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return { symbols, dependencies };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|