codeviz 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +446 -1
- package/dist/browser.d.mts +19 -0
- package/dist/browser.d.ts +19 -0
- package/dist/browser.js +7164 -0
- package/dist/browser.mjs +270 -0
- package/dist/chunk-HMMRZXQ4.mjs +7118 -0
- package/dist/index.d.mts +731 -5
- package/dist/index.d.ts +731 -5
- package/dist/index.js +9978 -5
- package/dist/index.mjs +3021 -4
- package/dist/useOverlayPort-dGxZQTue.d.mts +2448 -0
- package/dist/useOverlayPort-dGxZQTue.d.ts +2448 -0
- package/package.json +72 -4
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,3026 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AggregateNode,
|
|
3
|
+
BundledEdge,
|
|
4
|
+
CallEdge,
|
|
5
|
+
CodeMap,
|
|
6
|
+
DEFAULT_ANIMATION_CONFIG,
|
|
7
|
+
DEFAULT_CODEMAP_CONFIG,
|
|
8
|
+
DEFAULT_DARK_SIGMA_THEME,
|
|
9
|
+
DEFAULT_LAYOUT_CONFIG,
|
|
10
|
+
DEFAULT_LIGHT_SIGMA_THEME,
|
|
11
|
+
DEFAULT_SIGMA_THEME,
|
|
12
|
+
DEFAULT_ZOOM_LEVELS,
|
|
13
|
+
EDGE_SIZES,
|
|
14
|
+
FileNode,
|
|
15
|
+
Flow,
|
|
16
|
+
ImportEdge,
|
|
17
|
+
NODE_SIZES,
|
|
18
|
+
OverlayLayer,
|
|
19
|
+
OverlayPort,
|
|
20
|
+
PortalOverlayLayer,
|
|
21
|
+
SVGOverlayLayer,
|
|
22
|
+
ScopeNode,
|
|
23
|
+
SigmaRenderer,
|
|
24
|
+
SymbolNode,
|
|
25
|
+
ThemeProvider,
|
|
26
|
+
assignClusterColors,
|
|
27
|
+
assignInitialPositions,
|
|
28
|
+
boundingBoxFromCorners,
|
|
29
|
+
boundingBoxesOverlap,
|
|
30
|
+
brightenColor,
|
|
31
|
+
buildDirectoryGraph,
|
|
32
|
+
calculateNodeSize,
|
|
33
|
+
calculateOverlayPosition,
|
|
34
|
+
clampPosition,
|
|
35
|
+
codeGraphToGraphology,
|
|
36
|
+
codeMapToReactFlowEdges,
|
|
37
|
+
codeMapToReactFlowNodes,
|
|
38
|
+
computeDirectoryForces,
|
|
39
|
+
computeEdges,
|
|
40
|
+
computeHierarchy,
|
|
41
|
+
computeLayout,
|
|
42
|
+
computeZoomLevels,
|
|
43
|
+
createBoundingBox,
|
|
44
|
+
createEdgeReducer,
|
|
45
|
+
createNodeReducer,
|
|
46
|
+
createOverlayPort,
|
|
47
|
+
createScopeColorGetter,
|
|
48
|
+
darkTheme,
|
|
49
|
+
darken,
|
|
50
|
+
detectCommunities,
|
|
51
|
+
detectLanguage,
|
|
52
|
+
dimColor,
|
|
53
|
+
distance,
|
|
54
|
+
edgeTypes,
|
|
55
|
+
expandBoundingBox,
|
|
56
|
+
formatLoc,
|
|
57
|
+
formatNumber,
|
|
58
|
+
generateCallEdgeId,
|
|
59
|
+
generateDirectoryId,
|
|
60
|
+
generateFileId,
|
|
61
|
+
generateImportEdgeId,
|
|
62
|
+
generateScopeDepthColors,
|
|
63
|
+
generateSymbolId,
|
|
64
|
+
getAnchorOffset,
|
|
65
|
+
getBasename,
|
|
66
|
+
getBoundingBoxCenter,
|
|
67
|
+
getChildScopes,
|
|
68
|
+
getClusterStats,
|
|
69
|
+
getContrastRatio,
|
|
70
|
+
getDefaultExpandedScopes,
|
|
71
|
+
getDefaultTheme,
|
|
72
|
+
getDirectory,
|
|
73
|
+
getDirectoryConnectionWeight,
|
|
74
|
+
getDirectoryPosition,
|
|
75
|
+
getEdgeLabel,
|
|
76
|
+
getEdgesAtZoomLevel,
|
|
77
|
+
getEntityId,
|
|
78
|
+
getExtension,
|
|
79
|
+
getFA2Settings,
|
|
80
|
+
getFileIcon,
|
|
81
|
+
getFilename,
|
|
82
|
+
getFilesInDirectory,
|
|
83
|
+
getIconName,
|
|
84
|
+
getLayoutDuration,
|
|
85
|
+
getLuminance,
|
|
86
|
+
getMediumFileLabel,
|
|
87
|
+
getNodeCluster,
|
|
88
|
+
getNodeKey,
|
|
89
|
+
getNodeMass,
|
|
90
|
+
getNodeOpacityAtLevel,
|
|
91
|
+
getNodeSizeAtLevel,
|
|
92
|
+
getNodesInCluster,
|
|
93
|
+
getParentDirectories,
|
|
94
|
+
getPathDepth,
|
|
95
|
+
getPositionsAtDepth,
|
|
96
|
+
getRootScopes,
|
|
97
|
+
getScaledNodeSize,
|
|
98
|
+
getScopeByDirectoryId,
|
|
99
|
+
getScopeDepthColor,
|
|
100
|
+
getShortFileLabel,
|
|
101
|
+
getSigmaTheme,
|
|
102
|
+
getSymbolIcon,
|
|
103
|
+
getSymbolKindLabel,
|
|
104
|
+
getTextColorForBackground,
|
|
105
|
+
getTheme,
|
|
106
|
+
getThemeIds,
|
|
107
|
+
getVisibleEdges,
|
|
108
|
+
getVisibleNodes,
|
|
109
|
+
getVisibleScopes,
|
|
110
|
+
getZoomLevel,
|
|
111
|
+
getZoomLevelFromZoom,
|
|
112
|
+
graphToScreen,
|
|
113
|
+
hexToRgb,
|
|
114
|
+
hslToHex,
|
|
115
|
+
hslToRgb,
|
|
116
|
+
isCodeFile,
|
|
117
|
+
isPointInBoundingBox,
|
|
118
|
+
lerp,
|
|
119
|
+
lightTheme,
|
|
120
|
+
lighten,
|
|
121
|
+
matchesPattern,
|
|
122
|
+
mergeTheme,
|
|
123
|
+
mix,
|
|
124
|
+
nexusToGraphology,
|
|
125
|
+
nodeTypes,
|
|
126
|
+
normalizePath,
|
|
127
|
+
normalizePositions,
|
|
128
|
+
positionAllNodes,
|
|
129
|
+
positionNodesInScope,
|
|
130
|
+
registerTheme,
|
|
131
|
+
removeAllOverlaps,
|
|
132
|
+
removeOverlapsInScope,
|
|
133
|
+
rgbToHex,
|
|
134
|
+
rgbToHsl,
|
|
135
|
+
rgbaToCss,
|
|
136
|
+
saturate,
|
|
137
|
+
scaleSize,
|
|
138
|
+
screenToGraph,
|
|
139
|
+
shouldUseDarkText,
|
|
140
|
+
shrinkBoundingBox,
|
|
141
|
+
structureToGraphology,
|
|
142
|
+
truncateLabel,
|
|
143
|
+
uniformPadding,
|
|
144
|
+
unionBoundingBoxes,
|
|
145
|
+
useCurrentTheme,
|
|
146
|
+
useIsDarkTheme,
|
|
147
|
+
useLayout,
|
|
148
|
+
useOverlayPort,
|
|
149
|
+
useSigma,
|
|
150
|
+
useTheme,
|
|
151
|
+
withOpacity
|
|
152
|
+
} from "./chunk-HMMRZXQ4.mjs";
|
|
153
|
+
|
|
154
|
+
// src/analyzer/index.ts
|
|
155
|
+
import * as fs5 from "fs";
|
|
156
|
+
import * as path4 from "path";
|
|
157
|
+
|
|
158
|
+
// src/analyzer/scanner.ts
|
|
159
|
+
import * as fs from "fs";
|
|
160
|
+
import * as path from "path";
|
|
161
|
+
import ignore from "ignore";
|
|
162
|
+
|
|
163
|
+
// src/analyzer/grammars/extensions.ts
|
|
164
|
+
var EXTENSION_TO_LANGUAGE = {
|
|
165
|
+
// Go
|
|
166
|
+
".go": "go",
|
|
167
|
+
// Rust
|
|
168
|
+
".rs": "rust",
|
|
169
|
+
// Java
|
|
170
|
+
".java": "java",
|
|
171
|
+
// Kotlin
|
|
172
|
+
".kt": "kotlin",
|
|
173
|
+
".kts": "kotlin",
|
|
174
|
+
// C
|
|
175
|
+
".c": "c",
|
|
176
|
+
".h": "c",
|
|
177
|
+
// C++
|
|
178
|
+
".cpp": "cpp",
|
|
179
|
+
".cc": "cpp",
|
|
180
|
+
".cxx": "cpp",
|
|
181
|
+
".c++": "cpp",
|
|
182
|
+
".hpp": "cpp",
|
|
183
|
+
".hh": "cpp",
|
|
184
|
+
".hxx": "cpp",
|
|
185
|
+
".h++": "cpp",
|
|
186
|
+
// C#
|
|
187
|
+
".cs": "c_sharp",
|
|
188
|
+
// Ruby
|
|
189
|
+
".rb": "ruby",
|
|
190
|
+
".rake": "ruby",
|
|
191
|
+
".gemspec": "ruby",
|
|
192
|
+
".ru": "ruby",
|
|
193
|
+
// PHP
|
|
194
|
+
".php": "php",
|
|
195
|
+
".phtml": "php",
|
|
196
|
+
".php3": "php",
|
|
197
|
+
".php4": "php",
|
|
198
|
+
".php5": "php",
|
|
199
|
+
".phps": "php",
|
|
200
|
+
// Swift
|
|
201
|
+
".swift": "swift",
|
|
202
|
+
// Scala
|
|
203
|
+
".scala": "scala",
|
|
204
|
+
".sc": "scala",
|
|
205
|
+
// Shell/Bash
|
|
206
|
+
".sh": "bash",
|
|
207
|
+
".bash": "bash",
|
|
208
|
+
".zsh": "bash",
|
|
209
|
+
".fish": "fish",
|
|
210
|
+
// Lua
|
|
211
|
+
".lua": "lua",
|
|
212
|
+
// Perl
|
|
213
|
+
".pl": "perl",
|
|
214
|
+
".pm": "perl",
|
|
215
|
+
// R
|
|
216
|
+
".r": "r",
|
|
217
|
+
".R": "r",
|
|
218
|
+
// Haskell
|
|
219
|
+
".hs": "haskell",
|
|
220
|
+
".lhs": "haskell",
|
|
221
|
+
// Elixir
|
|
222
|
+
".ex": "elixir",
|
|
223
|
+
".exs": "elixir",
|
|
224
|
+
// Erlang
|
|
225
|
+
".erl": "erlang",
|
|
226
|
+
".hrl": "erlang",
|
|
227
|
+
// Clojure
|
|
228
|
+
".clj": "clojure",
|
|
229
|
+
".cljs": "clojure",
|
|
230
|
+
".cljc": "clojure",
|
|
231
|
+
".edn": "clojure",
|
|
232
|
+
// OCaml
|
|
233
|
+
".ml": "ocaml",
|
|
234
|
+
".mli": "ocaml",
|
|
235
|
+
// F#
|
|
236
|
+
".fs": "fsharp",
|
|
237
|
+
".fsi": "fsharp",
|
|
238
|
+
".fsx": "fsharp",
|
|
239
|
+
// Julia
|
|
240
|
+
".jl": "julia",
|
|
241
|
+
// Dart
|
|
242
|
+
".dart": "dart",
|
|
243
|
+
// Objective-C
|
|
244
|
+
".m": "objc",
|
|
245
|
+
".mm": "objc",
|
|
246
|
+
// Groovy
|
|
247
|
+
".groovy": "groovy",
|
|
248
|
+
".gradle": "groovy",
|
|
249
|
+
// SQL
|
|
250
|
+
".sql": "sql",
|
|
251
|
+
// GraphQL
|
|
252
|
+
".graphql": "graphql",
|
|
253
|
+
".gql": "graphql",
|
|
254
|
+
// HTML
|
|
255
|
+
".html": "html",
|
|
256
|
+
".htm": "html",
|
|
257
|
+
// CSS
|
|
258
|
+
".css": "css",
|
|
259
|
+
// SCSS/Sass
|
|
260
|
+
".scss": "scss",
|
|
261
|
+
".sass": "scss",
|
|
262
|
+
// YAML
|
|
263
|
+
".yaml": "yaml",
|
|
264
|
+
".yml": "yaml",
|
|
265
|
+
// TOML
|
|
266
|
+
".toml": "toml",
|
|
267
|
+
// JSON (tree-sitter-json)
|
|
268
|
+
".json": "json",
|
|
269
|
+
// XML
|
|
270
|
+
".xml": "xml",
|
|
271
|
+
".xsd": "xml",
|
|
272
|
+
".xsl": "xml",
|
|
273
|
+
".xslt": "xml",
|
|
274
|
+
".svg": "xml",
|
|
275
|
+
// Markdown
|
|
276
|
+
".md": "markdown",
|
|
277
|
+
".markdown": "markdown",
|
|
278
|
+
// LaTeX
|
|
279
|
+
".tex": "latex",
|
|
280
|
+
".latex": "latex",
|
|
281
|
+
// Dockerfile
|
|
282
|
+
dockerfile: "dockerfile",
|
|
283
|
+
// Makefile
|
|
284
|
+
makefile: "make",
|
|
285
|
+
".mk": "make",
|
|
286
|
+
// CMake
|
|
287
|
+
".cmake": "cmake",
|
|
288
|
+
// Terraform/HCL
|
|
289
|
+
".tf": "hcl",
|
|
290
|
+
".hcl": "hcl",
|
|
291
|
+
".tfvars": "hcl",
|
|
292
|
+
// Zig
|
|
293
|
+
".zig": "zig",
|
|
294
|
+
// Nim
|
|
295
|
+
".nim": "nim",
|
|
296
|
+
// V
|
|
297
|
+
".v": "v",
|
|
298
|
+
// Elm
|
|
299
|
+
".elm": "elm",
|
|
300
|
+
// PureScript
|
|
301
|
+
".purs": "purescript",
|
|
302
|
+
// ReasonML
|
|
303
|
+
".re": "reason",
|
|
304
|
+
".rei": "reason",
|
|
305
|
+
// Nix
|
|
306
|
+
".nix": "nix",
|
|
307
|
+
// Solidity
|
|
308
|
+
".sol": "solidity",
|
|
309
|
+
// GLSL (shaders)
|
|
310
|
+
".glsl": "glsl",
|
|
311
|
+
".vert": "glsl",
|
|
312
|
+
".frag": "glsl",
|
|
313
|
+
// WGSL
|
|
314
|
+
".wgsl": "wgsl",
|
|
315
|
+
// Protocol Buffers
|
|
316
|
+
".proto": "proto",
|
|
317
|
+
// Thrift
|
|
318
|
+
".thrift": "thrift"
|
|
319
|
+
};
|
|
320
|
+
var FILENAME_TO_LANGUAGE = {
|
|
321
|
+
dockerfile: "dockerfile",
|
|
322
|
+
makefile: "make",
|
|
323
|
+
gnumakefile: "make",
|
|
324
|
+
cmakelists: "cmake",
|
|
325
|
+
rakefile: "ruby",
|
|
326
|
+
gemfile: "ruby",
|
|
327
|
+
podfile: "ruby",
|
|
328
|
+
vagrantfile: "ruby",
|
|
329
|
+
brewfile: "ruby"
|
|
330
|
+
};
|
|
331
|
+
function getLanguageForFile(filePath) {
|
|
332
|
+
const basename2 = filePath.split(/[/\\]/).pop() || "";
|
|
333
|
+
const lowerBasename = basename2.toLowerCase();
|
|
334
|
+
const basenameNoExt = lowerBasename.replace(/\.[^.]+$/, "");
|
|
335
|
+
if (FILENAME_TO_LANGUAGE[basenameNoExt]) {
|
|
336
|
+
return FILENAME_TO_LANGUAGE[basenameNoExt];
|
|
337
|
+
}
|
|
338
|
+
const lastDot = basename2.lastIndexOf(".");
|
|
339
|
+
if (lastDot === -1) {
|
|
340
|
+
return FILENAME_TO_LANGUAGE[lowerBasename] || null;
|
|
341
|
+
}
|
|
342
|
+
const ext = basename2.slice(lastDot).toLowerCase();
|
|
343
|
+
return EXTENSION_TO_LANGUAGE[ext] || null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/analyzer/languages/index.ts
|
|
347
|
+
var LanguageRegistry = class {
|
|
348
|
+
loaders = /* @__PURE__ */ new Map();
|
|
349
|
+
loaded = /* @__PURE__ */ new Map();
|
|
350
|
+
extensionMap = /* @__PURE__ */ new Map();
|
|
351
|
+
constructor() {
|
|
352
|
+
this.registerBuiltinLanguages();
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Register built-in language loaders
|
|
356
|
+
*/
|
|
357
|
+
registerBuiltinLanguages() {
|
|
358
|
+
this.registerLoader({
|
|
359
|
+
name: "typescript",
|
|
360
|
+
extensions: [".ts", ".tsx"],
|
|
361
|
+
load: async () => {
|
|
362
|
+
const lang = await import("tree-sitter-typescript");
|
|
363
|
+
return lang.default.typescript;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
this.registerLoader({
|
|
367
|
+
name: "javascript",
|
|
368
|
+
extensions: [".js", ".jsx", ".mjs", ".cjs"],
|
|
369
|
+
load: async () => {
|
|
370
|
+
const lang = await import("tree-sitter-javascript");
|
|
371
|
+
return lang.default;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
this.registerLoader({
|
|
375
|
+
name: "python",
|
|
376
|
+
extensions: [".py", ".pyi"],
|
|
377
|
+
load: async () => {
|
|
378
|
+
const lang = await import("tree-sitter-python");
|
|
379
|
+
return lang.default;
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Register a language loader
|
|
385
|
+
*/
|
|
386
|
+
registerLoader(loader) {
|
|
387
|
+
this.loaders.set(loader.name, loader);
|
|
388
|
+
for (const ext of loader.extensions) {
|
|
389
|
+
this.extensionMap.set(ext, loader.name);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Get language by name, loading if necessary
|
|
394
|
+
*/
|
|
395
|
+
async getLanguage(name) {
|
|
396
|
+
if (this.loaded.has(name)) {
|
|
397
|
+
return this.loaded.get(name);
|
|
398
|
+
}
|
|
399
|
+
const loader = this.loaders.get(name);
|
|
400
|
+
if (!loader) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
const grammar = await loader.load();
|
|
404
|
+
const language = {
|
|
405
|
+
name: loader.name,
|
|
406
|
+
extensions: loader.extensions,
|
|
407
|
+
grammar
|
|
408
|
+
};
|
|
409
|
+
this.loaded.set(name, language);
|
|
410
|
+
return language;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Get language by file extension
|
|
414
|
+
*/
|
|
415
|
+
async getLanguageForExtension(extension) {
|
|
416
|
+
const langName = this.extensionMap.get(extension);
|
|
417
|
+
if (!langName) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
return this.getLanguage(langName);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Get language for a file path
|
|
424
|
+
*/
|
|
425
|
+
async getLanguageForFile(filePath) {
|
|
426
|
+
const ext = this.getExtension(filePath);
|
|
427
|
+
if (!ext) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
return this.getLanguageForExtension(ext);
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Extract file extension from path
|
|
434
|
+
*/
|
|
435
|
+
getExtension(filePath) {
|
|
436
|
+
const lastDot = filePath.lastIndexOf(".");
|
|
437
|
+
if (lastDot === -1 || lastDot === filePath.length - 1) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
return filePath.slice(lastDot);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Get list of supported languages
|
|
444
|
+
*/
|
|
445
|
+
getSupportedLanguages() {
|
|
446
|
+
return Array.from(this.loaders.keys());
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Get list of supported extensions
|
|
450
|
+
*/
|
|
451
|
+
getSupportedExtensions() {
|
|
452
|
+
return Array.from(this.extensionMap.keys());
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Check if a file extension is supported (native only)
|
|
456
|
+
*/
|
|
457
|
+
isSupported(extension) {
|
|
458
|
+
return this.extensionMap.has(extension);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Check if a file extension is supported by WASM grammars
|
|
462
|
+
*/
|
|
463
|
+
isWasmSupported(extension) {
|
|
464
|
+
return extension in EXTENSION_TO_LANGUAGE;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Check if a file is supported based on its path
|
|
468
|
+
* Considers both native tree-sitter and WASM-based grammars
|
|
469
|
+
*/
|
|
470
|
+
isFileSupported(filePath) {
|
|
471
|
+
const ext = this.getExtension(filePath);
|
|
472
|
+
if (ext !== null && this.isSupported(ext)) {
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
const langName = getLanguageForFile(filePath);
|
|
476
|
+
return langName !== null;
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
var registry = null;
|
|
480
|
+
function getLanguageRegistry() {
|
|
481
|
+
if (!registry) {
|
|
482
|
+
registry = new LanguageRegistry();
|
|
483
|
+
}
|
|
484
|
+
return registry;
|
|
485
|
+
}
|
|
486
|
+
function resetLanguageRegistry() {
|
|
487
|
+
registry = null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/analyzer/scanner.ts
|
|
491
|
+
var DEFAULT_CONFIG = {
|
|
492
|
+
include: [],
|
|
493
|
+
exclude: [],
|
|
494
|
+
respectGitignore: true,
|
|
495
|
+
maxDepth: Infinity,
|
|
496
|
+
maxFiles: 1e4
|
|
497
|
+
};
|
|
498
|
+
var MAX_FILE_SIZE = 1024 * 1024;
|
|
499
|
+
var DirectoryScanner = class {
|
|
500
|
+
config;
|
|
501
|
+
ignoreStack = [];
|
|
502
|
+
files = [];
|
|
503
|
+
directories = [];
|
|
504
|
+
skipped = [];
|
|
505
|
+
fileCount = 0;
|
|
506
|
+
constructor(config) {
|
|
507
|
+
this.config = {
|
|
508
|
+
...DEFAULT_CONFIG,
|
|
509
|
+
...config,
|
|
510
|
+
rootPath: path.resolve(config.rootPath)
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Scan the directory tree
|
|
515
|
+
*/
|
|
516
|
+
async scan() {
|
|
517
|
+
this.files = [];
|
|
518
|
+
this.directories = [];
|
|
519
|
+
this.skipped = [];
|
|
520
|
+
this.fileCount = 0;
|
|
521
|
+
this.ignoreStack = [];
|
|
522
|
+
if (!fs.existsSync(this.config.rootPath)) {
|
|
523
|
+
throw new Error(`Root path does not exist: ${this.config.rootPath}`);
|
|
524
|
+
}
|
|
525
|
+
const stats = fs.statSync(this.config.rootPath);
|
|
526
|
+
if (!stats.isDirectory()) {
|
|
527
|
+
throw new Error(`Root path is not a directory: ${this.config.rootPath}`);
|
|
528
|
+
}
|
|
529
|
+
await this.scanDirectory(this.config.rootPath, 0, null);
|
|
530
|
+
return {
|
|
531
|
+
directories: this.directories,
|
|
532
|
+
files: this.files,
|
|
533
|
+
skipped: this.skipped,
|
|
534
|
+
rootPath: this.config.rootPath
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Recursively scan a directory
|
|
539
|
+
*/
|
|
540
|
+
async scanDirectory(dirPath, depth, parentId) {
|
|
541
|
+
if (depth > this.config.maxDepth) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (this.config.respectGitignore) {
|
|
545
|
+
await this.loadGitignore(dirPath);
|
|
546
|
+
}
|
|
547
|
+
const relativePath = path.relative(this.config.rootPath, dirPath);
|
|
548
|
+
const dirId = generateDirectoryId(relativePath || ".");
|
|
549
|
+
const dirName = path.basename(dirPath) || path.basename(this.config.rootPath);
|
|
550
|
+
const dirNode = {
|
|
551
|
+
id: dirId,
|
|
552
|
+
name: dirName,
|
|
553
|
+
path: relativePath || ".",
|
|
554
|
+
parentId,
|
|
555
|
+
children: [],
|
|
556
|
+
files: [],
|
|
557
|
+
metrics: {
|
|
558
|
+
fileCount: 0,
|
|
559
|
+
totalLoc: 0
|
|
560
|
+
},
|
|
561
|
+
depth
|
|
562
|
+
};
|
|
563
|
+
this.directories.push(dirNode);
|
|
564
|
+
let entries;
|
|
565
|
+
try {
|
|
566
|
+
entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
567
|
+
} catch {
|
|
568
|
+
this.skipped.push({ path: relativePath, reason: "permission" });
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
572
|
+
for (const entry of entries) {
|
|
573
|
+
if (this.fileCount >= this.config.maxFiles) {
|
|
574
|
+
this.skipped.push({
|
|
575
|
+
path: path.join(relativePath, entry.name),
|
|
576
|
+
reason: "max-files"
|
|
577
|
+
});
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
581
|
+
const entryRelativePath = path.join(relativePath, entry.name);
|
|
582
|
+
if (this.isIgnored(entryRelativePath, entry.isDirectory())) {
|
|
583
|
+
this.skipped.push({ path: entryRelativePath, reason: "gitignore" });
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (this.isExcluded(entryRelativePath)) {
|
|
587
|
+
this.skipped.push({ path: entryRelativePath, reason: "excluded" });
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
if (entry.isDirectory()) {
|
|
591
|
+
if (this.shouldSkipDirectory(entry.name)) {
|
|
592
|
+
this.skipped.push({ path: entryRelativePath, reason: "excluded" });
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
dirNode.children.push(generateDirectoryId(entryRelativePath));
|
|
596
|
+
await this.scanDirectory(entryPath, depth + 1, dirId);
|
|
597
|
+
} else if (entry.isFile()) {
|
|
598
|
+
const fileNode = await this.processFile(entryPath, entryRelativePath, dirId);
|
|
599
|
+
if (fileNode) {
|
|
600
|
+
dirNode.files.push(fileNode.id);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (this.config.respectGitignore) {
|
|
605
|
+
this.popGitignore(dirPath);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Process a single file
|
|
610
|
+
*/
|
|
611
|
+
async processFile(filePath, relativePath, parentId) {
|
|
612
|
+
const registry2 = getLanguageRegistry();
|
|
613
|
+
if (!registry2.isFileSupported(filePath)) {
|
|
614
|
+
this.skipped.push({ path: relativePath, reason: "not-code" });
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
let stats;
|
|
618
|
+
try {
|
|
619
|
+
stats = fs.statSync(filePath);
|
|
620
|
+
} catch {
|
|
621
|
+
this.skipped.push({ path: relativePath, reason: "permission" });
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
625
|
+
this.skipped.push({ path: relativePath, reason: "too-large" });
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
const language = detectLanguage(filePath) || "unknown";
|
|
629
|
+
const extension = getExtension(filePath) || "";
|
|
630
|
+
const fileId = generateFileId(relativePath);
|
|
631
|
+
const fileNode = {
|
|
632
|
+
id: fileId,
|
|
633
|
+
name: path.basename(filePath),
|
|
634
|
+
path: relativePath,
|
|
635
|
+
extension,
|
|
636
|
+
directoryId: parentId,
|
|
637
|
+
language,
|
|
638
|
+
symbols: [],
|
|
639
|
+
// Populated during symbol extraction
|
|
640
|
+
metrics: {
|
|
641
|
+
loc: 0,
|
|
642
|
+
// Populated during symbol extraction
|
|
643
|
+
totalLines: 0,
|
|
644
|
+
exportCount: 0,
|
|
645
|
+
importCount: 0
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
this.files.push(fileNode);
|
|
649
|
+
this.fileCount++;
|
|
650
|
+
return fileNode;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Load .gitignore file from directory
|
|
654
|
+
*/
|
|
655
|
+
async loadGitignore(dirPath) {
|
|
656
|
+
const gitignorePath = path.join(dirPath, ".gitignore");
|
|
657
|
+
if (fs.existsSync(gitignorePath)) {
|
|
658
|
+
try {
|
|
659
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
660
|
+
const ig = ignore().add(content);
|
|
661
|
+
this.ignoreStack.push({ path: dirPath, ig });
|
|
662
|
+
} catch {
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Pop gitignore when leaving directory
|
|
668
|
+
*/
|
|
669
|
+
popGitignore(dirPath) {
|
|
670
|
+
if (this.ignoreStack.length > 0 && this.ignoreStack[this.ignoreStack.length - 1].path === dirPath) {
|
|
671
|
+
this.ignoreStack.pop();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Check if a path is ignored by any gitignore in the stack
|
|
676
|
+
*/
|
|
677
|
+
isIgnored(relativePath, isDirectory) {
|
|
678
|
+
for (const { ig, path: igPath } of this.ignoreStack) {
|
|
679
|
+
const relativeToGitignore = path.relative(
|
|
680
|
+
path.relative(this.config.rootPath, igPath),
|
|
681
|
+
relativePath
|
|
682
|
+
);
|
|
683
|
+
if (relativeToGitignore && !relativeToGitignore.startsWith("..")) {
|
|
684
|
+
if (ig.ignores(isDirectory ? relativeToGitignore + "/" : relativeToGitignore)) {
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Check if path matches exclude patterns
|
|
693
|
+
*/
|
|
694
|
+
isExcluded(relativePath) {
|
|
695
|
+
if (this.config.exclude.length === 0) {
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
for (const pattern of this.config.exclude) {
|
|
699
|
+
if (this.matchGlob(relativePath, pattern)) {
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Simple glob matching
|
|
707
|
+
*/
|
|
708
|
+
matchGlob(filePath, pattern) {
|
|
709
|
+
const regex = pattern.replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/{{GLOBSTAR}}/g, ".*").replace(/\?/g, ".");
|
|
710
|
+
return new RegExp(`^${regex}$`).test(filePath);
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Check if directory should be skipped (common non-code directories)
|
|
714
|
+
*/
|
|
715
|
+
shouldSkipDirectory(name) {
|
|
716
|
+
const skipDirs = [
|
|
717
|
+
"node_modules",
|
|
718
|
+
".git",
|
|
719
|
+
".svn",
|
|
720
|
+
".hg",
|
|
721
|
+
"__pycache__",
|
|
722
|
+
".pytest_cache",
|
|
723
|
+
".mypy_cache",
|
|
724
|
+
"dist",
|
|
725
|
+
"build",
|
|
726
|
+
"out",
|
|
727
|
+
".next",
|
|
728
|
+
".nuxt",
|
|
729
|
+
"coverage",
|
|
730
|
+
".nyc_output",
|
|
731
|
+
"vendor",
|
|
732
|
+
".venv",
|
|
733
|
+
"venv",
|
|
734
|
+
"env",
|
|
735
|
+
".env",
|
|
736
|
+
".idea",
|
|
737
|
+
".vscode"
|
|
738
|
+
];
|
|
739
|
+
return skipDirs.includes(name);
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
async function scanDirectory(config) {
|
|
743
|
+
const scanner = new DirectoryScanner(config);
|
|
744
|
+
return scanner.scan();
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/analyzer/symbols.ts
|
|
748
|
+
import * as fs3 from "fs";
|
|
749
|
+
|
|
750
|
+
// src/analyzer/parser.ts
|
|
751
|
+
import Parser2 from "tree-sitter";
|
|
752
|
+
|
|
753
|
+
// src/analyzer/grammars/index.ts
|
|
754
|
+
import { Parser, Language } from "web-tree-sitter";
|
|
755
|
+
import * as fs2 from "fs";
|
|
756
|
+
import * as path2 from "path";
|
|
757
|
+
var WasmGrammarLoader = class {
|
|
758
|
+
parser = null;
|
|
759
|
+
initialized = false;
|
|
760
|
+
initPromise = null;
|
|
761
|
+
grammars = /* @__PURE__ */ new Map();
|
|
762
|
+
grammarPaths = /* @__PURE__ */ new Map();
|
|
763
|
+
discoveredGrammars = /* @__PURE__ */ new Set();
|
|
764
|
+
/**
|
|
765
|
+
* Initialize the WASM parser (lazy, singleton)
|
|
766
|
+
*/
|
|
767
|
+
async initialize() {
|
|
768
|
+
if (this.initialized) return;
|
|
769
|
+
if (this.initPromise) {
|
|
770
|
+
return this.initPromise;
|
|
771
|
+
}
|
|
772
|
+
this.initPromise = this.doInitialize();
|
|
773
|
+
return this.initPromise;
|
|
774
|
+
}
|
|
775
|
+
async doInitialize() {
|
|
776
|
+
try {
|
|
777
|
+
await Parser.init();
|
|
778
|
+
this.parser = new Parser();
|
|
779
|
+
this.initialized = true;
|
|
780
|
+
await this.discoverGrammars();
|
|
781
|
+
} catch (error) {
|
|
782
|
+
this.initialized = false;
|
|
783
|
+
this.initPromise = null;
|
|
784
|
+
throw new Error(
|
|
785
|
+
`Failed to initialize WASM parser: ${error instanceof Error ? error.message : String(error)}`
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Discover available WASM grammars from known locations
|
|
791
|
+
*/
|
|
792
|
+
async discoverGrammars() {
|
|
793
|
+
const searchPaths = this.getGrammarSearchPaths();
|
|
794
|
+
for (const searchPath of searchPaths) {
|
|
795
|
+
if (!fs2.existsSync(searchPath)) continue;
|
|
796
|
+
try {
|
|
797
|
+
const files = fs2.readdirSync(searchPath);
|
|
798
|
+
for (const file of files) {
|
|
799
|
+
if (file.endsWith(".wasm") && file.startsWith("tree-sitter-")) {
|
|
800
|
+
const langName = file.replace("tree-sitter-", "").replace(".wasm", "");
|
|
801
|
+
const fullPath = path2.join(searchPath, file);
|
|
802
|
+
this.grammarPaths.set(langName, fullPath);
|
|
803
|
+
this.discoveredGrammars.add(langName);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
} catch {
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Get paths to search for WASM grammars
|
|
812
|
+
*/
|
|
813
|
+
getGrammarSearchPaths() {
|
|
814
|
+
const paths = [];
|
|
815
|
+
const bundledPath = path2.join(__dirname, "wasm");
|
|
816
|
+
paths.push(bundledPath);
|
|
817
|
+
const cwd = process.cwd();
|
|
818
|
+
const userPath = path2.join(cwd, ".codeviz", "grammars");
|
|
819
|
+
paths.push(userPath);
|
|
820
|
+
const nodeModulesPath = path2.join(cwd, "node_modules");
|
|
821
|
+
if (fs2.existsSync(nodeModulesPath)) {
|
|
822
|
+
const wasmsPkgPath = path2.join(nodeModulesPath, "tree-sitter-wasms", "out");
|
|
823
|
+
if (fs2.existsSync(wasmsPkgPath)) {
|
|
824
|
+
paths.push(wasmsPkgPath);
|
|
825
|
+
}
|
|
826
|
+
const potentialPackages = [
|
|
827
|
+
"tree-sitter-go",
|
|
828
|
+
"tree-sitter-rust",
|
|
829
|
+
"tree-sitter-java",
|
|
830
|
+
"tree-sitter-c",
|
|
831
|
+
"tree-sitter-ruby",
|
|
832
|
+
"tree-sitter-cpp",
|
|
833
|
+
"tree-sitter-c-sharp",
|
|
834
|
+
"tree-sitter-php",
|
|
835
|
+
"tree-sitter-swift",
|
|
836
|
+
"tree-sitter-kotlin"
|
|
837
|
+
];
|
|
838
|
+
for (const pkg of potentialPackages) {
|
|
839
|
+
const pkgPath = path2.join(nodeModulesPath, pkg);
|
|
840
|
+
if (fs2.existsSync(pkgPath)) {
|
|
841
|
+
paths.push(pkgPath);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return paths;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Check if a grammar is available for a language
|
|
849
|
+
*/
|
|
850
|
+
async hasGrammar(language) {
|
|
851
|
+
await this.initialize();
|
|
852
|
+
return this.grammarPaths.has(language) || this.grammars.has(language);
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Load a grammar by language name
|
|
856
|
+
*/
|
|
857
|
+
async loadGrammar(language) {
|
|
858
|
+
await this.initialize();
|
|
859
|
+
if (this.grammars.has(language)) {
|
|
860
|
+
return this.grammars.get(language);
|
|
861
|
+
}
|
|
862
|
+
const grammarPath = this.grammarPaths.get(language);
|
|
863
|
+
if (!grammarPath) {
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
try {
|
|
867
|
+
const grammar = await Language.load(grammarPath);
|
|
868
|
+
this.grammars.set(language, grammar);
|
|
869
|
+
return grammar;
|
|
870
|
+
} catch (error) {
|
|
871
|
+
console.warn(
|
|
872
|
+
`Failed to load grammar for ${language}: ${error instanceof Error ? error.message : String(error)}`
|
|
873
|
+
);
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Get the parser instance (must be initialized first)
|
|
879
|
+
*/
|
|
880
|
+
getParser() {
|
|
881
|
+
return this.parser;
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Parse content with a specific language grammar
|
|
885
|
+
*/
|
|
886
|
+
async parse(content, language) {
|
|
887
|
+
const grammar = await this.loadGrammar(language);
|
|
888
|
+
if (!grammar || !this.parser) {
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
this.parser.setLanguage(grammar);
|
|
892
|
+
return this.parser.parse(content);
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* List all available grammar languages
|
|
896
|
+
*/
|
|
897
|
+
async listAvailableGrammars() {
|
|
898
|
+
await this.initialize();
|
|
899
|
+
return Array.from(this.discoveredGrammars);
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Add a grammar path manually
|
|
903
|
+
*/
|
|
904
|
+
addGrammarPath(language, wasmPath) {
|
|
905
|
+
if (fs2.existsSync(wasmPath)) {
|
|
906
|
+
this.grammarPaths.set(language, wasmPath);
|
|
907
|
+
this.discoveredGrammars.add(language);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Check if the loader is initialized
|
|
912
|
+
*/
|
|
913
|
+
isInitialized() {
|
|
914
|
+
return this.initialized;
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Reset the loader (useful for testing)
|
|
918
|
+
*/
|
|
919
|
+
reset() {
|
|
920
|
+
this.parser = null;
|
|
921
|
+
this.initialized = false;
|
|
922
|
+
this.initPromise = null;
|
|
923
|
+
this.grammars.clear();
|
|
924
|
+
this.grammarPaths.clear();
|
|
925
|
+
this.discoveredGrammars.clear();
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
var loaderInstance = null;
|
|
929
|
+
function getWasmGrammarLoader() {
|
|
930
|
+
if (!loaderInstance) {
|
|
931
|
+
loaderInstance = new WasmGrammarLoader();
|
|
932
|
+
}
|
|
933
|
+
return loaderInstance;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// src/analyzer/parser.ts
|
|
937
|
+
var ParserManager = class {
|
|
938
|
+
parsers = /* @__PURE__ */ new Map();
|
|
939
|
+
treeCache = /* @__PURE__ */ new Map();
|
|
940
|
+
maxCacheSize;
|
|
941
|
+
constructor(maxCacheSize = 1e3) {
|
|
942
|
+
this.maxCacheSize = maxCacheSize;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Get or create a parser for the specified language
|
|
946
|
+
*/
|
|
947
|
+
async getParser(language) {
|
|
948
|
+
const langName = language.name;
|
|
949
|
+
if (this.parsers.has(langName)) {
|
|
950
|
+
return this.parsers.get(langName);
|
|
951
|
+
}
|
|
952
|
+
const parser = new Parser2();
|
|
953
|
+
parser.setLanguage(language.grammar);
|
|
954
|
+
this.parsers.set(langName, parser);
|
|
955
|
+
return parser;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Parse source code and return the syntax tree
|
|
959
|
+
*/
|
|
960
|
+
async parse(filePath, content, language) {
|
|
961
|
+
const cached = this.treeCache.get(filePath);
|
|
962
|
+
if (cached) {
|
|
963
|
+
return cached;
|
|
964
|
+
}
|
|
965
|
+
const parser = await this.getParser(language);
|
|
966
|
+
const startTime = performance.now();
|
|
967
|
+
const tree = parser.parse(content);
|
|
968
|
+
const parseTime = performance.now() - startTime;
|
|
969
|
+
const result = {
|
|
970
|
+
tree,
|
|
971
|
+
language: language.name,
|
|
972
|
+
filePath,
|
|
973
|
+
parseTime
|
|
974
|
+
};
|
|
975
|
+
this.addToCache(filePath, result);
|
|
976
|
+
return result;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Parse with error handling - returns result or error
|
|
980
|
+
*/
|
|
981
|
+
async safeParse(filePath, content, language) {
|
|
982
|
+
try {
|
|
983
|
+
const result = await this.parse(filePath, content, language);
|
|
984
|
+
return { result };
|
|
985
|
+
} catch (err) {
|
|
986
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
987
|
+
return {
|
|
988
|
+
error: {
|
|
989
|
+
message,
|
|
990
|
+
filePath,
|
|
991
|
+
language: language.name
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Add parsed tree to cache with LRU eviction
|
|
998
|
+
*/
|
|
999
|
+
addToCache(filePath, tree) {
|
|
1000
|
+
if (this.treeCache.size >= this.maxCacheSize) {
|
|
1001
|
+
const firstKey = this.treeCache.keys().next().value;
|
|
1002
|
+
if (firstKey) {
|
|
1003
|
+
this.treeCache.delete(firstKey);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
this.treeCache.set(filePath, tree);
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Clear the cache for a specific file (e.g., when file changes)
|
|
1010
|
+
*/
|
|
1011
|
+
invalidate(filePath) {
|
|
1012
|
+
this.treeCache.delete(filePath);
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Clear all cached trees
|
|
1016
|
+
*/
|
|
1017
|
+
clearCache() {
|
|
1018
|
+
this.treeCache.clear();
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Get cache statistics
|
|
1022
|
+
*/
|
|
1023
|
+
getCacheStats() {
|
|
1024
|
+
return {
|
|
1025
|
+
size: this.treeCache.size,
|
|
1026
|
+
maxSize: this.maxCacheSize
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
var defaultParserManager = null;
|
|
1031
|
+
function getParserManager() {
|
|
1032
|
+
if (!defaultParserManager) {
|
|
1033
|
+
defaultParserManager = new ParserManager();
|
|
1034
|
+
}
|
|
1035
|
+
return defaultParserManager;
|
|
1036
|
+
}
|
|
1037
|
+
function resetParserManager() {
|
|
1038
|
+
if (defaultParserManager) {
|
|
1039
|
+
defaultParserManager.clearCache();
|
|
1040
|
+
defaultParserManager = null;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
var NATIVE_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "javascript", "python"]);
|
|
1044
|
+
var HybridParserManager = class {
|
|
1045
|
+
nativeManager;
|
|
1046
|
+
treeCache = /* @__PURE__ */ new Map();
|
|
1047
|
+
maxCacheSize;
|
|
1048
|
+
constructor(maxCacheSize = 1e3) {
|
|
1049
|
+
this.nativeManager = new ParserManager(maxCacheSize);
|
|
1050
|
+
this.maxCacheSize = maxCacheSize;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Check if a language has a native parser
|
|
1054
|
+
*/
|
|
1055
|
+
hasNativeParser(language) {
|
|
1056
|
+
return NATIVE_LANGUAGES.has(language);
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Check if a WASM grammar is available for a language
|
|
1060
|
+
*/
|
|
1061
|
+
async hasWasmGrammar(language) {
|
|
1062
|
+
const wasmLoader = getWasmGrammarLoader();
|
|
1063
|
+
return wasmLoader.hasGrammar(language);
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Detect language from file path
|
|
1067
|
+
*/
|
|
1068
|
+
detectLanguage(filePath) {
|
|
1069
|
+
return getLanguageForFile(filePath);
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Parse a file using the appropriate parser
|
|
1073
|
+
* Returns null if no parser is available for the language
|
|
1074
|
+
*/
|
|
1075
|
+
async parse(filePath, content, nativeLanguage) {
|
|
1076
|
+
const cached = this.treeCache.get(filePath);
|
|
1077
|
+
if (cached) {
|
|
1078
|
+
return cached;
|
|
1079
|
+
}
|
|
1080
|
+
const langName = nativeLanguage?.name || this.detectLanguage(filePath);
|
|
1081
|
+
if (!langName) {
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
if (nativeLanguage && this.hasNativeParser(langName)) {
|
|
1085
|
+
const parsed = await this.nativeManager.parse(filePath, content, nativeLanguage);
|
|
1086
|
+
const result = {
|
|
1087
|
+
tree: parsed.tree,
|
|
1088
|
+
parserType: "native",
|
|
1089
|
+
language: langName,
|
|
1090
|
+
filePath,
|
|
1091
|
+
parseTime: parsed.parseTime
|
|
1092
|
+
};
|
|
1093
|
+
this.addToCache(filePath, result);
|
|
1094
|
+
return result;
|
|
1095
|
+
}
|
|
1096
|
+
const wasmLoader = getWasmGrammarLoader();
|
|
1097
|
+
if (await wasmLoader.hasGrammar(langName)) {
|
|
1098
|
+
const startTime = performance.now();
|
|
1099
|
+
const tree = await wasmLoader.parse(content, langName);
|
|
1100
|
+
const parseTime = performance.now() - startTime;
|
|
1101
|
+
if (tree) {
|
|
1102
|
+
const result = {
|
|
1103
|
+
tree,
|
|
1104
|
+
parserType: "wasm",
|
|
1105
|
+
language: langName,
|
|
1106
|
+
filePath,
|
|
1107
|
+
parseTime
|
|
1108
|
+
};
|
|
1109
|
+
this.addToCache(filePath, result);
|
|
1110
|
+
return result;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Parse with error handling
|
|
1117
|
+
*/
|
|
1118
|
+
async safeParse(filePath, content, nativeLanguage) {
|
|
1119
|
+
try {
|
|
1120
|
+
const result = await this.parse(filePath, content, nativeLanguage);
|
|
1121
|
+
if (!result) {
|
|
1122
|
+
const langName = nativeLanguage?.name || this.detectLanguage(filePath) || "unknown";
|
|
1123
|
+
return {
|
|
1124
|
+
error: {
|
|
1125
|
+
message: `No parser available for language: ${langName}`,
|
|
1126
|
+
filePath,
|
|
1127
|
+
language: langName
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
return { result };
|
|
1132
|
+
} catch (err) {
|
|
1133
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1134
|
+
return {
|
|
1135
|
+
error: {
|
|
1136
|
+
message,
|
|
1137
|
+
filePath,
|
|
1138
|
+
language: nativeLanguage?.name || this.detectLanguage(filePath) || void 0
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* Check if a file can be parsed (has available parser)
|
|
1145
|
+
*/
|
|
1146
|
+
async canParse(filePath) {
|
|
1147
|
+
const langName = this.detectLanguage(filePath);
|
|
1148
|
+
if (!langName) {
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
if (this.hasNativeParser(langName)) {
|
|
1152
|
+
return true;
|
|
1153
|
+
}
|
|
1154
|
+
return this.hasWasmGrammar(langName);
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Get the parser type that would be used for a file
|
|
1158
|
+
*/
|
|
1159
|
+
async getParserType(filePath) {
|
|
1160
|
+
const langName = this.detectLanguage(filePath);
|
|
1161
|
+
if (!langName) {
|
|
1162
|
+
return null;
|
|
1163
|
+
}
|
|
1164
|
+
if (this.hasNativeParser(langName)) {
|
|
1165
|
+
return "native";
|
|
1166
|
+
}
|
|
1167
|
+
if (await this.hasWasmGrammar(langName)) {
|
|
1168
|
+
return "wasm";
|
|
1169
|
+
}
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Add to cache with LRU eviction
|
|
1174
|
+
*/
|
|
1175
|
+
addToCache(filePath, result) {
|
|
1176
|
+
if (this.treeCache.size >= this.maxCacheSize) {
|
|
1177
|
+
const firstKey = this.treeCache.keys().next().value;
|
|
1178
|
+
if (firstKey) {
|
|
1179
|
+
this.treeCache.delete(firstKey);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
this.treeCache.set(filePath, result);
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Clear cache for a specific file
|
|
1186
|
+
*/
|
|
1187
|
+
invalidate(filePath) {
|
|
1188
|
+
this.treeCache.delete(filePath);
|
|
1189
|
+
this.nativeManager.invalidate(filePath);
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Clear all caches
|
|
1193
|
+
*/
|
|
1194
|
+
clearCache() {
|
|
1195
|
+
this.treeCache.clear();
|
|
1196
|
+
this.nativeManager.clearCache();
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Get cache statistics
|
|
1200
|
+
*/
|
|
1201
|
+
getCacheStats() {
|
|
1202
|
+
const nativeStats = this.nativeManager.getCacheStats();
|
|
1203
|
+
return {
|
|
1204
|
+
hybridSize: this.treeCache.size,
|
|
1205
|
+
nativeSize: nativeStats.size,
|
|
1206
|
+
maxSize: this.maxCacheSize
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
var defaultHybridManager = null;
|
|
1211
|
+
function getHybridParserManager() {
|
|
1212
|
+
if (!defaultHybridManager) {
|
|
1213
|
+
defaultHybridManager = new HybridParserManager();
|
|
1214
|
+
}
|
|
1215
|
+
return defaultHybridManager;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// src/analyzer/languages/base.ts
|
|
1219
|
+
function nodeToLocation(node) {
|
|
1220
|
+
return {
|
|
1221
|
+
startLine: node.startPosition.row + 1,
|
|
1222
|
+
endLine: node.endPosition.row + 1,
|
|
1223
|
+
startColumn: node.startPosition.column,
|
|
1224
|
+
endColumn: node.endPosition.column
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
function getNodeText(node) {
|
|
1228
|
+
return node.text;
|
|
1229
|
+
}
|
|
1230
|
+
function findChild(node, type) {
|
|
1231
|
+
for (const child of node.children) {
|
|
1232
|
+
if (child.type === type) {
|
|
1233
|
+
return child;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
function findChildren(node, type) {
|
|
1239
|
+
return node.children.filter((child) => child.type === type);
|
|
1240
|
+
}
|
|
1241
|
+
function findChildByTypes(node, types) {
|
|
1242
|
+
for (const child of node.children) {
|
|
1243
|
+
if (types.includes(child.type)) {
|
|
1244
|
+
return child;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return null;
|
|
1248
|
+
}
|
|
1249
|
+
function collectNodes(root, types) {
|
|
1250
|
+
const result = [];
|
|
1251
|
+
function walk(node) {
|
|
1252
|
+
if (types.includes(node.type)) {
|
|
1253
|
+
result.push(node);
|
|
1254
|
+
}
|
|
1255
|
+
for (const child of node.children) {
|
|
1256
|
+
walk(child);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
walk(root);
|
|
1260
|
+
return result;
|
|
1261
|
+
}
|
|
1262
|
+
function calculateMetrics(content, commentPatterns) {
|
|
1263
|
+
const lines = content.split("\n");
|
|
1264
|
+
const totalLines = lines.length;
|
|
1265
|
+
let blankLines = 0;
|
|
1266
|
+
let commentLines = 0;
|
|
1267
|
+
let inBlockComment = false;
|
|
1268
|
+
for (const line of lines) {
|
|
1269
|
+
const trimmed = line.trim();
|
|
1270
|
+
if (trimmed === "") {
|
|
1271
|
+
blankLines++;
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
if (commentPatterns.blockStart && commentPatterns.blockEnd) {
|
|
1275
|
+
if (inBlockComment) {
|
|
1276
|
+
commentLines++;
|
|
1277
|
+
if (commentPatterns.blockEnd.test(trimmed)) {
|
|
1278
|
+
inBlockComment = false;
|
|
1279
|
+
}
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
if (commentPatterns.blockStart.test(trimmed)) {
|
|
1283
|
+
commentLines++;
|
|
1284
|
+
if (!commentPatterns.blockEnd.test(trimmed)) {
|
|
1285
|
+
inBlockComment = true;
|
|
1286
|
+
}
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
if (commentPatterns.line && commentPatterns.line.test(trimmed)) {
|
|
1291
|
+
commentLines++;
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
const loc = totalLines - blankLines - commentLines;
|
|
1296
|
+
return { loc, totalLines, blankLines, commentLines };
|
|
1297
|
+
}
|
|
1298
|
+
function rawSymbolsToNodes(rawSymbols, fileId, filePath) {
|
|
1299
|
+
return rawSymbols.map((raw) => {
|
|
1300
|
+
const id = generateSymbolId(filePath, raw.name, raw.location.startLine);
|
|
1301
|
+
return {
|
|
1302
|
+
id,
|
|
1303
|
+
name: raw.name,
|
|
1304
|
+
kind: raw.kind,
|
|
1305
|
+
fileId,
|
|
1306
|
+
location: raw.location,
|
|
1307
|
+
exported: raw.exported,
|
|
1308
|
+
metrics: {
|
|
1309
|
+
loc: raw.loc || raw.location.endLine - raw.location.startLine + 1
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
function extractDocstring(node) {
|
|
1315
|
+
const parent = node.parent;
|
|
1316
|
+
if (!parent) return void 0;
|
|
1317
|
+
const nodeIndex = parent.children.indexOf(node);
|
|
1318
|
+
if (nodeIndex <= 0) return void 0;
|
|
1319
|
+
const prevNode = parent.children[nodeIndex - 1];
|
|
1320
|
+
if (prevNode.type === "comment" || prevNode.type === "block_comment") {
|
|
1321
|
+
return cleanDocstring(prevNode.text);
|
|
1322
|
+
}
|
|
1323
|
+
return void 0;
|
|
1324
|
+
}
|
|
1325
|
+
function cleanDocstring(text) {
|
|
1326
|
+
return text.replace(/^\/\*\*?/, "").replace(/\*\/$/, "").replace(/^\/\/\s?/, "").replace(/^\s*\*\s?/gm, "").replace(/^#\s?/, "").replace(/^"""/gm, "").replace(/"""$/gm, "").trim();
|
|
1327
|
+
}
|
|
1328
|
+
function isTopLevel(node) {
|
|
1329
|
+
let current = node.parent;
|
|
1330
|
+
while (current) {
|
|
1331
|
+
if (current.type === "function_body" || current.type === "block" || current.type === "class_body" || current.type === "statement_block") {
|
|
1332
|
+
return false;
|
|
1333
|
+
}
|
|
1334
|
+
if (current.type === "program" || current.type === "module") {
|
|
1335
|
+
return true;
|
|
1336
|
+
}
|
|
1337
|
+
current = current.parent;
|
|
1338
|
+
}
|
|
1339
|
+
return true;
|
|
1340
|
+
}
|
|
1341
|
+
function findContainingScope(node, scopeTypes) {
|
|
1342
|
+
let current = node.parent;
|
|
1343
|
+
while (current) {
|
|
1344
|
+
if (scopeTypes.includes(current.type)) {
|
|
1345
|
+
return current;
|
|
1346
|
+
}
|
|
1347
|
+
current = current.parent;
|
|
1348
|
+
}
|
|
1349
|
+
return null;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// src/analyzer/languages/typescript.ts
|
|
1353
|
+
var TS_COMMENT_PATTERNS = {
|
|
1354
|
+
line: /^\/\//,
|
|
1355
|
+
blockStart: /^\/\*/,
|
|
1356
|
+
blockEnd: /\*\/$/
|
|
1357
|
+
};
|
|
1358
|
+
function extractTSSymbols(tree, content) {
|
|
1359
|
+
const symbols = [];
|
|
1360
|
+
const root = tree.rootNode;
|
|
1361
|
+
extractFunctions(root, symbols);
|
|
1362
|
+
extractClasses(root, symbols);
|
|
1363
|
+
extractInterfaces(root, symbols);
|
|
1364
|
+
extractTypes(root, symbols);
|
|
1365
|
+
extractEnums(root, symbols);
|
|
1366
|
+
extractVariables(root, symbols);
|
|
1367
|
+
const baseMetrics = calculateMetrics(content, TS_COMMENT_PATTERNS);
|
|
1368
|
+
const exportCount = symbols.filter((s) => s.exported).length;
|
|
1369
|
+
const importCount = extractTSImports(tree).length;
|
|
1370
|
+
return {
|
|
1371
|
+
symbols,
|
|
1372
|
+
metrics: {
|
|
1373
|
+
...baseMetrics,
|
|
1374
|
+
exportCount,
|
|
1375
|
+
importCount
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
function extractFunctions(root, symbols) {
|
|
1380
|
+
const funcDecls = collectNodes(root, [
|
|
1381
|
+
"function_declaration",
|
|
1382
|
+
"function",
|
|
1383
|
+
"generator_function_declaration",
|
|
1384
|
+
"generator_function"
|
|
1385
|
+
]);
|
|
1386
|
+
for (const node of funcDecls) {
|
|
1387
|
+
const nameNode = findChild(node, "identifier");
|
|
1388
|
+
if (!nameNode) continue;
|
|
1389
|
+
symbols.push({
|
|
1390
|
+
name: getNodeText(nameNode),
|
|
1391
|
+
kind: "function",
|
|
1392
|
+
location: nodeToLocation(node),
|
|
1393
|
+
exported: isExported(node),
|
|
1394
|
+
docstring: extractDocstring(node)
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
const varDecls = collectNodes(root, ["lexical_declaration", "variable_declaration"]);
|
|
1398
|
+
for (const decl of varDecls) {
|
|
1399
|
+
const declarators = findChildren(decl, "variable_declarator");
|
|
1400
|
+
for (const declarator of declarators) {
|
|
1401
|
+
const nameNode = findChild(declarator, "identifier");
|
|
1402
|
+
const valueNode = findChildByTypes(declarator, ["arrow_function", "function"]);
|
|
1403
|
+
if (nameNode && valueNode) {
|
|
1404
|
+
symbols.push({
|
|
1405
|
+
name: getNodeText(nameNode),
|
|
1406
|
+
kind: "function",
|
|
1407
|
+
location: nodeToLocation(decl),
|
|
1408
|
+
exported: isExported(decl),
|
|
1409
|
+
docstring: extractDocstring(decl)
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
function extractClasses(root, symbols) {
|
|
1416
|
+
const classDecls = collectNodes(root, [
|
|
1417
|
+
"class_declaration",
|
|
1418
|
+
"class",
|
|
1419
|
+
"abstract_class_declaration"
|
|
1420
|
+
]);
|
|
1421
|
+
for (const node of classDecls) {
|
|
1422
|
+
const nameNode = findChildByTypes(node, ["identifier", "type_identifier"]);
|
|
1423
|
+
if (!nameNode) continue;
|
|
1424
|
+
const className = getNodeText(nameNode);
|
|
1425
|
+
symbols.push({
|
|
1426
|
+
name: className,
|
|
1427
|
+
kind: "class",
|
|
1428
|
+
location: nodeToLocation(node),
|
|
1429
|
+
exported: isExported(node),
|
|
1430
|
+
docstring: extractDocstring(node)
|
|
1431
|
+
});
|
|
1432
|
+
const classBody = findChild(node, "class_body");
|
|
1433
|
+
if (classBody) {
|
|
1434
|
+
extractMethods(classBody, symbols, className);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
function extractMethods(classBody, symbols, className) {
|
|
1439
|
+
const methodTypes = [
|
|
1440
|
+
"method_definition",
|
|
1441
|
+
"public_field_definition",
|
|
1442
|
+
"field_definition"
|
|
1443
|
+
];
|
|
1444
|
+
for (const child of classBody.children) {
|
|
1445
|
+
if (!methodTypes.includes(child.type)) continue;
|
|
1446
|
+
if (child.type === "method_definition") {
|
|
1447
|
+
const nameNode = findChild(child, "property_identifier");
|
|
1448
|
+
if (nameNode) {
|
|
1449
|
+
symbols.push({
|
|
1450
|
+
name: getNodeText(nameNode),
|
|
1451
|
+
kind: "method",
|
|
1452
|
+
location: nodeToLocation(child),
|
|
1453
|
+
exported: true,
|
|
1454
|
+
// Methods are accessible through the class
|
|
1455
|
+
parentName: className,
|
|
1456
|
+
docstring: extractDocstring(child)
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
if (child.type === "public_field_definition" || child.type === "field_definition") {
|
|
1461
|
+
const nameNode = findChild(child, "property_identifier");
|
|
1462
|
+
const valueNode = findChild(child, "arrow_function");
|
|
1463
|
+
if (nameNode && valueNode) {
|
|
1464
|
+
symbols.push({
|
|
1465
|
+
name: getNodeText(nameNode),
|
|
1466
|
+
kind: "method",
|
|
1467
|
+
location: nodeToLocation(child),
|
|
1468
|
+
exported: true,
|
|
1469
|
+
parentName: className,
|
|
1470
|
+
docstring: extractDocstring(child)
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
function extractInterfaces(root, symbols) {
|
|
1477
|
+
const interfaces = collectNodes(root, ["interface_declaration"]);
|
|
1478
|
+
for (const node of interfaces) {
|
|
1479
|
+
const nameNode = findChild(node, "type_identifier");
|
|
1480
|
+
if (!nameNode) continue;
|
|
1481
|
+
symbols.push({
|
|
1482
|
+
name: getNodeText(nameNode),
|
|
1483
|
+
kind: "interface",
|
|
1484
|
+
location: nodeToLocation(node),
|
|
1485
|
+
exported: isExported(node),
|
|
1486
|
+
docstring: extractDocstring(node)
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
function extractTypes(root, symbols) {
|
|
1491
|
+
const typeAliases = collectNodes(root, ["type_alias_declaration"]);
|
|
1492
|
+
for (const node of typeAliases) {
|
|
1493
|
+
const nameNode = findChild(node, "type_identifier");
|
|
1494
|
+
if (!nameNode) continue;
|
|
1495
|
+
symbols.push({
|
|
1496
|
+
name: getNodeText(nameNode),
|
|
1497
|
+
kind: "type",
|
|
1498
|
+
location: nodeToLocation(node),
|
|
1499
|
+
exported: isExported(node),
|
|
1500
|
+
docstring: extractDocstring(node)
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
function extractEnums(root, symbols) {
|
|
1505
|
+
const enums = collectNodes(root, ["enum_declaration"]);
|
|
1506
|
+
for (const node of enums) {
|
|
1507
|
+
const nameNode = findChild(node, "identifier");
|
|
1508
|
+
if (!nameNode) continue;
|
|
1509
|
+
symbols.push({
|
|
1510
|
+
name: getNodeText(nameNode),
|
|
1511
|
+
kind: "enum",
|
|
1512
|
+
location: nodeToLocation(node),
|
|
1513
|
+
exported: isExported(node),
|
|
1514
|
+
docstring: extractDocstring(node)
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
function extractVariables(root, symbols) {
|
|
1519
|
+
const varDecls = collectNodes(root, ["lexical_declaration", "variable_declaration"]);
|
|
1520
|
+
for (const decl of varDecls) {
|
|
1521
|
+
if (!isTopLevel(decl)) continue;
|
|
1522
|
+
const declarators = findChildren(decl, "variable_declarator");
|
|
1523
|
+
for (const declarator of declarators) {
|
|
1524
|
+
const nameNode = findChild(declarator, "identifier");
|
|
1525
|
+
const valueNode = findChildByTypes(declarator, ["arrow_function", "function"]);
|
|
1526
|
+
if (valueNode) continue;
|
|
1527
|
+
if (nameNode) {
|
|
1528
|
+
symbols.push({
|
|
1529
|
+
name: getNodeText(nameNode),
|
|
1530
|
+
kind: "variable",
|
|
1531
|
+
location: nodeToLocation(decl),
|
|
1532
|
+
exported: isExported(decl),
|
|
1533
|
+
docstring: extractDocstring(decl)
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
function isExported(node) {
|
|
1540
|
+
const parent = node.parent;
|
|
1541
|
+
if (parent?.type === "export_statement") {
|
|
1542
|
+
return true;
|
|
1543
|
+
}
|
|
1544
|
+
for (const child of node.children) {
|
|
1545
|
+
if (child.type === "export") {
|
|
1546
|
+
return true;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
return false;
|
|
1550
|
+
}
|
|
1551
|
+
function extractTSImports(tree) {
|
|
1552
|
+
const imports = [];
|
|
1553
|
+
const root = tree.rootNode;
|
|
1554
|
+
const importStatements = collectNodes(root, ["import_statement"]);
|
|
1555
|
+
for (const node of importStatements) {
|
|
1556
|
+
const source = extractImportSource(node);
|
|
1557
|
+
if (!source) continue;
|
|
1558
|
+
const isTypeOnly = hasTypeOnlyModifier(node);
|
|
1559
|
+
const importedSymbols = extractImportedSymbols(node);
|
|
1560
|
+
imports.push({
|
|
1561
|
+
source,
|
|
1562
|
+
importType: isTypeOnly ? "type-only" : "static",
|
|
1563
|
+
importedSymbols: importedSymbols.names,
|
|
1564
|
+
isDefault: importedSymbols.hasDefault,
|
|
1565
|
+
isNamespace: importedSymbols.isNamespace,
|
|
1566
|
+
location: nodeToLocation(node)
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
const callExpressions = collectNodes(root, ["call_expression"]);
|
|
1570
|
+
for (const call of callExpressions) {
|
|
1571
|
+
const fnNode = call.children[0];
|
|
1572
|
+
if (fnNode?.type === "import") {
|
|
1573
|
+
const args = findChild(call, "arguments");
|
|
1574
|
+
if (args) {
|
|
1575
|
+
const stringNode = findChildByTypes(args, ["string", "template_string"]);
|
|
1576
|
+
if (stringNode) {
|
|
1577
|
+
imports.push({
|
|
1578
|
+
source: extractStringContent(stringNode),
|
|
1579
|
+
importType: "dynamic",
|
|
1580
|
+
importedSymbols: [],
|
|
1581
|
+
isDefault: false,
|
|
1582
|
+
isNamespace: true,
|
|
1583
|
+
location: nodeToLocation(call)
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
for (const call of callExpressions) {
|
|
1590
|
+
const fnNode = call.children[0];
|
|
1591
|
+
if (fnNode?.type === "identifier" && getNodeText(fnNode) === "require") {
|
|
1592
|
+
const args = findChild(call, "arguments");
|
|
1593
|
+
if (args) {
|
|
1594
|
+
const stringNode = findChildByTypes(args, ["string", "template_string"]);
|
|
1595
|
+
if (stringNode) {
|
|
1596
|
+
imports.push({
|
|
1597
|
+
source: extractStringContent(stringNode),
|
|
1598
|
+
importType: "static",
|
|
1599
|
+
importedSymbols: [],
|
|
1600
|
+
isDefault: false,
|
|
1601
|
+
isNamespace: true,
|
|
1602
|
+
location: nodeToLocation(call)
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
return imports;
|
|
1609
|
+
}
|
|
1610
|
+
function extractImportSource(node) {
|
|
1611
|
+
const stringNode = findChildByTypes(node, ["string", "template_string"]);
|
|
1612
|
+
if (!stringNode) return null;
|
|
1613
|
+
return extractStringContent(stringNode);
|
|
1614
|
+
}
|
|
1615
|
+
function extractStringContent(node) {
|
|
1616
|
+
const text = getNodeText(node);
|
|
1617
|
+
return text.slice(1, -1);
|
|
1618
|
+
}
|
|
1619
|
+
function hasTypeOnlyModifier(node) {
|
|
1620
|
+
for (const child of node.children) {
|
|
1621
|
+
if (child.type === "type" && getNodeText(child) === "type") {
|
|
1622
|
+
return true;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
return false;
|
|
1626
|
+
}
|
|
1627
|
+
function extractImportedSymbols(node) {
|
|
1628
|
+
const names = [];
|
|
1629
|
+
let hasDefault = false;
|
|
1630
|
+
let isNamespace = false;
|
|
1631
|
+
const importClause = findChild(node, "import_clause");
|
|
1632
|
+
if (!importClause) {
|
|
1633
|
+
return { names, hasDefault, isNamespace };
|
|
1634
|
+
}
|
|
1635
|
+
for (const child of importClause.children) {
|
|
1636
|
+
if (child.type === "identifier") {
|
|
1637
|
+
hasDefault = true;
|
|
1638
|
+
names.push(getNodeText(child));
|
|
1639
|
+
}
|
|
1640
|
+
if (child.type === "namespace_import") {
|
|
1641
|
+
isNamespace = true;
|
|
1642
|
+
const asNode = findChild(child, "identifier");
|
|
1643
|
+
if (asNode) {
|
|
1644
|
+
names.push(getNodeText(asNode));
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
if (child.type === "named_imports") {
|
|
1648
|
+
const specifiers = findChildren(child, "import_specifier");
|
|
1649
|
+
for (const spec of specifiers) {
|
|
1650
|
+
const nameNode = findChild(spec, "identifier");
|
|
1651
|
+
if (nameNode) {
|
|
1652
|
+
names.push(getNodeText(nameNode));
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
return { names, hasDefault, isNamespace };
|
|
1658
|
+
}
|
|
1659
|
+
function extractTSCalls(tree, symbols) {
|
|
1660
|
+
const calls = [];
|
|
1661
|
+
const root = tree.rootNode;
|
|
1662
|
+
const symbolNames = new Set(symbols.map((s) => s.name));
|
|
1663
|
+
const callExpressions = collectNodes(root, ["call_expression", "new_expression"]);
|
|
1664
|
+
for (const call of callExpressions) {
|
|
1665
|
+
const callInfo = extractCallInfo(call, symbolNames);
|
|
1666
|
+
if (callInfo) {
|
|
1667
|
+
const scope = findContainingScope(call, [
|
|
1668
|
+
"function_declaration",
|
|
1669
|
+
"generator_function_declaration",
|
|
1670
|
+
"generator_function",
|
|
1671
|
+
"method_definition",
|
|
1672
|
+
"arrow_function",
|
|
1673
|
+
"function"
|
|
1674
|
+
]);
|
|
1675
|
+
if (scope) {
|
|
1676
|
+
const callerName = getScopeName(scope);
|
|
1677
|
+
if (callerName) {
|
|
1678
|
+
calls.push({
|
|
1679
|
+
callerName,
|
|
1680
|
+
calleeName: callInfo.name,
|
|
1681
|
+
callType: callInfo.type,
|
|
1682
|
+
location: nodeToLocation(call)
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
return calls;
|
|
1689
|
+
}
|
|
1690
|
+
function extractCallInfo(node, symbolNames) {
|
|
1691
|
+
if (node.type === "new_expression") {
|
|
1692
|
+
const constructorNode = node.children[1];
|
|
1693
|
+
if (constructorNode?.type === "identifier") {
|
|
1694
|
+
const name = getNodeText(constructorNode);
|
|
1695
|
+
if (symbolNames.has(name)) {
|
|
1696
|
+
return { name, type: "constructor" };
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
return null;
|
|
1700
|
+
}
|
|
1701
|
+
const fnNode = node.children[0];
|
|
1702
|
+
if (fnNode?.type === "identifier") {
|
|
1703
|
+
const name = getNodeText(fnNode);
|
|
1704
|
+
if (symbolNames.has(name)) {
|
|
1705
|
+
return { name, type: "direct" };
|
|
1706
|
+
}
|
|
1707
|
+
return null;
|
|
1708
|
+
}
|
|
1709
|
+
if (fnNode?.type === "member_expression") {
|
|
1710
|
+
const objectNode = findChild(fnNode, "this");
|
|
1711
|
+
const propertyNode = findChild(fnNode, "property_identifier");
|
|
1712
|
+
if (objectNode && propertyNode) {
|
|
1713
|
+
return {
|
|
1714
|
+
name: getNodeText(propertyNode),
|
|
1715
|
+
type: "method"
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
return null;
|
|
1720
|
+
}
|
|
1721
|
+
function getScopeName(scope) {
|
|
1722
|
+
if (scope.type === "function_declaration" || scope.type === "function" || scope.type === "generator_function_declaration" || scope.type === "generator_function") {
|
|
1723
|
+
const nameNode = findChild(scope, "identifier");
|
|
1724
|
+
return nameNode ? getNodeText(nameNode) : null;
|
|
1725
|
+
}
|
|
1726
|
+
if (scope.type === "method_definition") {
|
|
1727
|
+
const nameNode = findChild(scope, "property_identifier");
|
|
1728
|
+
return nameNode ? getNodeText(nameNode) : null;
|
|
1729
|
+
}
|
|
1730
|
+
if (scope.type === "arrow_function") {
|
|
1731
|
+
const parent = scope.parent;
|
|
1732
|
+
if (parent?.type === "variable_declarator") {
|
|
1733
|
+
const nameNode = findChild(parent, "identifier");
|
|
1734
|
+
return nameNode ? getNodeText(nameNode) : null;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
return null;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
// src/analyzer/languages/python.ts
|
|
1741
|
+
var PYTHON_COMMENT_PATTERNS = {
|
|
1742
|
+
line: /^#/,
|
|
1743
|
+
blockStart: /^"""/,
|
|
1744
|
+
blockEnd: /"""$/
|
|
1745
|
+
};
|
|
1746
|
+
function extractPythonSymbols(tree, content) {
|
|
1747
|
+
const symbols = [];
|
|
1748
|
+
const root = tree.rootNode;
|
|
1749
|
+
extractFunctions2(root, symbols);
|
|
1750
|
+
extractClasses2(root, symbols);
|
|
1751
|
+
extractVariables2(root, symbols);
|
|
1752
|
+
const baseMetrics = calculateMetrics(content, PYTHON_COMMENT_PATTERNS);
|
|
1753
|
+
const exportCount = symbols.filter((s) => s.exported).length;
|
|
1754
|
+
const importCount = extractPythonImports(tree).length;
|
|
1755
|
+
return {
|
|
1756
|
+
symbols,
|
|
1757
|
+
metrics: {
|
|
1758
|
+
...baseMetrics,
|
|
1759
|
+
exportCount,
|
|
1760
|
+
importCount
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
function extractFunctions2(root, symbols) {
|
|
1765
|
+
const funcDefs = collectNodes(root, ["function_definition"]);
|
|
1766
|
+
for (const node of funcDefs) {
|
|
1767
|
+
if (isMethod(node)) continue;
|
|
1768
|
+
const nameNode = findChild(node, "identifier");
|
|
1769
|
+
if (!nameNode) continue;
|
|
1770
|
+
const name = getNodeText(nameNode);
|
|
1771
|
+
const isPrivate = name.startsWith("_") && !name.startsWith("__");
|
|
1772
|
+
symbols.push({
|
|
1773
|
+
name,
|
|
1774
|
+
kind: "function",
|
|
1775
|
+
location: nodeToLocation(node),
|
|
1776
|
+
exported: !isPrivate,
|
|
1777
|
+
// All top-level non-private symbols are exported
|
|
1778
|
+
docstring: extractPythonDocstring(node)
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
function extractClasses2(root, symbols) {
|
|
1783
|
+
const classDefs = collectNodes(root, ["class_definition"]);
|
|
1784
|
+
for (const node of classDefs) {
|
|
1785
|
+
const nameNode = findChild(node, "identifier");
|
|
1786
|
+
if (!nameNode) continue;
|
|
1787
|
+
const className = getNodeText(nameNode);
|
|
1788
|
+
const isPrivate = className.startsWith("_") && !className.startsWith("__");
|
|
1789
|
+
symbols.push({
|
|
1790
|
+
name: className,
|
|
1791
|
+
kind: "class",
|
|
1792
|
+
location: nodeToLocation(node),
|
|
1793
|
+
exported: !isPrivate,
|
|
1794
|
+
docstring: extractPythonDocstring(node)
|
|
1795
|
+
});
|
|
1796
|
+
const body = findChild(node, "block");
|
|
1797
|
+
if (body) {
|
|
1798
|
+
extractMethods2(body, symbols, className);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
function extractMethods2(classBody, symbols, className) {
|
|
1803
|
+
const methods = collectNodes(classBody, ["function_definition"]);
|
|
1804
|
+
for (const method of methods) {
|
|
1805
|
+
const nameNode = findChild(method, "identifier");
|
|
1806
|
+
if (!nameNode) continue;
|
|
1807
|
+
const name = getNodeText(nameNode);
|
|
1808
|
+
const isDunder = name.startsWith("__") && name.endsWith("__");
|
|
1809
|
+
const isPrivate = name.startsWith("_") && !isDunder;
|
|
1810
|
+
symbols.push({
|
|
1811
|
+
name,
|
|
1812
|
+
kind: "method",
|
|
1813
|
+
location: nodeToLocation(method),
|
|
1814
|
+
exported: !isPrivate,
|
|
1815
|
+
// Methods are accessible through the class
|
|
1816
|
+
parentName: className,
|
|
1817
|
+
docstring: extractPythonDocstring(method)
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
function extractVariables2(root, symbols) {
|
|
1822
|
+
for (const child of root.children) {
|
|
1823
|
+
if (child.type === "expression_statement") {
|
|
1824
|
+
const assignment = findChild(child, "assignment");
|
|
1825
|
+
if (assignment) {
|
|
1826
|
+
const leftNode = assignment.children[0];
|
|
1827
|
+
if (leftNode?.type === "identifier") {
|
|
1828
|
+
const name = getNodeText(leftNode);
|
|
1829
|
+
const isPrivate = name.startsWith("_");
|
|
1830
|
+
symbols.push({
|
|
1831
|
+
name,
|
|
1832
|
+
kind: "variable",
|
|
1833
|
+
location: nodeToLocation(child),
|
|
1834
|
+
exported: !isPrivate,
|
|
1835
|
+
docstring: void 0
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
function isMethod(node) {
|
|
1843
|
+
let current = node.parent;
|
|
1844
|
+
while (current) {
|
|
1845
|
+
if (current.type === "class_definition") {
|
|
1846
|
+
return true;
|
|
1847
|
+
}
|
|
1848
|
+
if (current.type === "function_definition") {
|
|
1849
|
+
return false;
|
|
1850
|
+
}
|
|
1851
|
+
current = current.parent;
|
|
1852
|
+
}
|
|
1853
|
+
return false;
|
|
1854
|
+
}
|
|
1855
|
+
function extractPythonDocstring(node) {
|
|
1856
|
+
const body = findChild(node, "block");
|
|
1857
|
+
if (!body) return void 0;
|
|
1858
|
+
for (const child of body.children) {
|
|
1859
|
+
if (child.type === "expression_statement") {
|
|
1860
|
+
const stringNode = findChild(child, "string");
|
|
1861
|
+
if (stringNode) {
|
|
1862
|
+
return cleanPythonDocstring(getNodeText(stringNode));
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
if (child.type !== "comment") {
|
|
1866
|
+
break;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
return void 0;
|
|
1870
|
+
}
|
|
1871
|
+
function cleanPythonDocstring(text) {
|
|
1872
|
+
return text.replace(/^["']{3}/, "").replace(/["']{3}$/, "").trim();
|
|
1873
|
+
}
|
|
1874
|
+
function extractPythonImports(tree) {
|
|
1875
|
+
const imports = [];
|
|
1876
|
+
const root = tree.rootNode;
|
|
1877
|
+
const importStatements = collectNodes(root, ["import_statement"]);
|
|
1878
|
+
for (const node of importStatements) {
|
|
1879
|
+
const modules = extractImportedModules(node);
|
|
1880
|
+
for (const moduleName of modules) {
|
|
1881
|
+
imports.push({
|
|
1882
|
+
source: moduleName,
|
|
1883
|
+
importType: "static",
|
|
1884
|
+
importedSymbols: [],
|
|
1885
|
+
isDefault: false,
|
|
1886
|
+
isNamespace: true,
|
|
1887
|
+
location: nodeToLocation(node)
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
const fromStatements = collectNodes(root, ["import_from_statement"]);
|
|
1892
|
+
for (const node of fromStatements) {
|
|
1893
|
+
const moduleInfo = extractFromImport(node);
|
|
1894
|
+
if (moduleInfo) {
|
|
1895
|
+
imports.push({
|
|
1896
|
+
source: moduleInfo.module,
|
|
1897
|
+
importType: "static",
|
|
1898
|
+
importedSymbols: moduleInfo.symbols,
|
|
1899
|
+
isDefault: false,
|
|
1900
|
+
isNamespace: moduleInfo.isWildcard,
|
|
1901
|
+
location: nodeToLocation(node)
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
return imports;
|
|
1906
|
+
}
|
|
1907
|
+
function extractImportedModules(node) {
|
|
1908
|
+
const modules = [];
|
|
1909
|
+
for (const child of node.children) {
|
|
1910
|
+
if (child.type === "dotted_name") {
|
|
1911
|
+
modules.push(getNodeText(child));
|
|
1912
|
+
}
|
|
1913
|
+
if (child.type === "aliased_import") {
|
|
1914
|
+
const dottedName = findChild(child, "dotted_name");
|
|
1915
|
+
if (dottedName) {
|
|
1916
|
+
modules.push(getNodeText(dottedName));
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
return modules;
|
|
1921
|
+
}
|
|
1922
|
+
function extractFromImport(node) {
|
|
1923
|
+
let module = "";
|
|
1924
|
+
const symbols = [];
|
|
1925
|
+
let isWildcard = false;
|
|
1926
|
+
let foundModule = false;
|
|
1927
|
+
for (const child of node.children) {
|
|
1928
|
+
if (child.type === "import") {
|
|
1929
|
+
foundModule = true;
|
|
1930
|
+
}
|
|
1931
|
+
if (!foundModule && child.type === "dotted_name") {
|
|
1932
|
+
module = getNodeText(child);
|
|
1933
|
+
}
|
|
1934
|
+
if (child.type === "relative_import") {
|
|
1935
|
+
const dots = collectNodes(child, ["import_prefix"]);
|
|
1936
|
+
const dottedName = findChild(child, "dotted_name");
|
|
1937
|
+
const prefix = dots.length > 0 ? getNodeText(dots[0]) : "";
|
|
1938
|
+
const modulePart = dottedName ? getNodeText(dottedName) : "";
|
|
1939
|
+
module = prefix + modulePart;
|
|
1940
|
+
foundModule = true;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
let afterImport = false;
|
|
1944
|
+
for (const child of node.children) {
|
|
1945
|
+
if (child.type === "import") {
|
|
1946
|
+
afterImport = true;
|
|
1947
|
+
continue;
|
|
1948
|
+
}
|
|
1949
|
+
if (!afterImport) continue;
|
|
1950
|
+
if (child.type === "wildcard_import") {
|
|
1951
|
+
isWildcard = true;
|
|
1952
|
+
}
|
|
1953
|
+
if (child.type === "dotted_name") {
|
|
1954
|
+
symbols.push(getNodeText(child));
|
|
1955
|
+
}
|
|
1956
|
+
if (child.type === "aliased_import") {
|
|
1957
|
+
const name = findChild(child, "dotted_name");
|
|
1958
|
+
if (name) {
|
|
1959
|
+
symbols.push(getNodeText(name));
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
if (child.type === "identifier") {
|
|
1963
|
+
symbols.push(getNodeText(child));
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
if (!module && !isWildcard) {
|
|
1967
|
+
return null;
|
|
1968
|
+
}
|
|
1969
|
+
return { module, symbols, isWildcard };
|
|
1970
|
+
}
|
|
1971
|
+
function extractPythonCalls(tree, symbols) {
|
|
1972
|
+
const calls = [];
|
|
1973
|
+
const root = tree.rootNode;
|
|
1974
|
+
const symbolNames = new Set(symbols.map((s) => s.name));
|
|
1975
|
+
const callNodes = collectNodes(root, ["call"]);
|
|
1976
|
+
for (const call of callNodes) {
|
|
1977
|
+
const callInfo = extractCallInfo2(call, symbolNames);
|
|
1978
|
+
if (callInfo) {
|
|
1979
|
+
const scope = findContainingScope(call, ["function_definition"]);
|
|
1980
|
+
if (scope) {
|
|
1981
|
+
const callerName = getScopeName2(scope);
|
|
1982
|
+
if (callerName) {
|
|
1983
|
+
calls.push({
|
|
1984
|
+
callerName,
|
|
1985
|
+
calleeName: callInfo.name,
|
|
1986
|
+
callType: callInfo.type,
|
|
1987
|
+
location: nodeToLocation(call)
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
return calls;
|
|
1994
|
+
}
|
|
1995
|
+
function extractCallInfo2(node, symbolNames) {
|
|
1996
|
+
const fnNode = node.children[0];
|
|
1997
|
+
if (fnNode?.type === "identifier") {
|
|
1998
|
+
const name = getNodeText(fnNode);
|
|
1999
|
+
if (symbolNames.has(name)) {
|
|
2000
|
+
const isClass = /^[A-Z]/.test(name);
|
|
2001
|
+
return {
|
|
2002
|
+
name,
|
|
2003
|
+
type: isClass ? "constructor" : "direct"
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
return null;
|
|
2007
|
+
}
|
|
2008
|
+
if (fnNode?.type === "attribute") {
|
|
2009
|
+
const objectNode = fnNode.children[0];
|
|
2010
|
+
const identifiers = fnNode.children.filter((c) => c.type === "identifier");
|
|
2011
|
+
const attrNode = identifiers.length > 1 ? identifiers[identifiers.length - 1] : null;
|
|
2012
|
+
if (objectNode?.type === "identifier" && getNodeText(objectNode) === "self") {
|
|
2013
|
+
if (attrNode) {
|
|
2014
|
+
return {
|
|
2015
|
+
name: getNodeText(attrNode),
|
|
2016
|
+
type: "method"
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
return null;
|
|
2022
|
+
}
|
|
2023
|
+
function getScopeName2(scope) {
|
|
2024
|
+
const nameNode = findChild(scope, "identifier");
|
|
2025
|
+
return nameNode ? getNodeText(nameNode) : null;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
// src/analyzer/languages/generic.ts
|
|
2029
|
+
var SYMBOL_PATTERNS = {
|
|
2030
|
+
function: [
|
|
2031
|
+
/^function_declaration$/,
|
|
2032
|
+
/^function_definition$/,
|
|
2033
|
+
/^function_item$/,
|
|
2034
|
+
// Rust
|
|
2035
|
+
/^func_literal$/,
|
|
2036
|
+
// Go
|
|
2037
|
+
/^method_declaration$/,
|
|
2038
|
+
/^function_expression$/,
|
|
2039
|
+
/^arrow_function$/,
|
|
2040
|
+
/^lambda_expression$/,
|
|
2041
|
+
/^anonymous_function$/
|
|
2042
|
+
],
|
|
2043
|
+
method: [
|
|
2044
|
+
/^method_definition$/,
|
|
2045
|
+
/^method_declaration$/,
|
|
2046
|
+
/^instance_method$/,
|
|
2047
|
+
/^class_method$/,
|
|
2048
|
+
/^static_method$/
|
|
2049
|
+
],
|
|
2050
|
+
class: [
|
|
2051
|
+
/^class_declaration$/,
|
|
2052
|
+
/^class_definition$/,
|
|
2053
|
+
/^class$/,
|
|
2054
|
+
// Ruby
|
|
2055
|
+
/^class_specifier$/,
|
|
2056
|
+
// C++
|
|
2057
|
+
/^struct_item$/,
|
|
2058
|
+
// Rust
|
|
2059
|
+
/^struct_declaration$/,
|
|
2060
|
+
// C
|
|
2061
|
+
/^struct_specifier$/,
|
|
2062
|
+
// C
|
|
2063
|
+
/^impl_item$/
|
|
2064
|
+
// Rust
|
|
2065
|
+
],
|
|
2066
|
+
interface: [
|
|
2067
|
+
/^interface_declaration$/,
|
|
2068
|
+
/^interface_definition$/,
|
|
2069
|
+
/^trait_item$/,
|
|
2070
|
+
// Rust
|
|
2071
|
+
/^protocol_declaration$/
|
|
2072
|
+
// Swift
|
|
2073
|
+
],
|
|
2074
|
+
type: [
|
|
2075
|
+
/^type_alias_declaration$/,
|
|
2076
|
+
/^type_declaration$/,
|
|
2077
|
+
/^type_definition$/,
|
|
2078
|
+
/^type_spec$/,
|
|
2079
|
+
// Go
|
|
2080
|
+
/^typedef_declaration$/
|
|
2081
|
+
// C
|
|
2082
|
+
],
|
|
2083
|
+
enum: [
|
|
2084
|
+
/^enum_declaration$/,
|
|
2085
|
+
/^enum_definition$/,
|
|
2086
|
+
/^enum_item$/,
|
|
2087
|
+
// Rust
|
|
2088
|
+
/^enum_specifier$/
|
|
2089
|
+
// C
|
|
2090
|
+
],
|
|
2091
|
+
variable: [
|
|
2092
|
+
/^variable_declaration$/,
|
|
2093
|
+
/^const_declaration$/,
|
|
2094
|
+
/^let_declaration$/,
|
|
2095
|
+
/^const_item$/,
|
|
2096
|
+
// Rust
|
|
2097
|
+
/^static_item$/,
|
|
2098
|
+
// Rust
|
|
2099
|
+
/^var_declaration$/,
|
|
2100
|
+
// Go
|
|
2101
|
+
/^short_var_declaration$/
|
|
2102
|
+
// Go
|
|
2103
|
+
],
|
|
2104
|
+
constant: [
|
|
2105
|
+
/^const_declaration$/,
|
|
2106
|
+
/^const_item$/,
|
|
2107
|
+
// Rust
|
|
2108
|
+
/^constant_declaration$/
|
|
2109
|
+
],
|
|
2110
|
+
property: [
|
|
2111
|
+
/^field_declaration$/,
|
|
2112
|
+
/^property_declaration$/,
|
|
2113
|
+
/^field_definition$/
|
|
2114
|
+
],
|
|
2115
|
+
namespace: [
|
|
2116
|
+
/^module_declaration$/,
|
|
2117
|
+
/^module$/,
|
|
2118
|
+
// Ruby
|
|
2119
|
+
/^mod_item$/,
|
|
2120
|
+
// Rust
|
|
2121
|
+
/^package_declaration$/,
|
|
2122
|
+
// Java
|
|
2123
|
+
/^namespace_declaration$/
|
|
2124
|
+
]
|
|
2125
|
+
};
|
|
2126
|
+
var IMPORT_PATTERNS = [
|
|
2127
|
+
/^import_declaration$/,
|
|
2128
|
+
/^import_statement$/,
|
|
2129
|
+
/^import_from_statement$/,
|
|
2130
|
+
/^use_declaration$/,
|
|
2131
|
+
// Rust
|
|
2132
|
+
/^use_statement$/,
|
|
2133
|
+
/^include_directive$/,
|
|
2134
|
+
// C/C++
|
|
2135
|
+
/^preproc_include$/,
|
|
2136
|
+
// C/C++
|
|
2137
|
+
/^require_expression$/,
|
|
2138
|
+
/^require_statement$/,
|
|
2139
|
+
/^import_spec$/
|
|
2140
|
+
// Go
|
|
2141
|
+
];
|
|
2142
|
+
var NAME_NODE_TYPES = [
|
|
2143
|
+
"identifier",
|
|
2144
|
+
"name",
|
|
2145
|
+
"type_identifier",
|
|
2146
|
+
"property_identifier",
|
|
2147
|
+
"field_identifier",
|
|
2148
|
+
"constant_identifier",
|
|
2149
|
+
"simple_identifier",
|
|
2150
|
+
"constant"
|
|
2151
|
+
// Ruby class/module names
|
|
2152
|
+
];
|
|
2153
|
+
var COMMENT_NODE_TYPES = [
|
|
2154
|
+
"comment",
|
|
2155
|
+
"line_comment",
|
|
2156
|
+
"block_comment",
|
|
2157
|
+
"doc_comment",
|
|
2158
|
+
"multiline_comment"
|
|
2159
|
+
];
|
|
2160
|
+
function nodeToLocation2(node) {
|
|
2161
|
+
return {
|
|
2162
|
+
startLine: node.startPosition.row + 1,
|
|
2163
|
+
endLine: node.endPosition.row + 1,
|
|
2164
|
+
startColumn: node.startPosition.column,
|
|
2165
|
+
endColumn: node.endPosition.column
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
function findName(node) {
|
|
2169
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2170
|
+
const child = node.child(i);
|
|
2171
|
+
if (child && NAME_NODE_TYPES.includes(child.type)) {
|
|
2172
|
+
return child.text;
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2176
|
+
const child = node.namedChild(i);
|
|
2177
|
+
if (child && NAME_NODE_TYPES.includes(child.type)) {
|
|
2178
|
+
return child.text;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2182
|
+
const child = node.child(i);
|
|
2183
|
+
if (child) {
|
|
2184
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
2185
|
+
const grandchild = child.child(j);
|
|
2186
|
+
if (grandchild && NAME_NODE_TYPES.includes(grandchild.type)) {
|
|
2187
|
+
return grandchild.text;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
return null;
|
|
2193
|
+
}
|
|
2194
|
+
function getSymbolKind(nodeType) {
|
|
2195
|
+
for (const [kind, patterns] of Object.entries(SYMBOL_PATTERNS)) {
|
|
2196
|
+
for (const pattern of patterns) {
|
|
2197
|
+
if (pattern.test(nodeType)) {
|
|
2198
|
+
return kind;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
function isImportNode(nodeType) {
|
|
2205
|
+
return IMPORT_PATTERNS.some((pattern) => pattern.test(nodeType));
|
|
2206
|
+
}
|
|
2207
|
+
function extractImportSource2(node) {
|
|
2208
|
+
const systemLib = node.descendantsOfType("system_lib_string")[0];
|
|
2209
|
+
if (systemLib) {
|
|
2210
|
+
const text = systemLib.text;
|
|
2211
|
+
return text.replace(/^<|>$/g, "");
|
|
2212
|
+
}
|
|
2213
|
+
const stringLiteral = node.descendantsOfType("string_literal")[0];
|
|
2214
|
+
if (stringLiteral) {
|
|
2215
|
+
const text = stringLiteral.text;
|
|
2216
|
+
return text.replace(/^["']|["']$/g, "");
|
|
2217
|
+
}
|
|
2218
|
+
const stringNode = node.descendantsOfType("string")[0];
|
|
2219
|
+
if (stringNode) {
|
|
2220
|
+
const text = stringNode.text;
|
|
2221
|
+
return text.replace(/^["']|["']$/g, "");
|
|
2222
|
+
}
|
|
2223
|
+
const stringContent = node.descendantsOfType("string_content")[0];
|
|
2224
|
+
if (stringContent) {
|
|
2225
|
+
return stringContent.text;
|
|
2226
|
+
}
|
|
2227
|
+
const interpretedString = node.descendantsOfType("interpreted_string_literal")[0];
|
|
2228
|
+
if (interpretedString) {
|
|
2229
|
+
const text = interpretedString.text;
|
|
2230
|
+
return text.replace(/^["']|["']$/g, "");
|
|
2231
|
+
}
|
|
2232
|
+
const dottedName = node.descendantsOfType("dotted_name")[0];
|
|
2233
|
+
if (dottedName) {
|
|
2234
|
+
return dottedName.text;
|
|
2235
|
+
}
|
|
2236
|
+
const scopedId = node.descendantsOfType("scoped_identifier")[0];
|
|
2237
|
+
if (scopedId) {
|
|
2238
|
+
return scopedId.text;
|
|
2239
|
+
}
|
|
2240
|
+
const identifier = node.descendantsOfType("identifier")[0];
|
|
2241
|
+
if (identifier) {
|
|
2242
|
+
return identifier.text;
|
|
2243
|
+
}
|
|
2244
|
+
return null;
|
|
2245
|
+
}
|
|
2246
|
+
function isTopLevel2(node) {
|
|
2247
|
+
let current = node.parent;
|
|
2248
|
+
while (current) {
|
|
2249
|
+
const type = current.type;
|
|
2250
|
+
if (type.includes("body") || type.includes("block") || type === "compound_statement") {
|
|
2251
|
+
if (!current.parent || current.parent.type === "source_file") {
|
|
2252
|
+
return true;
|
|
2253
|
+
}
|
|
2254
|
+
return false;
|
|
2255
|
+
}
|
|
2256
|
+
if (type === "source_file" || type === "program" || type === "module" || type === "translation_unit") {
|
|
2257
|
+
return true;
|
|
2258
|
+
}
|
|
2259
|
+
current = current.parent;
|
|
2260
|
+
}
|
|
2261
|
+
return true;
|
|
2262
|
+
}
|
|
2263
|
+
function walkTree(node, callback) {
|
|
2264
|
+
callback(node);
|
|
2265
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
2266
|
+
const child = node.child(i);
|
|
2267
|
+
if (child) {
|
|
2268
|
+
walkTree(child, callback);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
function calculateMetrics2(content, tree) {
|
|
2273
|
+
const lines = content.split("\n");
|
|
2274
|
+
const totalLines = lines.length;
|
|
2275
|
+
let blankLines = 0;
|
|
2276
|
+
let commentLines = 0;
|
|
2277
|
+
for (const line of lines) {
|
|
2278
|
+
if (line.trim() === "") {
|
|
2279
|
+
blankLines++;
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
walkTree(tree.rootNode, (node) => {
|
|
2283
|
+
if (COMMENT_NODE_TYPES.includes(node.type)) {
|
|
2284
|
+
const startLine = node.startPosition.row;
|
|
2285
|
+
const endLine = node.endPosition.row;
|
|
2286
|
+
commentLines += endLine - startLine + 1;
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
2289
|
+
const loc = Math.max(0, totalLines - blankLines - commentLines);
|
|
2290
|
+
return {
|
|
2291
|
+
loc,
|
|
2292
|
+
totalLines,
|
|
2293
|
+
blankLines,
|
|
2294
|
+
commentLines
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
function extractGeneric(tree, content) {
|
|
2298
|
+
const symbols = [];
|
|
2299
|
+
const imports = [];
|
|
2300
|
+
walkTree(tree.rootNode, (node) => {
|
|
2301
|
+
const nodeType = node.type;
|
|
2302
|
+
const kind = getSymbolKind(nodeType);
|
|
2303
|
+
if (kind) {
|
|
2304
|
+
if (kind !== "method" && !isTopLevel2(node)) {
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
const name = findName(node);
|
|
2308
|
+
if (name) {
|
|
2309
|
+
symbols.push({
|
|
2310
|
+
name,
|
|
2311
|
+
kind,
|
|
2312
|
+
location: nodeToLocation2(node),
|
|
2313
|
+
exported: true
|
|
2314
|
+
// Can't reliably detect exports in generic mode
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
if (isImportNode(nodeType)) {
|
|
2319
|
+
const source = extractImportSource2(node);
|
|
2320
|
+
if (source) {
|
|
2321
|
+
imports.push({
|
|
2322
|
+
source,
|
|
2323
|
+
location: nodeToLocation2(node)
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
});
|
|
2328
|
+
const metrics = calculateMetrics2(content, tree);
|
|
2329
|
+
return {
|
|
2330
|
+
symbols,
|
|
2331
|
+
imports,
|
|
2332
|
+
metrics
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
// src/analyzer/symbols.ts
|
|
2337
|
+
async function extractSymbols(filePath, content, fileId) {
|
|
2338
|
+
const registry2 = getLanguageRegistry();
|
|
2339
|
+
const language = await registry2.getLanguageForFile(filePath);
|
|
2340
|
+
if (language) {
|
|
2341
|
+
const parserManager = getParserManager();
|
|
2342
|
+
const parseResult = await parserManager.safeParse(filePath, content, language);
|
|
2343
|
+
if ("error" in parseResult) {
|
|
2344
|
+
return {
|
|
2345
|
+
symbols: [],
|
|
2346
|
+
metrics: { loc: 0, totalLines: 0, exportCount: 0, importCount: 0 },
|
|
2347
|
+
parseErrors: [parseResult.error.message]
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
const tree = parseResult.result.tree;
|
|
2351
|
+
let extractionResult;
|
|
2352
|
+
switch (language.name) {
|
|
2353
|
+
case "typescript":
|
|
2354
|
+
case "javascript":
|
|
2355
|
+
extractionResult = extractTSSymbols(tree, content);
|
|
2356
|
+
break;
|
|
2357
|
+
case "python":
|
|
2358
|
+
extractionResult = extractPythonSymbols(tree, content);
|
|
2359
|
+
break;
|
|
2360
|
+
default:
|
|
2361
|
+
break;
|
|
2362
|
+
}
|
|
2363
|
+
if (extractionResult) {
|
|
2364
|
+
const symbols2 = rawSymbolsToNodes(extractionResult.symbols, fileId, filePath);
|
|
2365
|
+
return {
|
|
2366
|
+
symbols: symbols2,
|
|
2367
|
+
metrics: {
|
|
2368
|
+
loc: extractionResult.metrics.loc,
|
|
2369
|
+
totalLines: extractionResult.metrics.totalLines,
|
|
2370
|
+
exportCount: extractionResult.metrics.exportCount,
|
|
2371
|
+
importCount: extractionResult.metrics.importCount
|
|
2372
|
+
}
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
const hybridManager = getHybridParserManager();
|
|
2377
|
+
const hybridResult = await hybridManager.safeParse(filePath, content);
|
|
2378
|
+
if ("error" in hybridResult) {
|
|
2379
|
+
return {
|
|
2380
|
+
symbols: [],
|
|
2381
|
+
metrics: { loc: 0, totalLines: 0, exportCount: 0, importCount: 0 },
|
|
2382
|
+
parseErrors: [hybridResult.error.message]
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
if (hybridResult.result.parserType !== "wasm") {
|
|
2386
|
+
return {
|
|
2387
|
+
symbols: [],
|
|
2388
|
+
metrics: { loc: 0, totalLines: 0, exportCount: 0, importCount: 0 },
|
|
2389
|
+
parseErrors: [`No extractor available for: ${hybridResult.result.language}`]
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
const genericResult = extractGeneric(
|
|
2393
|
+
hybridResult.result.tree,
|
|
2394
|
+
content
|
|
2395
|
+
);
|
|
2396
|
+
const rawSymbols = genericResult.symbols.map((sym) => ({
|
|
2397
|
+
name: sym.name,
|
|
2398
|
+
kind: sym.kind,
|
|
2399
|
+
location: sym.location,
|
|
2400
|
+
exported: sym.exported
|
|
2401
|
+
}));
|
|
2402
|
+
const symbols = rawSymbolsToNodes(rawSymbols, fileId, filePath);
|
|
2403
|
+
return {
|
|
2404
|
+
symbols,
|
|
2405
|
+
metrics: {
|
|
2406
|
+
loc: genericResult.metrics.loc,
|
|
2407
|
+
totalLines: genericResult.metrics.totalLines,
|
|
2408
|
+
exportCount: symbols.filter((s) => s.exported).length,
|
|
2409
|
+
importCount: genericResult.imports.length
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
async function extractSymbolsFromFile(filePath, fileId) {
|
|
2414
|
+
try {
|
|
2415
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
2416
|
+
return extractSymbols(filePath, content, fileId);
|
|
2417
|
+
} catch (err) {
|
|
2418
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2419
|
+
return {
|
|
2420
|
+
symbols: [],
|
|
2421
|
+
metrics: { loc: 0, totalLines: 0, exportCount: 0, importCount: 0 },
|
|
2422
|
+
parseErrors: [`Failed to read file: ${message}`]
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
// src/analyzer/imports.ts
|
|
2428
|
+
import * as fs4 from "fs";
|
|
2429
|
+
import * as path3 from "path";
|
|
2430
|
+
async function extractImports(filePath, content) {
|
|
2431
|
+
const registry2 = getLanguageRegistry();
|
|
2432
|
+
const language = await registry2.getLanguageForFile(filePath);
|
|
2433
|
+
if (!language) {
|
|
2434
|
+
return [];
|
|
2435
|
+
}
|
|
2436
|
+
const parserManager = getParserManager();
|
|
2437
|
+
const parseResult = await parserManager.safeParse(filePath, content, language);
|
|
2438
|
+
if ("error" in parseResult) {
|
|
2439
|
+
return [];
|
|
2440
|
+
}
|
|
2441
|
+
const tree = parseResult.result.tree;
|
|
2442
|
+
let rawImports;
|
|
2443
|
+
switch (language.name) {
|
|
2444
|
+
case "typescript":
|
|
2445
|
+
case "javascript":
|
|
2446
|
+
rawImports = extractTSImports(tree);
|
|
2447
|
+
break;
|
|
2448
|
+
case "python":
|
|
2449
|
+
rawImports = extractPythonImports(tree);
|
|
2450
|
+
break;
|
|
2451
|
+
default:
|
|
2452
|
+
return [];
|
|
2453
|
+
}
|
|
2454
|
+
return rawImports.map((raw) => ({
|
|
2455
|
+
source: raw.source,
|
|
2456
|
+
importType: raw.importType,
|
|
2457
|
+
importedSymbols: raw.importedSymbols,
|
|
2458
|
+
location: raw.location
|
|
2459
|
+
}));
|
|
2460
|
+
}
|
|
2461
|
+
function resolveImport(importInfo, fromFileId, fromFilePath, rootPath, fileIndex) {
|
|
2462
|
+
const { source, importType, importedSymbols, location } = importInfo;
|
|
2463
|
+
const isRelative = source.startsWith("./") || source.startsWith("../");
|
|
2464
|
+
const isBareSpecifier = !isRelative && !source.startsWith("/");
|
|
2465
|
+
if (isBareSpecifier) {
|
|
2466
|
+
return createUnresolvedImport(fromFileId, source, importType, importedSymbols, location);
|
|
2467
|
+
}
|
|
2468
|
+
const resolvedPath = resolveRelativeImport(fromFilePath, source, rootPath);
|
|
2469
|
+
if (resolvedPath) {
|
|
2470
|
+
const normalizedPath = normalizePathForIndex(resolvedPath, rootPath);
|
|
2471
|
+
const toFileId = fileIndex.get(normalizedPath);
|
|
2472
|
+
if (toFileId) {
|
|
2473
|
+
return {
|
|
2474
|
+
edge: {
|
|
2475
|
+
id: `import-${fromFileId}-${toFileId}-${location.startLine}`,
|
|
2476
|
+
sourceId: fromFileId,
|
|
2477
|
+
targetId: toFileId,
|
|
2478
|
+
importType,
|
|
2479
|
+
importedSymbols,
|
|
2480
|
+
resolved: true
|
|
2481
|
+
},
|
|
2482
|
+
resolved: true,
|
|
2483
|
+
resolvedPath
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
return createUnresolvedImport(fromFileId, source, importType, importedSymbols, location);
|
|
2488
|
+
}
|
|
2489
|
+
function createUnresolvedImport(fromFileId, source, importType, importedSymbols, location) {
|
|
2490
|
+
const externalId = `external:${source}`;
|
|
2491
|
+
return {
|
|
2492
|
+
edge: {
|
|
2493
|
+
id: `import-${fromFileId}-${externalId}-${location.startLine}`,
|
|
2494
|
+
sourceId: fromFileId,
|
|
2495
|
+
targetId: externalId,
|
|
2496
|
+
importType,
|
|
2497
|
+
importedSymbols,
|
|
2498
|
+
resolved: false
|
|
2499
|
+
},
|
|
2500
|
+
resolved: false
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
function resolveRelativeImport(fromFilePath, importSource, _rootPath) {
|
|
2504
|
+
const fromDir = path3.dirname(fromFilePath);
|
|
2505
|
+
const baseResolved = path3.resolve(fromDir, importSource);
|
|
2506
|
+
const candidates = generateResolutionCandidates(baseResolved);
|
|
2507
|
+
for (const candidate of candidates) {
|
|
2508
|
+
if (fs4.existsSync(candidate) && fs4.statSync(candidate).isFile()) {
|
|
2509
|
+
return candidate;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
return null;
|
|
2513
|
+
}
|
|
2514
|
+
function generateResolutionCandidates(basePath) {
|
|
2515
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2516
|
+
const indexFiles = extensions.map((ext) => `index${ext}`);
|
|
2517
|
+
const candidates = [];
|
|
2518
|
+
candidates.push(basePath);
|
|
2519
|
+
for (const ext of extensions) {
|
|
2520
|
+
candidates.push(basePath + ext);
|
|
2521
|
+
}
|
|
2522
|
+
for (const indexFile of indexFiles) {
|
|
2523
|
+
candidates.push(path3.join(basePath, indexFile));
|
|
2524
|
+
}
|
|
2525
|
+
return candidates;
|
|
2526
|
+
}
|
|
2527
|
+
function normalizePathForIndex(filePath, rootPath) {
|
|
2528
|
+
const relativePath = path3.relative(rootPath, filePath);
|
|
2529
|
+
return relativePath.replace(/\\/g, "/");
|
|
2530
|
+
}
|
|
2531
|
+
function buildFileIndex(files) {
|
|
2532
|
+
const index = /* @__PURE__ */ new Map();
|
|
2533
|
+
for (const file of files) {
|
|
2534
|
+
const normalized = file.path.replace(/\\/g, "/");
|
|
2535
|
+
index.set(normalized, file.id);
|
|
2536
|
+
const withoutExt = removeExtension(normalized);
|
|
2537
|
+
if (withoutExt !== normalized && !index.has(withoutExt)) {
|
|
2538
|
+
index.set(withoutExt, file.id);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
return index;
|
|
2542
|
+
}
|
|
2543
|
+
function removeExtension(filePath) {
|
|
2544
|
+
const ext = path3.extname(filePath);
|
|
2545
|
+
if (ext) {
|
|
2546
|
+
return filePath.slice(0, -ext.length);
|
|
2547
|
+
}
|
|
2548
|
+
return filePath;
|
|
2549
|
+
}
|
|
2550
|
+
async function resolveFileImports(filePath, content, fileId, rootPath, fileIndex) {
|
|
2551
|
+
const imports = await extractImports(filePath, content);
|
|
2552
|
+
return imports.map(
|
|
2553
|
+
(importInfo) => resolveImport(importInfo, fileId, filePath, rootPath, fileIndex)
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// src/analyzer/calls.ts
|
|
2558
|
+
async function extractCalls(filePath, content, symbols) {
|
|
2559
|
+
const registry2 = getLanguageRegistry();
|
|
2560
|
+
const language = await registry2.getLanguageForFile(filePath);
|
|
2561
|
+
if (!language) {
|
|
2562
|
+
return [];
|
|
2563
|
+
}
|
|
2564
|
+
const parserManager = getParserManager();
|
|
2565
|
+
const parseResult = await parserManager.safeParse(filePath, content, language);
|
|
2566
|
+
if ("error" in parseResult) {
|
|
2567
|
+
return [];
|
|
2568
|
+
}
|
|
2569
|
+
const tree = parseResult.result.tree;
|
|
2570
|
+
const rawSymbols = symbols.map((s) => ({
|
|
2571
|
+
name: s.name,
|
|
2572
|
+
kind: s.kind,
|
|
2573
|
+
location: s.location,
|
|
2574
|
+
exported: s.exported,
|
|
2575
|
+
parentName: void 0
|
|
2576
|
+
}));
|
|
2577
|
+
switch (language.name) {
|
|
2578
|
+
case "typescript":
|
|
2579
|
+
case "javascript":
|
|
2580
|
+
return extractTSCalls(tree, rawSymbols);
|
|
2581
|
+
case "python":
|
|
2582
|
+
return extractPythonCalls(tree, rawSymbols);
|
|
2583
|
+
default:
|
|
2584
|
+
return [];
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
function resolveCall(call, fileSymbols, symbolIndex, callCounts) {
|
|
2588
|
+
const callerSymbol = fileSymbols.find(
|
|
2589
|
+
(s) => s.name === call.callerName && (s.kind === "function" || s.kind === "method")
|
|
2590
|
+
);
|
|
2591
|
+
if (!callerSymbol) {
|
|
2592
|
+
return null;
|
|
2593
|
+
}
|
|
2594
|
+
let calleeSymbol;
|
|
2595
|
+
if (call.callType === "method") {
|
|
2596
|
+
const callerClass = fileSymbols.find(
|
|
2597
|
+
(s) => s.kind === "class" && fileSymbols.some(
|
|
2598
|
+
(m) => m.kind === "method" && m.name === call.callerName && m.fileId === s.fileId
|
|
2599
|
+
)
|
|
2600
|
+
);
|
|
2601
|
+
if (callerClass) {
|
|
2602
|
+
calleeSymbol = fileSymbols.find(
|
|
2603
|
+
(s) => s.kind === "method" && s.name === call.calleeName && s.fileId === callerClass.fileId
|
|
2604
|
+
);
|
|
2605
|
+
}
|
|
2606
|
+
} else {
|
|
2607
|
+
calleeSymbol = symbolIndex.get(call.calleeName);
|
|
2608
|
+
if (!calleeSymbol) {
|
|
2609
|
+
calleeSymbol = fileSymbols.find((s) => s.name === call.calleeName);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
const edgeKey = calleeSymbol ? `${callerSymbol.id}-${calleeSymbol.id}` : `${callerSymbol.id}-unresolved:${call.calleeName}`;
|
|
2613
|
+
const currentCount = callCounts.get(edgeKey) || 0;
|
|
2614
|
+
callCounts.set(edgeKey, currentCount + 1);
|
|
2615
|
+
if (!calleeSymbol) {
|
|
2616
|
+
return {
|
|
2617
|
+
edge: {
|
|
2618
|
+
id: `call-${callerSymbol.id}-unresolved-${call.calleeName}`,
|
|
2619
|
+
sourceId: callerSymbol.id,
|
|
2620
|
+
targetId: `unresolved:${call.calleeName}`,
|
|
2621
|
+
callCount: callCounts.get(edgeKey) || 1,
|
|
2622
|
+
resolved: false
|
|
2623
|
+
},
|
|
2624
|
+
resolved: false
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
2627
|
+
return {
|
|
2628
|
+
edge: {
|
|
2629
|
+
id: `call-${callerSymbol.id}-${calleeSymbol.id}`,
|
|
2630
|
+
sourceId: callerSymbol.id,
|
|
2631
|
+
targetId: calleeSymbol.id,
|
|
2632
|
+
callCount: callCounts.get(edgeKey) || 1,
|
|
2633
|
+
resolved: true
|
|
2634
|
+
},
|
|
2635
|
+
resolved: true
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2638
|
+
async function resolveFileCalls(filePath, content, fileSymbols, globalSymbolIndex) {
|
|
2639
|
+
const rawCalls = await extractCalls(filePath, content, fileSymbols);
|
|
2640
|
+
const resolvedCalls = [];
|
|
2641
|
+
const callCounts = /* @__PURE__ */ new Map();
|
|
2642
|
+
const seenEdges = /* @__PURE__ */ new Set();
|
|
2643
|
+
for (const call of rawCalls) {
|
|
2644
|
+
const resolved = resolveCall(call, fileSymbols, globalSymbolIndex, callCounts);
|
|
2645
|
+
if (resolved && !seenEdges.has(resolved.edge.id)) {
|
|
2646
|
+
seenEdges.add(resolved.edge.id);
|
|
2647
|
+
resolvedCalls.push(resolved);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
for (const resolvedCall of resolvedCalls) {
|
|
2651
|
+
const actualEdgeKey = resolvedCall.edge.sourceId + "-" + (resolvedCall.resolved ? resolvedCall.edge.targetId : resolvedCall.edge.targetId.replace("unresolved:", ""));
|
|
2652
|
+
resolvedCall.edge.callCount = callCounts.get(actualEdgeKey) || 1;
|
|
2653
|
+
}
|
|
2654
|
+
return resolvedCalls;
|
|
2655
|
+
}
|
|
2656
|
+
function buildGlobalSymbolIndex(allSymbols) {
|
|
2657
|
+
const index = /* @__PURE__ */ new Map();
|
|
2658
|
+
for (const symbol of allSymbols) {
|
|
2659
|
+
if (symbol.exported) {
|
|
2660
|
+
if (!index.has(symbol.name)) {
|
|
2661
|
+
index.set(symbol.name, symbol);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
return index;
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
// src/analyzer/index.ts
|
|
2669
|
+
var DEFAULT_CONFIG2 = {
|
|
2670
|
+
include: [],
|
|
2671
|
+
exclude: [],
|
|
2672
|
+
respectGitignore: true,
|
|
2673
|
+
languages: [],
|
|
2674
|
+
extractCalls: true,
|
|
2675
|
+
maxFiles: 1e4
|
|
2676
|
+
};
|
|
2677
|
+
async function analyzeCodebase(config) {
|
|
2678
|
+
const startTime = performance.now();
|
|
2679
|
+
const errors = [];
|
|
2680
|
+
const fullConfig = {
|
|
2681
|
+
...DEFAULT_CONFIG2,
|
|
2682
|
+
...config,
|
|
2683
|
+
rootPath: path4.resolve(config.rootPath)
|
|
2684
|
+
};
|
|
2685
|
+
const { onProgress } = config;
|
|
2686
|
+
onProgress?.({ phase: "scanning", current: 0, total: 1 });
|
|
2687
|
+
const scanConfig = {
|
|
2688
|
+
rootPath: fullConfig.rootPath,
|
|
2689
|
+
include: fullConfig.include,
|
|
2690
|
+
exclude: fullConfig.exclude,
|
|
2691
|
+
respectGitignore: fullConfig.respectGitignore,
|
|
2692
|
+
maxFiles: fullConfig.maxFiles
|
|
2693
|
+
};
|
|
2694
|
+
let scanResult;
|
|
2695
|
+
try {
|
|
2696
|
+
scanResult = await scanDirectory(scanConfig);
|
|
2697
|
+
} catch (err) {
|
|
2698
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2699
|
+
throw new Error(`Failed to scan directory: ${message}`);
|
|
2700
|
+
}
|
|
2701
|
+
const { directories, files, skipped } = scanResult;
|
|
2702
|
+
const fileIndex = buildFileIndex(
|
|
2703
|
+
files.map((f) => ({ id: f.id, path: f.path }))
|
|
2704
|
+
);
|
|
2705
|
+
const parseStartTime = performance.now();
|
|
2706
|
+
const allSymbols = [];
|
|
2707
|
+
const fileSymbolMap = /* @__PURE__ */ new Map();
|
|
2708
|
+
const fileContentMap = /* @__PURE__ */ new Map();
|
|
2709
|
+
const detectedLanguages = /* @__PURE__ */ new Set();
|
|
2710
|
+
for (let i = 0; i < files.length; i++) {
|
|
2711
|
+
const file = files[i];
|
|
2712
|
+
const filePath = path4.join(fullConfig.rootPath, file.path);
|
|
2713
|
+
onProgress?.({
|
|
2714
|
+
phase: "parsing",
|
|
2715
|
+
current: i + 1,
|
|
2716
|
+
total: files.length,
|
|
2717
|
+
currentFile: file.path
|
|
2718
|
+
});
|
|
2719
|
+
try {
|
|
2720
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
2721
|
+
fileContentMap.set(file.id, content);
|
|
2722
|
+
const result = await extractSymbols(filePath, content, file.id);
|
|
2723
|
+
file.metrics = result.metrics;
|
|
2724
|
+
file.symbols = result.symbols.map((s) => s.id);
|
|
2725
|
+
if (file.language !== "unknown") {
|
|
2726
|
+
detectedLanguages.add(file.language);
|
|
2727
|
+
}
|
|
2728
|
+
allSymbols.push(...result.symbols);
|
|
2729
|
+
fileSymbolMap.set(file.id, result.symbols);
|
|
2730
|
+
if (result.parseErrors) {
|
|
2731
|
+
for (const error of result.parseErrors) {
|
|
2732
|
+
errors.push({ file: file.path, message: error, phase: "parse" });
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
} catch (err) {
|
|
2736
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2737
|
+
errors.push({ file: file.path, message, phase: "parse" });
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
const parseTime = performance.now() - parseStartTime;
|
|
2741
|
+
const importEdges = [];
|
|
2742
|
+
const callEdges = [];
|
|
2743
|
+
let resolvedImports = 0;
|
|
2744
|
+
let resolvedCalls = 0;
|
|
2745
|
+
const globalSymbolIndex = buildGlobalSymbolIndex(allSymbols);
|
|
2746
|
+
for (let i = 0; i < files.length; i++) {
|
|
2747
|
+
const file = files[i];
|
|
2748
|
+
const filePath = path4.join(fullConfig.rootPath, file.path);
|
|
2749
|
+
const content = fileContentMap.get(file.id);
|
|
2750
|
+
const fileSymbols = fileSymbolMap.get(file.id) || [];
|
|
2751
|
+
onProgress?.({
|
|
2752
|
+
phase: "resolving",
|
|
2753
|
+
current: i + 1,
|
|
2754
|
+
total: files.length,
|
|
2755
|
+
currentFile: file.path
|
|
2756
|
+
});
|
|
2757
|
+
if (!content) continue;
|
|
2758
|
+
try {
|
|
2759
|
+
const imports = await resolveFileImports(
|
|
2760
|
+
filePath,
|
|
2761
|
+
content,
|
|
2762
|
+
file.id,
|
|
2763
|
+
fullConfig.rootPath,
|
|
2764
|
+
fileIndex
|
|
2765
|
+
);
|
|
2766
|
+
for (const imp of imports) {
|
|
2767
|
+
importEdges.push(imp.edge);
|
|
2768
|
+
if (imp.resolved) {
|
|
2769
|
+
resolvedImports++;
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
if (fullConfig.extractCalls) {
|
|
2773
|
+
const calls = await resolveFileCalls(
|
|
2774
|
+
filePath,
|
|
2775
|
+
content,
|
|
2776
|
+
fileSymbols,
|
|
2777
|
+
globalSymbolIndex
|
|
2778
|
+
);
|
|
2779
|
+
for (const call of calls) {
|
|
2780
|
+
callEdges.push(call.edge);
|
|
2781
|
+
if (call.resolved) {
|
|
2782
|
+
resolvedCalls++;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
} catch (err) {
|
|
2787
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2788
|
+
errors.push({ file: file.path, message, phase: "resolve" });
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
aggregateDirectoryMetrics(directories, files);
|
|
2792
|
+
const totalTime = performance.now() - startTime;
|
|
2793
|
+
const graph = {
|
|
2794
|
+
directories,
|
|
2795
|
+
files,
|
|
2796
|
+
symbols: allSymbols,
|
|
2797
|
+
imports: importEdges,
|
|
2798
|
+
calls: callEdges,
|
|
2799
|
+
metadata: {
|
|
2800
|
+
rootPath: fullConfig.rootPath,
|
|
2801
|
+
analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2802
|
+
totalFiles: files.length,
|
|
2803
|
+
totalDirectories: directories.length,
|
|
2804
|
+
totalSymbols: allSymbols.length,
|
|
2805
|
+
languages: Array.from(detectedLanguages),
|
|
2806
|
+
analysisDurationMs: totalTime
|
|
2807
|
+
}
|
|
2808
|
+
};
|
|
2809
|
+
const stats = {
|
|
2810
|
+
totalFiles: files.length,
|
|
2811
|
+
totalDirectories: directories.length,
|
|
2812
|
+
totalSymbols: allSymbols.length,
|
|
2813
|
+
totalImports: importEdges.length,
|
|
2814
|
+
totalCalls: callEdges.length,
|
|
2815
|
+
resolvedImports,
|
|
2816
|
+
resolvedCalls,
|
|
2817
|
+
parseTime,
|
|
2818
|
+
totalTime,
|
|
2819
|
+
skippedFiles: skipped.length
|
|
2820
|
+
};
|
|
2821
|
+
return { graph, stats, errors };
|
|
2822
|
+
}
|
|
2823
|
+
function aggregateDirectoryMetrics(directories, files) {
|
|
2824
|
+
const dirMap = /* @__PURE__ */ new Map();
|
|
2825
|
+
for (const dir of directories) {
|
|
2826
|
+
dirMap.set(dir.id, dir);
|
|
2827
|
+
}
|
|
2828
|
+
const filesByDir = /* @__PURE__ */ new Map();
|
|
2829
|
+
for (const file of files) {
|
|
2830
|
+
const dirFiles = filesByDir.get(file.directoryId) || [];
|
|
2831
|
+
dirFiles.push(file);
|
|
2832
|
+
filesByDir.set(file.directoryId, dirFiles);
|
|
2833
|
+
}
|
|
2834
|
+
const sortedDirs = [...directories].sort((a, b) => b.depth - a.depth);
|
|
2835
|
+
for (const dir of sortedDirs) {
|
|
2836
|
+
const dirFiles = filesByDir.get(dir.id) || [];
|
|
2837
|
+
let totalLoc = dirFiles.reduce((sum, f) => sum + f.metrics.loc, 0);
|
|
2838
|
+
let totalFiles = dirFiles.length;
|
|
2839
|
+
for (const childId of dir.children) {
|
|
2840
|
+
const child = dirMap.get(childId);
|
|
2841
|
+
if (child) {
|
|
2842
|
+
totalLoc += child.metrics.totalLoc;
|
|
2843
|
+
totalFiles += child.metrics.fileCount;
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
dir.metrics = {
|
|
2847
|
+
fileCount: totalFiles,
|
|
2848
|
+
totalLoc
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
function resetAnalyzer() {
|
|
2853
|
+
resetParserManager();
|
|
2854
|
+
resetLanguageRegistry();
|
|
2855
|
+
}
|
|
2856
|
+
|
|
1
2857
|
// src/index.ts
|
|
2
2858
|
var VERSION = "0.1.0";
|
|
3
|
-
function createCodeViz(_config) {
|
|
4
|
-
console.log("codeviz", VERSION);
|
|
5
|
-
}
|
|
6
2859
|
export {
|
|
2860
|
+
AggregateNode as AggregateNodeComponent,
|
|
2861
|
+
BundledEdge as BundledEdgeComponent,
|
|
2862
|
+
CallEdge as CallEdgeComponent,
|
|
2863
|
+
CodeMap as CodeMapComponent,
|
|
2864
|
+
DEFAULT_ANIMATION_CONFIG,
|
|
2865
|
+
DEFAULT_CODEMAP_CONFIG,
|
|
2866
|
+
DEFAULT_DARK_SIGMA_THEME,
|
|
2867
|
+
DEFAULT_LAYOUT_CONFIG,
|
|
2868
|
+
DEFAULT_LIGHT_SIGMA_THEME,
|
|
2869
|
+
DEFAULT_SIGMA_THEME,
|
|
2870
|
+
DEFAULT_ZOOM_LEVELS,
|
|
2871
|
+
EDGE_SIZES,
|
|
2872
|
+
FileNode as FileNodeComponent,
|
|
2873
|
+
Flow,
|
|
2874
|
+
ImportEdge as ImportEdgeComponent,
|
|
2875
|
+
NODE_SIZES,
|
|
2876
|
+
OverlayLayer,
|
|
2877
|
+
OverlayPort,
|
|
2878
|
+
PortalOverlayLayer,
|
|
2879
|
+
SVGOverlayLayer,
|
|
2880
|
+
ScopeNode as ScopeNodeComponent,
|
|
2881
|
+
SigmaRenderer,
|
|
2882
|
+
SymbolNode as SymbolNodeComponent,
|
|
2883
|
+
ThemeProvider,
|
|
7
2884
|
VERSION,
|
|
8
|
-
|
|
2885
|
+
analyzeCodebase,
|
|
2886
|
+
assignClusterColors,
|
|
2887
|
+
assignInitialPositions,
|
|
2888
|
+
boundingBoxFromCorners,
|
|
2889
|
+
boundingBoxesOverlap,
|
|
2890
|
+
brightenColor,
|
|
2891
|
+
buildDirectoryGraph,
|
|
2892
|
+
buildFileIndex,
|
|
2893
|
+
buildGlobalSymbolIndex,
|
|
2894
|
+
calculateNodeSize,
|
|
2895
|
+
calculateOverlayPosition,
|
|
2896
|
+
clampPosition,
|
|
2897
|
+
codeGraphToGraphology,
|
|
2898
|
+
codeMapToReactFlowEdges,
|
|
2899
|
+
codeMapToReactFlowNodes,
|
|
2900
|
+
computeDirectoryForces,
|
|
2901
|
+
computeEdges,
|
|
2902
|
+
computeHierarchy,
|
|
2903
|
+
computeLayout,
|
|
2904
|
+
computeZoomLevels,
|
|
2905
|
+
createBoundingBox,
|
|
2906
|
+
createEdgeReducer,
|
|
2907
|
+
createNodeReducer,
|
|
2908
|
+
createOverlayPort,
|
|
2909
|
+
createScopeColorGetter,
|
|
2910
|
+
darkTheme,
|
|
2911
|
+
darken,
|
|
2912
|
+
detectCommunities,
|
|
2913
|
+
detectLanguage,
|
|
2914
|
+
dimColor,
|
|
2915
|
+
distance,
|
|
2916
|
+
edgeTypes,
|
|
2917
|
+
expandBoundingBox,
|
|
2918
|
+
extractCalls,
|
|
2919
|
+
extractImports,
|
|
2920
|
+
extractSymbols,
|
|
2921
|
+
extractSymbolsFromFile,
|
|
2922
|
+
formatLoc,
|
|
2923
|
+
formatNumber,
|
|
2924
|
+
generateCallEdgeId,
|
|
2925
|
+
generateDirectoryId,
|
|
2926
|
+
generateFileId,
|
|
2927
|
+
generateImportEdgeId,
|
|
2928
|
+
generateScopeDepthColors,
|
|
2929
|
+
generateSymbolId,
|
|
2930
|
+
getAnchorOffset,
|
|
2931
|
+
getBasename,
|
|
2932
|
+
getBoundingBoxCenter,
|
|
2933
|
+
getChildScopes,
|
|
2934
|
+
getClusterStats,
|
|
2935
|
+
getContrastRatio,
|
|
2936
|
+
getDefaultExpandedScopes,
|
|
2937
|
+
getDefaultTheme,
|
|
2938
|
+
getDirectory,
|
|
2939
|
+
getDirectoryConnectionWeight,
|
|
2940
|
+
getDirectoryPosition,
|
|
2941
|
+
getEdgeLabel,
|
|
2942
|
+
getEdgesAtZoomLevel,
|
|
2943
|
+
getEntityId,
|
|
2944
|
+
getExtension,
|
|
2945
|
+
getFA2Settings,
|
|
2946
|
+
getFileIcon,
|
|
2947
|
+
getFilename,
|
|
2948
|
+
getFilesInDirectory,
|
|
2949
|
+
getIconName,
|
|
2950
|
+
getLanguageRegistry,
|
|
2951
|
+
getLayoutDuration,
|
|
2952
|
+
getLuminance,
|
|
2953
|
+
getMediumFileLabel,
|
|
2954
|
+
getNodeCluster,
|
|
2955
|
+
getNodeKey,
|
|
2956
|
+
getNodeMass,
|
|
2957
|
+
getNodeOpacityAtLevel,
|
|
2958
|
+
getNodeSizeAtLevel,
|
|
2959
|
+
getNodesInCluster,
|
|
2960
|
+
getParentDirectories,
|
|
2961
|
+
getParserManager,
|
|
2962
|
+
getPathDepth,
|
|
2963
|
+
getPositionsAtDepth,
|
|
2964
|
+
getRootScopes,
|
|
2965
|
+
getScaledNodeSize,
|
|
2966
|
+
getScopeByDirectoryId,
|
|
2967
|
+
getScopeDepthColor,
|
|
2968
|
+
getShortFileLabel,
|
|
2969
|
+
getSigmaTheme,
|
|
2970
|
+
getSymbolIcon,
|
|
2971
|
+
getSymbolKindLabel,
|
|
2972
|
+
getTextColorForBackground,
|
|
2973
|
+
getTheme,
|
|
2974
|
+
getThemeIds,
|
|
2975
|
+
getVisibleEdges,
|
|
2976
|
+
getVisibleNodes,
|
|
2977
|
+
getVisibleScopes,
|
|
2978
|
+
getZoomLevel,
|
|
2979
|
+
getZoomLevelFromZoom,
|
|
2980
|
+
graphToScreen,
|
|
2981
|
+
hexToRgb,
|
|
2982
|
+
hslToHex,
|
|
2983
|
+
hslToRgb,
|
|
2984
|
+
isCodeFile,
|
|
2985
|
+
isPointInBoundingBox,
|
|
2986
|
+
lerp,
|
|
2987
|
+
lightTheme,
|
|
2988
|
+
lighten,
|
|
2989
|
+
matchesPattern,
|
|
2990
|
+
mergeTheme,
|
|
2991
|
+
mix,
|
|
2992
|
+
nexusToGraphology,
|
|
2993
|
+
nodeTypes,
|
|
2994
|
+
normalizePath,
|
|
2995
|
+
normalizePositions,
|
|
2996
|
+
positionAllNodes,
|
|
2997
|
+
positionNodesInScope,
|
|
2998
|
+
registerTheme,
|
|
2999
|
+
removeAllOverlaps,
|
|
3000
|
+
removeOverlapsInScope,
|
|
3001
|
+
resetAnalyzer,
|
|
3002
|
+
resetLanguageRegistry,
|
|
3003
|
+
resetParserManager,
|
|
3004
|
+
resolveFileCalls,
|
|
3005
|
+
resolveImport,
|
|
3006
|
+
rgbToHex,
|
|
3007
|
+
rgbToHsl,
|
|
3008
|
+
rgbaToCss,
|
|
3009
|
+
saturate,
|
|
3010
|
+
scaleSize,
|
|
3011
|
+
scanDirectory,
|
|
3012
|
+
screenToGraph,
|
|
3013
|
+
shouldUseDarkText,
|
|
3014
|
+
shrinkBoundingBox,
|
|
3015
|
+
structureToGraphology,
|
|
3016
|
+
truncateLabel,
|
|
3017
|
+
uniformPadding,
|
|
3018
|
+
unionBoundingBoxes,
|
|
3019
|
+
useCurrentTheme,
|
|
3020
|
+
useIsDarkTheme,
|
|
3021
|
+
useLayout,
|
|
3022
|
+
useOverlayPort,
|
|
3023
|
+
useSigma,
|
|
3024
|
+
useTheme,
|
|
3025
|
+
withOpacity
|
|
9
3026
|
};
|