victor-coding 0.14.0__tar.gz → 0.15.0__tar.gz

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.
Files changed (141) hide show
  1. {victor_coding-0.14.0 → victor_coding-0.15.0}/PKG-INFO +1 -1
  2. {victor_coding-0.14.0 → victor_coding-0.15.0}/pyproject.toml +1 -1
  3. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/base.py +92 -1
  4. victor_coding-0.15.0/victor_coding/languages/plugins/_ecmascript.py +208 -0
  5. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/plugins/additional.py +24 -117
  6. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/plugins/cpp.py +22 -92
  7. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/plugins/go.py +14 -39
  8. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/plugins/java.py +0 -16
  9. victor_coding-0.15.0/victor_coding/languages/plugins/javascript.py +163 -0
  10. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/plugins/python.py +7 -50
  11. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/plugins/rust.py +26 -57
  12. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/plugins/typescript.py +10 -160
  13. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding.egg-info/PKG-INFO +1 -1
  14. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding.egg-info/SOURCES.txt +1 -0
  15. victor_coding-0.14.0/victor_coding/languages/plugins/javascript.py +0 -324
  16. {victor_coding-0.14.0 → victor_coding-0.15.0}/LICENSE +0 -0
  17. {victor_coding-0.14.0 → victor_coding-0.15.0}/README.md +0 -0
  18. {victor_coding-0.14.0 → victor_coding-0.15.0}/setup.cfg +0 -0
  19. {victor_coding-0.14.0 → victor_coding-0.15.0}/tests/test_api_router_provider.py +0 -0
  20. {victor_coding-0.14.0 → victor_coding-0.15.0}/tests/test_coding_capabilities.py +0 -0
  21. {victor_coding-0.14.0 → victor_coding-0.15.0}/tests/test_coding_handlers.py +0 -0
  22. {victor_coding-0.14.0 → victor_coding-0.15.0}/tests/test_coding_vertical_rl.py +0 -0
  23. {victor_coding-0.14.0 → victor_coding-0.15.0}/tests/test_coding_vertical_teams.py +0 -0
  24. {victor_coding-0.14.0 → victor_coding-0.15.0}/tests/test_coding_vertical_workflows.py +0 -0
  25. {victor_coding-0.14.0 → victor_coding-0.15.0}/tests/test_enhanced_integration.py +0 -0
  26. {victor_coding-0.14.0 → victor_coding-0.15.0}/tests/test_extension_cache.py +0 -0
  27. {victor_coding-0.14.0 → victor_coding-0.15.0}/tests/test_framework_lsp_adapter.py +0 -0
  28. {victor_coding-0.14.0 → victor_coding-0.15.0}/tests/test_graph_backend_marker.py +0 -0
  29. {victor_coding-0.14.0 → victor_coding-0.15.0}/tests/test_personas.py +0 -0
  30. {victor_coding-0.14.0 → victor_coding-0.15.0}/tests/test_protocol_providers.py +0 -0
  31. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/__init__.py +0 -0
  32. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/api/__init__.py +0 -0
  33. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/api/router_provider.py +0 -0
  34. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/assistant.py +0 -0
  35. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/capabilities.py +0 -0
  36. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/__init__.py +0 -0
  37. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/architecture_analyzer.py +0 -0
  38. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/ccg_builder.py +0 -0
  39. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/chunker.py +0 -0
  40. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/embeddings/__init__.py +0 -0
  41. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/embeddings/chunker.py +0 -0
  42. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/graph/__init__.py +0 -0
  43. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/graph/registry.py +0 -0
  44. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/ignore_patterns.py +0 -0
  45. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/indexer.py +0 -0
  46. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/query_expander.py +0 -0
  47. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/symbol_extractor.py +0 -0
  48. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/symbol_resolver.py +0 -0
  49. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/symbol_store.py +0 -0
  50. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/tree_sitter_analysis.py +0 -0
  51. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/tree_sitter_extractor.py +0 -0
  52. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/tree_sitter_manager.py +0 -0
  53. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/tree_sitter_service.py +0 -0
  54. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase/unified_extractor.py +0 -0
  55. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/codebase_analyzer.py +0 -0
  56. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/compat/__init__.py +0 -0
  57. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/compat/settings.py +0 -0
  58. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/composed_chains.py +0 -0
  59. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/conversation_enhanced.py +0 -0
  60. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/coverage/__init__.py +0 -0
  61. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/coverage/manager.py +0 -0
  62. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/coverage/parser.py +0 -0
  63. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/coverage/parsers/__init__.py +0 -0
  64. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/coverage/protocol.py +0 -0
  65. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/coverage/visualizer.py +0 -0
  66. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/docgen/__init__.py +0 -0
  67. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/docgen/formatter.py +0 -0
  68. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/docgen/manager.py +0 -0
  69. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/docgen/parser.py +0 -0
  70. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/docgen/protocol.py +0 -0
  71. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/editing/__init__.py +0 -0
  72. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/enrichment.py +0 -0
  73. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/escape_hatches.py +0 -0
  74. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/handlers.py +0 -0
  75. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/__init__.py +0 -0
  76. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/manager.py +0 -0
  77. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/plugins/__init__.py +0 -0
  78. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/plugins/config.py +0 -0
  79. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/registry.py +0 -0
  80. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/languages/tiers.py +0 -0
  81. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/lsp/__init__.py +0 -0
  82. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/lsp/client.py +0 -0
  83. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/lsp/config.py +0 -0
  84. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/lsp/framework_adapter.py +0 -0
  85. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/lsp/manager.py +0 -0
  86. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/middleware.py +0 -0
  87. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/mode_config.py +0 -0
  88. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/native/__init__.py +0 -0
  89. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/performance/__init__.py +0 -0
  90. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/performance/factory.py +0 -0
  91. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/performance/protocols.py +0 -0
  92. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/performance/registry.py +0 -0
  93. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/performance/service_provider.py +0 -0
  94. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/performance/settings.py +0 -0
  95. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/performance/wrappers.py +0 -0
  96. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/plugin.py +0 -0
  97. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/prompts.py +0 -0
  98. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/protocols.py +0 -0
  99. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/refactor/__init__.py +0 -0
  100. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/refactor/analyzer.py +0 -0
  101. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/refactor/manager.py +0 -0
  102. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/refactor/protocol.py +0 -0
  103. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/refactor/transforms/__init__.py +0 -0
  104. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/refactor/transforms/base.py +0 -0
  105. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/refactor/transforms/extract.py +0 -0
  106. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/refactor/transforms/rename.py +0 -0
  107. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/review/__init__.py +0 -0
  108. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/review/analyzers.py +0 -0
  109. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/review/manager.py +0 -0
  110. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/review/protocol.py +0 -0
  111. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/review/rules.py +0 -0
  112. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/rl/__init__.py +0 -0
  113. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/rl/config.py +0 -0
  114. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/rl/hooks.py +0 -0
  115. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/safety.py +0 -0
  116. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/safety_enhanced.py +0 -0
  117. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/service_provider.py +0 -0
  118. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/skills.py +0 -0
  119. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/teams/__init__.py +0 -0
  120. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/teams/personas.py +0 -0
  121. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/teams/specs.py +0 -0
  122. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/testgen/__init__.py +0 -0
  123. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/testgen/analyzer.py +0 -0
  124. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/testgen/generator.py +0 -0
  125. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/testgen/manager.py +0 -0
  126. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/testgen/protocol.py +0 -0
  127. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/tool_dependencies.py +0 -0
  128. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/tools/__init__.py +0 -0
  129. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/tools/code_executor_tool.py +0 -0
  130. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/tools/code_intelligence_tool.py +0 -0
  131. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/tools/code_search_tool.py +0 -0
  132. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/tools/graph_tool.py +0 -0
  133. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/workflows/__init__.py +0 -0
  134. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/workflows/bugfix.py +0 -0
  135. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/workflows/feature.py +0 -0
  136. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/workflows/provider.py +0 -0
  137. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding/workflows/review.py +0 -0
  138. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding.egg-info/dependency_links.txt +0 -0
  139. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding.egg-info/entry_points.txt +0 -0
  140. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding.egg-info/requires.txt +0 -0
  141. {victor_coding-0.14.0 → victor_coding-0.15.0}/victor_coding.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: victor-coding
3
- Version: 0.14.0
3
+ Version: 0.15.0
4
4
  Summary: Coding vertical for Victor AI - Software development, debugging, and code generation
5
5
  Author-email: Vijaykumar Singh <vijay@anvaiops.com>
6
6
  Maintainer-email: Vijaykumar Singh <vijay@anvaiops.com>
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "victor-coding"
10
- version = "0.14.0"
10
+ version = "0.15.0"
11
11
  description = "Coding vertical for Victor AI - Software development, debugging, and code generation"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.12"
@@ -25,12 +25,16 @@ from abc import ABC, abstractmethod
25
25
  from dataclasses import dataclass, field
26
26
  from enum import Enum
27
27
  from pathlib import Path
28
+ from types import MappingProxyType
28
29
  from typing import (
29
30
  TYPE_CHECKING,
31
+ AbstractSet,
30
32
  Any,
33
+ Callable,
31
34
  Dict,
32
35
  Iterable,
33
36
  List,
37
+ Mapping,
34
38
  Optional,
35
39
  Protocol,
36
40
  Tuple,
@@ -42,6 +46,67 @@ if TYPE_CHECKING:
42
46
 
43
47
  logger = logging.getLogger(__name__)
44
48
 
49
+ #: ``(leaf_types, nested_types)`` arguments for :func:`last_named_child`.
50
+ PathNameTypes = Tuple[AbstractSet[str], AbstractSet[str]]
51
+
52
+
53
+ def last_named_child(
54
+ node: "Node",
55
+ leaf_types: AbstractSet[str],
56
+ nested_types: AbstractSet[str],
57
+ get_text: Callable[["Node"], Optional[str]],
58
+ ) -> Optional[str]:
59
+ """Name at the right end of a dotted / scoped path node.
60
+
61
+ Scans ``node``'s children right to left. The first ``leaf_types`` child
62
+ wins and its text is returned; a ``nested_types`` child (the same path
63
+ shape one level down) is searched recursively and its result returned
64
+ when non-empty. For ``obj.a.method`` / ``std::mem::drop`` this yields
65
+ ``method`` / ``drop``.
66
+
67
+ Args:
68
+ node: The path node (attribute, field/selector expression, ...).
69
+ leaf_types: Child node types holding a name segment.
70
+ nested_types: Child node types to recurse into; empty disables recursion.
71
+ get_text: Node-text accessor (typically ``BaseLanguagePlugin._get_node_text``).
72
+ """
73
+ for child in reversed(node.children):
74
+ if child.type in leaf_types:
75
+ return get_text(child)
76
+ if child.type in nested_types:
77
+ result = last_named_child(child, leaf_types, nested_types, get_text)
78
+ if result:
79
+ return result
80
+ return None
81
+
82
+
83
+ def declarator_name(
84
+ node: "Node",
85
+ name_types: AbstractSet[str],
86
+ get_text: Callable[["Node"], Optional[str]],
87
+ ) -> Optional[str]:
88
+ """Function name of a C-family ``function_definition`` node.
89
+
90
+ Looks in the ``function_declarator`` child first (also one level inside a
91
+ child typed ``declarator``), then falls back to a direct child of the
92
+ definition. ``name_types`` are the node types accepted as the name.
93
+ """
94
+ for child in node.children:
95
+ if child.type == "function_declarator":
96
+ for grandchild in child.children:
97
+ if grandchild.type == "declarator":
98
+ for ggchild in grandchild.children:
99
+ if ggchild.type in name_types:
100
+ return get_text(ggchild)
101
+ elif grandchild.type in name_types:
102
+ return get_text(grandchild)
103
+
104
+ for child in node.children:
105
+ if child.type in name_types:
106
+ return get_text(child)
107
+
108
+ return None
109
+
45
110
 
46
111
  # ---------------------------------------------------------------------------
47
112
  # Tree-sitter Query Types
@@ -665,8 +730,34 @@ class BaseLanguagePlugin(ABC):
665
730
  """
666
731
  return ()
667
732
 
733
+ #: Child node types whose own text is the callee name (``foo()``).
734
+ _callee_name_types: AbstractSet[str] = frozenset()
735
+ #: Child node type -> ``(leaf_types, nested_types)``: the callee is the
736
+ #: :func:`last_named_child` of that child (``obj.method()``, ``a::b()``).
737
+ _callee_path_types: Mapping[str, PathNameTypes] = MappingProxyType({})
738
+ #: Child node types wrapping another callee expression (``(fn)()``):
739
+ #: :meth:`_extract_callee_name` is re-entered on that child.
740
+ _callee_wrapper_types: AbstractSet[str] = frozenset()
741
+
668
742
  def _extract_callee_name(self, call_node: "Node") -> Optional[str]:
669
- """Return the called symbol's name for ``call_node``, or ``None``."""
743
+ """Return the called symbol's name for ``call_node``, or ``None``.
744
+
745
+ Default: the first child of ``call_node`` whose type appears in
746
+ :attr:`_callee_name_types`, :attr:`_callee_path_types` or
747
+ :attr:`_callee_wrapper_types` decides the result (which may be
748
+ ``None``); later children are not consulted. With the empty default
749
+ tables no callee is ever found.
750
+ """
751
+ for child in call_node.children:
752
+ child_type = child.type
753
+ if child_type in self._callee_name_types:
754
+ return self._get_node_text(child)
755
+ path_types = self._callee_path_types.get(child_type)
756
+ if path_types is not None:
757
+ leaf_types, nested_types = path_types
758
+ return last_named_child(child, leaf_types, nested_types, self._get_node_text)
759
+ if child_type in self._callee_wrapper_types:
760
+ return self._extract_callee_name(child)
670
761
  return None
671
762
 
672
763
 
@@ -0,0 +1,208 @@
1
+ # Copyright 2025 Vijaykumar Singh <vijay@anvaiops.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Shared base for the ECMAScript-family (JavaScript/TypeScript) plugins.
16
+
17
+ Holds the logic both plugins share verbatim -- capabilities, package.json test
18
+ runner detection, prettier formatting, call-node traversal and callee/member
19
+ name extraction. The genuine differences are expressed as class-attribute
20
+ hooks that ``JavaScriptPlugin`` and ``TypeScriptPlugin`` override.
21
+ """
22
+
23
+ from pathlib import Path
24
+ from typing import ClassVar, List, Optional, Tuple, TYPE_CHECKING
25
+
26
+ from victor_coding.languages.base import (
27
+ BaseLanguagePlugin,
28
+ ConfigurableASTTraverser,
29
+ Formatter,
30
+ LanguageCapabilities,
31
+ TestRunner,
32
+ TraversalConfig,
33
+ )
34
+
35
+ if TYPE_CHECKING:
36
+ from tree_sitter import Node
37
+
38
+
39
+ class EcmaScriptPluginBase(BaseLanguagePlugin):
40
+ """Common behaviour of the JavaScript and TypeScript plugins.
41
+
42
+ Subclasses provide ``_create_config``/``_create_tree_sitter_queries`` and
43
+ may override the class-attribute hooks below.
44
+ """
45
+
46
+ # --- Hooks --------------------------------------------------------------
47
+ _supports_type_checking: ClassVar[bool] = False
48
+ _test_file_pattern: ClassVar[str] = "*.test.js"
49
+ _vitest_parallel_args: ClassVar[Tuple[str, ...]] = ()
50
+ _jest_parallel_args: ClassVar[Tuple[str, ...]] = ()
51
+ _detects_mocha: ClassVar[bool] = False
52
+ # Node types whose name is the first property_identifier/identifier child.
53
+ _method_node_types: ClassVar[Tuple[str, ...]] = ("method_definition",)
54
+ _extra_function_types: ClassVar[Tuple[str, ...]] = ()
55
+ _class_types: ClassVar[Tuple[str, ...]] = ("class_declaration",)
56
+ _scope_body_types: ClassVar[Tuple[str, ...]] = ("class_body",)
57
+
58
+ def _create_capabilities(self) -> LanguageCapabilities:
59
+ return LanguageCapabilities(
60
+ supports_syntax_analysis=True,
61
+ supports_semantic_analysis=True,
62
+ supports_type_checking=self._supports_type_checking,
63
+ supports_rename=True,
64
+ supports_extract_function=True,
65
+ supports_inline=True,
66
+ supports_organize_imports=True,
67
+ supports_test_discovery=True,
68
+ supports_test_execution=True,
69
+ supports_coverage=True,
70
+ supports_debugging=True,
71
+ supports_breakpoints=True,
72
+ supports_step_debugging=True,
73
+ supports_formatting=True,
74
+ supports_linting=True,
75
+ supports_completion=True,
76
+ supports_control_flow_graph=True,
77
+ )
78
+
79
+ def get_test_runner(self, project_root: Path) -> Optional[TestRunner]:
80
+ """Detect vitest, jest (or mocha, when enabled) from package.json; default jest."""
81
+ package_json = project_root / "package.json"
82
+ pattern = self._test_file_pattern
83
+
84
+ if package_json.exists():
85
+ content = package_json.read_text()
86
+
87
+ if '"vitest"' in content:
88
+ return TestRunner(
89
+ name="vitest",
90
+ command=["npx", "vitest"],
91
+ file_pattern=pattern,
92
+ run_args=["run"],
93
+ coverage_args=["--coverage"],
94
+ parallel_args=list(self._vitest_parallel_args),
95
+ output_format="json",
96
+ json_args=["--reporter=json"],
97
+ )
98
+
99
+ if '"jest"' in content:
100
+ return TestRunner(
101
+ name="jest",
102
+ command=["npx", "jest"],
103
+ file_pattern=pattern,
104
+ coverage_args=["--coverage"],
105
+ parallel_args=list(self._jest_parallel_args),
106
+ output_format="json",
107
+ json_args=["--json"],
108
+ )
109
+
110
+ if self._detects_mocha and '"mocha"' in content:
111
+ return TestRunner(
112
+ name="mocha",
113
+ command=["npx", "mocha"],
114
+ file_pattern=pattern,
115
+ run_args=["--recursive"],
116
+ )
117
+
118
+ return TestRunner(
119
+ name="jest",
120
+ command=["npx", "jest"],
121
+ file_pattern=pattern,
122
+ )
123
+
124
+ def get_formatter(self, project_root: Path) -> Optional[Formatter]:
125
+ """Get prettier formatter."""
126
+ return Formatter(
127
+ name="prettier",
128
+ command=["npx", "prettier", "--write", "."],
129
+ check_args=["--check"],
130
+ config_file=".prettierrc",
131
+ )
132
+
133
+ def _find_call_nodes(
134
+ self,
135
+ root: "Node",
136
+ ) -> List[tuple["Node", str, Optional[int]]]:
137
+ """Find all call nodes with their enclosing function context."""
138
+ config = TraversalConfig(
139
+ function_types=[
140
+ "function_declaration",
141
+ "function_expression",
142
+ "method_definition",
143
+ "arrow_function",
144
+ *self._extra_function_types,
145
+ ],
146
+ class_types=list(self._class_types),
147
+ call_types=["call_expression", "new_expression"],
148
+ name_field="identifier",
149
+ scope_body_types=list(self._scope_body_types),
150
+ )
151
+ traverser = ConfigurableASTTraverser(config, self._get_node_text)
152
+ return traverser.find_call_nodes(root)
153
+
154
+ def _extract_function_name(self, node: "Node") -> Optional[str]:
155
+ """Extract function name from a function node."""
156
+ if node.type == "function_declaration":
157
+ for child in node.children:
158
+ if child.type == "identifier":
159
+ return self._get_node_text(child)
160
+
161
+ if node.type in self._method_node_types:
162
+ for child in node.children:
163
+ if child.type in ("property_identifier", "identifier"):
164
+ return self._get_node_text(child)
165
+
166
+ if node.type == "function_expression":
167
+ found_function = False
168
+ for child in node.children:
169
+ if child.type == "identifier":
170
+ if found_function:
171
+ return self._get_node_text(child)
172
+ elif child.type == "function":
173
+ found_function = True
174
+
175
+ return None
176
+
177
+ def _extract_callee_name(self, call_node: "Node") -> Optional[str]:
178
+ """Extract the name of the called function from a call node."""
179
+ for child in call_node.children:
180
+ if child.type == "member_expression":
181
+ return self._extract_member_name(child)
182
+ elif child.type == "identifier":
183
+ return self._get_node_text(child)
184
+ elif child.type == "subscript_expression":
185
+ return self._extract_subscript_name(child)
186
+
187
+ return None
188
+
189
+ def _extract_member_name(self, member_node: "Node") -> Optional[str]:
190
+ """Extract member name from a member_expression node."""
191
+ for child in reversed(member_node.children):
192
+ if child.type == "property_identifier":
193
+ return self._get_node_text(child)
194
+ elif child.type == "member_expression":
195
+ result = self._extract_member_name(child)
196
+ if result:
197
+ return result
198
+
199
+ return None
200
+
201
+ def _extract_subscript_name(self, subscript_node: "Node") -> Optional[str]:
202
+ """Extract method name from subscript expression."""
203
+ for child in subscript_node.children:
204
+ if child.type == "string":
205
+ text = self._get_node_text(child)
206
+ if text:
207
+ return text.strip("\"'")
208
+ return None
@@ -32,6 +32,7 @@ These plugins support embedding-based indexing for:
32
32
  - Markdown/reStructuredText
33
33
  """
34
34
 
35
+ from types import MappingProxyType
35
36
  from typing import Any, List, Optional, TYPE_CHECKING
36
37
 
37
38
  from victor_coding.languages.base import (
@@ -41,6 +42,7 @@ from victor_coding.languages.base import (
41
42
  LanguageConfig,
42
43
  QueryPattern,
43
44
  TreeSitterQueries,
45
+ declarator_name,
44
46
  )
45
47
 
46
48
  if TYPE_CHECKING:
@@ -50,6 +52,14 @@ if TYPE_CHECKING:
50
52
  class CPlugin(BaseLanguagePlugin):
51
53
  """C language plugin."""
52
54
 
55
+ _function_name_types = frozenset({"identifier"})
56
+ # Callee hooks: foo() / ptr->method() (field_identifier of the outermost
57
+ # field_expression only; C does not recurse into nested field_expressions).
58
+ _callee_name_types = frozenset({"identifier"})
59
+ _callee_path_types = MappingProxyType(
60
+ {"field_expression": (frozenset({"field_identifier"}), frozenset())}
61
+ )
62
+
53
63
  def _create_config(self) -> LanguageConfig:
54
64
  return LanguageConfig(
55
65
  name="c",
@@ -176,80 +186,25 @@ class CPlugin(BaseLanguagePlugin):
176
186
  return results
177
187
 
178
188
  def _extract_function_name_c(self, node: "Node") -> Optional[str]:
179
- """Extract function name from a function definition node.
180
-
181
- Args:
182
- node: Function definition node
183
-
184
- Returns:
185
- Function name or None
186
- """
187
- # Look for function_declarator
188
- for child in node.children:
189
- if child.type == "function_declarator":
190
- # Get the declarator (identifier)
191
- for grandchild in child.children:
192
- if grandchild.type == "declarator":
193
- for ggchild in grandchild.children:
194
- if ggchild.type == "identifier":
195
- return self._get_node_text(ggchild)
196
- elif grandchild.type == "identifier":
197
- return self._get_node_text(grandchild)
198
-
199
- # Fallback: look for identifier directly
200
- for child in node.children:
201
- if child.type == "identifier":
202
- return self._get_node_text(child)
203
-
204
- return None
205
-
206
- def _extract_callee_name(self, call_node: "Node") -> Optional[str]:
207
- """Extract the name of the called function from a call node.
208
-
209
- Handles:
210
- - Simple calls: foo()
211
- - Field expression calls: ptr->method()
212
-
213
- Args:
214
- call_node: Call expression node
215
-
216
- Returns:
217
- Callee name or None
218
- """
219
- # Get the function part of the call
220
- for child in call_node.children:
221
- if child.type == "field_expression":
222
- # ptr->method() -> extract "method"
223
- return self._extract_field_name_c(child)
224
- elif child.type == "identifier":
225
- # foo() -> extract "foo"
226
- return self._get_node_text(child)
227
-
228
- return None
229
-
230
- def _extract_field_name_c(self, field_node: "Node") -> Optional[str]:
231
- """Extract field name from a field_expression node.
232
-
233
- For ptr->method, extracts "method".
234
-
235
- Args:
236
- field_node: Field expression node
237
-
238
- Returns:
239
- Field name or None
240
- """
241
- # field_expression: argument . field: (field_identifier)
242
- # or for pointer access: argument -> field: (field_identifier)
243
- for child in reversed(field_node.children):
244
- if child.type == "field_identifier":
245
- return self._get_node_text(child)
246
-
247
- return None
189
+ """Extract function name from a function definition node."""
190
+ return declarator_name(node, self._function_name_types, self._get_node_text)
248
191
 
249
192
 
250
193
  class KotlinPlugin(BaseLanguagePlugin):
251
194
  """Kotlin language plugin."""
252
195
 
196
+ # Callee hooks: foo() / obj?.a.method() (last identifier of a
197
+ # navigation_expression chain).
198
+ _callee_name_types = frozenset({"identifier"})
199
+ _callee_path_types = MappingProxyType(
200
+ {
201
+ "navigation_expression": (
202
+ frozenset({"identifier"}),
203
+ frozenset({"navigation_expression"}),
204
+ )
205
+ }
206
+ )
207
+
253
208
  def _create_config(self) -> LanguageConfig:
254
209
  return LanguageConfig(
255
210
  name="kotlin",
@@ -412,54 +367,6 @@ class KotlinPlugin(BaseLanguagePlugin):
412
367
  traverse(root)
413
368
  return results
414
369
 
415
- def _extract_callee_name(self, call_node: "Node") -> Optional[str]:
416
- """Extract the name of the called function from a call node.
417
-
418
- Handles:
419
- - Simple calls: foo()
420
- - Navigation expressions: obj.method()
421
- - Safe navigation: obj?.method()
422
-
423
- Args:
424
- call_node: Call expression node
425
-
426
- Returns:
427
- Callee name or None
428
- """
429
- for child in call_node.children:
430
- if child.type == "navigation_expression":
431
- # obj.method() -> extract "method"
432
- return self._extract_navigation_name(child)
433
- elif child.type == "identifier":
434
- # foo() -> extract "foo"
435
- return self._get_node_text(child)
436
-
437
- return None
438
-
439
- def _extract_navigation_name(self, navigation_node: "Node") -> Optional[str]:
440
- """Extract method name from a navigation_expression node.
441
-
442
- For obj.method, extracts "method".
443
- Handles safe navigation: obj?.method
444
-
445
- Args:
446
- navigation_node: Navigation expression node
447
-
448
- Returns:
449
- Method name or None
450
- """
451
- # navigation_expression can be nested: obj1.obj2.method
452
- # We want the last identifier (not simple_identifier!)
453
- for child in reversed(navigation_node.children):
454
- if child.type == "identifier":
455
- return self._get_node_text(child)
456
- elif child.type == "navigation_expression":
457
- result = self._extract_navigation_name(child)
458
- if result:
459
- return result
460
-
461
- return None
462
-
463
370
 
464
371
  class CSharpPlugin(BaseLanguagePlugin):
465
372
  """C# language plugin."""
@@ -15,6 +15,7 @@
15
15
  """C++ language plugin."""
16
16
 
17
17
  from pathlib import Path
18
+ from types import MappingProxyType
18
19
  from typing import List, Optional, TYPE_CHECKING
19
20
 
20
21
  from victor_coding.languages.base import (
@@ -29,6 +30,7 @@ from victor_coding.languages.base import (
29
30
  QueryPattern,
30
31
  TestRunner,
31
32
  TreeSitterQueries,
33
+ declarator_name,
32
34
  )
33
35
 
34
36
  if TYPE_CHECKING:
@@ -45,6 +47,25 @@ class CppPlugin(BaseLanguagePlugin):
45
47
  - Building: CMake, Make
46
48
  """
47
49
 
50
+ _function_name_types = frozenset({"identifier", "field_identifier"})
51
+ # Callee hooks: foo() / obj.a.method() / ns::Klass::create() (last name
52
+ # segment of the path) / foo<T>() (template_function re-entered: its only
53
+ # name child is the template identifier).
54
+ _callee_name_types = frozenset({"identifier"})
55
+ _callee_path_types = MappingProxyType(
56
+ {
57
+ "field_expression": (
58
+ frozenset({"field_identifier"}),
59
+ frozenset({"field_expression"}),
60
+ ),
61
+ "qualified_identifier": (
62
+ frozenset({"identifier"}),
63
+ frozenset({"qualified_identifier"}),
64
+ ),
65
+ }
66
+ )
67
+ _callee_wrapper_types = frozenset({"template_function"})
68
+
48
69
  def _create_config(self) -> LanguageConfig:
49
70
  return LanguageConfig(
50
71
  name="cpp",
@@ -311,95 +332,4 @@ class CppPlugin(BaseLanguagePlugin):
311
332
 
312
333
  C++ functions can have complex declarators. This handles the common cases.
313
334
  """
314
- # Look for function_declarator
315
- for child in node.children:
316
- if child.type == "function_declarator":
317
- # Get the declarator (identifier or field_identifier)
318
- for grandchild in child.children:
319
- if grandchild.type == "declarator":
320
- for ggchild in grandchild.children:
321
- if ggchild.type in ("identifier", "field_identifier"):
322
- return self._get_node_text(ggchild)
323
- elif grandchild.type in ("identifier", "field_identifier"):
324
- return self._get_node_text(grandchild)
325
-
326
- # Fallback: look for identifier directly
327
- for child in node.children:
328
- if child.type in ("identifier", "field_identifier"):
329
- return self._get_node_text(child)
330
-
331
- return None
332
-
333
- def _extract_callee_name(self, call_node: "Node") -> Optional[str]:
334
- """Extract the name of the called function from a call node.
335
-
336
- Handles:
337
- - Simple calls: foo()
338
- - Field access calls: obj.method()
339
- - Template calls: foo<T>()
340
- - Namespace-qualified calls: std::foo()
341
- """
342
- # Get the function part of the call
343
- for child in call_node.children:
344
- if child.type == "field_expression":
345
- # obj.method() -> extract "method"
346
- return self._extract_field_name(child)
347
- elif child.type == "template_function":
348
- # foo<T>() -> extract "foo"
349
- return self._extract_template_name(child)
350
- elif child.type == "identifier":
351
- # foo() -> extract "foo"
352
- return self._get_node_text(child)
353
- elif child.type == "qualified_identifier":
354
- # std::foo -> extract "foo"
355
- return self._extract_qualified_name(child)
356
-
357
- return None
358
-
359
- def _extract_field_name(self, field_node: "Node") -> Optional[str]:
360
- """Extract field name from a field_expression node.
361
-
362
- For obj.method, extracts "method".
363
- For obj.field1.field2, extracts "field2" (the final field).
364
- """
365
- # field_expression: object (identifier or field_expression) . field: (field_identifier)
366
- for child in reversed(field_node.children):
367
- if child.type == "field_identifier":
368
- return self._get_node_text(child)
369
- elif child.type == "field_expression":
370
- result = self._extract_field_name(child)
371
- if result:
372
- return result
373
-
374
- return None
375
-
376
- def _extract_template_name(self, template_node: "Node") -> Optional[str]:
377
- """Extract the base name from a template function.
378
-
379
- For foo<T>(), extracts "foo".
380
- """
381
- for child in template_node.children:
382
- if child.type == "identifier":
383
- return self._get_node_text(child)
384
- elif child.type == "field_expression":
385
- return self._extract_field_name(child)
386
-
387
- return None
388
-
389
- def _extract_qualified_name(self, qualified_node: "Node") -> Optional[str]:
390
- """Extract the final identifier from a qualified identifier.
391
-
392
- For std::foo, extracts "foo".
393
- For ns::Class::method, extracts "method".
394
- """
395
- # qualified_identifier: identifier :: identifier (or nested)
396
- # We want the last identifier
397
- for child in reversed(qualified_node.children):
398
- if child.type == "identifier":
399
- return self._get_node_text(child)
400
- elif child.type == "qualified_identifier":
401
- result = self._extract_qualified_name(child)
402
- if result:
403
- return result
404
-
405
- return None
335
+ return declarator_name(node, self._function_name_types, self._get_node_text)