codexa 0.4.0__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.
- codexa-0.4.0.dist-info/METADATA +650 -0
- codexa-0.4.0.dist-info/RECORD +189 -0
- codexa-0.4.0.dist-info/WHEEL +5 -0
- codexa-0.4.0.dist-info/entry_points.txt +2 -0
- codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
- codexa-0.4.0.dist-info/top_level.txt +1 -0
- semantic_code_intelligence/__init__.py +5 -0
- semantic_code_intelligence/analysis/__init__.py +21 -0
- semantic_code_intelligence/analysis/ai_features.py +351 -0
- semantic_code_intelligence/bridge/__init__.py +28 -0
- semantic_code_intelligence/bridge/context_provider.py +245 -0
- semantic_code_intelligence/bridge/protocol.py +167 -0
- semantic_code_intelligence/bridge/server.py +348 -0
- semantic_code_intelligence/bridge/vscode.py +271 -0
- semantic_code_intelligence/ci/__init__.py +13 -0
- semantic_code_intelligence/ci/hooks.py +98 -0
- semantic_code_intelligence/ci/hotspots.py +272 -0
- semantic_code_intelligence/ci/impact.py +246 -0
- semantic_code_intelligence/ci/metrics.py +591 -0
- semantic_code_intelligence/ci/pr.py +412 -0
- semantic_code_intelligence/ci/quality.py +557 -0
- semantic_code_intelligence/ci/templates.py +164 -0
- semantic_code_intelligence/ci/trace.py +224 -0
- semantic_code_intelligence/cli/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
- semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
- semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
- semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
- semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
- semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
- semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
- semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
- semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
- semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
- semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
- semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
- semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
- semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
- semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
- semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
- semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
- semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
- semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
- semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
- semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
- semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
- semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
- semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
- semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
- semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
- semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
- semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
- semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
- semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
- semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
- semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
- semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
- semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
- semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
- semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
- semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
- semantic_code_intelligence/cli/main.py +65 -0
- semantic_code_intelligence/cli/router.py +92 -0
- semantic_code_intelligence/config/__init__.py +0 -0
- semantic_code_intelligence/config/settings.py +260 -0
- semantic_code_intelligence/context/__init__.py +19 -0
- semantic_code_intelligence/context/engine.py +429 -0
- semantic_code_intelligence/context/memory.py +253 -0
- semantic_code_intelligence/daemon/__init__.py +1 -0
- semantic_code_intelligence/daemon/watcher.py +515 -0
- semantic_code_intelligence/docs/__init__.py +1080 -0
- semantic_code_intelligence/embeddings/__init__.py +0 -0
- semantic_code_intelligence/embeddings/enhanced.py +131 -0
- semantic_code_intelligence/embeddings/generator.py +149 -0
- semantic_code_intelligence/embeddings/model_registry.py +100 -0
- semantic_code_intelligence/evolution/__init__.py +1 -0
- semantic_code_intelligence/evolution/budget_guard.py +111 -0
- semantic_code_intelligence/evolution/commit_manager.py +88 -0
- semantic_code_intelligence/evolution/context_builder.py +131 -0
- semantic_code_intelligence/evolution/engine.py +249 -0
- semantic_code_intelligence/evolution/patch_generator.py +229 -0
- semantic_code_intelligence/evolution/task_selector.py +214 -0
- semantic_code_intelligence/evolution/test_runner.py +111 -0
- semantic_code_intelligence/indexing/__init__.py +0 -0
- semantic_code_intelligence/indexing/chunker.py +174 -0
- semantic_code_intelligence/indexing/parallel.py +86 -0
- semantic_code_intelligence/indexing/scanner.py +146 -0
- semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
- semantic_code_intelligence/llm/__init__.py +62 -0
- semantic_code_intelligence/llm/cache.py +219 -0
- semantic_code_intelligence/llm/cached_provider.py +145 -0
- semantic_code_intelligence/llm/conversation.py +190 -0
- semantic_code_intelligence/llm/cross_refactor.py +272 -0
- semantic_code_intelligence/llm/investigation.py +274 -0
- semantic_code_intelligence/llm/mock_provider.py +77 -0
- semantic_code_intelligence/llm/ollama_provider.py +122 -0
- semantic_code_intelligence/llm/openai_provider.py +100 -0
- semantic_code_intelligence/llm/provider.py +92 -0
- semantic_code_intelligence/llm/rate_limiter.py +164 -0
- semantic_code_intelligence/llm/reasoning.py +438 -0
- semantic_code_intelligence/llm/safety.py +110 -0
- semantic_code_intelligence/llm/streaming.py +251 -0
- semantic_code_intelligence/lsp/__init__.py +609 -0
- semantic_code_intelligence/mcp/__init__.py +393 -0
- semantic_code_intelligence/parsing/__init__.py +19 -0
- semantic_code_intelligence/parsing/parser.py +375 -0
- semantic_code_intelligence/plugins/__init__.py +255 -0
- semantic_code_intelligence/plugins/examples/__init__.py +1 -0
- semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
- semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
- semantic_code_intelligence/scalability/__init__.py +205 -0
- semantic_code_intelligence/search/__init__.py +0 -0
- semantic_code_intelligence/search/formatter.py +123 -0
- semantic_code_intelligence/search/grep.py +361 -0
- semantic_code_intelligence/search/hybrid_search.py +170 -0
- semantic_code_intelligence/search/keyword_search.py +311 -0
- semantic_code_intelligence/search/section_expander.py +103 -0
- semantic_code_intelligence/services/__init__.py +0 -0
- semantic_code_intelligence/services/indexing_service.py +630 -0
- semantic_code_intelligence/services/search_service.py +269 -0
- semantic_code_intelligence/storage/__init__.py +0 -0
- semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
- semantic_code_intelligence/storage/hash_store.py +66 -0
- semantic_code_intelligence/storage/index_manifest.py +85 -0
- semantic_code_intelligence/storage/index_stats.py +138 -0
- semantic_code_intelligence/storage/query_history.py +160 -0
- semantic_code_intelligence/storage/symbol_registry.py +209 -0
- semantic_code_intelligence/storage/vector_store.py +297 -0
- semantic_code_intelligence/tests/__init__.py +0 -0
- semantic_code_intelligence/tests/test_ai_features.py +351 -0
- semantic_code_intelligence/tests/test_chunker.py +119 -0
- semantic_code_intelligence/tests/test_cli.py +188 -0
- semantic_code_intelligence/tests/test_config.py +154 -0
- semantic_code_intelligence/tests/test_context.py +381 -0
- semantic_code_intelligence/tests/test_embeddings.py +73 -0
- semantic_code_intelligence/tests/test_endtoend.py +1142 -0
- semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
- semantic_code_intelligence/tests/test_hash_store.py +79 -0
- semantic_code_intelligence/tests/test_logging.py +55 -0
- semantic_code_intelligence/tests/test_new_cli.py +138 -0
- semantic_code_intelligence/tests/test_parser.py +495 -0
- semantic_code_intelligence/tests/test_phase10.py +355 -0
- semantic_code_intelligence/tests/test_phase11.py +593 -0
- semantic_code_intelligence/tests/test_phase12.py +375 -0
- semantic_code_intelligence/tests/test_phase13.py +663 -0
- semantic_code_intelligence/tests/test_phase14.py +568 -0
- semantic_code_intelligence/tests/test_phase15.py +814 -0
- semantic_code_intelligence/tests/test_phase16.py +792 -0
- semantic_code_intelligence/tests/test_phase17.py +815 -0
- semantic_code_intelligence/tests/test_phase18.py +934 -0
- semantic_code_intelligence/tests/test_phase19.py +986 -0
- semantic_code_intelligence/tests/test_phase20.py +2753 -0
- semantic_code_intelligence/tests/test_phase20b.py +2058 -0
- semantic_code_intelligence/tests/test_phase20c.py +962 -0
- semantic_code_intelligence/tests/test_phase21.py +428 -0
- semantic_code_intelligence/tests/test_phase22.py +799 -0
- semantic_code_intelligence/tests/test_phase23.py +783 -0
- semantic_code_intelligence/tests/test_phase24.py +715 -0
- semantic_code_intelligence/tests/test_phase25.py +496 -0
- semantic_code_intelligence/tests/test_phase26.py +251 -0
- semantic_code_intelligence/tests/test_phase27.py +531 -0
- semantic_code_intelligence/tests/test_phase8.py +592 -0
- semantic_code_intelligence/tests/test_phase9.py +643 -0
- semantic_code_intelligence/tests/test_plugins.py +293 -0
- semantic_code_intelligence/tests/test_priority_features.py +727 -0
- semantic_code_intelligence/tests/test_router.py +41 -0
- semantic_code_intelligence/tests/test_scalability.py +138 -0
- semantic_code_intelligence/tests/test_scanner.py +125 -0
- semantic_code_intelligence/tests/test_search.py +160 -0
- semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
- semantic_code_intelligence/tests/test_tools.py +182 -0
- semantic_code_intelligence/tests/test_vector_store.py +151 -0
- semantic_code_intelligence/tests/test_watcher.py +211 -0
- semantic_code_intelligence/tools/__init__.py +442 -0
- semantic_code_intelligence/tools/executor.py +232 -0
- semantic_code_intelligence/tools/protocol.py +200 -0
- semantic_code_intelligence/tui/__init__.py +454 -0
- semantic_code_intelligence/utils/__init__.py +0 -0
- semantic_code_intelligence/utils/logging.py +112 -0
- semantic_code_intelligence/version.py +3 -0
- semantic_code_intelligence/web/__init__.py +11 -0
- semantic_code_intelligence/web/api.py +289 -0
- semantic_code_intelligence/web/server.py +397 -0
- semantic_code_intelligence/web/ui.py +659 -0
- semantic_code_intelligence/web/visualize.py +226 -0
- semantic_code_intelligence/workspace/__init__.py +427 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
"""Tests for Phase 11 — Multi-Language Parsing Expansion.
|
|
2
|
+
|
|
3
|
+
Covers: language detection, tree-sitter grammar loading, and symbol
|
|
4
|
+
extraction for TypeScript, TSX, C++, C#, Ruby, and PHP.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from semantic_code_intelligence.parsing.parser import (
|
|
12
|
+
EXTENSION_TO_LANGUAGE,
|
|
13
|
+
FUNCTION_NODE_TYPES,
|
|
14
|
+
CLASS_NODE_TYPES,
|
|
15
|
+
IMPORT_NODE_TYPES,
|
|
16
|
+
detect_language,
|
|
17
|
+
get_language,
|
|
18
|
+
parse_file,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# =========================================================================
|
|
23
|
+
# Language detection tests
|
|
24
|
+
# =========================================================================
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TestNewLanguageDetection:
|
|
28
|
+
"""Verify file extensions map to the correct language names."""
|
|
29
|
+
|
|
30
|
+
def test_typescript(self):
|
|
31
|
+
assert detect_language("app.ts") == "typescript"
|
|
32
|
+
|
|
33
|
+
def test_tsx(self):
|
|
34
|
+
assert detect_language("Component.tsx") == "tsx"
|
|
35
|
+
|
|
36
|
+
def test_cpp_extensions(self):
|
|
37
|
+
assert detect_language("main.cpp") == "cpp"
|
|
38
|
+
assert detect_language("util.cc") == "cpp"
|
|
39
|
+
assert detect_language("header.hpp") == "cpp"
|
|
40
|
+
assert detect_language("types.h") == "cpp"
|
|
41
|
+
|
|
42
|
+
def test_csharp(self):
|
|
43
|
+
assert detect_language("Program.cs") == "csharp"
|
|
44
|
+
|
|
45
|
+
def test_ruby(self):
|
|
46
|
+
assert detect_language("app.rb") == "ruby"
|
|
47
|
+
|
|
48
|
+
def test_php(self):
|
|
49
|
+
assert detect_language("index.php") == "php"
|
|
50
|
+
|
|
51
|
+
def test_case_insensitive(self):
|
|
52
|
+
assert detect_language("APP.TS") == "typescript"
|
|
53
|
+
assert detect_language("MAIN.CPP") == "cpp"
|
|
54
|
+
assert detect_language("PROG.CS") == "csharp"
|
|
55
|
+
assert detect_language("APP.RB") == "ruby"
|
|
56
|
+
assert detect_language("PAGE.PHP") == "php"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# =========================================================================
|
|
60
|
+
# Grammar loading tests
|
|
61
|
+
# =========================================================================
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TestGrammarLoading:
|
|
65
|
+
"""Verify tree-sitter grammars load without errors."""
|
|
66
|
+
|
|
67
|
+
@pytest.mark.parametrize("lang", [
|
|
68
|
+
"typescript", "tsx", "cpp", "csharp", "ruby", "php",
|
|
69
|
+
])
|
|
70
|
+
def test_load_language(self, lang):
|
|
71
|
+
result = get_language(lang)
|
|
72
|
+
assert result is not None, f"Failed to load grammar for {lang}"
|
|
73
|
+
|
|
74
|
+
def test_unknown_language_returns_none(self):
|
|
75
|
+
assert get_language("brainfuck") is None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# =========================================================================
|
|
79
|
+
# Node type mapping coverage
|
|
80
|
+
# =========================================================================
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class TestNodeTypeMappings:
|
|
84
|
+
"""Ensure all new languages have entries in every mapping dict."""
|
|
85
|
+
|
|
86
|
+
@pytest.mark.parametrize("lang", [
|
|
87
|
+
"typescript", "tsx", "cpp", "csharp", "ruby", "php",
|
|
88
|
+
])
|
|
89
|
+
def test_function_types(self, lang):
|
|
90
|
+
assert lang in FUNCTION_NODE_TYPES
|
|
91
|
+
assert len(FUNCTION_NODE_TYPES[lang]) > 0
|
|
92
|
+
|
|
93
|
+
@pytest.mark.parametrize("lang", [
|
|
94
|
+
"typescript", "tsx", "cpp", "csharp", "ruby", "php",
|
|
95
|
+
])
|
|
96
|
+
def test_class_types(self, lang):
|
|
97
|
+
assert lang in CLASS_NODE_TYPES
|
|
98
|
+
assert len(CLASS_NODE_TYPES[lang]) > 0
|
|
99
|
+
|
|
100
|
+
@pytest.mark.parametrize("lang", [
|
|
101
|
+
"typescript", "tsx", "cpp", "csharp", "ruby", "php",
|
|
102
|
+
])
|
|
103
|
+
def test_import_types(self, lang):
|
|
104
|
+
assert lang in IMPORT_NODE_TYPES
|
|
105
|
+
assert len(IMPORT_NODE_TYPES[lang]) > 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# =========================================================================
|
|
109
|
+
# TypeScript parsing
|
|
110
|
+
# =========================================================================
|
|
111
|
+
|
|
112
|
+
TYPESCRIPT_CODE = """\
|
|
113
|
+
import { useState } from 'react';
|
|
114
|
+
|
|
115
|
+
interface Config {
|
|
116
|
+
host: string;
|
|
117
|
+
port: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
class Server {
|
|
121
|
+
constructor(private config: Config) {}
|
|
122
|
+
|
|
123
|
+
start(): void {
|
|
124
|
+
console.log('starting');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function createServer(config: Config): Server {
|
|
129
|
+
return new Server(config);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const helper = (x: number): number => x * 2;
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class TestTypeScriptParsing:
|
|
137
|
+
@pytest.fixture(autouse=True)
|
|
138
|
+
def setup(self):
|
|
139
|
+
self.symbols = parse_file("server.ts", TYPESCRIPT_CODE)
|
|
140
|
+
self.kinds = {s.kind for s in self.symbols}
|
|
141
|
+
self.names = {s.name for s in self.symbols}
|
|
142
|
+
|
|
143
|
+
def test_detects_import(self):
|
|
144
|
+
imports = [s for s in self.symbols if s.kind == "import"]
|
|
145
|
+
assert len(imports) >= 1
|
|
146
|
+
|
|
147
|
+
def test_detects_interface(self):
|
|
148
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
149
|
+
class_names = {s.name for s in classes}
|
|
150
|
+
assert "Config" in class_names
|
|
151
|
+
|
|
152
|
+
def test_detects_class(self):
|
|
153
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
154
|
+
class_names = {s.name for s in classes}
|
|
155
|
+
assert "Server" in class_names
|
|
156
|
+
|
|
157
|
+
def test_detects_function(self):
|
|
158
|
+
fns = [s for s in self.symbols if s.kind == "function"]
|
|
159
|
+
fn_names = {s.name for s in fns}
|
|
160
|
+
assert "createServer" in fn_names
|
|
161
|
+
|
|
162
|
+
def test_detects_method(self):
|
|
163
|
+
methods = [s for s in self.symbols if s.kind == "method"]
|
|
164
|
+
method_names = {s.name for s in methods}
|
|
165
|
+
assert "start" in method_names
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# =========================================================================
|
|
169
|
+
# TSX parsing
|
|
170
|
+
# =========================================================================
|
|
171
|
+
|
|
172
|
+
TSX_CODE = """\
|
|
173
|
+
import React from 'react';
|
|
174
|
+
|
|
175
|
+
interface Props {
|
|
176
|
+
title: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
class Header extends React.Component<Props> {
|
|
180
|
+
render() {
|
|
181
|
+
return <h1>{this.props.title}</h1>;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function App(): JSX.Element {
|
|
186
|
+
return <Header title="Hello" />;
|
|
187
|
+
}
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class TestTSXParsing:
|
|
192
|
+
@pytest.fixture(autouse=True)
|
|
193
|
+
def setup(self):
|
|
194
|
+
self.symbols = parse_file("App.tsx", TSX_CODE)
|
|
195
|
+
|
|
196
|
+
def test_has_symbols(self):
|
|
197
|
+
assert len(self.symbols) > 0
|
|
198
|
+
|
|
199
|
+
def test_detects_class(self):
|
|
200
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
201
|
+
assert any(s.name == "Header" for s in classes)
|
|
202
|
+
|
|
203
|
+
def test_detects_function(self):
|
|
204
|
+
fns = [s for s in self.symbols if s.kind == "function"]
|
|
205
|
+
assert any(s.name == "App" for s in fns)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# =========================================================================
|
|
209
|
+
# C++ parsing
|
|
210
|
+
# =========================================================================
|
|
211
|
+
|
|
212
|
+
CPP_CODE = """\
|
|
213
|
+
#include <iostream>
|
|
214
|
+
#include <string>
|
|
215
|
+
|
|
216
|
+
class Animal {
|
|
217
|
+
public:
|
|
218
|
+
Animal(std::string name) : name_(name) {}
|
|
219
|
+
virtual void speak() const = 0;
|
|
220
|
+
private:
|
|
221
|
+
std::string name_;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
struct Point {
|
|
225
|
+
double x, y;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
void greet(const std::string& name) {
|
|
229
|
+
std::cout << "Hello, " << name << std::endl;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
int main() {
|
|
233
|
+
greet("world");
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class TestCppParsing:
|
|
240
|
+
@pytest.fixture(autouse=True)
|
|
241
|
+
def setup(self):
|
|
242
|
+
self.symbols = parse_file("main.cpp", CPP_CODE)
|
|
243
|
+
|
|
244
|
+
def test_has_symbols(self):
|
|
245
|
+
assert len(self.symbols) > 0
|
|
246
|
+
|
|
247
|
+
def test_detects_include(self):
|
|
248
|
+
imports = [s for s in self.symbols if s.kind == "import"]
|
|
249
|
+
assert len(imports) >= 2
|
|
250
|
+
|
|
251
|
+
def test_detects_class(self):
|
|
252
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
253
|
+
class_names = {s.name for s in classes}
|
|
254
|
+
assert "Animal" in class_names
|
|
255
|
+
|
|
256
|
+
def test_detects_struct(self):
|
|
257
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
258
|
+
class_names = {s.name for s in classes}
|
|
259
|
+
assert "Point" in class_names
|
|
260
|
+
|
|
261
|
+
def test_detects_function(self):
|
|
262
|
+
fns = [s for s in self.symbols if s.kind == "function"]
|
|
263
|
+
fn_names = {s.name for s in fns}
|
|
264
|
+
assert "greet" in fn_names
|
|
265
|
+
assert "main" in fn_names
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# =========================================================================
|
|
269
|
+
# C# parsing
|
|
270
|
+
# =========================================================================
|
|
271
|
+
|
|
272
|
+
CSHARP_CODE = """\
|
|
273
|
+
using System;
|
|
274
|
+
using System.Collections.Generic;
|
|
275
|
+
|
|
276
|
+
namespace MyApp
|
|
277
|
+
{
|
|
278
|
+
interface IGreeter
|
|
279
|
+
{
|
|
280
|
+
void Greet(string name);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
class Greeter : IGreeter
|
|
284
|
+
{
|
|
285
|
+
public Greeter() { }
|
|
286
|
+
|
|
287
|
+
public void Greet(string name)
|
|
288
|
+
{
|
|
289
|
+
Console.WriteLine($"Hello, {name}!");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
public static int Add(int a, int b)
|
|
293
|
+
{
|
|
294
|
+
return a + b;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
enum Color
|
|
299
|
+
{
|
|
300
|
+
Red,
|
|
301
|
+
Green,
|
|
302
|
+
Blue
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class TestCSharpParsing:
|
|
309
|
+
@pytest.fixture(autouse=True)
|
|
310
|
+
def setup(self):
|
|
311
|
+
self.symbols = parse_file("Program.cs", CSHARP_CODE)
|
|
312
|
+
|
|
313
|
+
def test_has_symbols(self):
|
|
314
|
+
assert len(self.symbols) > 0
|
|
315
|
+
|
|
316
|
+
def test_detects_using(self):
|
|
317
|
+
imports = [s for s in self.symbols if s.kind == "import"]
|
|
318
|
+
assert len(imports) >= 2
|
|
319
|
+
|
|
320
|
+
def test_detects_interface(self):
|
|
321
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
322
|
+
class_names = {s.name for s in classes}
|
|
323
|
+
assert "IGreeter" in class_names
|
|
324
|
+
|
|
325
|
+
def test_detects_class(self):
|
|
326
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
327
|
+
class_names = {s.name for s in classes}
|
|
328
|
+
assert "Greeter" in class_names
|
|
329
|
+
|
|
330
|
+
def test_detects_enum(self):
|
|
331
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
332
|
+
class_names = {s.name for s in classes}
|
|
333
|
+
assert "Color" in class_names
|
|
334
|
+
|
|
335
|
+
def test_detects_method(self):
|
|
336
|
+
methods = [s for s in self.symbols if s.kind == "method"]
|
|
337
|
+
method_names = {s.name for s in methods}
|
|
338
|
+
assert "Greet" in method_names
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# =========================================================================
|
|
342
|
+
# Ruby parsing
|
|
343
|
+
# =========================================================================
|
|
344
|
+
|
|
345
|
+
RUBY_CODE = """\
|
|
346
|
+
require 'json'
|
|
347
|
+
require_relative 'helpers'
|
|
348
|
+
|
|
349
|
+
module Serializable
|
|
350
|
+
def to_json
|
|
351
|
+
JSON.generate(to_h)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
class User
|
|
356
|
+
include Serializable
|
|
357
|
+
|
|
358
|
+
attr_reader :name, :age
|
|
359
|
+
|
|
360
|
+
def initialize(name, age)
|
|
361
|
+
@name = name
|
|
362
|
+
@age = age
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def greet
|
|
366
|
+
puts "Hello, #{@name}"
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def self.create(name, age)
|
|
370
|
+
new(name, age)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def to_h
|
|
374
|
+
{ name: @name, age: @age }
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def standalone_function(x)
|
|
379
|
+
x * 2
|
|
380
|
+
end
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class TestRubyParsing:
|
|
385
|
+
@pytest.fixture(autouse=True)
|
|
386
|
+
def setup(self):
|
|
387
|
+
self.symbols = parse_file("app.rb", RUBY_CODE)
|
|
388
|
+
|
|
389
|
+
def test_has_symbols(self):
|
|
390
|
+
assert len(self.symbols) > 0
|
|
391
|
+
|
|
392
|
+
def test_detects_require(self):
|
|
393
|
+
imports = [s for s in self.symbols if s.kind == "import"]
|
|
394
|
+
assert len(imports) >= 2
|
|
395
|
+
import_text = " ".join(s.body for s in imports)
|
|
396
|
+
assert "json" in import_text
|
|
397
|
+
assert "helpers" in import_text
|
|
398
|
+
|
|
399
|
+
def test_does_not_treat_include_as_import(self):
|
|
400
|
+
"""include is a method call but not require/require_relative."""
|
|
401
|
+
imports = [s for s in self.symbols if s.kind == "import"]
|
|
402
|
+
for imp in imports:
|
|
403
|
+
assert "include" not in imp.name.split()[0]
|
|
404
|
+
|
|
405
|
+
def test_detects_module(self):
|
|
406
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
407
|
+
class_names = {s.name for s in classes}
|
|
408
|
+
assert "Serializable" in class_names
|
|
409
|
+
|
|
410
|
+
def test_detects_class(self):
|
|
411
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
412
|
+
class_names = {s.name for s in classes}
|
|
413
|
+
assert "User" in class_names
|
|
414
|
+
|
|
415
|
+
def test_detects_method(self):
|
|
416
|
+
methods = [s for s in self.symbols if s.kind == "method"]
|
|
417
|
+
method_names = {s.name for s in methods}
|
|
418
|
+
assert "initialize" in method_names
|
|
419
|
+
assert "greet" in method_names
|
|
420
|
+
|
|
421
|
+
def test_detects_standalone_function(self):
|
|
422
|
+
fns = [s for s in self.symbols if s.kind == "function"]
|
|
423
|
+
fn_names = {s.name for s in fns}
|
|
424
|
+
assert "standalone_function" in fn_names
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# =========================================================================
|
|
428
|
+
# PHP parsing
|
|
429
|
+
# =========================================================================
|
|
430
|
+
|
|
431
|
+
PHP_CODE = """\
|
|
432
|
+
<?php
|
|
433
|
+
|
|
434
|
+
use App\\Models\\User;
|
|
435
|
+
|
|
436
|
+
interface Cacheable
|
|
437
|
+
{
|
|
438
|
+
public function cacheKey(): string;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
trait Timestampable
|
|
442
|
+
{
|
|
443
|
+
public function createdAt(): string
|
|
444
|
+
{
|
|
445
|
+
return $this->created_at;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
class UserService implements Cacheable
|
|
450
|
+
{
|
|
451
|
+
use Timestampable;
|
|
452
|
+
|
|
453
|
+
public function __construct(
|
|
454
|
+
private readonly string $name
|
|
455
|
+
) {}
|
|
456
|
+
|
|
457
|
+
public function cacheKey(): string
|
|
458
|
+
{
|
|
459
|
+
return "user_" . $this->name;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
public static function create(string $name): self
|
|
463
|
+
{
|
|
464
|
+
return new self($name);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function helper(int $x): int
|
|
469
|
+
{
|
|
470
|
+
return $x * 2;
|
|
471
|
+
}
|
|
472
|
+
"""
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
class TestPHPParsing:
|
|
476
|
+
@pytest.fixture(autouse=True)
|
|
477
|
+
def setup(self):
|
|
478
|
+
self.symbols = parse_file("index.php", PHP_CODE)
|
|
479
|
+
|
|
480
|
+
def test_has_symbols(self):
|
|
481
|
+
assert len(self.symbols) > 0
|
|
482
|
+
|
|
483
|
+
def test_detects_use(self):
|
|
484
|
+
imports = [s for s in self.symbols if s.kind == "import"]
|
|
485
|
+
assert len(imports) >= 1
|
|
486
|
+
|
|
487
|
+
def test_detects_interface(self):
|
|
488
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
489
|
+
class_names = {s.name for s in classes}
|
|
490
|
+
assert "Cacheable" in class_names
|
|
491
|
+
|
|
492
|
+
def test_detects_trait(self):
|
|
493
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
494
|
+
class_names = {s.name for s in classes}
|
|
495
|
+
assert "Timestampable" in class_names
|
|
496
|
+
|
|
497
|
+
def test_detects_class(self):
|
|
498
|
+
classes = [s for s in self.symbols if s.kind == "class"]
|
|
499
|
+
class_names = {s.name for s in classes}
|
|
500
|
+
assert "UserService" in class_names
|
|
501
|
+
|
|
502
|
+
def test_detects_method(self):
|
|
503
|
+
methods = [s for s in self.symbols if s.kind == "method"]
|
|
504
|
+
method_names = {s.name for s in methods}
|
|
505
|
+
assert "cacheKey" in method_names
|
|
506
|
+
|
|
507
|
+
def test_detects_function(self):
|
|
508
|
+
fns = [s for s in self.symbols if s.kind == "function"]
|
|
509
|
+
fn_names = {s.name for s in fns}
|
|
510
|
+
assert "helper" in fn_names
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
# =========================================================================
|
|
514
|
+
# Semantic chunker integration
|
|
515
|
+
# =========================================================================
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
class TestSemanticChunkerIntegration:
|
|
519
|
+
"""Verify the semantic chunker works with new languages."""
|
|
520
|
+
|
|
521
|
+
@pytest.mark.parametrize("ext,code", [
|
|
522
|
+
("server.ts", TYPESCRIPT_CODE),
|
|
523
|
+
("App.tsx", TSX_CODE),
|
|
524
|
+
("main.cpp", CPP_CODE),
|
|
525
|
+
("Program.cs", CSHARP_CODE),
|
|
526
|
+
("app.rb", RUBY_CODE),
|
|
527
|
+
("index.php", PHP_CODE),
|
|
528
|
+
])
|
|
529
|
+
def test_semantic_chunking(self, ext, code):
|
|
530
|
+
from semantic_code_intelligence.indexing.semantic_chunker import semantic_chunk_code
|
|
531
|
+
chunks = semantic_chunk_code(code, ext, chunk_size=2048)
|
|
532
|
+
assert len(chunks) > 0
|
|
533
|
+
# At least some chunks should have symbol metadata
|
|
534
|
+
symbol_chunks = [c for c in chunks if c.symbol_name]
|
|
535
|
+
assert len(symbol_chunks) > 0
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
# =========================================================================
|
|
539
|
+
# Edge cases
|
|
540
|
+
# =========================================================================
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
class TestEdgeCases:
|
|
544
|
+
def test_empty_typescript_file(self):
|
|
545
|
+
symbols = parse_file("empty.ts", "")
|
|
546
|
+
assert symbols == []
|
|
547
|
+
|
|
548
|
+
def test_empty_cpp_file(self):
|
|
549
|
+
symbols = parse_file("empty.cpp", "")
|
|
550
|
+
assert symbols == []
|
|
551
|
+
|
|
552
|
+
def test_empty_ruby_file(self):
|
|
553
|
+
symbols = parse_file("empty.rb", "")
|
|
554
|
+
assert symbols == []
|
|
555
|
+
|
|
556
|
+
def test_minimal_typescript(self):
|
|
557
|
+
symbols = parse_file("min.ts", "const x = 1;")
|
|
558
|
+
assert isinstance(symbols, list)
|
|
559
|
+
|
|
560
|
+
def test_minimal_cpp(self):
|
|
561
|
+
symbols = parse_file("min.cpp", "int main() { return 0; }")
|
|
562
|
+
fns = [s for s in symbols if s.kind == "function"]
|
|
563
|
+
assert any(s.name == "main" for s in fns)
|
|
564
|
+
|
|
565
|
+
def test_minimal_csharp(self):
|
|
566
|
+
symbols = parse_file("min.cs", "using System;")
|
|
567
|
+
imports = [s for s in symbols if s.kind == "import"]
|
|
568
|
+
assert len(imports) >= 1
|
|
569
|
+
|
|
570
|
+
def test_minimal_ruby(self):
|
|
571
|
+
symbols = parse_file("min.rb", "def hello; end")
|
|
572
|
+
fns = [s for s in symbols if s.kind == "function"]
|
|
573
|
+
assert any(s.name == "hello" for s in fns)
|
|
574
|
+
|
|
575
|
+
def test_minimal_php(self):
|
|
576
|
+
code = "<?php\nfunction test() { return 1; }\n"
|
|
577
|
+
symbols = parse_file("min.php", code)
|
|
578
|
+
fns = [s for s in symbols if s.kind == "function"]
|
|
579
|
+
assert any(s.name == "test" for s in fns)
|
|
580
|
+
|
|
581
|
+
def test_symbol_line_numbers(self):
|
|
582
|
+
"""Symbol start_line should be >= 1."""
|
|
583
|
+
for code, path in [
|
|
584
|
+
(TYPESCRIPT_CODE, "t.ts"),
|
|
585
|
+
(CPP_CODE, "t.cpp"),
|
|
586
|
+
(CSHARP_CODE, "t.cs"),
|
|
587
|
+
(RUBY_CODE, "t.rb"),
|
|
588
|
+
(PHP_CODE, "t.php"),
|
|
589
|
+
]:
|
|
590
|
+
symbols = parse_file(path, code)
|
|
591
|
+
for s in symbols:
|
|
592
|
+
assert s.start_line >= 1, f"{path}: {s.name} has start_line {s.start_line}"
|
|
593
|
+
assert s.end_line >= s.start_line
|