jarvis-ai-assistant 0.7.16__py3-none-any.whl → 1.0.2__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +567 -222
- jarvis/jarvis_agent/agent_manager.py +19 -12
- jarvis/jarvis_agent/builtin_input_handler.py +79 -11
- jarvis/jarvis_agent/config_editor.py +7 -2
- jarvis/jarvis_agent/event_bus.py +24 -13
- jarvis/jarvis_agent/events.py +19 -1
- jarvis/jarvis_agent/file_context_handler.py +67 -64
- jarvis/jarvis_agent/file_methodology_manager.py +38 -24
- jarvis/jarvis_agent/jarvis.py +186 -114
- jarvis/jarvis_agent/language_extractors/__init__.py +8 -1
- jarvis/jarvis_agent/language_extractors/c_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/cpp_extractor.py +9 -4
- jarvis/jarvis_agent/language_extractors/go_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/java_extractor.py +27 -20
- jarvis/jarvis_agent/language_extractors/javascript_extractor.py +22 -17
- jarvis/jarvis_agent/language_extractors/python_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/rust_extractor.py +7 -4
- jarvis/jarvis_agent/language_extractors/typescript_extractor.py +22 -17
- jarvis/jarvis_agent/language_support_info.py +250 -219
- jarvis/jarvis_agent/main.py +19 -23
- jarvis/jarvis_agent/memory_manager.py +9 -6
- jarvis/jarvis_agent/methodology_share_manager.py +21 -15
- jarvis/jarvis_agent/output_handler.py +4 -2
- jarvis/jarvis_agent/prompt_builder.py +7 -6
- jarvis/jarvis_agent/prompt_manager.py +113 -8
- jarvis/jarvis_agent/prompts.py +317 -85
- jarvis/jarvis_agent/protocols.py +5 -2
- jarvis/jarvis_agent/run_loop.py +192 -32
- jarvis/jarvis_agent/session_manager.py +7 -3
- jarvis/jarvis_agent/share_manager.py +23 -13
- jarvis/jarvis_agent/shell_input_handler.py +12 -8
- jarvis/jarvis_agent/stdio_redirect.py +25 -26
- jarvis/jarvis_agent/task_analyzer.py +29 -23
- jarvis/jarvis_agent/task_list.py +869 -0
- jarvis/jarvis_agent/task_manager.py +26 -23
- jarvis/jarvis_agent/tool_executor.py +6 -5
- jarvis/jarvis_agent/tool_share_manager.py +24 -14
- jarvis/jarvis_agent/user_interaction.py +3 -3
- jarvis/jarvis_agent/utils.py +9 -1
- jarvis/jarvis_agent/web_bridge.py +37 -17
- jarvis/jarvis_agent/web_output_sink.py +5 -2
- jarvis/jarvis_agent/web_server.py +165 -36
- jarvis/jarvis_c2rust/__init__.py +1 -1
- jarvis/jarvis_c2rust/cli.py +260 -141
- jarvis/jarvis_c2rust/collector.py +37 -18
- jarvis/jarvis_c2rust/constants.py +60 -0
- jarvis/jarvis_c2rust/library_replacer.py +242 -1010
- jarvis/jarvis_c2rust/library_replacer_checkpoint.py +133 -0
- jarvis/jarvis_c2rust/library_replacer_llm.py +287 -0
- jarvis/jarvis_c2rust/library_replacer_loader.py +191 -0
- jarvis/jarvis_c2rust/library_replacer_output.py +134 -0
- jarvis/jarvis_c2rust/library_replacer_prompts.py +124 -0
- jarvis/jarvis_c2rust/library_replacer_utils.py +188 -0
- jarvis/jarvis_c2rust/llm_module_agent.py +98 -1044
- jarvis/jarvis_c2rust/llm_module_agent_apply.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_executor.py +288 -0
- jarvis/jarvis_c2rust/llm_module_agent_loader.py +170 -0
- jarvis/jarvis_c2rust/llm_module_agent_prompts.py +268 -0
- jarvis/jarvis_c2rust/llm_module_agent_types.py +57 -0
- jarvis/jarvis_c2rust/llm_module_agent_utils.py +150 -0
- jarvis/jarvis_c2rust/llm_module_agent_validator.py +119 -0
- jarvis/jarvis_c2rust/loaders.py +28 -10
- jarvis/jarvis_c2rust/models.py +5 -2
- jarvis/jarvis_c2rust/optimizer.py +192 -1974
- jarvis/jarvis_c2rust/optimizer_build_fix.py +286 -0
- jarvis/jarvis_c2rust/optimizer_clippy.py +766 -0
- jarvis/jarvis_c2rust/optimizer_config.py +49 -0
- jarvis/jarvis_c2rust/optimizer_docs.py +183 -0
- jarvis/jarvis_c2rust/optimizer_options.py +48 -0
- jarvis/jarvis_c2rust/optimizer_progress.py +469 -0
- jarvis/jarvis_c2rust/optimizer_report.py +52 -0
- jarvis/jarvis_c2rust/optimizer_unsafe.py +309 -0
- jarvis/jarvis_c2rust/optimizer_utils.py +469 -0
- jarvis/jarvis_c2rust/optimizer_visibility.py +185 -0
- jarvis/jarvis_c2rust/scanner.py +229 -166
- jarvis/jarvis_c2rust/transpiler.py +531 -2732
- jarvis/jarvis_c2rust/transpiler_agents.py +503 -0
- jarvis/jarvis_c2rust/transpiler_build.py +1294 -0
- jarvis/jarvis_c2rust/transpiler_codegen.py +204 -0
- jarvis/jarvis_c2rust/transpiler_compile.py +146 -0
- jarvis/jarvis_c2rust/transpiler_config.py +178 -0
- jarvis/jarvis_c2rust/transpiler_context.py +122 -0
- jarvis/jarvis_c2rust/transpiler_executor.py +516 -0
- jarvis/jarvis_c2rust/transpiler_generation.py +278 -0
- jarvis/jarvis_c2rust/transpiler_git.py +163 -0
- jarvis/jarvis_c2rust/transpiler_mod_utils.py +225 -0
- jarvis/jarvis_c2rust/transpiler_modules.py +336 -0
- jarvis/jarvis_c2rust/transpiler_planning.py +394 -0
- jarvis/jarvis_c2rust/transpiler_review.py +1196 -0
- jarvis/jarvis_c2rust/transpiler_symbols.py +176 -0
- jarvis/jarvis_c2rust/utils.py +269 -79
- jarvis/jarvis_code_agent/after_change.py +233 -0
- jarvis/jarvis_code_agent/build_validation_config.py +37 -30
- jarvis/jarvis_code_agent/builtin_rules.py +68 -0
- jarvis/jarvis_code_agent/code_agent.py +976 -1517
- jarvis/jarvis_code_agent/code_agent_build.py +227 -0
- jarvis/jarvis_code_agent/code_agent_diff.py +246 -0
- jarvis/jarvis_code_agent/code_agent_git.py +525 -0
- jarvis/jarvis_code_agent/code_agent_impact.py +177 -0
- jarvis/jarvis_code_agent/code_agent_lint.py +283 -0
- jarvis/jarvis_code_agent/code_agent_llm.py +159 -0
- jarvis/jarvis_code_agent/code_agent_postprocess.py +105 -0
- jarvis/jarvis_code_agent/code_agent_prompts.py +46 -0
- jarvis/jarvis_code_agent/code_agent_rules.py +305 -0
- jarvis/jarvis_code_agent/code_analyzer/__init__.py +52 -48
- jarvis/jarvis_code_agent/code_analyzer/base_language.py +12 -10
- jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +12 -11
- jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +16 -12
- jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +26 -17
- jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +558 -104
- jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +22 -18
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +21 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +20 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +27 -16
- jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +47 -23
- jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +71 -37
- jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +162 -35
- jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +111 -57
- jarvis/jarvis_code_agent/code_analyzer/build_validator.py +18 -12
- jarvis/jarvis_code_agent/code_analyzer/context_manager.py +185 -183
- jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +2 -1
- jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +24 -15
- jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +227 -141
- jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +321 -247
- jarvis/jarvis_code_agent/code_analyzer/language_registry.py +37 -29
- jarvis/jarvis_code_agent/code_analyzer/language_support.py +21 -13
- jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +15 -9
- jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +75 -45
- jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +87 -52
- jarvis/jarvis_code_agent/code_analyzer/languages/java_language.py +84 -51
- jarvis/jarvis_code_agent/code_analyzer/languages/javascript_language.py +94 -64
- jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +109 -71
- jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +97 -63
- jarvis/jarvis_code_agent/code_analyzer/languages/typescript_language.py +103 -69
- jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +271 -268
- jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +76 -64
- jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +92 -19
- jarvis/jarvis_code_agent/diff_visualizer.py +998 -0
- jarvis/jarvis_code_agent/lint.py +223 -524
- jarvis/jarvis_code_agent/rule_share_manager.py +158 -0
- jarvis/jarvis_code_agent/rules/clean_code.md +144 -0
- jarvis/jarvis_code_agent/rules/code_review.md +115 -0
- jarvis/jarvis_code_agent/rules/documentation.md +165 -0
- jarvis/jarvis_code_agent/rules/generate_rules.md +52 -0
- jarvis/jarvis_code_agent/rules/performance.md +158 -0
- jarvis/jarvis_code_agent/rules/refactoring.md +139 -0
- jarvis/jarvis_code_agent/rules/security.md +160 -0
- jarvis/jarvis_code_agent/rules/tdd.md +78 -0
- jarvis/jarvis_code_agent/test_rules/cpp_test.md +118 -0
- jarvis/jarvis_code_agent/test_rules/go_test.md +98 -0
- jarvis/jarvis_code_agent/test_rules/java_test.md +99 -0
- jarvis/jarvis_code_agent/test_rules/javascript_test.md +113 -0
- jarvis/jarvis_code_agent/test_rules/php_test.md +117 -0
- jarvis/jarvis_code_agent/test_rules/python_test.md +91 -0
- jarvis/jarvis_code_agent/test_rules/ruby_test.md +102 -0
- jarvis/jarvis_code_agent/test_rules/rust_test.md +86 -0
- jarvis/jarvis_code_agent/utils.py +36 -26
- jarvis/jarvis_code_analysis/checklists/loader.py +21 -21
- jarvis/jarvis_code_analysis/code_review.py +64 -33
- jarvis/jarvis_data/config_schema.json +285 -192
- jarvis/jarvis_git_squash/main.py +8 -6
- jarvis/jarvis_git_utils/git_commiter.py +53 -76
- jarvis/jarvis_mcp/__init__.py +5 -2
- jarvis/jarvis_mcp/sse_mcp_client.py +40 -30
- jarvis/jarvis_mcp/stdio_mcp_client.py +27 -19
- jarvis/jarvis_mcp/streamable_mcp_client.py +35 -26
- jarvis/jarvis_memory_organizer/memory_organizer.py +78 -55
- jarvis/jarvis_methodology/main.py +48 -39
- jarvis/jarvis_multi_agent/__init__.py +56 -23
- jarvis/jarvis_multi_agent/main.py +15 -18
- jarvis/jarvis_platform/base.py +179 -111
- jarvis/jarvis_platform/human.py +27 -16
- jarvis/jarvis_platform/kimi.py +52 -45
- jarvis/jarvis_platform/openai.py +101 -40
- jarvis/jarvis_platform/registry.py +51 -33
- jarvis/jarvis_platform/tongyi.py +68 -38
- jarvis/jarvis_platform/yuanbao.py +59 -43
- jarvis/jarvis_platform_manager/main.py +68 -76
- jarvis/jarvis_platform_manager/service.py +24 -14
- jarvis/jarvis_rag/README_CONFIG.md +314 -0
- jarvis/jarvis_rag/README_DYNAMIC_LOADING.md +311 -0
- jarvis/jarvis_rag/README_ONLINE_MODELS.md +230 -0
- jarvis/jarvis_rag/__init__.py +57 -4
- jarvis/jarvis_rag/cache.py +3 -1
- jarvis/jarvis_rag/cli.py +48 -68
- jarvis/jarvis_rag/embedding_interface.py +39 -0
- jarvis/jarvis_rag/embedding_manager.py +7 -230
- jarvis/jarvis_rag/embeddings/__init__.py +41 -0
- jarvis/jarvis_rag/embeddings/base.py +114 -0
- jarvis/jarvis_rag/embeddings/cohere.py +66 -0
- jarvis/jarvis_rag/embeddings/edgefn.py +117 -0
- jarvis/jarvis_rag/embeddings/local.py +260 -0
- jarvis/jarvis_rag/embeddings/openai.py +62 -0
- jarvis/jarvis_rag/embeddings/registry.py +293 -0
- jarvis/jarvis_rag/llm_interface.py +8 -6
- jarvis/jarvis_rag/query_rewriter.py +8 -9
- jarvis/jarvis_rag/rag_pipeline.py +61 -52
- jarvis/jarvis_rag/reranker.py +7 -75
- jarvis/jarvis_rag/reranker_interface.py +32 -0
- jarvis/jarvis_rag/rerankers/__init__.py +41 -0
- jarvis/jarvis_rag/rerankers/base.py +109 -0
- jarvis/jarvis_rag/rerankers/cohere.py +67 -0
- jarvis/jarvis_rag/rerankers/edgefn.py +140 -0
- jarvis/jarvis_rag/rerankers/jina.py +79 -0
- jarvis/jarvis_rag/rerankers/local.py +89 -0
- jarvis/jarvis_rag/rerankers/registry.py +293 -0
- jarvis/jarvis_rag/retriever.py +58 -43
- jarvis/jarvis_sec/__init__.py +66 -141
- jarvis/jarvis_sec/agents.py +21 -17
- jarvis/jarvis_sec/analysis.py +80 -33
- jarvis/jarvis_sec/checkers/__init__.py +7 -13
- jarvis/jarvis_sec/checkers/c_checker.py +356 -164
- jarvis/jarvis_sec/checkers/rust_checker.py +47 -29
- jarvis/jarvis_sec/cli.py +43 -21
- jarvis/jarvis_sec/clustering.py +430 -272
- jarvis/jarvis_sec/file_manager.py +99 -55
- jarvis/jarvis_sec/parsers.py +9 -6
- jarvis/jarvis_sec/prompts.py +4 -3
- jarvis/jarvis_sec/report.py +44 -22
- jarvis/jarvis_sec/review.py +180 -107
- jarvis/jarvis_sec/status.py +50 -41
- jarvis/jarvis_sec/types.py +3 -0
- jarvis/jarvis_sec/utils.py +160 -83
- jarvis/jarvis_sec/verification.py +411 -181
- jarvis/jarvis_sec/workflow.py +132 -21
- jarvis/jarvis_smart_shell/main.py +28 -41
- jarvis/jarvis_stats/cli.py +14 -12
- jarvis/jarvis_stats/stats.py +28 -19
- jarvis/jarvis_stats/storage.py +14 -8
- jarvis/jarvis_stats/visualizer.py +12 -7
- jarvis/jarvis_tools/base.py +5 -2
- jarvis/jarvis_tools/clear_memory.py +13 -9
- jarvis/jarvis_tools/cli/main.py +23 -18
- jarvis/jarvis_tools/edit_file.py +572 -873
- jarvis/jarvis_tools/execute_script.py +10 -7
- jarvis/jarvis_tools/file_analyzer.py +7 -8
- jarvis/jarvis_tools/meta_agent.py +287 -0
- jarvis/jarvis_tools/methodology.py +5 -3
- jarvis/jarvis_tools/read_code.py +305 -1438
- jarvis/jarvis_tools/read_symbols.py +50 -17
- jarvis/jarvis_tools/read_webpage.py +19 -18
- jarvis/jarvis_tools/registry.py +435 -156
- jarvis/jarvis_tools/retrieve_memory.py +16 -11
- jarvis/jarvis_tools/save_memory.py +8 -6
- jarvis/jarvis_tools/search_web.py +31 -31
- jarvis/jarvis_tools/sub_agent.py +32 -28
- jarvis/jarvis_tools/sub_code_agent.py +44 -60
- jarvis/jarvis_tools/task_list_manager.py +1811 -0
- jarvis/jarvis_tools/virtual_tty.py +29 -19
- jarvis/jarvis_utils/__init__.py +4 -0
- jarvis/jarvis_utils/builtin_replace_map.py +2 -1
- jarvis/jarvis_utils/clipboard.py +9 -8
- jarvis/jarvis_utils/collections.py +331 -0
- jarvis/jarvis_utils/config.py +699 -194
- jarvis/jarvis_utils/dialogue_recorder.py +294 -0
- jarvis/jarvis_utils/embedding.py +6 -3
- jarvis/jarvis_utils/file_processors.py +7 -1
- jarvis/jarvis_utils/fzf.py +9 -3
- jarvis/jarvis_utils/git_utils.py +71 -42
- jarvis/jarvis_utils/globals.py +116 -32
- jarvis/jarvis_utils/http.py +6 -2
- jarvis/jarvis_utils/input.py +318 -83
- jarvis/jarvis_utils/jsonnet_compat.py +119 -104
- jarvis/jarvis_utils/methodology.py +37 -28
- jarvis/jarvis_utils/output.py +201 -44
- jarvis/jarvis_utils/utils.py +986 -628
- {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/METADATA +49 -33
- jarvis_ai_assistant-1.0.2.dist-info/RECORD +304 -0
- jarvis/jarvis_code_agent/code_analyzer/structured_code.py +0 -556
- jarvis/jarvis_tools/generate_new_tool.py +0 -205
- jarvis/jarvis_tools/lsp_client.py +0 -1552
- jarvis/jarvis_tools/rewrite_file.py +0 -105
- jarvis_ai_assistant-0.7.16.dist-info/RECORD +0 -218
- {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.7.16.dist-info → jarvis_ai_assistant-1.0.2.dist-info}/top_level.txt +0 -0
|
@@ -2,18 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
|
-
from typing import List
|
|
5
|
+
from typing import List
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from typing import Set
|
|
8
|
+
from typing import cast
|
|
6
9
|
|
|
7
10
|
from ..base_language import BaseLanguageSupport
|
|
8
|
-
from ..dependency_analyzer import Dependency
|
|
11
|
+
from ..dependency_analyzer import Dependency
|
|
12
|
+
from ..dependency_analyzer import DependencyAnalyzer
|
|
13
|
+
from ..dependency_analyzer import DependencyGraph
|
|
9
14
|
from ..file_ignore import filter_walk_dirs
|
|
10
|
-
from ..symbol_extractor import Symbol
|
|
15
|
+
from ..symbol_extractor import Symbol
|
|
16
|
+
from ..symbol_extractor import SymbolExtractor
|
|
11
17
|
from ..tree_sitter_extractor import TreeSitterExtractor
|
|
12
18
|
|
|
13
19
|
try:
|
|
14
|
-
from tree_sitter import Language, Node
|
|
15
20
|
import tree_sitter_javascript
|
|
16
|
-
|
|
21
|
+
from tree_sitter import Language
|
|
22
|
+
from tree_sitter import Node
|
|
23
|
+
|
|
24
|
+
JS_LANGUAGE: Optional[Language] = cast(
|
|
25
|
+
Optional[Language], tree_sitter_javascript.language()
|
|
26
|
+
)
|
|
17
27
|
except (ImportError, Exception):
|
|
18
28
|
JS_LANGUAGE = None
|
|
19
29
|
|
|
@@ -57,13 +67,12 @@ class JavaScriptSymbolExtractor(TreeSitterExtractor):
|
|
|
57
67
|
if not JS_LANGUAGE:
|
|
58
68
|
raise RuntimeError("JavaScript tree-sitter grammar not available.")
|
|
59
69
|
# 如果传入的是 PyCapsule,需要转换为 Language 对象
|
|
60
|
-
|
|
61
|
-
lang = Language(JS_LANGUAGE)
|
|
62
|
-
else:
|
|
63
|
-
lang = JS_LANGUAGE
|
|
70
|
+
lang = Language(JS_LANGUAGE)
|
|
64
71
|
super().__init__(lang, JS_SYMBOL_QUERY)
|
|
65
72
|
|
|
66
|
-
def _create_symbol_from_capture(
|
|
73
|
+
def _create_symbol_from_capture(
|
|
74
|
+
self, node: Node, name: str, file_path: str
|
|
75
|
+
) -> Optional[Symbol]:
|
|
67
76
|
"""Maps a tree-sitter capture to a Symbol object."""
|
|
68
77
|
kind_map = {
|
|
69
78
|
"function.name": "function",
|
|
@@ -73,7 +82,7 @@ class JavaScriptSymbolExtractor(TreeSitterExtractor):
|
|
|
73
82
|
"class.name": "class",
|
|
74
83
|
"variable.name": "variable",
|
|
75
84
|
}
|
|
76
|
-
|
|
85
|
+
|
|
77
86
|
symbol_kind = kind_map.get(name)
|
|
78
87
|
if not symbol_kind:
|
|
79
88
|
return None
|
|
@@ -83,10 +92,14 @@ class JavaScriptSymbolExtractor(TreeSitterExtractor):
|
|
|
83
92
|
symbol_name = "<anonymous_arrow_function>"
|
|
84
93
|
elif name == "generator.name":
|
|
85
94
|
# Generator functions are also functions
|
|
86
|
-
|
|
95
|
+
if node.text is None:
|
|
96
|
+
return None
|
|
97
|
+
symbol_name = node.text.decode("utf8")
|
|
87
98
|
else:
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
if node.text is None:
|
|
100
|
+
return None
|
|
101
|
+
symbol_name = node.text.decode("utf8")
|
|
102
|
+
|
|
90
103
|
if not symbol_name:
|
|
91
104
|
return None
|
|
92
105
|
|
|
@@ -101,31 +114,32 @@ class JavaScriptSymbolExtractor(TreeSitterExtractor):
|
|
|
101
114
|
|
|
102
115
|
# --- JavaScript Dependency Analyzer ---
|
|
103
116
|
|
|
117
|
+
|
|
104
118
|
class JavaScriptDependencyAnalyzer(DependencyAnalyzer):
|
|
105
119
|
"""Analyzes JavaScript import/require dependencies."""
|
|
106
120
|
|
|
107
121
|
def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
|
|
108
122
|
"""Analyzes JavaScript import and require statements."""
|
|
109
123
|
dependencies: List[Dependency] = []
|
|
110
|
-
|
|
124
|
+
|
|
111
125
|
# ES6 import statements
|
|
112
126
|
# import module from 'path'
|
|
113
127
|
# import { symbol } from 'path'
|
|
114
128
|
# import * as alias from 'path'
|
|
115
129
|
import_pattern = re.compile(
|
|
116
130
|
r'import\s+(?:(?:\*\s+as\s+(\w+)|(\{[^}]*\})|(\w+))\s+from\s+)?["\']([^"\']+)["\']',
|
|
117
|
-
re.MULTILINE
|
|
131
|
+
re.MULTILINE,
|
|
118
132
|
)
|
|
119
|
-
|
|
133
|
+
|
|
120
134
|
# CommonJS require statements
|
|
121
135
|
# const module = require('path')
|
|
122
136
|
# require('path')
|
|
123
137
|
require_pattern = re.compile(
|
|
124
138
|
r'(?:const|let|var)\s+\w+\s*=\s*require\(["\']([^"\']+)["\']\)|require\(["\']([^"\']+)["\']\)',
|
|
125
|
-
re.MULTILINE
|
|
139
|
+
re.MULTILINE,
|
|
126
140
|
)
|
|
127
|
-
|
|
128
|
-
for line_num, line in enumerate(content.split(
|
|
141
|
+
|
|
142
|
+
for line_num, line in enumerate(content.split("\n"), start=1):
|
|
129
143
|
# Check for ES6 imports
|
|
130
144
|
import_match = import_pattern.search(line)
|
|
131
145
|
if import_match:
|
|
@@ -134,95 +148,111 @@ class JavaScriptDependencyAnalyzer(DependencyAnalyzer):
|
|
|
134
148
|
# Extract imported symbols if any
|
|
135
149
|
symbols_group = import_match.group(2) or import_match.group(1)
|
|
136
150
|
imported_symbol = None
|
|
137
|
-
if symbols_group and symbols_group.startswith(
|
|
151
|
+
if symbols_group and symbols_group.startswith("{"):
|
|
138
152
|
# Extract first symbol from { symbol1, symbol2 }
|
|
139
|
-
symbol_match = re.search(r
|
|
153
|
+
symbol_match = re.search(r"\{([^}]+)\}", symbols_group)
|
|
140
154
|
if symbol_match:
|
|
141
|
-
first_symbol = symbol_match.group(1).split(
|
|
142
|
-
imported_symbol = first_symbol.split(
|
|
143
|
-
|
|
144
|
-
dependencies.append(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
155
|
+
first_symbol = symbol_match.group(1).split(",")[0].strip()
|
|
156
|
+
imported_symbol = first_symbol.split(" as ")[0].strip()
|
|
157
|
+
|
|
158
|
+
dependencies.append(
|
|
159
|
+
Dependency(
|
|
160
|
+
from_module=module_path,
|
|
161
|
+
imported_symbol=imported_symbol,
|
|
162
|
+
file_path=file_path,
|
|
163
|
+
line=line_num,
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
151
167
|
# Check for require statements
|
|
152
168
|
require_match = require_pattern.search(line)
|
|
153
169
|
if require_match:
|
|
154
170
|
module_path = require_match.group(1) or require_match.group(2)
|
|
155
171
|
if module_path:
|
|
156
|
-
dependencies.append(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
172
|
+
dependencies.append(
|
|
173
|
+
Dependency(
|
|
174
|
+
from_module=module_path,
|
|
175
|
+
imported_symbol=None,
|
|
176
|
+
file_path=file_path,
|
|
177
|
+
line=line_num,
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|
|
163
181
|
return dependencies
|
|
164
182
|
|
|
165
183
|
def build_dependency_graph(self, project_root: str) -> DependencyGraph:
|
|
166
184
|
"""Builds a dependency graph for a JavaScript project."""
|
|
167
185
|
graph = DependencyGraph()
|
|
168
|
-
|
|
186
|
+
|
|
169
187
|
for root, dirs, files in os.walk(project_root):
|
|
170
188
|
dirs[:] = filter_walk_dirs(dirs)
|
|
171
|
-
|
|
189
|
+
|
|
172
190
|
for file in files:
|
|
173
|
-
if not file.endswith((
|
|
191
|
+
if not file.endswith((".js", ".jsx", ".mjs", ".cjs")):
|
|
174
192
|
continue
|
|
175
|
-
|
|
193
|
+
|
|
176
194
|
file_path = os.path.join(root, file)
|
|
177
195
|
try:
|
|
178
|
-
with open(file_path,
|
|
196
|
+
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
|
179
197
|
content = f.read()
|
|
180
|
-
|
|
198
|
+
|
|
181
199
|
dependencies = self.analyze_imports(file_path, content)
|
|
182
200
|
for dep in dependencies:
|
|
183
201
|
# Skip node_modules and external packages
|
|
184
|
-
if dep.from_module.startswith(
|
|
185
|
-
|
|
202
|
+
if dep.from_module.startswith(
|
|
203
|
+
(".", "/")
|
|
204
|
+
) or not dep.from_module.startswith("http"):
|
|
205
|
+
dep_path = self._resolve_module_path(
|
|
206
|
+
project_root, dep.from_module, file_path
|
|
207
|
+
)
|
|
186
208
|
if dep_path and dep_path != file_path:
|
|
187
209
|
graph.add_dependency(file_path, dep_path)
|
|
188
210
|
except Exception:
|
|
189
211
|
continue
|
|
190
|
-
|
|
212
|
+
|
|
191
213
|
return graph
|
|
192
214
|
|
|
193
|
-
def _resolve_module_path(
|
|
215
|
+
def _resolve_module_path(
|
|
216
|
+
self, project_root: str, module_name: str, from_file: str
|
|
217
|
+
) -> Optional[str]:
|
|
194
218
|
"""Resolve a JavaScript module name to a file path."""
|
|
195
219
|
if not module_name:
|
|
196
220
|
return None
|
|
197
|
-
|
|
221
|
+
|
|
198
222
|
# Handle relative imports
|
|
199
|
-
if module_name.startswith(
|
|
223
|
+
if module_name.startswith("."):
|
|
200
224
|
base_dir = os.path.dirname(from_file)
|
|
201
225
|
# Resolve relative path
|
|
202
|
-
if module_name.endswith(
|
|
226
|
+
if module_name.endswith(".js") or module_name.endswith(".jsx"):
|
|
203
227
|
# Direct file reference
|
|
204
228
|
resolved = os.path.normpath(os.path.join(base_dir, module_name))
|
|
205
229
|
else:
|
|
206
230
|
# Try with .js extension
|
|
207
|
-
resolved = os.path.normpath(os.path.join(base_dir, module_name +
|
|
231
|
+
resolved = os.path.normpath(os.path.join(base_dir, module_name + ".js"))
|
|
208
232
|
if not os.path.exists(resolved):
|
|
209
233
|
# Try with .jsx extension
|
|
210
|
-
resolved = os.path.normpath(
|
|
234
|
+
resolved = os.path.normpath(
|
|
235
|
+
os.path.join(base_dir, module_name + ".jsx")
|
|
236
|
+
)
|
|
211
237
|
if not os.path.exists(resolved):
|
|
212
238
|
# Try index.js
|
|
213
|
-
resolved = os.path.normpath(
|
|
214
|
-
|
|
239
|
+
resolved = os.path.normpath(
|
|
240
|
+
os.path.join(base_dir, module_name, "index.js")
|
|
241
|
+
)
|
|
242
|
+
|
|
215
243
|
if os.path.exists(resolved) and os.path.isfile(resolved):
|
|
216
244
|
return resolved
|
|
217
|
-
|
|
245
|
+
|
|
218
246
|
# Handle absolute imports (from project root)
|
|
219
|
-
if module_name.startswith(
|
|
220
|
-
resolved = os.path.normpath(
|
|
221
|
-
|
|
222
|
-
|
|
247
|
+
if module_name.startswith("/"):
|
|
248
|
+
resolved = os.path.normpath(
|
|
249
|
+
os.path.join(project_root, module_name.lstrip("/"))
|
|
250
|
+
)
|
|
251
|
+
if not resolved.endswith((".js", ".jsx")):
|
|
252
|
+
resolved += ".js"
|
|
223
253
|
if os.path.exists(resolved) and os.path.isfile(resolved):
|
|
224
254
|
return resolved
|
|
225
|
-
|
|
255
|
+
|
|
226
256
|
# For node_modules and external packages, we can't resolve without package.json
|
|
227
257
|
# Return None to skip them
|
|
228
258
|
return None
|
|
@@ -230,6 +260,7 @@ class JavaScriptDependencyAnalyzer(DependencyAnalyzer):
|
|
|
230
260
|
|
|
231
261
|
# --- JavaScript Language Support ---
|
|
232
262
|
|
|
263
|
+
|
|
233
264
|
class JavaScriptLanguageSupport(BaseLanguageSupport):
|
|
234
265
|
"""JavaScript语言支持。"""
|
|
235
266
|
|
|
@@ -239,7 +270,7 @@ class JavaScriptLanguageSupport(BaseLanguageSupport):
|
|
|
239
270
|
|
|
240
271
|
@property
|
|
241
272
|
def file_extensions(self) -> Set[str]:
|
|
242
|
-
return {
|
|
273
|
+
return {".js", ".jsx", ".mjs", ".cjs"}
|
|
243
274
|
|
|
244
275
|
def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
|
|
245
276
|
if not JS_LANGUAGE:
|
|
@@ -251,4 +282,3 @@ class JavaScriptLanguageSupport(BaseLanguageSupport):
|
|
|
251
282
|
|
|
252
283
|
def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
|
|
253
284
|
return JavaScriptDependencyAnalyzer()
|
|
254
|
-
|
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
import os
|
|
5
|
-
from typing import List
|
|
5
|
+
from typing import List
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from typing import Set
|
|
8
|
+
from typing import Union
|
|
6
9
|
|
|
7
10
|
from ..base_language import BaseLanguageSupport
|
|
8
|
-
from ..dependency_analyzer import Dependency
|
|
11
|
+
from ..dependency_analyzer import Dependency
|
|
12
|
+
from ..dependency_analyzer import DependencyAnalyzer
|
|
13
|
+
from ..dependency_analyzer import DependencyGraph
|
|
9
14
|
from ..file_ignore import filter_walk_dirs
|
|
10
|
-
from ..symbol_extractor import Symbol
|
|
15
|
+
from ..symbol_extractor import Symbol
|
|
16
|
+
from ..symbol_extractor import SymbolExtractor
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
class PythonSymbolExtractor(SymbolExtractor):
|
|
@@ -19,10 +25,10 @@ class PythonSymbolExtractor(SymbolExtractor):
|
|
|
19
25
|
# 对于超大文件,限制解析内容长度(避免内存和性能问题)
|
|
20
26
|
# 只解析前 50000 行,通常足够提取主要符号
|
|
21
27
|
max_lines = 50000
|
|
22
|
-
lines = content.split(
|
|
28
|
+
lines = content.split("\n")
|
|
23
29
|
if len(lines) > max_lines:
|
|
24
|
-
content =
|
|
25
|
-
|
|
30
|
+
content = "\n".join(lines[:max_lines])
|
|
31
|
+
|
|
26
32
|
tree = ast.parse(content, filename=file_path)
|
|
27
33
|
self._traverse_node(tree, file_path, symbols, parent_name=None)
|
|
28
34
|
except SyntaxError:
|
|
@@ -33,7 +39,13 @@ class PythonSymbolExtractor(SymbolExtractor):
|
|
|
33
39
|
pass
|
|
34
40
|
return symbols
|
|
35
41
|
|
|
36
|
-
def _traverse_node(
|
|
42
|
+
def _traverse_node(
|
|
43
|
+
self,
|
|
44
|
+
node: ast.AST,
|
|
45
|
+
file_path: str,
|
|
46
|
+
symbols: List[Symbol],
|
|
47
|
+
parent_name: Optional[str],
|
|
48
|
+
):
|
|
37
49
|
if isinstance(node, ast.FunctionDef):
|
|
38
50
|
# Extract decorators before the function
|
|
39
51
|
if node.decorator_list:
|
|
@@ -41,7 +53,7 @@ class PythonSymbolExtractor(SymbolExtractor):
|
|
|
41
53
|
if isinstance(decorator, ast.Name):
|
|
42
54
|
decorator_symbol = Symbol(
|
|
43
55
|
name=decorator.id,
|
|
44
|
-
kind=
|
|
56
|
+
kind="decorator",
|
|
45
57
|
file_path=file_path,
|
|
46
58
|
line_start=decorator.lineno,
|
|
47
59
|
line_end=decorator.lineno,
|
|
@@ -58,14 +70,16 @@ class PythonSymbolExtractor(SymbolExtractor):
|
|
|
58
70
|
if isinstance(decorator, ast.Name):
|
|
59
71
|
decorator_symbol = Symbol(
|
|
60
72
|
name=decorator.id,
|
|
61
|
-
kind=
|
|
73
|
+
kind="decorator",
|
|
62
74
|
file_path=file_path,
|
|
63
75
|
line_start=decorator.lineno,
|
|
64
76
|
line_end=decorator.lineno,
|
|
65
77
|
parent=parent_name,
|
|
66
78
|
)
|
|
67
79
|
symbols.append(decorator_symbol)
|
|
68
|
-
symbol = self._create_symbol_from_func(
|
|
80
|
+
symbol = self._create_symbol_from_func(
|
|
81
|
+
node, file_path, parent_name, is_async=True
|
|
82
|
+
)
|
|
69
83
|
symbols.append(symbol)
|
|
70
84
|
parent_name = node.name
|
|
71
85
|
elif isinstance(node, ast.ClassDef):
|
|
@@ -75,7 +89,7 @@ class PythonSymbolExtractor(SymbolExtractor):
|
|
|
75
89
|
if isinstance(decorator, ast.Name):
|
|
76
90
|
decorator_symbol = Symbol(
|
|
77
91
|
name=decorator.id,
|
|
78
|
-
kind=
|
|
92
|
+
kind="decorator",
|
|
79
93
|
file_path=file_path,
|
|
80
94
|
line_start=decorator.lineno,
|
|
81
95
|
line_end=decorator.lineno,
|
|
@@ -86,16 +100,24 @@ class PythonSymbolExtractor(SymbolExtractor):
|
|
|
86
100
|
symbols.append(symbol)
|
|
87
101
|
parent_name = node.name
|
|
88
102
|
elif isinstance(node, (ast.Import, ast.ImportFrom)):
|
|
89
|
-
|
|
103
|
+
symbols.extend(
|
|
104
|
+
self._create_symbols_from_import(node, file_path, parent_name)
|
|
105
|
+
)
|
|
90
106
|
|
|
91
107
|
for child in ast.iter_child_nodes(node):
|
|
92
108
|
self._traverse_node(child, file_path, symbols, parent_name=parent_name)
|
|
93
109
|
|
|
94
|
-
def _create_symbol_from_func(
|
|
110
|
+
def _create_symbol_from_func(
|
|
111
|
+
self,
|
|
112
|
+
node: Union[ast.FunctionDef, ast.AsyncFunctionDef],
|
|
113
|
+
file_path: str,
|
|
114
|
+
parent: Optional[str],
|
|
115
|
+
is_async: bool = False,
|
|
116
|
+
) -> Symbol:
|
|
95
117
|
signature = f"{'async ' if is_async else ''}def {node.name}(...)"
|
|
96
118
|
return Symbol(
|
|
97
119
|
name=node.name,
|
|
98
|
-
kind=
|
|
120
|
+
kind="function",
|
|
99
121
|
file_path=file_path,
|
|
100
122
|
line_start=node.lineno,
|
|
101
123
|
line_end=node.end_lineno or node.lineno,
|
|
@@ -104,28 +126,37 @@ class PythonSymbolExtractor(SymbolExtractor):
|
|
|
104
126
|
parent=parent,
|
|
105
127
|
)
|
|
106
128
|
|
|
107
|
-
def _create_symbol_from_class(
|
|
129
|
+
def _create_symbol_from_class(
|
|
130
|
+
self, node: ast.ClassDef, file_path: str, parent: Optional[str]
|
|
131
|
+
) -> Symbol:
|
|
108
132
|
return Symbol(
|
|
109
133
|
name=node.name,
|
|
110
|
-
kind=
|
|
134
|
+
kind="class",
|
|
111
135
|
file_path=file_path,
|
|
112
136
|
line_start=node.lineno,
|
|
113
137
|
line_end=node.end_lineno or node.lineno,
|
|
114
138
|
docstring=ast.get_docstring(node),
|
|
115
139
|
parent=parent,
|
|
116
140
|
)
|
|
117
|
-
|
|
118
|
-
def _create_symbols_from_import(
|
|
141
|
+
|
|
142
|
+
def _create_symbols_from_import(
|
|
143
|
+
self,
|
|
144
|
+
node: Union[ast.Import, ast.ImportFrom],
|
|
145
|
+
file_path: str,
|
|
146
|
+
parent: Optional[str],
|
|
147
|
+
) -> List[Symbol]:
|
|
119
148
|
symbols = []
|
|
120
149
|
for alias in node.names:
|
|
121
|
-
symbols.append(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
150
|
+
symbols.append(
|
|
151
|
+
Symbol(
|
|
152
|
+
name=alias.asname or alias.name,
|
|
153
|
+
kind="import",
|
|
154
|
+
file_path=file_path,
|
|
155
|
+
line_start=node.lineno,
|
|
156
|
+
line_end=node.end_lineno or node.lineno,
|
|
157
|
+
parent=parent,
|
|
158
|
+
)
|
|
159
|
+
)
|
|
129
160
|
return symbols
|
|
130
161
|
|
|
131
162
|
|
|
@@ -135,119 +166,127 @@ class PythonDependencyAnalyzer(DependencyAnalyzer):
|
|
|
135
166
|
def analyze_imports(self, file_path: str, content: str) -> List[Dependency]:
|
|
136
167
|
"""Analyzes Python import statements."""
|
|
137
168
|
dependencies: List[Dependency] = []
|
|
138
|
-
|
|
169
|
+
|
|
139
170
|
try:
|
|
140
171
|
tree = ast.parse(content, filename=file_path)
|
|
141
172
|
for node in ast.walk(tree):
|
|
142
173
|
if isinstance(node, ast.Import):
|
|
143
174
|
# import module
|
|
144
175
|
for alias in node.names:
|
|
145
|
-
dependencies.append(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
176
|
+
dependencies.append(
|
|
177
|
+
Dependency(
|
|
178
|
+
from_module=alias.name,
|
|
179
|
+
imported_symbol=None,
|
|
180
|
+
file_path=file_path,
|
|
181
|
+
line=node.lineno,
|
|
182
|
+
)
|
|
183
|
+
)
|
|
151
184
|
elif isinstance(node, ast.ImportFrom):
|
|
152
185
|
# from module import symbol
|
|
153
186
|
module = node.module or ""
|
|
154
187
|
for alias in node.names:
|
|
155
|
-
dependencies.append(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
188
|
+
dependencies.append(
|
|
189
|
+
Dependency(
|
|
190
|
+
from_module=module,
|
|
191
|
+
imported_symbol=alias.name,
|
|
192
|
+
file_path=file_path,
|
|
193
|
+
line=node.lineno,
|
|
194
|
+
)
|
|
195
|
+
)
|
|
161
196
|
except SyntaxError:
|
|
162
197
|
# Skip files with syntax errors
|
|
163
198
|
pass
|
|
164
|
-
|
|
199
|
+
|
|
165
200
|
return dependencies
|
|
166
201
|
|
|
167
202
|
def build_dependency_graph(self, project_root: str) -> DependencyGraph:
|
|
168
203
|
"""Builds a dependency graph for a Python project."""
|
|
169
204
|
graph = DependencyGraph()
|
|
170
|
-
|
|
205
|
+
|
|
171
206
|
# Walk through all Python files
|
|
172
207
|
for root, dirs, files in os.walk(project_root):
|
|
173
208
|
# Skip hidden directories and common ignore patterns
|
|
174
209
|
dirs[:] = filter_walk_dirs(dirs)
|
|
175
|
-
|
|
210
|
+
|
|
176
211
|
for file in files:
|
|
177
|
-
if not file.endswith(
|
|
212
|
+
if not file.endswith(".py"):
|
|
178
213
|
continue
|
|
179
|
-
|
|
214
|
+
|
|
180
215
|
file_path = os.path.join(root, file)
|
|
181
216
|
try:
|
|
182
|
-
with open(file_path,
|
|
217
|
+
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
|
183
218
|
content = f.read()
|
|
184
|
-
|
|
219
|
+
|
|
185
220
|
dependencies = self.analyze_imports(file_path, content)
|
|
186
221
|
for dep in dependencies:
|
|
187
222
|
# Resolve module to file path
|
|
188
|
-
dep_path = self._resolve_module_path(
|
|
223
|
+
dep_path = self._resolve_module_path(
|
|
224
|
+
project_root, dep.from_module, file_path
|
|
225
|
+
)
|
|
189
226
|
if dep_path and dep_path != file_path:
|
|
190
227
|
graph.add_dependency(file_path, dep_path)
|
|
191
228
|
except Exception:
|
|
192
229
|
continue
|
|
193
|
-
|
|
230
|
+
|
|
194
231
|
return graph
|
|
195
232
|
|
|
196
|
-
def _resolve_module_path(
|
|
233
|
+
def _resolve_module_path(
|
|
234
|
+
self, project_root: str, module_name: str, from_file: str
|
|
235
|
+
) -> Optional[str]:
|
|
197
236
|
"""Resolve a Python module name to a file path."""
|
|
198
237
|
if not module_name:
|
|
199
238
|
return None
|
|
200
|
-
|
|
239
|
+
|
|
201
240
|
# Handle relative imports
|
|
202
|
-
if module_name.startswith(
|
|
241
|
+
if module_name.startswith("."):
|
|
203
242
|
# Relative import - resolve from the importing file's directory
|
|
204
243
|
base_dir = os.path.dirname(from_file)
|
|
205
|
-
parts = module_name.split(
|
|
206
|
-
depth = len([p for p in parts if p ==
|
|
244
|
+
parts = module_name.split(".")
|
|
245
|
+
depth = len([p for p in parts if p == ""])
|
|
207
246
|
module_parts = [p for p in parts if p]
|
|
208
|
-
|
|
247
|
+
|
|
209
248
|
# Navigate up directories
|
|
210
249
|
current_dir = base_dir
|
|
211
250
|
for _ in range(depth - 1):
|
|
212
251
|
current_dir = os.path.dirname(current_dir)
|
|
213
|
-
|
|
252
|
+
|
|
214
253
|
if module_parts:
|
|
215
254
|
module_path = os.path.join(current_dir, *module_parts)
|
|
216
255
|
else:
|
|
217
256
|
module_path = current_dir
|
|
218
|
-
|
|
257
|
+
|
|
219
258
|
# Try to find the module file
|
|
220
259
|
if os.path.isdir(module_path):
|
|
221
|
-
init_path = os.path.join(module_path,
|
|
260
|
+
init_path = os.path.join(module_path, "__init__.py")
|
|
222
261
|
if os.path.exists(init_path):
|
|
223
262
|
return init_path
|
|
224
|
-
elif os.path.exists(module_path +
|
|
225
|
-
return module_path +
|
|
263
|
+
elif os.path.exists(module_path + ".py"):
|
|
264
|
+
return module_path + ".py"
|
|
226
265
|
else:
|
|
227
266
|
# Absolute import
|
|
228
|
-
parts = module_name.split(
|
|
229
|
-
|
|
267
|
+
parts = module_name.split(".")
|
|
268
|
+
|
|
230
269
|
# Search in project root
|
|
231
270
|
for root, dirs, files in os.walk(project_root):
|
|
232
271
|
# Skip hidden directories and common ignore patterns
|
|
233
272
|
dirs[:] = filter_walk_dirs(dirs)
|
|
234
|
-
|
|
273
|
+
|
|
235
274
|
if parts[0] in dirs or f"{parts[0]}.py" in files:
|
|
236
275
|
module_path = os.path.join(root, *parts)
|
|
237
|
-
|
|
276
|
+
|
|
238
277
|
if os.path.isdir(module_path):
|
|
239
|
-
init_path = os.path.join(module_path,
|
|
278
|
+
init_path = os.path.join(module_path, "__init__.py")
|
|
240
279
|
if os.path.exists(init_path):
|
|
241
280
|
return init_path
|
|
242
|
-
elif os.path.exists(module_path +
|
|
243
|
-
return module_path +
|
|
281
|
+
elif os.path.exists(module_path + ".py"):
|
|
282
|
+
return module_path + ".py"
|
|
244
283
|
break
|
|
245
|
-
|
|
284
|
+
|
|
246
285
|
return None
|
|
247
286
|
|
|
248
287
|
def _is_source_file(self, file_path: str) -> bool:
|
|
249
288
|
"""Check if a file is a Python source file."""
|
|
250
|
-
return file_path.endswith(
|
|
289
|
+
return file_path.endswith(".py")
|
|
251
290
|
|
|
252
291
|
|
|
253
292
|
class PythonLanguageSupport(BaseLanguageSupport):
|
|
@@ -255,15 +294,14 @@ class PythonLanguageSupport(BaseLanguageSupport):
|
|
|
255
294
|
|
|
256
295
|
@property
|
|
257
296
|
def language_name(self) -> str:
|
|
258
|
-
return
|
|
297
|
+
return "python"
|
|
259
298
|
|
|
260
299
|
@property
|
|
261
300
|
def file_extensions(self) -> Set[str]:
|
|
262
|
-
return {
|
|
301
|
+
return {".py", ".pyw", ".pyi"}
|
|
263
302
|
|
|
264
303
|
def create_symbol_extractor(self) -> Optional[SymbolExtractor]:
|
|
265
304
|
return PythonSymbolExtractor()
|
|
266
305
|
|
|
267
306
|
def create_dependency_analyzer(self) -> Optional[DependencyAnalyzer]:
|
|
268
307
|
return PythonDependencyAnalyzer()
|
|
269
|
-
|