emdash-core 0.1.7__py3-none-any.whl
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.
- emdash_core/__init__.py +3 -0
- emdash_core/agent/__init__.py +37 -0
- emdash_core/agent/agents.py +225 -0
- emdash_core/agent/code_reviewer.py +476 -0
- emdash_core/agent/compaction.py +143 -0
- emdash_core/agent/context_manager.py +140 -0
- emdash_core/agent/events.py +338 -0
- emdash_core/agent/handlers.py +224 -0
- emdash_core/agent/inprocess_subagent.py +377 -0
- emdash_core/agent/mcp/__init__.py +50 -0
- emdash_core/agent/mcp/client.py +346 -0
- emdash_core/agent/mcp/config.py +302 -0
- emdash_core/agent/mcp/manager.py +496 -0
- emdash_core/agent/mcp/tool_factory.py +213 -0
- emdash_core/agent/prompts/__init__.py +38 -0
- emdash_core/agent/prompts/main_agent.py +104 -0
- emdash_core/agent/prompts/subagents.py +131 -0
- emdash_core/agent/prompts/workflow.py +136 -0
- emdash_core/agent/providers/__init__.py +34 -0
- emdash_core/agent/providers/base.py +143 -0
- emdash_core/agent/providers/factory.py +80 -0
- emdash_core/agent/providers/models.py +220 -0
- emdash_core/agent/providers/openai_provider.py +463 -0
- emdash_core/agent/providers/transformers_provider.py +217 -0
- emdash_core/agent/research/__init__.py +81 -0
- emdash_core/agent/research/agent.py +143 -0
- emdash_core/agent/research/controller.py +254 -0
- emdash_core/agent/research/critic.py +428 -0
- emdash_core/agent/research/macros.py +469 -0
- emdash_core/agent/research/planner.py +449 -0
- emdash_core/agent/research/researcher.py +436 -0
- emdash_core/agent/research/state.py +523 -0
- emdash_core/agent/research/synthesizer.py +594 -0
- emdash_core/agent/reviewer_profile.py +475 -0
- emdash_core/agent/rules.py +123 -0
- emdash_core/agent/runner.py +601 -0
- emdash_core/agent/session.py +262 -0
- emdash_core/agent/spec_schema.py +66 -0
- emdash_core/agent/specification.py +479 -0
- emdash_core/agent/subagent.py +397 -0
- emdash_core/agent/subagent_prompts.py +13 -0
- emdash_core/agent/toolkit.py +482 -0
- emdash_core/agent/toolkits/__init__.py +64 -0
- emdash_core/agent/toolkits/base.py +96 -0
- emdash_core/agent/toolkits/explore.py +47 -0
- emdash_core/agent/toolkits/plan.py +55 -0
- emdash_core/agent/tools/__init__.py +141 -0
- emdash_core/agent/tools/analytics.py +436 -0
- emdash_core/agent/tools/base.py +131 -0
- emdash_core/agent/tools/coding.py +484 -0
- emdash_core/agent/tools/github_mcp.py +592 -0
- emdash_core/agent/tools/history.py +13 -0
- emdash_core/agent/tools/modes.py +153 -0
- emdash_core/agent/tools/plan.py +206 -0
- emdash_core/agent/tools/plan_write.py +135 -0
- emdash_core/agent/tools/search.py +412 -0
- emdash_core/agent/tools/spec.py +341 -0
- emdash_core/agent/tools/task.py +262 -0
- emdash_core/agent/tools/task_output.py +204 -0
- emdash_core/agent/tools/tasks.py +454 -0
- emdash_core/agent/tools/traversal.py +588 -0
- emdash_core/agent/tools/web.py +179 -0
- emdash_core/analytics/__init__.py +5 -0
- emdash_core/analytics/engine.py +1286 -0
- emdash_core/api/__init__.py +5 -0
- emdash_core/api/agent.py +308 -0
- emdash_core/api/agents.py +154 -0
- emdash_core/api/analyze.py +264 -0
- emdash_core/api/auth.py +173 -0
- emdash_core/api/context.py +77 -0
- emdash_core/api/db.py +121 -0
- emdash_core/api/embed.py +131 -0
- emdash_core/api/feature.py +143 -0
- emdash_core/api/health.py +93 -0
- emdash_core/api/index.py +162 -0
- emdash_core/api/plan.py +110 -0
- emdash_core/api/projectmd.py +210 -0
- emdash_core/api/query.py +320 -0
- emdash_core/api/research.py +122 -0
- emdash_core/api/review.py +161 -0
- emdash_core/api/router.py +76 -0
- emdash_core/api/rules.py +116 -0
- emdash_core/api/search.py +119 -0
- emdash_core/api/spec.py +99 -0
- emdash_core/api/swarm.py +223 -0
- emdash_core/api/tasks.py +109 -0
- emdash_core/api/team.py +120 -0
- emdash_core/auth/__init__.py +17 -0
- emdash_core/auth/github.py +389 -0
- emdash_core/config.py +74 -0
- emdash_core/context/__init__.py +52 -0
- emdash_core/context/models.py +50 -0
- emdash_core/context/providers/__init__.py +11 -0
- emdash_core/context/providers/base.py +74 -0
- emdash_core/context/providers/explored_areas.py +183 -0
- emdash_core/context/providers/touched_areas.py +360 -0
- emdash_core/context/registry.py +73 -0
- emdash_core/context/reranker.py +199 -0
- emdash_core/context/service.py +260 -0
- emdash_core/context/session.py +352 -0
- emdash_core/core/__init__.py +104 -0
- emdash_core/core/config.py +454 -0
- emdash_core/core/exceptions.py +55 -0
- emdash_core/core/models.py +265 -0
- emdash_core/core/review_config.py +57 -0
- emdash_core/db/__init__.py +67 -0
- emdash_core/db/auth.py +134 -0
- emdash_core/db/models.py +91 -0
- emdash_core/db/provider.py +222 -0
- emdash_core/db/providers/__init__.py +5 -0
- emdash_core/db/providers/supabase.py +452 -0
- emdash_core/embeddings/__init__.py +24 -0
- emdash_core/embeddings/indexer.py +534 -0
- emdash_core/embeddings/models.py +192 -0
- emdash_core/embeddings/providers/__init__.py +7 -0
- emdash_core/embeddings/providers/base.py +112 -0
- emdash_core/embeddings/providers/fireworks.py +141 -0
- emdash_core/embeddings/providers/openai.py +104 -0
- emdash_core/embeddings/registry.py +146 -0
- emdash_core/embeddings/service.py +215 -0
- emdash_core/graph/__init__.py +26 -0
- emdash_core/graph/builder.py +134 -0
- emdash_core/graph/connection.py +692 -0
- emdash_core/graph/schema.py +416 -0
- emdash_core/graph/writer.py +667 -0
- emdash_core/ingestion/__init__.py +7 -0
- emdash_core/ingestion/change_detector.py +150 -0
- emdash_core/ingestion/git/__init__.py +5 -0
- emdash_core/ingestion/git/commit_analyzer.py +196 -0
- emdash_core/ingestion/github/__init__.py +6 -0
- emdash_core/ingestion/github/pr_fetcher.py +296 -0
- emdash_core/ingestion/github/task_extractor.py +100 -0
- emdash_core/ingestion/orchestrator.py +540 -0
- emdash_core/ingestion/parsers/__init__.py +10 -0
- emdash_core/ingestion/parsers/base_parser.py +66 -0
- emdash_core/ingestion/parsers/call_graph_builder.py +121 -0
- emdash_core/ingestion/parsers/class_extractor.py +154 -0
- emdash_core/ingestion/parsers/function_extractor.py +202 -0
- emdash_core/ingestion/parsers/import_analyzer.py +119 -0
- emdash_core/ingestion/parsers/python_parser.py +123 -0
- emdash_core/ingestion/parsers/registry.py +72 -0
- emdash_core/ingestion/parsers/ts_ast_parser.js +313 -0
- emdash_core/ingestion/parsers/typescript_parser.py +278 -0
- emdash_core/ingestion/repository.py +346 -0
- emdash_core/models/__init__.py +38 -0
- emdash_core/models/agent.py +68 -0
- emdash_core/models/index.py +77 -0
- emdash_core/models/query.py +113 -0
- emdash_core/planning/__init__.py +7 -0
- emdash_core/planning/agent_api.py +413 -0
- emdash_core/planning/context_builder.py +265 -0
- emdash_core/planning/feature_context.py +232 -0
- emdash_core/planning/feature_expander.py +646 -0
- emdash_core/planning/llm_explainer.py +198 -0
- emdash_core/planning/similarity.py +509 -0
- emdash_core/planning/team_focus.py +821 -0
- emdash_core/server.py +153 -0
- emdash_core/sse/__init__.py +5 -0
- emdash_core/sse/stream.py +196 -0
- emdash_core/swarm/__init__.py +17 -0
- emdash_core/swarm/merge_agent.py +383 -0
- emdash_core/swarm/session_manager.py +274 -0
- emdash_core/swarm/swarm_runner.py +226 -0
- emdash_core/swarm/task_definition.py +137 -0
- emdash_core/swarm/worker_spawner.py +319 -0
- emdash_core/swarm/worktree_manager.py +278 -0
- emdash_core/templates/__init__.py +10 -0
- emdash_core/templates/defaults/agent-builder.md.template +82 -0
- emdash_core/templates/defaults/focus.md.template +115 -0
- emdash_core/templates/defaults/pr-review-enhanced.md.template +309 -0
- emdash_core/templates/defaults/pr-review.md.template +80 -0
- emdash_core/templates/defaults/project.md.template +85 -0
- emdash_core/templates/defaults/research_critic.md.template +112 -0
- emdash_core/templates/defaults/research_planner.md.template +85 -0
- emdash_core/templates/defaults/research_synthesizer.md.template +128 -0
- emdash_core/templates/defaults/reviewer.md.template +81 -0
- emdash_core/templates/defaults/spec.md.template +41 -0
- emdash_core/templates/defaults/tasks.md.template +78 -0
- emdash_core/templates/loader.py +296 -0
- emdash_core/utils/__init__.py +45 -0
- emdash_core/utils/git.py +84 -0
- emdash_core/utils/image.py +502 -0
- emdash_core/utils/logger.py +51 -0
- emdash_core-0.1.7.dist-info/METADATA +35 -0
- emdash_core-0.1.7.dist-info/RECORD +187 -0
- emdash_core-0.1.7.dist-info/WHEEL +4 -0
- emdash_core-0.1.7.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Main Python AST parser - coordinates all entity extraction."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import hashlib
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from ...core.exceptions import ParsingError
|
|
9
|
+
from ...core.models import FileEntity, FileEntities
|
|
10
|
+
from .base_parser import BaseLanguageParser
|
|
11
|
+
from .class_extractor import ClassExtractor
|
|
12
|
+
from .function_extractor import FunctionExtractor
|
|
13
|
+
from .import_analyzer import ImportAnalyzer
|
|
14
|
+
from .call_graph_builder import CallGraphBuilder
|
|
15
|
+
from ...utils.logger import log
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PythonParser(BaseLanguageParser):
|
|
19
|
+
"""Parses Python files and extracts code entities."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, file_path: Path, repo_root: Optional[Path] = None):
|
|
22
|
+
"""Initialize Python parser.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
file_path: Path to Python file
|
|
26
|
+
repo_root: Root directory of repository (for resolving qualified names)
|
|
27
|
+
"""
|
|
28
|
+
super().__init__(file_path, repo_root)
|
|
29
|
+
self.tree: Optional[ast.AST] = None
|
|
30
|
+
self.content: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def get_supported_extensions(cls) -> List[str]:
|
|
34
|
+
"""Return list of supported file extensions.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
List containing '.py'
|
|
38
|
+
"""
|
|
39
|
+
return ['.py']
|
|
40
|
+
|
|
41
|
+
def parse(self) -> FileEntities:
|
|
42
|
+
"""Parse the file and extract all entities.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
FileEntities containing all extracted entities
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ParsingError: If file cannot be parsed
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
# Read file content
|
|
52
|
+
with open(self.file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
53
|
+
self.content = f.read()
|
|
54
|
+
|
|
55
|
+
# Parse AST
|
|
56
|
+
try:
|
|
57
|
+
self.tree = ast.parse(self.content, filename=str(self.file_path))
|
|
58
|
+
except SyntaxError as e:
|
|
59
|
+
log.warning(f"Syntax error in {self.file_path}: {e}")
|
|
60
|
+
# Return empty entities for files with syntax errors
|
|
61
|
+
return FileEntities()
|
|
62
|
+
|
|
63
|
+
# Calculate module name
|
|
64
|
+
self.module_name = self._calculate_module_name()
|
|
65
|
+
|
|
66
|
+
# Extract file metadata
|
|
67
|
+
file_entity = self._extract_file_entity()
|
|
68
|
+
|
|
69
|
+
# Extract entities using specialized extractors
|
|
70
|
+
class_extractor = ClassExtractor(
|
|
71
|
+
self.tree, self.file_path, self.module_name
|
|
72
|
+
)
|
|
73
|
+
classes = class_extractor.extract()
|
|
74
|
+
|
|
75
|
+
function_extractor = FunctionExtractor(
|
|
76
|
+
self.tree, self.file_path, self.module_name
|
|
77
|
+
)
|
|
78
|
+
functions = function_extractor.extract()
|
|
79
|
+
|
|
80
|
+
import_analyzer = ImportAnalyzer(
|
|
81
|
+
self.tree, self.file_path, self.module_name
|
|
82
|
+
)
|
|
83
|
+
imports, modules = import_analyzer.extract()
|
|
84
|
+
|
|
85
|
+
call_graph_builder = CallGraphBuilder(
|
|
86
|
+
self.tree, self.module_name
|
|
87
|
+
)
|
|
88
|
+
call_graph_builder.build(functions)
|
|
89
|
+
|
|
90
|
+
return FileEntities(
|
|
91
|
+
file=file_entity,
|
|
92
|
+
classes=classes,
|
|
93
|
+
functions=functions,
|
|
94
|
+
modules=modules,
|
|
95
|
+
imports=imports,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
raise ParsingError(f"Failed to parse {self.file_path}: {e}")
|
|
100
|
+
|
|
101
|
+
def _extract_file_entity(self) -> FileEntity:
|
|
102
|
+
"""Extract file metadata."""
|
|
103
|
+
content_hash = hashlib.sha256(self.content.encode()).hexdigest()
|
|
104
|
+
return FileEntity.from_path(self.file_path, content_hash)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def parse_file(file_path: Path, repo_root: Optional[Path] = None) -> FileEntities:
|
|
108
|
+
"""Convenience function to parse a single file.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
file_path: Path to Python file
|
|
112
|
+
repo_root: Root directory of repository
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
FileEntities containing all extracted entities
|
|
116
|
+
"""
|
|
117
|
+
parser = PythonParser(file_path, repo_root)
|
|
118
|
+
return parser.parse()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# Auto-register Python parser with registry
|
|
122
|
+
from .registry import ParserRegistry
|
|
123
|
+
ParserRegistry.register(PythonParser)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Registry for language-specific parsers."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Type, Optional, List
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .base_parser import BaseLanguageParser
|
|
7
|
+
from ...utils.logger import log
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ParserRegistry:
|
|
11
|
+
"""Registry for language-specific parsers.
|
|
12
|
+
|
|
13
|
+
Maps file extensions to parser classes and provides
|
|
14
|
+
lookup functionality for multi-language support.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
_parsers: Dict[str, Type[BaseLanguageParser]] = {}
|
|
18
|
+
|
|
19
|
+
@classmethod
|
|
20
|
+
def register(cls, parser_class: Type[BaseLanguageParser]):
|
|
21
|
+
"""Register a parser for its supported file extensions.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
parser_class: Parser class to register
|
|
25
|
+
"""
|
|
26
|
+
extensions = parser_class.get_supported_extensions()
|
|
27
|
+
for ext in extensions:
|
|
28
|
+
cls._parsers[ext.lower()] = parser_class
|
|
29
|
+
log.debug(f"Registered {parser_class.__name__} for {ext}")
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def get_parser(cls, file_path: Path) -> Optional[Type[BaseLanguageParser]]:
|
|
33
|
+
"""Get parser class for a file based on extension.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
file_path: Path to source file
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Parser class or None if not supported
|
|
40
|
+
"""
|
|
41
|
+
ext = file_path.suffix.lower()
|
|
42
|
+
return cls._parsers.get(ext)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def get_all_extensions(cls) -> List[str]:
|
|
46
|
+
"""Get all supported file extensions.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
List of file extensions (e.g., ['.py', '.ts', '.js'])
|
|
50
|
+
"""
|
|
51
|
+
return list(cls._parsers.keys())
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def is_supported(cls, file_path: Path) -> bool:
|
|
55
|
+
"""Check if file extension is supported.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
file_path: Path to check
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
True if supported, False otherwise
|
|
62
|
+
"""
|
|
63
|
+
return file_path.suffix.lower() in cls._parsers
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def list_parsers(cls) -> Dict[str, str]:
|
|
67
|
+
"""List all registered parsers.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Dictionary mapping extension to parser class name
|
|
71
|
+
"""
|
|
72
|
+
return {ext: parser.__name__ for ext, parser in cls._parsers.items()}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* TypeScript AST parser using TypeScript compiler API
|
|
4
|
+
* Outputs JSON representation of AST for Python consumption
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const ts = require('typescript');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
function parseTypeScriptFile(filePath) {
|
|
12
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
13
|
+
|
|
14
|
+
const sourceFile = ts.createSourceFile(
|
|
15
|
+
path.basename(filePath),
|
|
16
|
+
content,
|
|
17
|
+
ts.ScriptTarget.Latest,
|
|
18
|
+
true
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const entities = {
|
|
22
|
+
classes: [],
|
|
23
|
+
functions: [],
|
|
24
|
+
imports: []
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function visit(node) {
|
|
28
|
+
// Extract classes and interfaces
|
|
29
|
+
if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
|
|
30
|
+
entities.classes.push(extractClass(node, sourceFile));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Extract top-level functions (not methods)
|
|
34
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
35
|
+
entities.functions.push(extractFunction(node, sourceFile, false));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Extract arrow functions assigned to variables
|
|
39
|
+
if (ts.isVariableStatement(node)) {
|
|
40
|
+
node.declarationList.declarations.forEach(decl => {
|
|
41
|
+
if (decl.initializer && ts.isArrowFunction(decl.initializer)) {
|
|
42
|
+
const funcData = extractArrowFunction(decl, sourceFile);
|
|
43
|
+
if (funcData) {
|
|
44
|
+
entities.functions.push(funcData);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Extract imports
|
|
51
|
+
if (ts.isImportDeclaration(node)) {
|
|
52
|
+
entities.imports.push(extractImport(node, sourceFile));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
ts.forEachChild(node, visit);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
visit(sourceFile);
|
|
59
|
+
return entities;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function extractClass(node, sourceFile) {
|
|
63
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
64
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
65
|
+
|
|
66
|
+
const methods = [];
|
|
67
|
+
const properties = [];
|
|
68
|
+
const baseClasses = [];
|
|
69
|
+
|
|
70
|
+
// Extract heritage (extends/implements)
|
|
71
|
+
if (node.heritageClauses) {
|
|
72
|
+
node.heritageClauses.forEach(clause => {
|
|
73
|
+
clause.types.forEach(type => {
|
|
74
|
+
baseClasses.push(type.expression.getText(sourceFile));
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Extract members
|
|
80
|
+
const methodEntities = [];
|
|
81
|
+
if (node.members) {
|
|
82
|
+
node.members.forEach(member => {
|
|
83
|
+
if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member)) {
|
|
84
|
+
const methodName = member.name ? member.name.getText(sourceFile) : 'anonymous';
|
|
85
|
+
methods.push(methodName);
|
|
86
|
+
// Also extract method as a function entity with calls
|
|
87
|
+
methodEntities.push(extractFunction(member, sourceFile, true));
|
|
88
|
+
} else if (ts.isPropertyDeclaration(member) || ts.isPropertySignature(member)) {
|
|
89
|
+
const propName = member.name ? member.name.getText(sourceFile) : 'anonymous';
|
|
90
|
+
properties.push(propName);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Extract JSDoc comment
|
|
96
|
+
const docstring = extractDocstring(node, sourceFile);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
name: node.name ? node.name.text : 'anonymous',
|
|
100
|
+
line_start: pos.line + 1,
|
|
101
|
+
line_end: end.line + 1,
|
|
102
|
+
methods: methods,
|
|
103
|
+
properties: properties,
|
|
104
|
+
base_classes: baseClasses,
|
|
105
|
+
is_abstract: node.modifiers?.some(m => m.kind === ts.SyntaxKind.AbstractKeyword) || false,
|
|
106
|
+
decorators: extractDecorators(node),
|
|
107
|
+
docstring: docstring,
|
|
108
|
+
method_entities: methodEntities // Full method data with calls
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function extractFunction(node, sourceFile, isMethod = false) {
|
|
113
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
114
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
|
|
115
|
+
|
|
116
|
+
const parameters = node.parameters.map(p => p.name.getText(sourceFile));
|
|
117
|
+
|
|
118
|
+
let returnType = null;
|
|
119
|
+
if (node.type) {
|
|
120
|
+
returnType = node.type.getText(sourceFile);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const isAsync = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) || false;
|
|
124
|
+
const isStatic = node.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword) || false;
|
|
125
|
+
|
|
126
|
+
// Extract JSDoc comment
|
|
127
|
+
const docstring = extractDocstring(node, sourceFile);
|
|
128
|
+
|
|
129
|
+
// Extract function calls
|
|
130
|
+
const calls = extractCalls(node.body, sourceFile);
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
name: node.name ? node.name.text : 'anonymous',
|
|
134
|
+
line_start: pos.line + 1,
|
|
135
|
+
line_end: end.line + 1,
|
|
136
|
+
parameters: parameters,
|
|
137
|
+
return_type: returnType,
|
|
138
|
+
is_async: isAsync,
|
|
139
|
+
is_method: isMethod,
|
|
140
|
+
is_static: isStatic,
|
|
141
|
+
decorators: extractDecorators(node),
|
|
142
|
+
docstring: docstring,
|
|
143
|
+
calls: calls
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function extractArrowFunction(variableDecl, sourceFile) {
|
|
148
|
+
const arrowFunc = variableDecl.initializer;
|
|
149
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(arrowFunc.getStart());
|
|
150
|
+
const end = sourceFile.getLineAndCharacterOfPosition(arrowFunc.getEnd());
|
|
151
|
+
|
|
152
|
+
const name = variableDecl.name.getText(sourceFile);
|
|
153
|
+
const parameters = arrowFunc.parameters.map(p => p.name.getText(sourceFile));
|
|
154
|
+
|
|
155
|
+
let returnType = null;
|
|
156
|
+
if (arrowFunc.type) {
|
|
157
|
+
returnType = arrowFunc.type.getText(sourceFile);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Extract function calls from arrow function body
|
|
161
|
+
const calls = extractCalls(arrowFunc.body, sourceFile);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
name: name,
|
|
165
|
+
line_start: pos.line + 1,
|
|
166
|
+
line_end: end.line + 1,
|
|
167
|
+
parameters: parameters,
|
|
168
|
+
return_type: returnType,
|
|
169
|
+
is_async: false, // Arrow functions don't have async modifier at declaration level
|
|
170
|
+
is_method: false,
|
|
171
|
+
is_static: false,
|
|
172
|
+
decorators: [],
|
|
173
|
+
docstring: null,
|
|
174
|
+
calls: calls
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Extract all function calls from a node (function body)
|
|
180
|
+
* @param {ts.Node} node - The AST node to search for calls
|
|
181
|
+
* @param {ts.SourceFile} sourceFile - The source file
|
|
182
|
+
* @returns {string[]} - Array of called function names
|
|
183
|
+
*/
|
|
184
|
+
function extractCalls(node, sourceFile) {
|
|
185
|
+
if (!node) return [];
|
|
186
|
+
|
|
187
|
+
const calls = new Set();
|
|
188
|
+
|
|
189
|
+
function visitForCalls(n) {
|
|
190
|
+
if (ts.isCallExpression(n)) {
|
|
191
|
+
const callName = getCallName(n.expression, sourceFile);
|
|
192
|
+
if (callName) {
|
|
193
|
+
calls.add(callName);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
ts.forEachChild(n, visitForCalls);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
visitForCalls(node);
|
|
200
|
+
return Array.from(calls);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get the name of a called function from its expression
|
|
205
|
+
* @param {ts.Expression} expr - The call expression
|
|
206
|
+
* @param {ts.SourceFile} sourceFile - The source file
|
|
207
|
+
* @returns {string|null} - The function name or null
|
|
208
|
+
*/
|
|
209
|
+
function getCallName(expr, sourceFile) {
|
|
210
|
+
// Direct call: foo()
|
|
211
|
+
if (ts.isIdentifier(expr)) {
|
|
212
|
+
return expr.text;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Method call: obj.foo() or obj.bar.foo()
|
|
216
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
217
|
+
const parts = [];
|
|
218
|
+
let current = expr;
|
|
219
|
+
|
|
220
|
+
while (ts.isPropertyAccessExpression(current)) {
|
|
221
|
+
parts.unshift(current.name.text);
|
|
222
|
+
current = current.expression;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (ts.isIdentifier(current)) {
|
|
226
|
+
parts.unshift(current.text);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return parts.join('.');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Element access: obj['foo']() - less common but handle it
|
|
233
|
+
if (ts.isElementAccessExpression(expr)) {
|
|
234
|
+
const objName = getCallName(expr.expression, sourceFile);
|
|
235
|
+
if (expr.argumentExpression && ts.isStringLiteral(expr.argumentExpression)) {
|
|
236
|
+
return objName ? `${objName}.${expr.argumentExpression.text}` : expr.argumentExpression.text;
|
|
237
|
+
}
|
|
238
|
+
return objName;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Chained call: foo()() - get the inner function
|
|
242
|
+
if (ts.isCallExpression(expr)) {
|
|
243
|
+
return getCallName(expr.expression, sourceFile);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function extractImport(node, sourceFile) {
|
|
250
|
+
const moduleSpecifier = node.moduleSpecifier.text;
|
|
251
|
+
const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
|
|
252
|
+
|
|
253
|
+
let importedNames = [];
|
|
254
|
+
let alias = null;
|
|
255
|
+
|
|
256
|
+
if (node.importClause) {
|
|
257
|
+
// Default import: import Foo from 'module'
|
|
258
|
+
if (node.importClause.name) {
|
|
259
|
+
importedNames.push(node.importClause.name.text);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Named imports: import { A, B } from 'module'
|
|
263
|
+
if (node.importClause.namedBindings) {
|
|
264
|
+
if (ts.isNamedImports(node.importClause.namedBindings)) {
|
|
265
|
+
node.importClause.namedBindings.elements.forEach(element => {
|
|
266
|
+
importedNames.push(element.name.text);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
// Namespace import: import * as Foo from 'module'
|
|
270
|
+
else if (ts.isNamespaceImport(node.importClause.namedBindings)) {
|
|
271
|
+
alias = node.importClause.namedBindings.name.text;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
module: moduleSpecifier,
|
|
278
|
+
line_number: line,
|
|
279
|
+
imported_names: importedNames,
|
|
280
|
+
alias: alias
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function extractDecorators(node) {
|
|
285
|
+
if (!node.decorators) return [];
|
|
286
|
+
|
|
287
|
+
return node.decorators.map(decorator => {
|
|
288
|
+
return decorator.expression.getText();
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function extractDocstring(node, sourceFile) {
|
|
293
|
+
const jsDocComments = ts.getJSDocCommentsAndTags(node);
|
|
294
|
+
if (jsDocComments && jsDocComments.length > 0) {
|
|
295
|
+
return jsDocComments[0].comment || null;
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Main execution
|
|
301
|
+
const filePath = process.argv[2];
|
|
302
|
+
if (!filePath) {
|
|
303
|
+
console.error('Usage: node ts_ast_parser.js <file.ts>');
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
const result = parseTypeScriptFile(filePath);
|
|
309
|
+
console.log(JSON.stringify(result, null, 2));
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.error('Error parsing TypeScript:', error.message);
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|