shotgun-sh 0.3.3.dev1__py3-none-any.whl → 0.6.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.
- shotgun/agents/agent_manager.py +497 -30
- shotgun/agents/cancellation.py +103 -0
- shotgun/agents/common.py +90 -77
- shotgun/agents/config/README.md +0 -1
- shotgun/agents/config/manager.py +52 -8
- shotgun/agents/config/models.py +21 -27
- shotgun/agents/config/provider.py +44 -27
- shotgun/agents/conversation/history/file_content_deduplication.py +66 -43
- shotgun/agents/conversation/history/token_counting/base.py +51 -9
- shotgun/agents/export.py +12 -13
- shotgun/agents/file_read.py +176 -0
- shotgun/agents/messages.py +15 -3
- shotgun/agents/models.py +90 -2
- shotgun/agents/plan.py +12 -13
- shotgun/agents/research.py +13 -10
- shotgun/agents/router/__init__.py +47 -0
- shotgun/agents/router/models.py +384 -0
- shotgun/agents/router/router.py +185 -0
- shotgun/agents/router/tools/__init__.py +18 -0
- shotgun/agents/router/tools/delegation_tools.py +557 -0
- shotgun/agents/router/tools/plan_tools.py +403 -0
- shotgun/agents/runner.py +17 -2
- shotgun/agents/specify.py +12 -13
- shotgun/agents/tasks.py +12 -13
- shotgun/agents/tools/__init__.py +8 -0
- shotgun/agents/tools/codebase/directory_lister.py +27 -39
- shotgun/agents/tools/codebase/file_read.py +26 -35
- shotgun/agents/tools/codebase/query_graph.py +9 -0
- shotgun/agents/tools/codebase/retrieve_code.py +9 -0
- shotgun/agents/tools/file_management.py +81 -3
- shotgun/agents/tools/file_read_tools/__init__.py +7 -0
- shotgun/agents/tools/file_read_tools/multimodal_file_read.py +167 -0
- shotgun/agents/tools/markdown_tools/__init__.py +62 -0
- shotgun/agents/tools/markdown_tools/insert_section.py +148 -0
- shotgun/agents/tools/markdown_tools/models.py +86 -0
- shotgun/agents/tools/markdown_tools/remove_section.py +114 -0
- shotgun/agents/tools/markdown_tools/replace_section.py +119 -0
- shotgun/agents/tools/markdown_tools/utils.py +453 -0
- shotgun/agents/tools/registry.py +46 -6
- shotgun/agents/tools/web_search/__init__.py +1 -2
- shotgun/agents/tools/web_search/gemini.py +1 -3
- shotgun/agents/tools/web_search/openai.py +42 -23
- shotgun/attachments/__init__.py +41 -0
- shotgun/attachments/errors.py +60 -0
- shotgun/attachments/models.py +107 -0
- shotgun/attachments/parser.py +257 -0
- shotgun/attachments/processor.py +193 -0
- shotgun/build_constants.py +4 -7
- shotgun/cli/clear.py +2 -2
- shotgun/cli/codebase/commands.py +181 -65
- shotgun/cli/compact.py +2 -2
- shotgun/cli/context.py +2 -2
- shotgun/cli/error_handler.py +2 -2
- shotgun/cli/run.py +90 -0
- shotgun/cli/spec/backup.py +2 -1
- shotgun/codebase/__init__.py +2 -0
- shotgun/codebase/benchmarks/__init__.py +35 -0
- shotgun/codebase/benchmarks/benchmark_runner.py +309 -0
- shotgun/codebase/benchmarks/exporters.py +119 -0
- shotgun/codebase/benchmarks/formatters/__init__.py +49 -0
- shotgun/codebase/benchmarks/formatters/base.py +34 -0
- shotgun/codebase/benchmarks/formatters/json_formatter.py +106 -0
- shotgun/codebase/benchmarks/formatters/markdown.py +136 -0
- shotgun/codebase/benchmarks/models.py +129 -0
- shotgun/codebase/core/__init__.py +4 -0
- shotgun/codebase/core/call_resolution.py +91 -0
- shotgun/codebase/core/change_detector.py +11 -6
- shotgun/codebase/core/errors.py +159 -0
- shotgun/codebase/core/extractors/__init__.py +23 -0
- shotgun/codebase/core/extractors/base.py +138 -0
- shotgun/codebase/core/extractors/factory.py +63 -0
- shotgun/codebase/core/extractors/go/__init__.py +7 -0
- shotgun/codebase/core/extractors/go/extractor.py +122 -0
- shotgun/codebase/core/extractors/javascript/__init__.py +7 -0
- shotgun/codebase/core/extractors/javascript/extractor.py +132 -0
- shotgun/codebase/core/extractors/protocol.py +109 -0
- shotgun/codebase/core/extractors/python/__init__.py +7 -0
- shotgun/codebase/core/extractors/python/extractor.py +141 -0
- shotgun/codebase/core/extractors/rust/__init__.py +7 -0
- shotgun/codebase/core/extractors/rust/extractor.py +139 -0
- shotgun/codebase/core/extractors/types.py +15 -0
- shotgun/codebase/core/extractors/typescript/__init__.py +7 -0
- shotgun/codebase/core/extractors/typescript/extractor.py +92 -0
- shotgun/codebase/core/gitignore.py +252 -0
- shotgun/codebase/core/ingestor.py +644 -354
- shotgun/codebase/core/kuzu_compat.py +119 -0
- shotgun/codebase/core/language_config.py +239 -0
- shotgun/codebase/core/manager.py +256 -46
- shotgun/codebase/core/metrics_collector.py +310 -0
- shotgun/codebase/core/metrics_types.py +347 -0
- shotgun/codebase/core/parallel_executor.py +424 -0
- shotgun/codebase/core/work_distributor.py +254 -0
- shotgun/codebase/core/worker.py +768 -0
- shotgun/codebase/indexing_state.py +86 -0
- shotgun/codebase/models.py +94 -0
- shotgun/codebase/service.py +13 -0
- shotgun/exceptions.py +9 -9
- shotgun/main.py +3 -16
- shotgun/posthog_telemetry.py +165 -24
- shotgun/prompts/agents/export.j2 +2 -0
- shotgun/prompts/agents/file_read.j2 +48 -0
- shotgun/prompts/agents/partials/common_agent_system_prompt.j2 +19 -52
- shotgun/prompts/agents/partials/content_formatting.j2 +12 -33
- shotgun/prompts/agents/partials/interactive_mode.j2 +9 -32
- shotgun/prompts/agents/partials/router_delegation_mode.j2 +35 -0
- shotgun/prompts/agents/plan.j2 +38 -12
- shotgun/prompts/agents/research.j2 +70 -31
- shotgun/prompts/agents/router.j2 +713 -0
- shotgun/prompts/agents/specify.j2 +53 -16
- shotgun/prompts/agents/state/codebase/codebase_graphs_available.j2 +14 -1
- shotgun/prompts/agents/state/system_state.j2 +24 -13
- shotgun/prompts/agents/tasks.j2 +72 -34
- shotgun/settings.py +49 -10
- shotgun/tui/app.py +154 -24
- shotgun/tui/commands/__init__.py +9 -1
- shotgun/tui/components/attachment_bar.py +87 -0
- shotgun/tui/components/mode_indicator.py +120 -25
- shotgun/tui/components/prompt_input.py +25 -28
- shotgun/tui/components/status_bar.py +14 -7
- shotgun/tui/dependencies.py +58 -8
- shotgun/tui/protocols.py +55 -0
- shotgun/tui/screens/chat/chat.tcss +24 -1
- shotgun/tui/screens/chat/chat_screen.py +1376 -213
- shotgun/tui/screens/chat/codebase_index_prompt_screen.py +8 -4
- shotgun/tui/screens/chat_screen/attachment_hint.py +40 -0
- shotgun/tui/screens/chat_screen/command_providers.py +0 -97
- shotgun/tui/screens/chat_screen/history/agent_response.py +7 -3
- shotgun/tui/screens/chat_screen/history/chat_history.py +58 -6
- shotgun/tui/screens/chat_screen/history/formatters.py +75 -15
- shotgun/tui/screens/chat_screen/history/partial_response.py +11 -1
- shotgun/tui/screens/chat_screen/history/user_question.py +25 -3
- shotgun/tui/screens/chat_screen/messages.py +219 -0
- shotgun/tui/screens/database_locked_dialog.py +219 -0
- shotgun/tui/screens/database_timeout_dialog.py +158 -0
- shotgun/tui/screens/kuzu_error_dialog.py +135 -0
- shotgun/tui/screens/model_picker.py +1 -3
- shotgun/tui/screens/models.py +11 -0
- shotgun/tui/state/processing_state.py +19 -0
- shotgun/tui/utils/mode_progress.py +20 -86
- shotgun/tui/widgets/__init__.py +2 -1
- shotgun/tui/widgets/approval_widget.py +152 -0
- shotgun/tui/widgets/cascade_confirmation_widget.py +203 -0
- shotgun/tui/widgets/plan_panel.py +129 -0
- shotgun/tui/widgets/step_checkpoint_widget.py +180 -0
- shotgun/tui/widgets/widget_coordinator.py +18 -0
- shotgun/utils/file_system_utils.py +4 -1
- {shotgun_sh-0.3.3.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/METADATA +88 -35
- shotgun_sh-0.6.2.dist-info/RECORD +291 -0
- shotgun/cli/export.py +0 -81
- shotgun/cli/plan.py +0 -73
- shotgun/cli/research.py +0 -93
- shotgun/cli/specify.py +0 -70
- shotgun/cli/tasks.py +0 -78
- shotgun/sentry_telemetry.py +0 -232
- shotgun/tui/screens/onboarding.py +0 -580
- shotgun_sh-0.3.3.dev1.dist-info/RECORD +0 -229
- {shotgun_sh-0.3.3.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/WHEEL +0 -0
- {shotgun_sh-0.3.3.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/entry_points.txt +0 -0
- {shotgun_sh-0.3.3.dev1.dist-info → shotgun_sh-0.6.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Base extractor with shared extraction logic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from tree_sitter import Node
|
|
10
|
+
|
|
11
|
+
from .types import SupportedLanguage
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BaseExtractor(ABC):
|
|
15
|
+
"""Abstract base class for language extractors.
|
|
16
|
+
|
|
17
|
+
Provides shared extraction logic that works across languages,
|
|
18
|
+
with abstract methods for language-specific operations.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def language(self) -> SupportedLanguage:
|
|
24
|
+
"""The language this extractor handles."""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def extract_decorators(self, node: Node) -> list[str]:
|
|
29
|
+
"""Extract decorators/attributes from a function or class node."""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def extract_docstring(self, node: Node) -> str | None:
|
|
34
|
+
"""Extract documentation string from a function or class node."""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def extract_inheritance(self, class_node: Node) -> list[str]:
|
|
39
|
+
"""Extract parent class names from a class definition."""
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def parse_call_node(self, call_node: Node) -> tuple[str | None, str | None]:
|
|
44
|
+
"""Parse a call expression node to extract callee information."""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def _class_definition_types(self) -> list[str]:
|
|
49
|
+
"""Return node types that represent class definitions."""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def _function_definition_types(self) -> list[str]:
|
|
54
|
+
"""Return node types that represent function definitions."""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
def find_parent_class(self, func_node: Node, module_qn: str) -> str | None:
|
|
58
|
+
"""Find the parent class of a function node.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
func_node: The function definition AST node
|
|
62
|
+
module_qn: Module qualified name
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Qualified name of parent class, or None if not in a class
|
|
66
|
+
"""
|
|
67
|
+
current = func_node.parent
|
|
68
|
+
|
|
69
|
+
while current:
|
|
70
|
+
if current.type in self._class_definition_types():
|
|
71
|
+
for child in current.children:
|
|
72
|
+
if child.type == "identifier" and child.text:
|
|
73
|
+
class_name = child.text.decode("utf-8")
|
|
74
|
+
return f"{module_qn}.{class_name}"
|
|
75
|
+
|
|
76
|
+
current = current.parent
|
|
77
|
+
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
def find_containing_function(self, node: Node, module_qn: str) -> str | None:
|
|
81
|
+
"""Find the containing function/method of a node.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
node: The AST node
|
|
85
|
+
module_qn: Module qualified name
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Qualified name of containing function, or None
|
|
89
|
+
"""
|
|
90
|
+
current = node.parent
|
|
91
|
+
|
|
92
|
+
while current:
|
|
93
|
+
if current.type in self._function_definition_types():
|
|
94
|
+
for child in current.children:
|
|
95
|
+
if child.type == "identifier" and child.text:
|
|
96
|
+
func_name = child.text.decode("utf-8")
|
|
97
|
+
|
|
98
|
+
parent_class = self.find_parent_class(current, module_qn)
|
|
99
|
+
if parent_class:
|
|
100
|
+
return f"{parent_class}.{func_name}"
|
|
101
|
+
else:
|
|
102
|
+
return f"{module_qn}.{func_name}"
|
|
103
|
+
|
|
104
|
+
current = current.parent
|
|
105
|
+
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
def count_ast_nodes(self, node: Node) -> int:
|
|
109
|
+
"""Count total AST nodes for metrics.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
node: Root AST node
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Total node count
|
|
116
|
+
"""
|
|
117
|
+
count = 1
|
|
118
|
+
for child in node.children:
|
|
119
|
+
count += self.count_ast_nodes(child)
|
|
120
|
+
return count
|
|
121
|
+
|
|
122
|
+
def _extract_full_name(self, node: Node, parts: list[str]) -> None:
|
|
123
|
+
"""Recursively extract full qualified name from attribute access.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
node: The AST node
|
|
127
|
+
parts: List to accumulate name parts (modified in place)
|
|
128
|
+
"""
|
|
129
|
+
if node.type == "identifier" and node.text:
|
|
130
|
+
parts.insert(0, node.text.decode("utf-8"))
|
|
131
|
+
elif node.type == "attribute":
|
|
132
|
+
attr_node = node.child_by_field_name("attribute")
|
|
133
|
+
if attr_node and attr_node.text:
|
|
134
|
+
parts.insert(0, attr_node.text.decode("utf-8"))
|
|
135
|
+
|
|
136
|
+
obj_node = node.child_by_field_name("object")
|
|
137
|
+
if obj_node:
|
|
138
|
+
self._extract_full_name(obj_node, parts)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Factory for creating language-specific extractors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .protocol import LanguageExtractor
|
|
6
|
+
from .types import SupportedLanguage
|
|
7
|
+
|
|
8
|
+
# Cache of extractor instances (created lazily)
|
|
9
|
+
_extractors: dict[SupportedLanguage, LanguageExtractor] = {}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_extractor(language: SupportedLanguage | str) -> LanguageExtractor:
|
|
13
|
+
"""Get the extractor for a language.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
language: The language as enum or string
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
The language extractor instance
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
ValueError: If language is not supported
|
|
23
|
+
"""
|
|
24
|
+
if isinstance(language, str):
|
|
25
|
+
language = SupportedLanguage(language)
|
|
26
|
+
|
|
27
|
+
if language not in _extractors:
|
|
28
|
+
_extractors[language] = _create_extractor(language)
|
|
29
|
+
|
|
30
|
+
return _extractors[language]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _create_extractor(language: SupportedLanguage) -> LanguageExtractor:
|
|
34
|
+
"""Create a new extractor instance for the language.
|
|
35
|
+
|
|
36
|
+
Uses lazy imports to avoid loading all extractors at once.
|
|
37
|
+
"""
|
|
38
|
+
match language:
|
|
39
|
+
case SupportedLanguage.PYTHON:
|
|
40
|
+
from .python.extractor import PythonExtractor
|
|
41
|
+
|
|
42
|
+
return PythonExtractor()
|
|
43
|
+
case SupportedLanguage.JAVASCRIPT:
|
|
44
|
+
from .javascript.extractor import JavaScriptExtractor
|
|
45
|
+
|
|
46
|
+
return JavaScriptExtractor()
|
|
47
|
+
case SupportedLanguage.TYPESCRIPT:
|
|
48
|
+
from .typescript.extractor import TypeScriptExtractor
|
|
49
|
+
|
|
50
|
+
return TypeScriptExtractor()
|
|
51
|
+
case SupportedLanguage.GO:
|
|
52
|
+
from .go.extractor import GoExtractor
|
|
53
|
+
|
|
54
|
+
return GoExtractor()
|
|
55
|
+
case SupportedLanguage.RUST:
|
|
56
|
+
from .rust.extractor import RustExtractor
|
|
57
|
+
|
|
58
|
+
return RustExtractor()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def clear_extractor_cache() -> None:
|
|
62
|
+
"""Clear the extractor cache (useful for testing)."""
|
|
63
|
+
_extractors.clear()
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Go language extractor implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from shotgun.codebase.core.extractors.base import BaseExtractor
|
|
8
|
+
from shotgun.codebase.core.extractors.types import SupportedLanguage
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from tree_sitter import Node
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GoExtractor(BaseExtractor):
|
|
15
|
+
"""Extractor for Go source code."""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def language(self) -> SupportedLanguage:
|
|
19
|
+
"""The language this extractor handles."""
|
|
20
|
+
return SupportedLanguage.GO
|
|
21
|
+
|
|
22
|
+
def _class_definition_types(self) -> list[str]:
|
|
23
|
+
"""Return node types that represent type definitions.
|
|
24
|
+
|
|
25
|
+
Go doesn't have classes but has type definitions and interfaces.
|
|
26
|
+
"""
|
|
27
|
+
return ["type_declaration", "type_spec"]
|
|
28
|
+
|
|
29
|
+
def _function_definition_types(self) -> list[str]:
|
|
30
|
+
"""Return node types that represent function definitions."""
|
|
31
|
+
return ["function_declaration", "method_declaration"]
|
|
32
|
+
|
|
33
|
+
def extract_decorators(self, node: Node) -> list[str]:
|
|
34
|
+
"""Extract decorators from a function node.
|
|
35
|
+
|
|
36
|
+
Go doesn't have decorators. Returns empty list.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
node: The AST node
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Empty list (Go has no decorators)
|
|
43
|
+
"""
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
def extract_docstring(self, node: Node) -> str | None:
|
|
47
|
+
"""Extract godoc comment from a function or type node.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
node: The AST node
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
The godoc comment, or None if not present
|
|
54
|
+
"""
|
|
55
|
+
prev_sibling = node.prev_named_sibling
|
|
56
|
+
if prev_sibling and prev_sibling.type == "comment":
|
|
57
|
+
comment_text = prev_sibling.text
|
|
58
|
+
if comment_text:
|
|
59
|
+
text = comment_text.decode("utf-8")
|
|
60
|
+
if text.startswith("//"):
|
|
61
|
+
return text[2:].strip()
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
def extract_inheritance(self, class_node: Node) -> list[str]:
|
|
65
|
+
"""Extract embedded types from a struct or interface.
|
|
66
|
+
|
|
67
|
+
Go uses composition instead of inheritance.
|
|
68
|
+
This extracts embedded type names from struct/interface definitions.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
class_node: The type definition AST node
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
List of embedded type names
|
|
75
|
+
"""
|
|
76
|
+
embedded: list[str] = []
|
|
77
|
+
|
|
78
|
+
for child in class_node.children:
|
|
79
|
+
if child.type == "struct_type":
|
|
80
|
+
field_list = child.child_by_field_name("fields")
|
|
81
|
+
if field_list:
|
|
82
|
+
for field in field_list.children:
|
|
83
|
+
if field.type == "field_declaration":
|
|
84
|
+
if len(field.named_children) == 1:
|
|
85
|
+
type_node = field.named_children[0]
|
|
86
|
+
if (
|
|
87
|
+
type_node.type == "type_identifier"
|
|
88
|
+
and type_node.text
|
|
89
|
+
):
|
|
90
|
+
embedded.append(type_node.text.decode("utf-8"))
|
|
91
|
+
elif child.type == "interface_type":
|
|
92
|
+
for iface_child in child.children:
|
|
93
|
+
if iface_child.type == "type_identifier" and iface_child.text:
|
|
94
|
+
embedded.append(iface_child.text.decode("utf-8"))
|
|
95
|
+
|
|
96
|
+
return embedded
|
|
97
|
+
|
|
98
|
+
def parse_call_node(self, call_node: Node) -> tuple[str | None, str | None]:
|
|
99
|
+
"""Parse a call expression node to extract callee information.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
call_node: The call expression AST node
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Tuple of (callee_name, object_name)
|
|
106
|
+
"""
|
|
107
|
+
callee_name = None
|
|
108
|
+
object_name = None
|
|
109
|
+
|
|
110
|
+
func_node = call_node.child_by_field_name("function")
|
|
111
|
+
if func_node:
|
|
112
|
+
if func_node.type == "identifier" and func_node.text:
|
|
113
|
+
callee_name = func_node.text.decode("utf-8")
|
|
114
|
+
elif func_node.type == "selector_expression":
|
|
115
|
+
operand = func_node.child_by_field_name("operand")
|
|
116
|
+
field = func_node.child_by_field_name("field")
|
|
117
|
+
if operand and operand.text:
|
|
118
|
+
object_name = operand.text.decode("utf-8")
|
|
119
|
+
if field and field.text:
|
|
120
|
+
callee_name = field.text.decode("utf-8")
|
|
121
|
+
|
|
122
|
+
return callee_name, object_name
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""JavaScript language extractor implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from shotgun.codebase.core.extractors.base import BaseExtractor
|
|
8
|
+
from shotgun.codebase.core.extractors.types import SupportedLanguage
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from tree_sitter import Node
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JavaScriptExtractor(BaseExtractor):
|
|
15
|
+
"""Extractor for JavaScript source code."""
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def language(self) -> SupportedLanguage:
|
|
19
|
+
"""The language this extractor handles."""
|
|
20
|
+
return SupportedLanguage.JAVASCRIPT
|
|
21
|
+
|
|
22
|
+
def _class_definition_types(self) -> list[str]:
|
|
23
|
+
"""Return node types that represent class definitions."""
|
|
24
|
+
return ["class_declaration", "class"]
|
|
25
|
+
|
|
26
|
+
def _function_definition_types(self) -> list[str]:
|
|
27
|
+
"""Return node types that represent function definitions."""
|
|
28
|
+
return ["function_declaration", "method_definition", "arrow_function"]
|
|
29
|
+
|
|
30
|
+
def extract_decorators(self, node: Node) -> list[str]:
|
|
31
|
+
"""Extract decorators from a function or class node.
|
|
32
|
+
|
|
33
|
+
JavaScript doesn't have native decorators (they're a proposal).
|
|
34
|
+
Returns empty list.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
node: The AST node
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Empty list (JavaScript has no decorators)
|
|
41
|
+
"""
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
def extract_docstring(self, node: Node) -> str | None:
|
|
45
|
+
"""Extract JSDoc comment from a function or class node.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
node: The AST node
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
The JSDoc content, or None if not present
|
|
52
|
+
"""
|
|
53
|
+
prev_sibling = node.prev_named_sibling
|
|
54
|
+
if prev_sibling and prev_sibling.type == "comment":
|
|
55
|
+
comment_text = prev_sibling.text
|
|
56
|
+
if comment_text:
|
|
57
|
+
text = comment_text.decode("utf-8")
|
|
58
|
+
if text.startswith("/**"):
|
|
59
|
+
text = text[3:]
|
|
60
|
+
if text.endswith("*/"):
|
|
61
|
+
text = text[:-2]
|
|
62
|
+
return text.strip()
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def extract_inheritance(self, class_node: Node) -> list[str]:
|
|
66
|
+
"""Extract parent class names from a class definition.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
class_node: The class definition AST node
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
List of parent class names
|
|
73
|
+
"""
|
|
74
|
+
parent_names: list[str] = []
|
|
75
|
+
|
|
76
|
+
heritage = class_node.child_by_field_name("heritage")
|
|
77
|
+
if heritage:
|
|
78
|
+
for child in heritage.children:
|
|
79
|
+
if child.type == "identifier" and child.text:
|
|
80
|
+
parent_names.append(child.text.decode("utf-8"))
|
|
81
|
+
elif child.type == "member_expression":
|
|
82
|
+
parts: list[str] = []
|
|
83
|
+
self._extract_member_expression(child, parts)
|
|
84
|
+
if parts:
|
|
85
|
+
parent_names.append(".".join(parts))
|
|
86
|
+
|
|
87
|
+
return parent_names
|
|
88
|
+
|
|
89
|
+
def parse_call_node(self, call_node: Node) -> tuple[str | None, str | None]:
|
|
90
|
+
"""Parse a call expression node to extract callee information.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
call_node: The call expression AST node
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Tuple of (callee_name, object_name)
|
|
97
|
+
"""
|
|
98
|
+
callee_name = None
|
|
99
|
+
object_name = None
|
|
100
|
+
|
|
101
|
+
for child in call_node.children:
|
|
102
|
+
if child.type == "identifier" and child.text:
|
|
103
|
+
callee_name = child.text.decode("utf-8")
|
|
104
|
+
break
|
|
105
|
+
elif child.type == "member_expression":
|
|
106
|
+
obj_node = child.child_by_field_name("object")
|
|
107
|
+
prop_node = child.child_by_field_name("property")
|
|
108
|
+
if obj_node and obj_node.text:
|
|
109
|
+
object_name = obj_node.text.decode("utf-8")
|
|
110
|
+
if prop_node and prop_node.text:
|
|
111
|
+
callee_name = prop_node.text.decode("utf-8")
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
return callee_name, object_name
|
|
115
|
+
|
|
116
|
+
def _extract_member_expression(self, node: Node, parts: list[str]) -> None:
|
|
117
|
+
"""Recursively extract full name from member expression.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
node: The AST node
|
|
121
|
+
parts: List to accumulate name parts (modified in place)
|
|
122
|
+
"""
|
|
123
|
+
if node.type == "identifier" and node.text:
|
|
124
|
+
parts.insert(0, node.text.decode("utf-8"))
|
|
125
|
+
elif node.type == "member_expression":
|
|
126
|
+
prop_node = node.child_by_field_name("property")
|
|
127
|
+
if prop_node and prop_node.text:
|
|
128
|
+
parts.insert(0, prop_node.text.decode("utf-8"))
|
|
129
|
+
|
|
130
|
+
obj_node = node.child_by_field_name("object")
|
|
131
|
+
if obj_node:
|
|
132
|
+
self._extract_member_expression(obj_node, parts)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Protocol definition for language extractors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Protocol, runtime_checkable
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from tree_sitter import Node
|
|
9
|
+
|
|
10
|
+
from .types import SupportedLanguage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@runtime_checkable
|
|
14
|
+
class LanguageExtractor(Protocol):
|
|
15
|
+
"""Protocol for language-specific AST extraction.
|
|
16
|
+
|
|
17
|
+
Each language implementation provides methods to extract:
|
|
18
|
+
- Decorators/attributes from function/class nodes
|
|
19
|
+
- Docstrings/documentation
|
|
20
|
+
- Inheritance information from class definitions
|
|
21
|
+
- Call expression parsing
|
|
22
|
+
- Node type information for the language
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def language(self) -> SupportedLanguage:
|
|
27
|
+
"""The language this extractor handles."""
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
def extract_decorators(self, node: Node) -> list[str]:
|
|
31
|
+
"""Extract decorators/attributes from a function or class node.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
node: The AST node (function or class definition)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
List of decorator/attribute names
|
|
38
|
+
"""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
def extract_docstring(self, node: Node) -> str | None:
|
|
42
|
+
"""Extract documentation string from a function or class node.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
node: The AST node (function or class definition)
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The docstring content, or None if not present
|
|
49
|
+
"""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
def extract_inheritance(self, class_node: Node) -> list[str]:
|
|
53
|
+
"""Extract parent class names from a class definition.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
class_node: The class definition AST node
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
List of parent class names (simple names, may need resolution)
|
|
60
|
+
"""
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
def parse_call_node(self, call_node: Node) -> tuple[str | None, str | None]:
|
|
64
|
+
"""Parse a call expression node to extract callee information.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
call_node: The call expression AST node
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Tuple of (callee_name, object_name) where:
|
|
71
|
+
- callee_name: The name of the function/method being called
|
|
72
|
+
- object_name: The object the method is called on (for method calls)
|
|
73
|
+
"""
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
def find_parent_class(self, func_node: Node, module_qn: str) -> str | None:
|
|
77
|
+
"""Find the parent class of a function node.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
func_node: The function definition AST node
|
|
81
|
+
module_qn: Module qualified name
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Qualified name of parent class, or None if not in a class
|
|
85
|
+
"""
|
|
86
|
+
...
|
|
87
|
+
|
|
88
|
+
def find_containing_function(self, node: Node, module_qn: str) -> str | None:
|
|
89
|
+
"""Find the containing function/method of a node.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
node: The AST node
|
|
93
|
+
module_qn: Module qualified name
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Qualified name of containing function, or None
|
|
97
|
+
"""
|
|
98
|
+
...
|
|
99
|
+
|
|
100
|
+
def count_ast_nodes(self, node: Node) -> int:
|
|
101
|
+
"""Count total AST nodes for metrics.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
node: Root AST node
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Total node count
|
|
108
|
+
"""
|
|
109
|
+
...
|