spec-gen-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1078 -0
- package/dist/api/analyze.d.ts +17 -0
- package/dist/api/analyze.d.ts.map +1 -0
- package/dist/api/analyze.js +109 -0
- package/dist/api/analyze.js.map +1 -0
- package/dist/api/drift.d.ts +21 -0
- package/dist/api/drift.d.ts.map +1 -0
- package/dist/api/drift.js +145 -0
- package/dist/api/drift.js.map +1 -0
- package/dist/api/generate.d.ts +18 -0
- package/dist/api/generate.d.ts.map +1 -0
- package/dist/api/generate.js +251 -0
- package/dist/api/generate.js.map +1 -0
- package/dist/api/index.d.ts +39 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +32 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/init.d.ts +18 -0
- package/dist/api/init.d.ts.map +1 -0
- package/dist/api/init.js +82 -0
- package/dist/api/init.js.map +1 -0
- package/dist/api/run.d.ts +19 -0
- package/dist/api/run.d.ts.map +1 -0
- package/dist/api/run.js +291 -0
- package/dist/api/run.js.map +1 -0
- package/dist/api/specs.d.ts +49 -0
- package/dist/api/specs.d.ts.map +1 -0
- package/dist/api/specs.js +136 -0
- package/dist/api/specs.js.map +1 -0
- package/dist/api/types.d.ts +176 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +9 -0
- package/dist/api/types.js.map +1 -0
- package/dist/api/verify.d.ts +20 -0
- package/dist/api/verify.d.ts.map +1 -0
- package/dist/api/verify.js +117 -0
- package/dist/api/verify.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +27 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +485 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/drift.d.ts +9 -0
- package/dist/cli/commands/drift.d.ts.map +1 -0
- package/dist/cli/commands/drift.js +540 -0
- package/dist/cli/commands/drift.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +9 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +633 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +171 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +638 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +574 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/run.d.ts +24 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +546 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/verify.d.ts +9 -0
- package/dist/cli/commands/verify.d.ts.map +1 -0
- package/dist/cli/commands/verify.js +417 -0
- package/dist/cli/commands/verify.js.map +1 -0
- package/dist/cli/commands/view.d.ts +9 -0
- package/dist/cli/commands/view.d.ts.map +1 -0
- package/dist/cli/commands/view.js +511 -0
- package/dist/cli/commands/view.js.map +1 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +83 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/analyzer/architecture-writer.d.ts +67 -0
- package/dist/core/analyzer/architecture-writer.d.ts.map +1 -0
- package/dist/core/analyzer/architecture-writer.js +209 -0
- package/dist/core/analyzer/architecture-writer.js.map +1 -0
- package/dist/core/analyzer/artifact-generator.d.ts +222 -0
- package/dist/core/analyzer/artifact-generator.d.ts.map +1 -0
- package/dist/core/analyzer/artifact-generator.js +726 -0
- package/dist/core/analyzer/artifact-generator.js.map +1 -0
- package/dist/core/analyzer/call-graph.d.ts +83 -0
- package/dist/core/analyzer/call-graph.d.ts.map +1 -0
- package/dist/core/analyzer/call-graph.js +827 -0
- package/dist/core/analyzer/call-graph.js.map +1 -0
- package/dist/core/analyzer/code-shaper.d.ts +33 -0
- package/dist/core/analyzer/code-shaper.d.ts.map +1 -0
- package/dist/core/analyzer/code-shaper.js +149 -0
- package/dist/core/analyzer/code-shaper.js.map +1 -0
- package/dist/core/analyzer/dependency-graph.d.ts +179 -0
- package/dist/core/analyzer/dependency-graph.d.ts.map +1 -0
- package/dist/core/analyzer/dependency-graph.js +574 -0
- package/dist/core/analyzer/dependency-graph.js.map +1 -0
- package/dist/core/analyzer/duplicate-detector.d.ts +52 -0
- package/dist/core/analyzer/duplicate-detector.d.ts.map +1 -0
- package/dist/core/analyzer/duplicate-detector.js +279 -0
- package/dist/core/analyzer/duplicate-detector.js.map +1 -0
- package/dist/core/analyzer/embedding-service.d.ts +50 -0
- package/dist/core/analyzer/embedding-service.d.ts.map +1 -0
- package/dist/core/analyzer/embedding-service.js +104 -0
- package/dist/core/analyzer/embedding-service.js.map +1 -0
- package/dist/core/analyzer/file-walker.d.ts +78 -0
- package/dist/core/analyzer/file-walker.d.ts.map +1 -0
- package/dist/core/analyzer/file-walker.js +531 -0
- package/dist/core/analyzer/file-walker.js.map +1 -0
- package/dist/core/analyzer/import-parser.d.ts +91 -0
- package/dist/core/analyzer/import-parser.d.ts.map +1 -0
- package/dist/core/analyzer/import-parser.js +720 -0
- package/dist/core/analyzer/import-parser.js.map +1 -0
- package/dist/core/analyzer/index.d.ts +10 -0
- package/dist/core/analyzer/index.d.ts.map +1 -0
- package/dist/core/analyzer/index.js +10 -0
- package/dist/core/analyzer/index.js.map +1 -0
- package/dist/core/analyzer/refactor-analyzer.d.ts +80 -0
- package/dist/core/analyzer/refactor-analyzer.d.ts.map +1 -0
- package/dist/core/analyzer/refactor-analyzer.js +339 -0
- package/dist/core/analyzer/refactor-analyzer.js.map +1 -0
- package/dist/core/analyzer/repository-mapper.d.ts +150 -0
- package/dist/core/analyzer/repository-mapper.d.ts.map +1 -0
- package/dist/core/analyzer/repository-mapper.js +731 -0
- package/dist/core/analyzer/repository-mapper.js.map +1 -0
- package/dist/core/analyzer/signature-extractor.d.ts +31 -0
- package/dist/core/analyzer/signature-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/signature-extractor.js +387 -0
- package/dist/core/analyzer/signature-extractor.js.map +1 -0
- package/dist/core/analyzer/significance-scorer.d.ts +79 -0
- package/dist/core/analyzer/significance-scorer.d.ts.map +1 -0
- package/dist/core/analyzer/significance-scorer.js +407 -0
- package/dist/core/analyzer/significance-scorer.js.map +1 -0
- package/dist/core/analyzer/subgraph-extractor.d.ts +43 -0
- package/dist/core/analyzer/subgraph-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/subgraph-extractor.js +129 -0
- package/dist/core/analyzer/subgraph-extractor.js.map +1 -0
- package/dist/core/analyzer/vector-index.d.ts +63 -0
- package/dist/core/analyzer/vector-index.d.ts.map +1 -0
- package/dist/core/analyzer/vector-index.js +169 -0
- package/dist/core/analyzer/vector-index.js.map +1 -0
- package/dist/core/drift/drift-detector.d.ts +102 -0
- package/dist/core/drift/drift-detector.d.ts.map +1 -0
- package/dist/core/drift/drift-detector.js +597 -0
- package/dist/core/drift/drift-detector.js.map +1 -0
- package/dist/core/drift/git-diff.d.ts +55 -0
- package/dist/core/drift/git-diff.d.ts.map +1 -0
- package/dist/core/drift/git-diff.js +356 -0
- package/dist/core/drift/git-diff.js.map +1 -0
- package/dist/core/drift/index.d.ts +12 -0
- package/dist/core/drift/index.d.ts.map +1 -0
- package/dist/core/drift/index.js +9 -0
- package/dist/core/drift/index.js.map +1 -0
- package/dist/core/drift/spec-mapper.d.ts +73 -0
- package/dist/core/drift/spec-mapper.d.ts.map +1 -0
- package/dist/core/drift/spec-mapper.js +353 -0
- package/dist/core/drift/spec-mapper.js.map +1 -0
- package/dist/core/generator/adr-generator.d.ts +32 -0
- package/dist/core/generator/adr-generator.d.ts.map +1 -0
- package/dist/core/generator/adr-generator.js +192 -0
- package/dist/core/generator/adr-generator.js.map +1 -0
- package/dist/core/generator/index.d.ts +9 -0
- package/dist/core/generator/index.d.ts.map +1 -0
- package/dist/core/generator/index.js +12 -0
- package/dist/core/generator/index.js.map +1 -0
- package/dist/core/generator/mapping-generator.d.ts +54 -0
- package/dist/core/generator/mapping-generator.d.ts.map +1 -0
- package/dist/core/generator/mapping-generator.js +239 -0
- package/dist/core/generator/mapping-generator.js.map +1 -0
- package/dist/core/generator/openspec-compat.d.ts +160 -0
- package/dist/core/generator/openspec-compat.d.ts.map +1 -0
- package/dist/core/generator/openspec-compat.js +523 -0
- package/dist/core/generator/openspec-compat.js.map +1 -0
- package/dist/core/generator/openspec-format-generator.d.ts +111 -0
- package/dist/core/generator/openspec-format-generator.d.ts.map +1 -0
- package/dist/core/generator/openspec-format-generator.js +817 -0
- package/dist/core/generator/openspec-format-generator.js.map +1 -0
- package/dist/core/generator/openspec-writer.d.ts +131 -0
- package/dist/core/generator/openspec-writer.d.ts.map +1 -0
- package/dist/core/generator/openspec-writer.js +379 -0
- package/dist/core/generator/openspec-writer.js.map +1 -0
- package/dist/core/generator/prompts.d.ts +35 -0
- package/dist/core/generator/prompts.d.ts.map +1 -0
- package/dist/core/generator/prompts.js +212 -0
- package/dist/core/generator/prompts.js.map +1 -0
- package/dist/core/generator/spec-pipeline.d.ts +94 -0
- package/dist/core/generator/spec-pipeline.d.ts.map +1 -0
- package/dist/core/generator/spec-pipeline.js +474 -0
- package/dist/core/generator/spec-pipeline.js.map +1 -0
- package/dist/core/generator/stages/stage1-survey.d.ts +19 -0
- package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -0
- package/dist/core/generator/stages/stage1-survey.js +105 -0
- package/dist/core/generator/stages/stage1-survey.js.map +1 -0
- package/dist/core/generator/stages/stage2-entities.d.ts +11 -0
- package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -0
- package/dist/core/generator/stages/stage2-entities.js +67 -0
- package/dist/core/generator/stages/stage2-entities.js.map +1 -0
- package/dist/core/generator/stages/stage3-services.d.ts +11 -0
- package/dist/core/generator/stages/stage3-services.d.ts.map +1 -0
- package/dist/core/generator/stages/stage3-services.js +75 -0
- package/dist/core/generator/stages/stage3-services.js.map +1 -0
- package/dist/core/generator/stages/stage4-api.d.ts +11 -0
- package/dist/core/generator/stages/stage4-api.d.ts.map +1 -0
- package/dist/core/generator/stages/stage4-api.js +65 -0
- package/dist/core/generator/stages/stage4-api.js.map +1 -0
- package/dist/core/generator/stages/stage5-architecture.d.ts +10 -0
- package/dist/core/generator/stages/stage5-architecture.d.ts.map +1 -0
- package/dist/core/generator/stages/stage5-architecture.js +62 -0
- package/dist/core/generator/stages/stage5-architecture.js.map +1 -0
- package/dist/core/generator/stages/stage6-adr.d.ts +8 -0
- package/dist/core/generator/stages/stage6-adr.d.ts.map +1 -0
- package/dist/core/generator/stages/stage6-adr.js +41 -0
- package/dist/core/generator/stages/stage6-adr.js.map +1 -0
- package/dist/core/services/chat-agent.d.ts +45 -0
- package/dist/core/services/chat-agent.d.ts.map +1 -0
- package/dist/core/services/chat-agent.js +310 -0
- package/dist/core/services/chat-agent.js.map +1 -0
- package/dist/core/services/chat-tools.d.ts +32 -0
- package/dist/core/services/chat-tools.d.ts.map +1 -0
- package/dist/core/services/chat-tools.js +270 -0
- package/dist/core/services/chat-tools.js.map +1 -0
- package/dist/core/services/config-manager.d.ts +61 -0
- package/dist/core/services/config-manager.d.ts.map +1 -0
- package/dist/core/services/config-manager.js +143 -0
- package/dist/core/services/config-manager.js.map +1 -0
- package/dist/core/services/gitignore-manager.d.ts +29 -0
- package/dist/core/services/gitignore-manager.d.ts.map +1 -0
- package/dist/core/services/gitignore-manager.js +106 -0
- package/dist/core/services/gitignore-manager.js.map +1 -0
- package/dist/core/services/index.d.ts +8 -0
- package/dist/core/services/index.d.ts.map +1 -0
- package/dist/core/services/index.js +8 -0
- package/dist/core/services/index.js.map +1 -0
- package/dist/core/services/llm-service.d.ts +336 -0
- package/dist/core/services/llm-service.d.ts.map +1 -0
- package/dist/core/services/llm-service.js +1155 -0
- package/dist/core/services/llm-service.js.map +1 -0
- package/dist/core/services/mcp-handlers/analysis.d.ts +42 -0
- package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/analysis.js +300 -0
- package/dist/core/services/mcp-handlers/analysis.js.map +1 -0
- package/dist/core/services/mcp-handlers/graph.d.ts +65 -0
- package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/graph.js +509 -0
- package/dist/core/services/mcp-handlers/graph.js.map +1 -0
- package/dist/core/services/mcp-handlers/semantic.d.ts +38 -0
- package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/semantic.js +172 -0
- package/dist/core/services/mcp-handlers/semantic.js.map +1 -0
- package/dist/core/services/mcp-handlers/utils.d.ts +21 -0
- package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/utils.js +62 -0
- package/dist/core/services/mcp-handlers/utils.js.map +1 -0
- package/dist/core/services/project-detector.d.ts +32 -0
- package/dist/core/services/project-detector.d.ts.map +1 -0
- package/dist/core/services/project-detector.js +111 -0
- package/dist/core/services/project-detector.js.map +1 -0
- package/dist/core/verifier/index.d.ts +5 -0
- package/dist/core/verifier/index.d.ts.map +1 -0
- package/dist/core/verifier/index.js +5 -0
- package/dist/core/verifier/index.js.map +1 -0
- package/dist/core/verifier/verification-engine.d.ts +226 -0
- package/dist/core/verifier/verification-engine.d.ts.map +1 -0
- package/dist/core/verifier/verification-engine.js +681 -0
- package/dist/core/verifier/verification-engine.js.map +1 -0
- package/dist/types/index.d.ts +252 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/pipeline.d.ts +148 -0
- package/dist/types/pipeline.d.ts.map +1 -0
- package/dist/types/pipeline.js +5 -0
- package/dist/types/pipeline.js.map +1 -0
- package/dist/utils/errors.d.ts +51 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +128 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +149 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +331 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/progress.d.ts +142 -0
- package/dist/utils/progress.d.ts.map +1 -0
- package/dist/utils/progress.js +280 -0
- package/dist/utils/progress.js.map +1 -0
- package/dist/utils/prompts.d.ts +53 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +199 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/shutdown.d.ts +89 -0
- package/dist/utils/shutdown.d.ts.map +1 -0
- package/dist/utils/shutdown.js +237 -0
- package/dist/utils/shutdown.js.map +1 -0
- package/package.json +114 -0
- package/src/viewer/InteractiveGraphViewer.jsx +1486 -0
- package/src/viewer/app/index.html +17 -0
- package/src/viewer/app/main.jsx +13 -0
- package/src/viewer/components/ArchitectureView.jsx +177 -0
- package/src/viewer/components/ChatPanel.jsx +448 -0
- package/src/viewer/components/ClusterGraph.jsx +441 -0
- package/src/viewer/components/FilterBar.jsx +179 -0
- package/src/viewer/components/FlatGraph.jsx +275 -0
- package/src/viewer/components/MicroComponents.jsx +83 -0
- package/src/viewer/hooks/usePanZoom.js +79 -0
- package/src/viewer/utils/constants.js +47 -0
- package/src/viewer/utils/graph-helpers.js +291 -0
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import/Export Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses source files to extract imports and exports.
|
|
5
|
+
* Uses regex-based parsing for speed and simplicity.
|
|
6
|
+
* Supports JavaScript/TypeScript and Python.
|
|
7
|
+
*/
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { dirname, join, resolve, extname } from 'node:path';
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// NODE.JS BUILTINS
|
|
12
|
+
// ============================================================================
|
|
13
|
+
const NODE_BUILTINS = new Set([
|
|
14
|
+
'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console',
|
|
15
|
+
'constants', 'crypto', 'dgram', 'diagnostics_channel', 'dns', 'domain',
|
|
16
|
+
'events', 'fs', 'http', 'http2', 'https', 'inspector', 'module', 'net',
|
|
17
|
+
'os', 'path', 'perf_hooks', 'process', 'punycode', 'querystring', 'readline',
|
|
18
|
+
'repl', 'stream', 'string_decoder', 'timers', 'tls', 'trace_events', 'tty',
|
|
19
|
+
'url', 'util', 'v8', 'vm', 'wasi', 'worker_threads', 'zlib',
|
|
20
|
+
// Node.js prefixed versions
|
|
21
|
+
'node:assert', 'node:async_hooks', 'node:buffer', 'node:child_process',
|
|
22
|
+
'node:cluster', 'node:console', 'node:constants', 'node:crypto', 'node:dgram',
|
|
23
|
+
'node:diagnostics_channel', 'node:dns', 'node:domain', 'node:events',
|
|
24
|
+
'node:fs', 'node:http', 'node:http2', 'node:https', 'node:inspector',
|
|
25
|
+
'node:module', 'node:net', 'node:os', 'node:path', 'node:perf_hooks',
|
|
26
|
+
'node:process', 'node:punycode', 'node:querystring', 'node:readline',
|
|
27
|
+
'node:repl', 'node:stream', 'node:string_decoder', 'node:timers', 'node:tls',
|
|
28
|
+
'node:trace_events', 'node:tty', 'node:url', 'node:util', 'node:v8',
|
|
29
|
+
'node:vm', 'node:wasi', 'node:worker_threads', 'node:zlib',
|
|
30
|
+
// fs/promises and other submodules
|
|
31
|
+
'fs/promises', 'node:fs/promises', 'stream/promises', 'node:stream/promises',
|
|
32
|
+
'timers/promises', 'node:timers/promises', 'util/types', 'node:util/types',
|
|
33
|
+
]);
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// HELPER FUNCTIONS
|
|
36
|
+
// ============================================================================
|
|
37
|
+
/**
|
|
38
|
+
* Check if a module path is a relative import
|
|
39
|
+
*/
|
|
40
|
+
function isRelativeImport(source) {
|
|
41
|
+
return source.startsWith('.') || source.startsWith('/');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Check if a module is a Node.js builtin
|
|
45
|
+
*/
|
|
46
|
+
function isBuiltinModule(source) {
|
|
47
|
+
return NODE_BUILTINS.has(source) || NODE_BUILTINS.has(source.split('/')[0]);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get line number for a match position in content
|
|
51
|
+
*/
|
|
52
|
+
function getLineNumber(content, position) {
|
|
53
|
+
return content.substring(0, position).split('\n').length;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Parse named imports from a string like "X, Y as Z, W"
|
|
57
|
+
*/
|
|
58
|
+
function parseNamedImports(namesStr) {
|
|
59
|
+
return namesStr
|
|
60
|
+
.split(',')
|
|
61
|
+
.map(name => {
|
|
62
|
+
const trimmed = name.trim();
|
|
63
|
+
// Handle "X as Y" - we want the local name Y
|
|
64
|
+
const asMatch = trimmed.match(/(\w+)\s+as\s+(\w+)/);
|
|
65
|
+
if (asMatch) {
|
|
66
|
+
return asMatch[2];
|
|
67
|
+
}
|
|
68
|
+
// Handle type imports like "type X"
|
|
69
|
+
if (trimmed.startsWith('type ')) {
|
|
70
|
+
return trimmed.slice(5).trim();
|
|
71
|
+
}
|
|
72
|
+
return trimmed;
|
|
73
|
+
})
|
|
74
|
+
.filter(name => name && !name.includes(' '));
|
|
75
|
+
}
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// JAVASCRIPT/TYPESCRIPT PARSER
|
|
78
|
+
// ============================================================================
|
|
79
|
+
/**
|
|
80
|
+
* Parse imports from JavaScript/TypeScript content
|
|
81
|
+
*/
|
|
82
|
+
function parseJSImports(content) {
|
|
83
|
+
const imports = [];
|
|
84
|
+
// Remove comments to avoid false matches
|
|
85
|
+
const cleanContent = content
|
|
86
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // Block comments
|
|
87
|
+
.replace(/\/\/.*$/gm, ''); // Line comments
|
|
88
|
+
// ES Module: import X from 'module'
|
|
89
|
+
let match;
|
|
90
|
+
const defaultImportRegex = /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g;
|
|
91
|
+
while ((match = defaultImportRegex.exec(cleanContent)) !== null) {
|
|
92
|
+
const source = match[2];
|
|
93
|
+
imports.push({
|
|
94
|
+
source,
|
|
95
|
+
isRelative: isRelativeImport(source),
|
|
96
|
+
isPackage: !isRelativeImport(source) && !isBuiltinModule(source),
|
|
97
|
+
isBuiltin: isBuiltinModule(source),
|
|
98
|
+
importedNames: [match[1]],
|
|
99
|
+
hasDefault: true,
|
|
100
|
+
hasNamespace: false,
|
|
101
|
+
isTypeOnly: false,
|
|
102
|
+
isDynamic: false,
|
|
103
|
+
line: getLineNumber(content, match.index),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// ES Module: import X, { Y, Z } from 'module' (mixed import - default + named)
|
|
107
|
+
const mixedImportRegex = /import\s+(\w+)\s*,\s*\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g;
|
|
108
|
+
while ((match = mixedImportRegex.exec(cleanContent)) !== null) {
|
|
109
|
+
const source = match[3];
|
|
110
|
+
const names = [match[1], ...parseNamedImports(match[2])];
|
|
111
|
+
imports.push({
|
|
112
|
+
source,
|
|
113
|
+
isRelative: isRelativeImport(source),
|
|
114
|
+
isPackage: !isRelativeImport(source) && !isBuiltinModule(source),
|
|
115
|
+
isBuiltin: isBuiltinModule(source),
|
|
116
|
+
importedNames: names,
|
|
117
|
+
hasDefault: true,
|
|
118
|
+
hasNamespace: false,
|
|
119
|
+
isTypeOnly: false,
|
|
120
|
+
isDynamic: false,
|
|
121
|
+
line: getLineNumber(content, match.index),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// ES Module: import { X, Y } from 'module'
|
|
125
|
+
const namedImportRegex = /import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g;
|
|
126
|
+
while ((match = namedImportRegex.exec(cleanContent)) !== null) {
|
|
127
|
+
const source = match[2];
|
|
128
|
+
const names = parseNamedImports(match[1]);
|
|
129
|
+
imports.push({
|
|
130
|
+
source,
|
|
131
|
+
isRelative: isRelativeImport(source),
|
|
132
|
+
isPackage: !isRelativeImport(source) && !isBuiltinModule(source),
|
|
133
|
+
isBuiltin: isBuiltinModule(source),
|
|
134
|
+
importedNames: names,
|
|
135
|
+
hasDefault: false,
|
|
136
|
+
hasNamespace: false,
|
|
137
|
+
isTypeOnly: false,
|
|
138
|
+
isDynamic: false,
|
|
139
|
+
line: getLineNumber(content, match.index),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// ES Module: import * as X from 'module'
|
|
143
|
+
const namespaceImportRegex = /import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g;
|
|
144
|
+
while ((match = namespaceImportRegex.exec(cleanContent)) !== null) {
|
|
145
|
+
const source = match[2];
|
|
146
|
+
imports.push({
|
|
147
|
+
source,
|
|
148
|
+
isRelative: isRelativeImport(source),
|
|
149
|
+
isPackage: !isRelativeImport(source) && !isBuiltinModule(source),
|
|
150
|
+
isBuiltin: isBuiltinModule(source),
|
|
151
|
+
importedNames: [match[1]],
|
|
152
|
+
hasDefault: false,
|
|
153
|
+
hasNamespace: true,
|
|
154
|
+
isTypeOnly: false,
|
|
155
|
+
isDynamic: false,
|
|
156
|
+
line: getLineNumber(content, match.index),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// ES Module: import 'module' (side effect)
|
|
160
|
+
const sideEffectRegex = /import\s+['"]([^'"]+)['"](?!\s*from)/g;
|
|
161
|
+
while ((match = sideEffectRegex.exec(cleanContent)) !== null) {
|
|
162
|
+
const source = match[1];
|
|
163
|
+
imports.push({
|
|
164
|
+
source,
|
|
165
|
+
isRelative: isRelativeImport(source),
|
|
166
|
+
isPackage: !isRelativeImport(source) && !isBuiltinModule(source),
|
|
167
|
+
isBuiltin: isBuiltinModule(source),
|
|
168
|
+
importedNames: [],
|
|
169
|
+
hasDefault: false,
|
|
170
|
+
hasNamespace: false,
|
|
171
|
+
isTypeOnly: false,
|
|
172
|
+
isDynamic: false,
|
|
173
|
+
line: getLineNumber(content, match.index),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// Type-only imports: import type { X } from 'module'
|
|
177
|
+
const typeImportRegex = /import\s+type\s+(?:\{([^}]+)\}|(\w+))\s+from\s+['"]([^'"]+)['"]/g;
|
|
178
|
+
while ((match = typeImportRegex.exec(cleanContent)) !== null) {
|
|
179
|
+
const source = match[3];
|
|
180
|
+
const names = match[1] ? parseNamedImports(match[1]) : [match[2]];
|
|
181
|
+
imports.push({
|
|
182
|
+
source,
|
|
183
|
+
isRelative: isRelativeImport(source),
|
|
184
|
+
isPackage: !isRelativeImport(source) && !isBuiltinModule(source),
|
|
185
|
+
isBuiltin: isBuiltinModule(source),
|
|
186
|
+
importedNames: names,
|
|
187
|
+
hasDefault: !!match[2],
|
|
188
|
+
hasNamespace: false,
|
|
189
|
+
isTypeOnly: true,
|
|
190
|
+
isDynamic: false,
|
|
191
|
+
line: getLineNumber(content, match.index),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// CommonJS: require('module')
|
|
195
|
+
const requireRegex = /(?:const|let|var)\s+(?:(\w+)|\{([^}]+)\})\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
196
|
+
while ((match = requireRegex.exec(cleanContent)) !== null) {
|
|
197
|
+
const source = match[3];
|
|
198
|
+
const names = match[1] ? [match[1]] : parseNamedImports(match[2]);
|
|
199
|
+
imports.push({
|
|
200
|
+
source,
|
|
201
|
+
isRelative: isRelativeImport(source),
|
|
202
|
+
isPackage: !isRelativeImport(source) && !isBuiltinModule(source),
|
|
203
|
+
isBuiltin: isBuiltinModule(source),
|
|
204
|
+
importedNames: names,
|
|
205
|
+
hasDefault: !!match[1],
|
|
206
|
+
hasNamespace: false,
|
|
207
|
+
isTypeOnly: false,
|
|
208
|
+
isDynamic: false,
|
|
209
|
+
line: getLineNumber(content, match.index),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
// Dynamic import: import('module')
|
|
213
|
+
const dynamicImportRegex = /(?:await\s+)?import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
214
|
+
while ((match = dynamicImportRegex.exec(cleanContent)) !== null) {
|
|
215
|
+
const source = match[1];
|
|
216
|
+
imports.push({
|
|
217
|
+
source,
|
|
218
|
+
isRelative: isRelativeImport(source),
|
|
219
|
+
isPackage: !isRelativeImport(source) && !isBuiltinModule(source),
|
|
220
|
+
isBuiltin: isBuiltinModule(source),
|
|
221
|
+
importedNames: [],
|
|
222
|
+
hasDefault: false,
|
|
223
|
+
hasNamespace: false,
|
|
224
|
+
isTypeOnly: false,
|
|
225
|
+
isDynamic: true,
|
|
226
|
+
line: getLineNumber(content, match.index),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
return imports;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Parse exports from JavaScript/TypeScript content
|
|
233
|
+
*/
|
|
234
|
+
function parseJSExports(content) {
|
|
235
|
+
const exports = [];
|
|
236
|
+
// Remove comments
|
|
237
|
+
const cleanContent = content
|
|
238
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
239
|
+
.replace(/\/\/.*$/gm, '');
|
|
240
|
+
let match;
|
|
241
|
+
// export default
|
|
242
|
+
const defaultExportRegex = /export\s+default\s+(?:(class|function)\s+(\w+)|(\w+))/g;
|
|
243
|
+
while ((match = defaultExportRegex.exec(cleanContent)) !== null) {
|
|
244
|
+
const kind = match[1];
|
|
245
|
+
const name = match[2] || match[3] || 'default';
|
|
246
|
+
exports.push({
|
|
247
|
+
name,
|
|
248
|
+
isDefault: true,
|
|
249
|
+
isType: false,
|
|
250
|
+
isReExport: false,
|
|
251
|
+
kind: kind || 'unknown',
|
|
252
|
+
line: getLineNumber(content, match.index),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
// export { X, Y } or export { X } from 'module'
|
|
256
|
+
const namedExportRegex = /export\s+\{([^}]+)\}(?:\s+from\s+['"]([^'"]+)['"])?/g;
|
|
257
|
+
while ((match = namedExportRegex.exec(cleanContent)) !== null) {
|
|
258
|
+
const names = parseNamedImports(match[1]);
|
|
259
|
+
const reExportSource = match[2];
|
|
260
|
+
for (const name of names) {
|
|
261
|
+
exports.push({
|
|
262
|
+
name,
|
|
263
|
+
isDefault: false,
|
|
264
|
+
isType: false,
|
|
265
|
+
isReExport: !!reExportSource,
|
|
266
|
+
reExportSource,
|
|
267
|
+
kind: 'unknown',
|
|
268
|
+
line: getLineNumber(content, match.index),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// export const/let/var
|
|
273
|
+
const varExportRegex = /export\s+(?:const|let|var)\s+(\w+)/g;
|
|
274
|
+
while ((match = varExportRegex.exec(cleanContent)) !== null) {
|
|
275
|
+
exports.push({
|
|
276
|
+
name: match[1],
|
|
277
|
+
isDefault: false,
|
|
278
|
+
isType: false,
|
|
279
|
+
isReExport: false,
|
|
280
|
+
kind: 'variable',
|
|
281
|
+
line: getLineNumber(content, match.index),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
// export function
|
|
285
|
+
const funcExportRegex = /export\s+function\s+(\w+)/g;
|
|
286
|
+
while ((match = funcExportRegex.exec(cleanContent)) !== null) {
|
|
287
|
+
exports.push({
|
|
288
|
+
name: match[1],
|
|
289
|
+
isDefault: false,
|
|
290
|
+
isType: false,
|
|
291
|
+
isReExport: false,
|
|
292
|
+
kind: 'function',
|
|
293
|
+
line: getLineNumber(content, match.index),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
// export class
|
|
297
|
+
const classExportRegex = /export\s+class\s+(\w+)/g;
|
|
298
|
+
while ((match = classExportRegex.exec(cleanContent)) !== null) {
|
|
299
|
+
exports.push({
|
|
300
|
+
name: match[1],
|
|
301
|
+
isDefault: false,
|
|
302
|
+
isType: false,
|
|
303
|
+
isReExport: false,
|
|
304
|
+
kind: 'class',
|
|
305
|
+
line: getLineNumber(content, match.index),
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
// export type
|
|
309
|
+
const typeExportRegex = /export\s+type\s+(\w+)/g;
|
|
310
|
+
while ((match = typeExportRegex.exec(cleanContent)) !== null) {
|
|
311
|
+
exports.push({
|
|
312
|
+
name: match[1],
|
|
313
|
+
isDefault: false,
|
|
314
|
+
isType: true,
|
|
315
|
+
isReExport: false,
|
|
316
|
+
kind: 'type',
|
|
317
|
+
line: getLineNumber(content, match.index),
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
// export interface
|
|
321
|
+
const interfaceExportRegex = /export\s+interface\s+(\w+)/g;
|
|
322
|
+
while ((match = interfaceExportRegex.exec(cleanContent)) !== null) {
|
|
323
|
+
exports.push({
|
|
324
|
+
name: match[1],
|
|
325
|
+
isDefault: false,
|
|
326
|
+
isType: true,
|
|
327
|
+
isReExport: false,
|
|
328
|
+
kind: 'interface',
|
|
329
|
+
line: getLineNumber(content, match.index),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
// export enum
|
|
333
|
+
const enumExportRegex = /export\s+enum\s+(\w+)/g;
|
|
334
|
+
while ((match = enumExportRegex.exec(cleanContent)) !== null) {
|
|
335
|
+
exports.push({
|
|
336
|
+
name: match[1],
|
|
337
|
+
isDefault: false,
|
|
338
|
+
isType: false,
|
|
339
|
+
isReExport: false,
|
|
340
|
+
kind: 'enum',
|
|
341
|
+
line: getLineNumber(content, match.index),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
// export * from 'module'
|
|
345
|
+
const reExportAllRegex = /export\s+\*\s+(?:as\s+(\w+)\s+)?from\s+['"]([^'"]+)['"]/g;
|
|
346
|
+
while ((match = reExportAllRegex.exec(cleanContent)) !== null) {
|
|
347
|
+
exports.push({
|
|
348
|
+
name: match[1] || '*',
|
|
349
|
+
isDefault: false,
|
|
350
|
+
isType: false,
|
|
351
|
+
isReExport: true,
|
|
352
|
+
reExportSource: match[2],
|
|
353
|
+
kind: 'unknown',
|
|
354
|
+
line: getLineNumber(content, match.index),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
// module.exports
|
|
358
|
+
const moduleExportsRegex = /module\.exports\s*=\s*(\w+)/g;
|
|
359
|
+
while ((match = moduleExportsRegex.exec(cleanContent)) !== null) {
|
|
360
|
+
exports.push({
|
|
361
|
+
name: match[1],
|
|
362
|
+
isDefault: true,
|
|
363
|
+
isType: false,
|
|
364
|
+
isReExport: false,
|
|
365
|
+
kind: 'unknown',
|
|
366
|
+
line: getLineNumber(content, match.index),
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
// exports.X
|
|
370
|
+
const exportsRegex = /exports\.(\w+)\s*=/g;
|
|
371
|
+
while ((match = exportsRegex.exec(cleanContent)) !== null) {
|
|
372
|
+
exports.push({
|
|
373
|
+
name: match[1],
|
|
374
|
+
isDefault: false,
|
|
375
|
+
isType: false,
|
|
376
|
+
isReExport: false,
|
|
377
|
+
kind: 'unknown',
|
|
378
|
+
line: getLineNumber(content, match.index),
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
return exports;
|
|
382
|
+
}
|
|
383
|
+
// ============================================================================
|
|
384
|
+
// PYTHON PARSER
|
|
385
|
+
// ============================================================================
|
|
386
|
+
const PYTHON_BUILTINS = new Set([
|
|
387
|
+
// stdlib modules (Python 3)
|
|
388
|
+
'abc', 'argparse', 'ast', 'asyncio', 'base64', 'builtins', 'cgi', 'cmath',
|
|
389
|
+
'cmd', 'code', 'codecs', 'collections', 'concurrent', 'configparser',
|
|
390
|
+
'contextlib', 'copy', 'csv', 'dataclasses', 'datetime', 'decimal',
|
|
391
|
+
'difflib', 'dis', 'email', 'enum', 'errno', 'fileinput', 'fnmatch',
|
|
392
|
+
'fractions', 'ftplib', 'functools', 'gc', 'getpass', 'gettext', 'glob',
|
|
393
|
+
'gzip', 'hashlib', 'heapq', 'hmac', 'html', 'http', 'imaplib', 'importlib',
|
|
394
|
+
'inspect', 'io', 'ipaddress', 'itertools', 'json', 'keyword', 'linecache',
|
|
395
|
+
'locale', 'logging', 'lzma', 'math', 'mimetypes', 'multiprocessing',
|
|
396
|
+
'numbers', 'operator', 'os', 'pathlib', 'pickle', 'platform', 'pprint',
|
|
397
|
+
'queue', 'random', 're', 'shlex', 'shutil', 'signal', 'smtplib', 'socket',
|
|
398
|
+
'socketserver', 'sqlite3', 'ssl', 'stat', 'statistics', 'string',
|
|
399
|
+
'struct', 'subprocess', 'sys', 'tarfile', 'tempfile', 'textwrap',
|
|
400
|
+
'threading', 'time', 'timeit', 'tkinter', 'token', 'tokenize', 'traceback',
|
|
401
|
+
'types', 'typing', 'unicodedata', 'unittest', 'urllib', 'uuid', 'warnings',
|
|
402
|
+
'weakref', 'xml', 'xmlrpc', 'zipfile', 'zipimport', 'zlib',
|
|
403
|
+
'__future__',
|
|
404
|
+
]);
|
|
405
|
+
/**
|
|
406
|
+
* Parse imports from Python content
|
|
407
|
+
*/
|
|
408
|
+
function parsePythonImports(content) {
|
|
409
|
+
const imports = [];
|
|
410
|
+
// Remove comments and collapse multi-line parenthesized imports onto one line
|
|
411
|
+
// e.g. "from x import (\n A,\n B\n)" → "from x import A, B"
|
|
412
|
+
const cleanContent = content
|
|
413
|
+
.replace(/#.*$/gm, '')
|
|
414
|
+
.replace(/\(\s*([\s\S]*?)\s*\)/g, (_, inner) => inner.replace(/\s*\n\s*/g, ', '));
|
|
415
|
+
let match;
|
|
416
|
+
// import X or import X, Y or import X.Y.Z
|
|
417
|
+
const importRegex = /^import[ \t]+([^\n\r]+)$/gm;
|
|
418
|
+
while ((match = importRegex.exec(cleanContent)) !== null) {
|
|
419
|
+
const modules = match[1].split(',').map(m => m.trim()).filter(Boolean);
|
|
420
|
+
for (const mod of modules) {
|
|
421
|
+
const source = mod.split(/\s+as\s+/)[0].trim();
|
|
422
|
+
imports.push({
|
|
423
|
+
source,
|
|
424
|
+
isRelative: source.startsWith('.'),
|
|
425
|
+
isPackage: !source.startsWith('.'),
|
|
426
|
+
isBuiltin: PYTHON_BUILTINS.has(source.split('.')[0]),
|
|
427
|
+
importedNames: [mod.includes(' as ') ? mod.split(/\s+as\s+/)[1].trim() : source.split('.').pop()],
|
|
428
|
+
hasDefault: false,
|
|
429
|
+
hasNamespace: true,
|
|
430
|
+
isTypeOnly: false,
|
|
431
|
+
isDynamic: false,
|
|
432
|
+
line: getLineNumber(content, match.index),
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// from X import Y or from X import Y, Z (including multi-line after collapsing)
|
|
437
|
+
const fromImportRegex = /^from\s+([\w.]+)\s+import\s+(.+)$/gm;
|
|
438
|
+
while ((match = fromImportRegex.exec(cleanContent)) !== null) {
|
|
439
|
+
const source = match[1];
|
|
440
|
+
const importsPart = match[2].trim().replace(/[()]/g, '');
|
|
441
|
+
if (importsPart === '*') {
|
|
442
|
+
imports.push({
|
|
443
|
+
source,
|
|
444
|
+
isRelative: source.startsWith('.'),
|
|
445
|
+
isPackage: !source.startsWith('.'),
|
|
446
|
+
isBuiltin: PYTHON_BUILTINS.has(source.split('.')[0]),
|
|
447
|
+
importedNames: ['*'],
|
|
448
|
+
hasDefault: false,
|
|
449
|
+
hasNamespace: true,
|
|
450
|
+
isTypeOnly: false,
|
|
451
|
+
isDynamic: false,
|
|
452
|
+
line: getLineNumber(content, match.index),
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
const names = importsPart.split(',').map(n => {
|
|
457
|
+
const trimmed = n.trim();
|
|
458
|
+
return trimmed.includes(' as ') ? trimmed.split(/\s+as\s+/)[1].trim() : trimmed;
|
|
459
|
+
}).filter(Boolean);
|
|
460
|
+
imports.push({
|
|
461
|
+
source,
|
|
462
|
+
isRelative: source.startsWith('.'),
|
|
463
|
+
isPackage: !source.startsWith('.'),
|
|
464
|
+
isBuiltin: PYTHON_BUILTINS.has(source.split('.')[0]),
|
|
465
|
+
importedNames: names,
|
|
466
|
+
hasDefault: false,
|
|
467
|
+
hasNamespace: false,
|
|
468
|
+
isTypeOnly: false,
|
|
469
|
+
isDynamic: false,
|
|
470
|
+
line: getLineNumber(content, match.index),
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return imports;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Parse exports from Python content (module-level definitions)
|
|
478
|
+
*/
|
|
479
|
+
function parsePythonExports(content) {
|
|
480
|
+
const exports = [];
|
|
481
|
+
// Remove comments
|
|
482
|
+
const cleanContent = content.replace(/#.*$/gm, '');
|
|
483
|
+
let match;
|
|
484
|
+
// Check for __all__ definition
|
|
485
|
+
const allMatch = cleanContent.match(/__all__\s*=\s*\[([^\]]+)\]/);
|
|
486
|
+
if (allMatch) {
|
|
487
|
+
const names = allMatch[1].match(/['"](\w+)['"]/g);
|
|
488
|
+
if (names) {
|
|
489
|
+
for (const name of names) {
|
|
490
|
+
const cleanName = name.replace(/['"]/g, '');
|
|
491
|
+
exports.push({
|
|
492
|
+
name: cleanName,
|
|
493
|
+
isDefault: false,
|
|
494
|
+
isType: false,
|
|
495
|
+
isReExport: false,
|
|
496
|
+
kind: 'unknown',
|
|
497
|
+
line: getLineNumber(content, allMatch.index ?? 0),
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// Class definitions at module level (no indentation)
|
|
503
|
+
const classRegex = /^class\s+(\w+)/gm;
|
|
504
|
+
while ((match = classRegex.exec(cleanContent)) !== null) {
|
|
505
|
+
exports.push({
|
|
506
|
+
name: match[1],
|
|
507
|
+
isDefault: false,
|
|
508
|
+
isType: false,
|
|
509
|
+
isReExport: false,
|
|
510
|
+
kind: 'class',
|
|
511
|
+
line: getLineNumber(content, match.index),
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
// Function definitions at module level (no indentation)
|
|
515
|
+
const funcRegex = /^def\s+(\w+)/gm;
|
|
516
|
+
while ((match = funcRegex.exec(cleanContent)) !== null) {
|
|
517
|
+
// Skip private functions
|
|
518
|
+
if (!match[1].startsWith('_')) {
|
|
519
|
+
exports.push({
|
|
520
|
+
name: match[1],
|
|
521
|
+
isDefault: false,
|
|
522
|
+
isType: false,
|
|
523
|
+
isReExport: false,
|
|
524
|
+
kind: 'function',
|
|
525
|
+
line: getLineNumber(content, match.index),
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// Module-level variables (UPPER_CASE constants)
|
|
530
|
+
const constRegex = /^([A-Z][A-Z0-9_]*)\s*=/gm;
|
|
531
|
+
while ((match = constRegex.exec(cleanContent)) !== null) {
|
|
532
|
+
exports.push({
|
|
533
|
+
name: match[1],
|
|
534
|
+
isDefault: false,
|
|
535
|
+
isType: false,
|
|
536
|
+
isReExport: false,
|
|
537
|
+
kind: 'variable',
|
|
538
|
+
line: getLineNumber(content, match.index),
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
return exports;
|
|
542
|
+
}
|
|
543
|
+
// ============================================================================
|
|
544
|
+
// IMPORT RESOLUTION
|
|
545
|
+
// ============================================================================
|
|
546
|
+
/**
|
|
547
|
+
* Resolve a relative import to an absolute file path
|
|
548
|
+
*/
|
|
549
|
+
export async function resolveImport(importSource, fromFile, options) {
|
|
550
|
+
// External package - return null
|
|
551
|
+
if (!isRelativeImport(importSource)) {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
const fromDir = dirname(fromFile);
|
|
555
|
+
const fromExt = extname(fromFile).toLowerCase();
|
|
556
|
+
const isPython = fromExt === '.py' || fromExt === '.pyw';
|
|
557
|
+
// Default extensions depend on the source file type
|
|
558
|
+
const extensions = options.extensions ?? (isPython
|
|
559
|
+
? ['.py', '.pyw']
|
|
560
|
+
: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
|
|
561
|
+
// Python relative imports use dot-prefix notation:
|
|
562
|
+
// from .utils import foo → source = ".utils" → resolve as "./utils"
|
|
563
|
+
// from ..models import X → source = "..models" → resolve as "../models"
|
|
564
|
+
// from ...pkg import Y → source = "...pkg" → resolve as "../../pkg"
|
|
565
|
+
// N dots = (N-1) levels up from fromDir. Also handles dotted paths: .db.models → ./db/models
|
|
566
|
+
let normalizedSource = importSource;
|
|
567
|
+
if (isPython && importSource.startsWith('.')) {
|
|
568
|
+
let dots = 0;
|
|
569
|
+
while (dots < importSource.length && importSource[dots] === '.')
|
|
570
|
+
dots++;
|
|
571
|
+
const rest = importSource.slice(dots).replace(/\./g, '/');
|
|
572
|
+
// dots=1 → './', dots=2 → '../', dots=3 → '../../', etc.
|
|
573
|
+
const prefix = dots === 1 ? './' : '../'.repeat(dots - 1);
|
|
574
|
+
normalizedSource = rest ? prefix + rest : prefix.replace(/\/$/, '') || '.';
|
|
575
|
+
}
|
|
576
|
+
const basePath = resolve(fromDir, normalizedSource);
|
|
577
|
+
// Strip any existing extension from the import source.
|
|
578
|
+
// This handles the TypeScript NodeNext convention where imports are written
|
|
579
|
+
// as `./foo.js` but the actual file on disk is `./foo.ts`.
|
|
580
|
+
// Without this, we'd try `foo.js.ts`, `foo.js.tsx`, etc. and find nothing.
|
|
581
|
+
const existingExt = extname(basePath); // e.g. ".js", ".ts", ""
|
|
582
|
+
const baseWithoutExt = existingExt
|
|
583
|
+
? basePath.slice(0, -existingExt.length)
|
|
584
|
+
: basePath;
|
|
585
|
+
// Build candidate list (order matters: most specific first):
|
|
586
|
+
// 1. Exact path as-is (the import may already point to the real file)
|
|
587
|
+
// 2. Strip the extension, try every known extension (handles .js -> .ts)
|
|
588
|
+
// 3. Directory index files (handles `./components` -> `./components/index.ts`)
|
|
589
|
+
// 4. Python packages: module/__init__.py
|
|
590
|
+
const candidates = [
|
|
591
|
+
basePath,
|
|
592
|
+
...extensions.map(ext => baseWithoutExt + ext),
|
|
593
|
+
...extensions.map(ext => join(basePath, `index${ext}`)),
|
|
594
|
+
...extensions.map(ext => join(baseWithoutExt, `index${ext}`)),
|
|
595
|
+
...(isPython ? extensions.map(ext => join(basePath, `__init__${ext}`)) : []),
|
|
596
|
+
];
|
|
597
|
+
// Deduplicate while preserving order
|
|
598
|
+
const seen = new Set();
|
|
599
|
+
for (const candidate of candidates) {
|
|
600
|
+
if (seen.has(candidate))
|
|
601
|
+
continue;
|
|
602
|
+
seen.add(candidate);
|
|
603
|
+
try {
|
|
604
|
+
await readFile(candidate);
|
|
605
|
+
return candidate;
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
// Not found, try next
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
// ============================================================================
|
|
614
|
+
// MAIN PARSER CLASS
|
|
615
|
+
// ============================================================================
|
|
616
|
+
/**
|
|
617
|
+
* Import/Export Parser
|
|
618
|
+
*/
|
|
619
|
+
export class ImportExportParser {
|
|
620
|
+
cache = new Map();
|
|
621
|
+
/**
|
|
622
|
+
* Clear the parse cache
|
|
623
|
+
*/
|
|
624
|
+
clearCache() {
|
|
625
|
+
this.cache.clear();
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Get file extension type
|
|
629
|
+
*/
|
|
630
|
+
getFileType(filePath) {
|
|
631
|
+
const ext = extname(filePath).toLowerCase();
|
|
632
|
+
if (['.ts', '.tsx', '.mts', '.cts'].includes(ext))
|
|
633
|
+
return 'ts';
|
|
634
|
+
if (['.js', '.jsx', '.mjs', '.cjs'].includes(ext))
|
|
635
|
+
return 'js';
|
|
636
|
+
if (['.py', '.pyw'].includes(ext))
|
|
637
|
+
return 'python';
|
|
638
|
+
return 'unknown';
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Parse a file and extract imports/exports
|
|
642
|
+
*/
|
|
643
|
+
async parseFile(filePath) {
|
|
644
|
+
// Check cache
|
|
645
|
+
const cached = this.cache.get(filePath);
|
|
646
|
+
if (cached) {
|
|
647
|
+
return cached;
|
|
648
|
+
}
|
|
649
|
+
const analysis = {
|
|
650
|
+
filePath,
|
|
651
|
+
imports: [],
|
|
652
|
+
exports: [],
|
|
653
|
+
localImports: [],
|
|
654
|
+
externalImports: [],
|
|
655
|
+
parseErrors: [],
|
|
656
|
+
};
|
|
657
|
+
try {
|
|
658
|
+
const content = await readFile(filePath, 'utf-8');
|
|
659
|
+
const fileType = this.getFileType(filePath);
|
|
660
|
+
if (fileType === 'js' || fileType === 'ts') {
|
|
661
|
+
analysis.imports = parseJSImports(content);
|
|
662
|
+
analysis.exports = parseJSExports(content);
|
|
663
|
+
}
|
|
664
|
+
else if (fileType === 'python') {
|
|
665
|
+
analysis.imports = parsePythonImports(content);
|
|
666
|
+
analysis.exports = parsePythonExports(content);
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
analysis.parseErrors.push(`Unsupported file type: ${extname(filePath)}`);
|
|
670
|
+
}
|
|
671
|
+
// Categorize imports
|
|
672
|
+
for (const imp of analysis.imports) {
|
|
673
|
+
if (imp.isRelative) {
|
|
674
|
+
analysis.localImports.push(imp.source);
|
|
675
|
+
}
|
|
676
|
+
else if (imp.isPackage) {
|
|
677
|
+
// Extract package name (first part of path)
|
|
678
|
+
const pkgName = imp.source.startsWith('@')
|
|
679
|
+
? imp.source.split('/').slice(0, 2).join('/')
|
|
680
|
+
: imp.source.split('/')[0];
|
|
681
|
+
if (!analysis.externalImports.includes(pkgName)) {
|
|
682
|
+
analysis.externalImports.push(pkgName);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
catch (error) {
|
|
688
|
+
analysis.parseErrors.push(`Failed to read file: ${error.message}`);
|
|
689
|
+
}
|
|
690
|
+
// Cache the result
|
|
691
|
+
this.cache.set(filePath, analysis);
|
|
692
|
+
return analysis;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Parse multiple files
|
|
696
|
+
*/
|
|
697
|
+
async parseFiles(filePaths) {
|
|
698
|
+
const results = new Map();
|
|
699
|
+
for (const filePath of filePaths) {
|
|
700
|
+
const analysis = await this.parseFile(filePath);
|
|
701
|
+
results.set(filePath, analysis);
|
|
702
|
+
}
|
|
703
|
+
return results;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Convenience function to parse a single file
|
|
708
|
+
*/
|
|
709
|
+
export async function parseFile(filePath) {
|
|
710
|
+
const parser = new ImportExportParser();
|
|
711
|
+
return parser.parseFile(filePath);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Convenience function to parse multiple files
|
|
715
|
+
*/
|
|
716
|
+
export async function parseFiles(filePaths) {
|
|
717
|
+
const parser = new ImportExportParser();
|
|
718
|
+
return parser.parseFiles(filePaths);
|
|
719
|
+
}
|
|
720
|
+
//# sourceMappingURL=import-parser.js.map
|