codesight 1.3.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Go structured parser for routes and models.
3
+ * Uses brace-tracking + regex for near-AST accuracy on Go's regular syntax.
4
+ *
5
+ * Go's syntax is regular enough that structured parsing (tracking braces,
6
+ * extracting struct bodies, parsing field tags) achieves AST-level accuracy
7
+ * without needing the Go compiler.
8
+ *
9
+ * Extracts:
10
+ * - Gin/Fiber/Echo/Chi/net-http routes with group prefixes
11
+ * - GORM model structs with field types, tags (primaryKey, unique, etc.)
12
+ */
13
+ import type { RouteInfo, SchemaModel, Framework } from "../types.js";
14
+ /**
15
+ * Extract routes from a Go file with group/prefix tracking.
16
+ * Works for Gin, Fiber, Echo, Chi, and net/http.
17
+ */
18
+ export declare function extractGoRoutesStructured(filePath: string, content: string, framework: Framework, tags: string[]): RouteInfo[];
19
+ /**
20
+ * Extract GORM model structs from a Go file.
21
+ * Parses struct bodies, field types, and gorm tags.
22
+ */
23
+ export declare function extractGORMModelsStructured(_filePath: string, content: string): SchemaModel[];
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Go structured parser for routes and models.
3
+ * Uses brace-tracking + regex for near-AST accuracy on Go's regular syntax.
4
+ *
5
+ * Go's syntax is regular enough that structured parsing (tracking braces,
6
+ * extracting struct bodies, parsing field tags) achieves AST-level accuracy
7
+ * without needing the Go compiler.
8
+ *
9
+ * Extracts:
10
+ * - Gin/Fiber/Echo/Chi/net-http routes with group prefixes
11
+ * - GORM model structs with field types, tags (primaryKey, unique, etc.)
12
+ */
13
+ /**
14
+ * Extract routes from a Go file with group/prefix tracking.
15
+ * Works for Gin, Fiber, Echo, Chi, and net/http.
16
+ */
17
+ export function extractGoRoutesStructured(filePath, content, framework, tags) {
18
+ const routes = [];
19
+ // Step 1: Find route groups with prefixes
20
+ // Gin: r.Group("/api") / g := r.Group("/v1")
21
+ // Echo: g := e.Group("/api")
22
+ // Fiber: api := app.Group("/api")
23
+ // Chi: r.Route("/api", func(r chi.Router) { ... })
24
+ const groups = extractRouteGroups(content, framework);
25
+ // Collect group variable names to exclude from top-level scan
26
+ const groupVarNames = new Set();
27
+ // Step 2: Extract routes from each group with prefix resolution
28
+ for (const group of groups) {
29
+ // Track which variable this group belongs to
30
+ if (group.varName)
31
+ groupVarNames.add(group.varName);
32
+ const groupRoutes = extractRoutesFromBlock(group.body, framework, filePath, tags);
33
+ for (const route of groupRoutes) {
34
+ route.path = normalizePath(group.prefix + "/" + route.path);
35
+ routes.push(route);
36
+ }
37
+ }
38
+ // Step 3: Extract top-level routes (only from lines NOT belonging to group vars)
39
+ // Filter out lines that reference group variables to avoid duplicates
40
+ if (groupVarNames.size > 0) {
41
+ const lines = content.split("\n");
42
+ const topLines = lines.filter((line) => {
43
+ for (const v of groupVarNames) {
44
+ if (line.includes(v + "."))
45
+ return false;
46
+ }
47
+ return true;
48
+ });
49
+ const topContent = topLines.join("\n");
50
+ const topLevelRoutes = extractRoutesFromBlock(topContent, framework, filePath, tags);
51
+ routes.push(...topLevelRoutes);
52
+ }
53
+ else {
54
+ const topLevelRoutes = extractRoutesFromBlock(content, framework, filePath, tags);
55
+ routes.push(...topLevelRoutes);
56
+ }
57
+ // Deduplicate
58
+ const seen = new Set();
59
+ return routes.filter((r) => {
60
+ const key = `${r.method}:${r.path}`;
61
+ if (seen.has(key))
62
+ return false;
63
+ seen.add(key);
64
+ return true;
65
+ });
66
+ }
67
+ function extractRouteGroups(content, framework) {
68
+ const groups = [];
69
+ if (framework === "chi") {
70
+ // Chi: r.Route("/prefix", func(r chi.Router) { ... })
71
+ const chiRoutePattern = /\.Route\s*\(\s*"([^"]+)"\s*,\s*func\s*\([^)]*\)\s*\{/g;
72
+ let match;
73
+ while ((match = chiRoutePattern.exec(content)) !== null) {
74
+ const prefix = match[1];
75
+ const bodyStart = match.index + match[0].length;
76
+ const body = extractBraceBlock(content, bodyStart);
77
+ if (body)
78
+ groups.push({ prefix, body });
79
+ }
80
+ }
81
+ else {
82
+ // Gin/Echo/Fiber: varName := receiver.Group("/prefix")
83
+ // Build a prefix map to resolve chained groups: api := r.Group("/api"), users := api.Group("/users")
84
+ const prefixMap = new Map(); // varName -> resolved full prefix
85
+ const groupPattern = /(\w+)\s*:?=\s*(\w+)\.Group\s*\(\s*"([^"]*)"/g;
86
+ let match;
87
+ // First pass: build prefix chain
88
+ while ((match = groupPattern.exec(content)) !== null) {
89
+ const varName = match[1];
90
+ const receiver = match[2];
91
+ const prefix = match[3];
92
+ // Resolve receiver prefix (if receiver is itself a group)
93
+ const receiverPrefix = prefixMap.get(receiver) || "";
94
+ const fullPrefix = normalizePath(receiverPrefix + "/" + prefix);
95
+ prefixMap.set(varName, fullPrefix);
96
+ }
97
+ // Second pass: extract routes for each group variable
98
+ for (const [varName, fullPrefix] of prefixMap) {
99
+ const varRoutes = [];
100
+ // Match routes — allow empty path strings with ([^"]*)
101
+ const varPattern = new RegExp(`${escapeRegex(varName)}\\.\\s*(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|Get|Post|Put|Patch|Delete|Options|Head)\\s*\\(\\s*"([^"]*)"`, "g");
102
+ let routeMatch;
103
+ while ((routeMatch = varPattern.exec(content)) !== null) {
104
+ varRoutes.push(`${routeMatch[1]}:${routeMatch[2]}`);
105
+ }
106
+ // Also handle HandleFunc for mixed patterns
107
+ const handlePattern = new RegExp(`${escapeRegex(varName)}\\.\\s*HandleFunc\\s*\\(\\s*"([^"]*)"`, "g");
108
+ while ((routeMatch = handlePattern.exec(content)) !== null) {
109
+ varRoutes.push(`ALL:${routeMatch[1]}`);
110
+ }
111
+ if (varRoutes.length > 0) {
112
+ groups.push({
113
+ prefix: fullPrefix,
114
+ varName,
115
+ body: varRoutes
116
+ .map((r) => {
117
+ const colonIdx = r.indexOf(":");
118
+ const m = r.slice(0, colonIdx);
119
+ const p = r.slice(colonIdx + 1);
120
+ return `FAKE.${m}("${p}")`;
121
+ })
122
+ .join("\n"),
123
+ });
124
+ }
125
+ }
126
+ }
127
+ return groups;
128
+ }
129
+ function extractRoutesFromBlock(block, framework, filePath, tags) {
130
+ const routes = [];
131
+ if (framework === "gin" || framework === "echo") {
132
+ // .GET("/path", handler) — uppercase methods (allow empty path)
133
+ const pattern = /\.(?:FAKE\.)?(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*\(\s*"([^"]*)"/g;
134
+ let match;
135
+ while ((match = pattern.exec(block)) !== null) {
136
+ routes.push({
137
+ method: match[1],
138
+ path: match[2],
139
+ file: filePath,
140
+ tags,
141
+ framework,
142
+ params: extractPathParams(match[2]),
143
+ confidence: "ast",
144
+ });
145
+ }
146
+ }
147
+ else if (framework === "fiber" || framework === "chi") {
148
+ // .Get("/path", handler) — PascalCase methods (allow empty path)
149
+ const pattern = /\.(?:FAKE\.)?(Get|Post|Put|Patch|Delete|Options|Head)\s*\(\s*"([^"]*)"/g;
150
+ let match;
151
+ while ((match = pattern.exec(block)) !== null) {
152
+ routes.push({
153
+ method: match[1].toUpperCase(),
154
+ path: match[2],
155
+ file: filePath,
156
+ tags,
157
+ framework,
158
+ params: extractPathParams(match[2]),
159
+ confidence: "ast",
160
+ });
161
+ }
162
+ }
163
+ // net/http: HandleFunc or Handle
164
+ if (framework === "go-net-http" || framework === "chi") {
165
+ const pattern = /\.(?:HandleFunc|Handle)\s*\(\s*"([^"]+)"/g;
166
+ let match;
167
+ while ((match = pattern.exec(block)) !== null) {
168
+ // Go 1.22+: "GET /path" patterns
169
+ const pathStr = match[1];
170
+ let method = "ALL";
171
+ let path = pathStr;
172
+ const methodMatch = pathStr.match(/^(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s+(\/.*)/);
173
+ if (methodMatch) {
174
+ method = methodMatch[1];
175
+ path = methodMatch[2];
176
+ }
177
+ routes.push({
178
+ method,
179
+ path,
180
+ file: filePath,
181
+ tags,
182
+ framework,
183
+ params: extractPathParams(path),
184
+ confidence: "ast",
185
+ });
186
+ }
187
+ }
188
+ // Chi: r.Get, r.Post etc. (also catch Method pattern)
189
+ if (framework === "chi") {
190
+ const methodPattern = /\.Method\s*\(\s*"(\w+)"\s*,\s*"([^"]+)"/g;
191
+ let match;
192
+ while ((match = methodPattern.exec(block)) !== null) {
193
+ routes.push({
194
+ method: match[1].toUpperCase(),
195
+ path: match[2],
196
+ file: filePath,
197
+ tags,
198
+ framework,
199
+ params: extractPathParams(match[2]),
200
+ confidence: "ast",
201
+ });
202
+ }
203
+ }
204
+ return routes;
205
+ }
206
+ // ─── GORM Model Extraction ───
207
+ /**
208
+ * Extract GORM model structs from a Go file.
209
+ * Parses struct bodies, field types, and gorm tags.
210
+ */
211
+ export function extractGORMModelsStructured(_filePath, content) {
212
+ const models = [];
213
+ // Find structs that embed gorm.Model or have gorm tags
214
+ const structPattern = /type\s+(\w+)\s+struct\s*\{/g;
215
+ let match;
216
+ while ((match = structPattern.exec(content)) !== null) {
217
+ const name = match[1];
218
+ const bodyStart = match.index + match[0].length;
219
+ const body = extractBraceBlock(content, bodyStart);
220
+ if (!body)
221
+ continue;
222
+ // Check if this is a GORM model
223
+ const isGormModel = body.includes("gorm.Model") ||
224
+ body.includes("gorm.DeletedAt") ||
225
+ body.includes("`gorm:") ||
226
+ body.includes("`json:");
227
+ if (!isGormModel)
228
+ continue;
229
+ const fields = [];
230
+ const relations = [];
231
+ const lines = body.split("\n");
232
+ for (const line of lines) {
233
+ const trimmed = line.trim();
234
+ if (!trimmed || trimmed.startsWith("//") || trimmed === "}" || trimmed === "{")
235
+ continue;
236
+ // Embedded model: gorm.Model
237
+ if (trimmed === "gorm.Model") {
238
+ fields.push({ name: "ID", type: "uint", flags: ["pk"] });
239
+ fields.push({ name: "CreatedAt", type: "time.Time", flags: [] });
240
+ fields.push({ name: "UpdatedAt", type: "time.Time", flags: [] });
241
+ fields.push({ name: "DeletedAt", type: "gorm.DeletedAt", flags: ["nullable"] });
242
+ continue;
243
+ }
244
+ // Parse field: Name Type `gorm:"..." json:"..."`
245
+ const fieldMatch = trimmed.match(/^(\w+)\s+([\w.*\[\]]+)\s*(?:`(.+)`)?/);
246
+ if (!fieldMatch)
247
+ continue;
248
+ const fieldName = fieldMatch[1];
249
+ const fieldType = fieldMatch[2];
250
+ const tagStr = fieldMatch[3] || "";
251
+ // Skip embedded structs that aren't fields
252
+ if (fieldType.startsWith("*") || fieldType.includes(".")) {
253
+ // Check if it's a relation
254
+ if (trimmed.includes("gorm:\"foreignKey") || trimmed.includes("gorm:\"many2many")) {
255
+ relations.push(`${fieldName}: ${fieldType.replace("*", "").replace("[]", "")}`);
256
+ continue;
257
+ }
258
+ // Check for belongs_to / has_many by type
259
+ if (fieldType.startsWith("[]") || fieldType.startsWith("*")) {
260
+ const relType = fieldType.replace("*", "").replace("[]", "");
261
+ if (relType[0] === relType[0]?.toUpperCase() && !relType.includes(".")) {
262
+ relations.push(`${fieldName}: ${relType}`);
263
+ continue;
264
+ }
265
+ }
266
+ }
267
+ const flags = [];
268
+ // Parse gorm tag
269
+ const gormTag = tagStr.match(/gorm:"([^"]+)"/)?.[1] || "";
270
+ if (gormTag) {
271
+ if (gormTag.includes("primaryKey") || gormTag.includes("primarykey"))
272
+ flags.push("pk");
273
+ if (gormTag.includes("unique"))
274
+ flags.push("unique");
275
+ if (gormTag.includes("not null"))
276
+ flags.push("required");
277
+ if (gormTag.includes("default:"))
278
+ flags.push("default");
279
+ if (gormTag.includes("index"))
280
+ flags.push("index");
281
+ if (gormTag.includes("foreignKey") || gormTag.includes("foreignkey"))
282
+ flags.push("fk");
283
+ }
284
+ fields.push({ name: fieldName, type: fieldType, flags });
285
+ }
286
+ if (fields.length > 0) {
287
+ models.push({
288
+ name,
289
+ fields,
290
+ relations,
291
+ orm: "gorm",
292
+ confidence: "ast",
293
+ });
294
+ }
295
+ }
296
+ return models;
297
+ }
298
+ // ─── Helpers ───
299
+ /**
300
+ * Extract the content between matched braces starting at position.
301
+ * Returns the content inside the braces (not including the opening brace).
302
+ */
303
+ function extractBraceBlock(content, startAfterOpenBrace) {
304
+ let depth = 1;
305
+ let i = startAfterOpenBrace;
306
+ while (i < content.length && depth > 0) {
307
+ if (content[i] === "{")
308
+ depth++;
309
+ else if (content[i] === "}")
310
+ depth--;
311
+ i++;
312
+ }
313
+ if (depth !== 0)
314
+ return null;
315
+ return content.slice(startAfterOpenBrace, i - 1);
316
+ }
317
+ function extractPathParams(path) {
318
+ const params = [];
319
+ // Gin/Echo: :param, Chi: {param}, Go 1.22: {param}
320
+ const regex = /[:{}](\w+)/g;
321
+ let m;
322
+ while ((m = regex.exec(path)) !== null)
323
+ params.push(m[1]);
324
+ return params;
325
+ }
326
+ function normalizePath(path) {
327
+ return path.replace(/\/+/g, "/").replace(/\/$/, "") || "/";
328
+ }
329
+ function escapeRegex(str) {
330
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
331
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Python AST extraction via subprocess.
3
+ * Spawns python3 with an inline script using stdlib `ast` module.
4
+ * Zero dependencies — if the project uses Python, the interpreter is there.
5
+ *
6
+ * Extracts:
7
+ * - FastAPI/Flask route decorators with precise path + method
8
+ * - Django urlpatterns with path() calls
9
+ * - SQLAlchemy model classes with Column types, flags, and relationships
10
+ */
11
+ import type { RouteInfo, SchemaModel, Framework } from "../types.js";
12
+ /**
13
+ * Extract routes from a Python file using AST.
14
+ * Returns routes with confidence: "ast", or null if Python is unavailable.
15
+ */
16
+ export declare function extractPythonRoutesAST(filePath: string, content: string, framework: Framework, tags: string[]): Promise<RouteInfo[] | null>;
17
+ /**
18
+ * Extract SQLAlchemy models from a Python file using AST.
19
+ * Returns models with confidence: "ast", or null if Python is unavailable.
20
+ */
21
+ export declare function extractSQLAlchemyAST(filePath: string, content: string): Promise<SchemaModel[] | null>;
22
+ /**
23
+ * Check if Python 3 is available on this system.
24
+ */
25
+ export declare function isPythonAvailable(): Promise<boolean>;