tree-sitter-analyzer 1.9.17.1__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.
- tree_sitter_analyzer/__init__.py +132 -0
- tree_sitter_analyzer/__main__.py +11 -0
- tree_sitter_analyzer/api.py +853 -0
- tree_sitter_analyzer/cli/__init__.py +39 -0
- tree_sitter_analyzer/cli/__main__.py +12 -0
- tree_sitter_analyzer/cli/argument_validator.py +89 -0
- tree_sitter_analyzer/cli/commands/__init__.py +26 -0
- tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
- tree_sitter_analyzer/cli/commands/base_command.py +181 -0
- tree_sitter_analyzer/cli/commands/default_command.py +18 -0
- tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
- tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
- tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
- tree_sitter_analyzer/cli/commands/query_command.py +109 -0
- tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
- tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
- tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
- tree_sitter_analyzer/cli/commands/table_command.py +414 -0
- tree_sitter_analyzer/cli/info_commands.py +124 -0
- tree_sitter_analyzer/cli_main.py +472 -0
- tree_sitter_analyzer/constants.py +85 -0
- tree_sitter_analyzer/core/__init__.py +15 -0
- tree_sitter_analyzer/core/analysis_engine.py +580 -0
- tree_sitter_analyzer/core/cache_service.py +333 -0
- tree_sitter_analyzer/core/engine.py +585 -0
- tree_sitter_analyzer/core/parser.py +293 -0
- tree_sitter_analyzer/core/query.py +605 -0
- tree_sitter_analyzer/core/query_filter.py +200 -0
- tree_sitter_analyzer/core/query_service.py +340 -0
- tree_sitter_analyzer/encoding_utils.py +530 -0
- tree_sitter_analyzer/exceptions.py +747 -0
- tree_sitter_analyzer/file_handler.py +246 -0
- tree_sitter_analyzer/formatters/__init__.py +1 -0
- tree_sitter_analyzer/formatters/base_formatter.py +201 -0
- tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
- tree_sitter_analyzer/formatters/formatter_config.py +197 -0
- tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
- tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
- tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
- tree_sitter_analyzer/formatters/go_formatter.py +368 -0
- tree_sitter_analyzer/formatters/html_formatter.py +498 -0
- tree_sitter_analyzer/formatters/java_formatter.py +423 -0
- tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
- tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
- tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
- tree_sitter_analyzer/formatters/php_formatter.py +301 -0
- tree_sitter_analyzer/formatters/python_formatter.py +830 -0
- tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
- tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
- tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
- tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
- tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
- tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
- tree_sitter_analyzer/interfaces/__init__.py +9 -0
- tree_sitter_analyzer/interfaces/cli.py +535 -0
- tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
- tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
- tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
- tree_sitter_analyzer/language_detector.py +553 -0
- tree_sitter_analyzer/language_loader.py +271 -0
- tree_sitter_analyzer/languages/__init__.py +10 -0
- tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
- tree_sitter_analyzer/languages/css_plugin.py +449 -0
- tree_sitter_analyzer/languages/go_plugin.py +836 -0
- tree_sitter_analyzer/languages/html_plugin.py +496 -0
- tree_sitter_analyzer/languages/java_plugin.py +1299 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
- tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
- tree_sitter_analyzer/languages/php_plugin.py +862 -0
- tree_sitter_analyzer/languages/python_plugin.py +1636 -0
- tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
- tree_sitter_analyzer/languages/rust_plugin.py +673 -0
- tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
- tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
- tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
- tree_sitter_analyzer/legacy_table_formatter.py +860 -0
- tree_sitter_analyzer/mcp/__init__.py +34 -0
- tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
- tree_sitter_analyzer/mcp/server.py +869 -0
- tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
- tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
- tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
- tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
- tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
- tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
- tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
- tree_sitter_analyzer/models.py +840 -0
- tree_sitter_analyzer/mypy_current_errors.txt +2 -0
- tree_sitter_analyzer/output_manager.py +255 -0
- tree_sitter_analyzer/platform_compat/__init__.py +3 -0
- tree_sitter_analyzer/platform_compat/adapter.py +324 -0
- tree_sitter_analyzer/platform_compat/compare.py +224 -0
- tree_sitter_analyzer/platform_compat/detector.py +67 -0
- tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
- tree_sitter_analyzer/platform_compat/profiles.py +217 -0
- tree_sitter_analyzer/platform_compat/record.py +55 -0
- tree_sitter_analyzer/platform_compat/recorder.py +155 -0
- tree_sitter_analyzer/platform_compat/report.py +92 -0
- tree_sitter_analyzer/plugins/__init__.py +280 -0
- tree_sitter_analyzer/plugins/base.py +647 -0
- tree_sitter_analyzer/plugins/manager.py +384 -0
- tree_sitter_analyzer/project_detector.py +328 -0
- tree_sitter_analyzer/queries/__init__.py +27 -0
- tree_sitter_analyzer/queries/csharp.py +216 -0
- tree_sitter_analyzer/queries/css.py +615 -0
- tree_sitter_analyzer/queries/go.py +275 -0
- tree_sitter_analyzer/queries/html.py +543 -0
- tree_sitter_analyzer/queries/java.py +402 -0
- tree_sitter_analyzer/queries/javascript.py +724 -0
- tree_sitter_analyzer/queries/kotlin.py +192 -0
- tree_sitter_analyzer/queries/markdown.py +258 -0
- tree_sitter_analyzer/queries/php.py +95 -0
- tree_sitter_analyzer/queries/python.py +859 -0
- tree_sitter_analyzer/queries/ruby.py +92 -0
- tree_sitter_analyzer/queries/rust.py +223 -0
- tree_sitter_analyzer/queries/sql.py +555 -0
- tree_sitter_analyzer/queries/typescript.py +871 -0
- tree_sitter_analyzer/queries/yaml.py +236 -0
- tree_sitter_analyzer/query_loader.py +272 -0
- tree_sitter_analyzer/security/__init__.py +22 -0
- tree_sitter_analyzer/security/boundary_manager.py +277 -0
- tree_sitter_analyzer/security/regex_checker.py +297 -0
- tree_sitter_analyzer/security/validator.py +599 -0
- tree_sitter_analyzer/table_formatter.py +782 -0
- tree_sitter_analyzer/utils/__init__.py +53 -0
- tree_sitter_analyzer/utils/logging.py +433 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from deepdiff import DeepDiff
|
|
5
|
+
|
|
6
|
+
from .profiles import BehaviorProfile
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class BehaviorDifference:
|
|
11
|
+
"""Represents a difference in behavior for a specific construct."""
|
|
12
|
+
|
|
13
|
+
construct_id: str
|
|
14
|
+
diff_type: (
|
|
15
|
+
str # "missing", "attribute_mismatch", "error_mismatch", "count_mismatch"
|
|
16
|
+
)
|
|
17
|
+
details: str
|
|
18
|
+
platform_a_value: Any
|
|
19
|
+
platform_b_value: Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ProfileComparison:
|
|
24
|
+
"""Result of comparing two profiles."""
|
|
25
|
+
|
|
26
|
+
platform_a: str
|
|
27
|
+
platform_b: str
|
|
28
|
+
differences: list[BehaviorDifference]
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def has_differences(self) -> bool:
|
|
32
|
+
return len(self.differences) > 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def compare_profiles(
|
|
36
|
+
profile_a: BehaviorProfile, profile_b: BehaviorProfile
|
|
37
|
+
) -> ProfileComparison:
|
|
38
|
+
"""
|
|
39
|
+
Compares two behavior profiles and identifies differences.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
profile_a: First profile.
|
|
43
|
+
profile_b: Second profile.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
ProfileComparison: The comparison result.
|
|
47
|
+
"""
|
|
48
|
+
differences = []
|
|
49
|
+
|
|
50
|
+
# Check for missing constructs
|
|
51
|
+
keys_a = set(profile_a.behaviors.keys())
|
|
52
|
+
keys_b = set(profile_b.behaviors.keys())
|
|
53
|
+
|
|
54
|
+
for key in keys_a - keys_b:
|
|
55
|
+
differences.append(
|
|
56
|
+
BehaviorDifference(
|
|
57
|
+
construct_id=key,
|
|
58
|
+
diff_type="missing",
|
|
59
|
+
details=f"Construct {key} missing in {profile_b.platform_key}",
|
|
60
|
+
platform_a_value="present",
|
|
61
|
+
platform_b_value="missing",
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
for key in keys_b - keys_a:
|
|
66
|
+
differences.append(
|
|
67
|
+
BehaviorDifference(
|
|
68
|
+
construct_id=key,
|
|
69
|
+
diff_type="missing",
|
|
70
|
+
details=f"Construct {key} missing in {profile_a.platform_key}",
|
|
71
|
+
platform_a_value="missing",
|
|
72
|
+
platform_b_value="present",
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Compare common constructs
|
|
77
|
+
for key in keys_a.intersection(keys_b):
|
|
78
|
+
beh_a = profile_a.behaviors[key]
|
|
79
|
+
beh_b = profile_b.behaviors[key]
|
|
80
|
+
|
|
81
|
+
# Compare error status
|
|
82
|
+
if beh_a.has_error != beh_b.has_error:
|
|
83
|
+
differences.append(
|
|
84
|
+
BehaviorDifference(
|
|
85
|
+
construct_id=key,
|
|
86
|
+
diff_type="error_mismatch",
|
|
87
|
+
details=f"Error status mismatch for {key}",
|
|
88
|
+
platform_a_value=beh_a.has_error,
|
|
89
|
+
platform_b_value=beh_b.has_error,
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Compare element count
|
|
94
|
+
if beh_a.element_count != beh_b.element_count:
|
|
95
|
+
differences.append(
|
|
96
|
+
BehaviorDifference(
|
|
97
|
+
construct_id=key,
|
|
98
|
+
diff_type="count_mismatch",
|
|
99
|
+
details=f"Element count mismatch for {key}",
|
|
100
|
+
platform_a_value=beh_a.element_count,
|
|
101
|
+
platform_b_value=beh_b.element_count,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Compare attributes
|
|
106
|
+
# We use DeepDiff for detailed comparison if needed, or just set comparison
|
|
107
|
+
if beh_a.attributes != beh_b.attributes:
|
|
108
|
+
# Use DeepDiff to get readable diff
|
|
109
|
+
diff = DeepDiff(beh_a.attributes, beh_b.attributes, ignore_order=True)
|
|
110
|
+
if diff:
|
|
111
|
+
differences.append(
|
|
112
|
+
BehaviorDifference(
|
|
113
|
+
construct_id=key,
|
|
114
|
+
diff_type="attribute_mismatch",
|
|
115
|
+
details=f"Attributes mismatch for {key}",
|
|
116
|
+
platform_a_value=beh_a.attributes,
|
|
117
|
+
platform_b_value=beh_b.attributes,
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return ProfileComparison(
|
|
122
|
+
platform_a=profile_a.platform_key,
|
|
123
|
+
platform_b=profile_b.platform_key,
|
|
124
|
+
differences=differences,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def generate_diff_report(comparison: ProfileComparison) -> str:
|
|
129
|
+
"""
|
|
130
|
+
Generates a human-readable report of the differences.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
comparison: The comparison result.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
str: The report text.
|
|
137
|
+
"""
|
|
138
|
+
if not comparison.has_differences:
|
|
139
|
+
return f"No differences found between {comparison.platform_a} and {comparison.platform_b}."
|
|
140
|
+
|
|
141
|
+
lines = [
|
|
142
|
+
f"Comparison Report: {comparison.platform_a} vs {comparison.platform_b}",
|
|
143
|
+
"=" * 60,
|
|
144
|
+
f"Total differences: {len(comparison.differences)}",
|
|
145
|
+
"",
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
for diff in comparison.differences:
|
|
149
|
+
lines.append(f"Construct: {diff.construct_id}")
|
|
150
|
+
lines.append(f"Type: {diff.diff_type}")
|
|
151
|
+
lines.append(f"Details: {diff.details}")
|
|
152
|
+
lines.append(f" {comparison.platform_a}: {diff.platform_a_value}")
|
|
153
|
+
lines.append(f" {comparison.platform_b}: {diff.platform_b_value}")
|
|
154
|
+
lines.append("-" * 40)
|
|
155
|
+
|
|
156
|
+
return "\n".join(lines)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if __name__ == "__main__":
|
|
160
|
+
import argparse
|
|
161
|
+
import sys
|
|
162
|
+
from pathlib import Path
|
|
163
|
+
|
|
164
|
+
parser = argparse.ArgumentParser(description="Compare two SQL behavior profiles")
|
|
165
|
+
parser.add_argument("profile_a", type=str, help="Path to first profile")
|
|
166
|
+
parser.add_argument("profile_b", type=str, help="Path to second profile")
|
|
167
|
+
parser.add_argument(
|
|
168
|
+
"--fail-on-diff",
|
|
169
|
+
action="store_true",
|
|
170
|
+
help="Exit with error code if differences found",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
args = parser.parse_args()
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
path_a = Path(args.profile_a)
|
|
177
|
+
path_b = Path(args.profile_b)
|
|
178
|
+
|
|
179
|
+
if not path_a.exists():
|
|
180
|
+
print(f"Error: Profile not found: {path_a}")
|
|
181
|
+
sys.exit(1)
|
|
182
|
+
|
|
183
|
+
if not path_b.exists():
|
|
184
|
+
print(f"Error: Profile not found: {path_b}")
|
|
185
|
+
sys.exit(1)
|
|
186
|
+
|
|
187
|
+
# Load profiles manually since BehaviorProfile.load expects a platform key
|
|
188
|
+
# We need to load from specific files
|
|
189
|
+
import json
|
|
190
|
+
|
|
191
|
+
from .profiles import BehaviorProfile, ParsingBehavior
|
|
192
|
+
|
|
193
|
+
def load_profile_from_file(path: Path) -> BehaviorProfile:
|
|
194
|
+
with open(path, encoding="utf-8") as f:
|
|
195
|
+
data = json.load(f)
|
|
196
|
+
|
|
197
|
+
# Convert behaviors dict to ParsingBehavior objects
|
|
198
|
+
behaviors = {}
|
|
199
|
+
for key, b_data in data.get("behaviors", {}).items():
|
|
200
|
+
if isinstance(b_data, dict):
|
|
201
|
+
behaviors[key] = ParsingBehavior(**b_data)
|
|
202
|
+
else:
|
|
203
|
+
behaviors[key] = b_data
|
|
204
|
+
|
|
205
|
+
return BehaviorProfile(
|
|
206
|
+
schema_version=data.get("schema_version", "1.0.0"),
|
|
207
|
+
platform_key=data["platform_key"],
|
|
208
|
+
behaviors=behaviors,
|
|
209
|
+
adaptation_rules=data.get("adaptation_rules", []),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
profile_a = load_profile_from_file(path_a)
|
|
213
|
+
profile_b = load_profile_from_file(path_b)
|
|
214
|
+
|
|
215
|
+
comparison = compare_profiles(profile_a, profile_b)
|
|
216
|
+
report = generate_diff_report(comparison)
|
|
217
|
+
print(report)
|
|
218
|
+
|
|
219
|
+
if args.fail_on_diff and comparison.has_differences:
|
|
220
|
+
sys.exit(1)
|
|
221
|
+
|
|
222
|
+
except Exception as e:
|
|
223
|
+
print(f"Error comparing profiles: {e}")
|
|
224
|
+
sys.exit(1)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import sys
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class PlatformInfo:
|
|
9
|
+
"""Platform identification information."""
|
|
10
|
+
|
|
11
|
+
os_name: str
|
|
12
|
+
os_version: str
|
|
13
|
+
python_version: str
|
|
14
|
+
platform_key: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PlatformDetector:
|
|
18
|
+
"""Detects current platform and Python version."""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def detect() -> PlatformInfo:
|
|
22
|
+
"""
|
|
23
|
+
Detects the current platform information.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
PlatformInfo: The detected platform information.
|
|
27
|
+
"""
|
|
28
|
+
os_name = platform.system().lower()
|
|
29
|
+
os_version = platform.release()
|
|
30
|
+
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
31
|
+
|
|
32
|
+
# Normalize os_name
|
|
33
|
+
if os_name == "darwin":
|
|
34
|
+
os_name = "macos"
|
|
35
|
+
|
|
36
|
+
platform_key = f"{os_name}-{python_version}"
|
|
37
|
+
|
|
38
|
+
return PlatformInfo(
|
|
39
|
+
os_name=os_name,
|
|
40
|
+
os_version=os_version,
|
|
41
|
+
python_version=python_version,
|
|
42
|
+
platform_key=platform_key,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def get_profile_path(
|
|
47
|
+
base_path: Path, platform_info: PlatformInfo | None = None
|
|
48
|
+
) -> Path:
|
|
49
|
+
"""
|
|
50
|
+
Resolves the path to the profile file for the given platform.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
base_path: The base directory where profiles are stored.
|
|
54
|
+
platform_info: The platform info to use. If None, detects current platform.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Path: The path to the profile file.
|
|
58
|
+
"""
|
|
59
|
+
if platform_info is None:
|
|
60
|
+
platform_info = PlatformDetector.detect()
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
base_path
|
|
64
|
+
/ platform_info.os_name
|
|
65
|
+
/ platform_info.python_version
|
|
66
|
+
/ "profile.json"
|
|
67
|
+
)
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class SQLTestFixture:
|
|
6
|
+
"""A SQL code sample for testing platform compatibility."""
|
|
7
|
+
|
|
8
|
+
id: str
|
|
9
|
+
sql: str
|
|
10
|
+
description: str
|
|
11
|
+
expected_constructs: list[
|
|
12
|
+
str
|
|
13
|
+
] # List of construct types expected (e.g. "table", "view")
|
|
14
|
+
is_edge_case: bool = False
|
|
15
|
+
known_platform_issues: list[str] | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Standard SQL constructs
|
|
19
|
+
|
|
20
|
+
FIXTURE_SIMPLE_TABLE = SQLTestFixture(
|
|
21
|
+
id="simple_table",
|
|
22
|
+
sql="""
|
|
23
|
+
CREATE TABLE users (
|
|
24
|
+
id INT PRIMARY KEY,
|
|
25
|
+
username VARCHAR(50) NOT NULL,
|
|
26
|
+
email VARCHAR(100)
|
|
27
|
+
);
|
|
28
|
+
""",
|
|
29
|
+
description="Basic table with columns",
|
|
30
|
+
expected_constructs=["table"],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
FIXTURE_COMPLEX_TABLE = SQLTestFixture(
|
|
34
|
+
id="complex_table",
|
|
35
|
+
sql="""
|
|
36
|
+
CREATE TABLE orders (
|
|
37
|
+
order_id INT PRIMARY KEY,
|
|
38
|
+
user_id INT,
|
|
39
|
+
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
40
|
+
total_amount DECIMAL(10, 2),
|
|
41
|
+
CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id)
|
|
42
|
+
);
|
|
43
|
+
""",
|
|
44
|
+
description="Table with constraints and foreign keys",
|
|
45
|
+
expected_constructs=["table"],
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
FIXTURE_VIEW_WITH_JOIN = SQLTestFixture(
|
|
49
|
+
id="view_with_join",
|
|
50
|
+
sql="""
|
|
51
|
+
CREATE VIEW user_orders AS
|
|
52
|
+
SELECT u.username, o.order_date, o.total_amount
|
|
53
|
+
FROM users u
|
|
54
|
+
JOIN orders o ON u.id = o.user_id;
|
|
55
|
+
""",
|
|
56
|
+
description="View with JOIN operations",
|
|
57
|
+
expected_constructs=["view"],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
FIXTURE_STORED_PROCEDURE = SQLTestFixture(
|
|
61
|
+
id="stored_procedure",
|
|
62
|
+
sql="""
|
|
63
|
+
CREATE PROCEDURE GetUserOrders(IN userId INT)
|
|
64
|
+
BEGIN
|
|
65
|
+
SELECT * FROM orders WHERE user_id = userId;
|
|
66
|
+
END;
|
|
67
|
+
""",
|
|
68
|
+
description="Procedure with parameters",
|
|
69
|
+
expected_constructs=["procedure"],
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
FIXTURE_FUNCTION_WITH_PARAMS = SQLTestFixture(
|
|
73
|
+
id="function_with_params",
|
|
74
|
+
sql="""
|
|
75
|
+
CREATE FUNCTION CalculateTax(amount DECIMAL(10,2))
|
|
76
|
+
RETURNS DECIMAL(10,2)
|
|
77
|
+
BEGIN
|
|
78
|
+
RETURN amount * 0.15;
|
|
79
|
+
END;
|
|
80
|
+
""",
|
|
81
|
+
description="Function with parameters and return type",
|
|
82
|
+
expected_constructs=["function"],
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
FIXTURE_TRIGGER_BEFORE_INSERT = SQLTestFixture(
|
|
86
|
+
id="trigger_before_insert",
|
|
87
|
+
sql="""
|
|
88
|
+
CREATE TRIGGER before_order_insert
|
|
89
|
+
BEFORE INSERT ON orders
|
|
90
|
+
FOR EACH ROW
|
|
91
|
+
BEGIN
|
|
92
|
+
SET NEW.order_date = NOW();
|
|
93
|
+
END;
|
|
94
|
+
""",
|
|
95
|
+
description="Trigger with timing and event",
|
|
96
|
+
expected_constructs=["trigger"],
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
FIXTURE_INDEX_UNIQUE = SQLTestFixture(
|
|
100
|
+
id="index_unique",
|
|
101
|
+
sql="""
|
|
102
|
+
CREATE UNIQUE INDEX idx_user_email ON users(email);
|
|
103
|
+
""",
|
|
104
|
+
description="Unique index on table",
|
|
105
|
+
expected_constructs=["index"],
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Edge case fixtures for platform issues
|
|
109
|
+
|
|
110
|
+
FIXTURE_FUNCTION_WITH_SELECT = SQLTestFixture(
|
|
111
|
+
id="function_with_select",
|
|
112
|
+
sql="""
|
|
113
|
+
CREATE FUNCTION GetTotalSales() RETURNS DECIMAL(10,2)
|
|
114
|
+
BEGIN
|
|
115
|
+
DECLARE total DECIMAL(10,2);
|
|
116
|
+
SELECT SUM(total_amount) INTO total FROM orders;
|
|
117
|
+
RETURN total;
|
|
118
|
+
END;
|
|
119
|
+
""",
|
|
120
|
+
description="Function with SELECT in body (Ubuntu 3.12 issue)",
|
|
121
|
+
expected_constructs=["function"],
|
|
122
|
+
is_edge_case=True,
|
|
123
|
+
known_platform_issues=["ubuntu-3.12"],
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
FIXTURE_TRIGGER_WITH_DESCRIPTION = SQLTestFixture(
|
|
127
|
+
id="trigger_with_description",
|
|
128
|
+
sql="""
|
|
129
|
+
CREATE TRIGGER update_description
|
|
130
|
+
BEFORE UPDATE ON products
|
|
131
|
+
FOR EACH ROW
|
|
132
|
+
BEGIN
|
|
133
|
+
-- Trigger logic here
|
|
134
|
+
END;
|
|
135
|
+
""",
|
|
136
|
+
description="Trigger name extraction (macOS issue where name might be confused with description keyword if present)",
|
|
137
|
+
expected_constructs=["trigger"],
|
|
138
|
+
is_edge_case=True,
|
|
139
|
+
known_platform_issues=["macos"],
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
FIXTURE_FUNCTION_WITH_AUTO_INCREMENT = SQLTestFixture(
|
|
143
|
+
id="function_with_auto_increment",
|
|
144
|
+
sql="""
|
|
145
|
+
CREATE TABLE items (
|
|
146
|
+
id INT AUTO_INCREMENT PRIMARY KEY
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
CREATE FUNCTION GetNextId() RETURNS INT
|
|
150
|
+
BEGIN
|
|
151
|
+
RETURN 1;
|
|
152
|
+
END;
|
|
153
|
+
""",
|
|
154
|
+
description="Function near AUTO_INCREMENT (Windows issue)",
|
|
155
|
+
expected_constructs=["table", "function"],
|
|
156
|
+
is_edge_case=True,
|
|
157
|
+
known_platform_issues=["windows"],
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
FIXTURE_VIEW_IN_ERROR_NODE = SQLTestFixture(
|
|
161
|
+
id="view_in_error_node",
|
|
162
|
+
sql="""
|
|
163
|
+
-- Some complex SQL that might confuse the parser
|
|
164
|
+
CREATE VIEW complex_view AS
|
|
165
|
+
WITH cte AS (SELECT 1)
|
|
166
|
+
SELECT * FROM cte;
|
|
167
|
+
""",
|
|
168
|
+
description="View that appears in ERROR nodes on some platforms",
|
|
169
|
+
expected_constructs=["view"],
|
|
170
|
+
is_edge_case=True,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
FIXTURE_PHANTOM_TRIGGER = SQLTestFixture(
|
|
174
|
+
id="phantom_trigger",
|
|
175
|
+
sql="""
|
|
176
|
+
-- A comment that looks like a trigger
|
|
177
|
+
-- CREATE TRIGGER phantom_trigger
|
|
178
|
+
CREATE TABLE real_table (id INT);
|
|
179
|
+
""",
|
|
180
|
+
description="Trigger that creates phantom elements on some platforms",
|
|
181
|
+
expected_constructs=["table"], # Should NOT contain trigger
|
|
182
|
+
is_edge_case=True,
|
|
183
|
+
known_platform_issues=["ubuntu-3.12"],
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
FIXTURE_PROCEDURE_WITH_COMMENTS = SQLTestFixture(
|
|
187
|
+
id="procedure_with_comments",
|
|
188
|
+
sql="""
|
|
189
|
+
CREATE PROCEDURE ComplexProc()
|
|
190
|
+
BEGIN
|
|
191
|
+
/*
|
|
192
|
+
Multi-line comment
|
|
193
|
+
that might confuse parser
|
|
194
|
+
*/
|
|
195
|
+
SELECT 1;
|
|
196
|
+
END;
|
|
197
|
+
""",
|
|
198
|
+
description="Procedure with complex comments",
|
|
199
|
+
expected_constructs=["procedure"],
|
|
200
|
+
is_edge_case=True,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
FIXTURE_INDEX_ON_EXPRESSION = SQLTestFixture(
|
|
204
|
+
id="index_on_expression",
|
|
205
|
+
sql="""
|
|
206
|
+
CREATE INDEX idx_lower_email ON users((lower(email)));
|
|
207
|
+
""",
|
|
208
|
+
description="Index on expression",
|
|
209
|
+
expected_constructs=["index"],
|
|
210
|
+
is_edge_case=True,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
ALL_FIXTURES = [
|
|
214
|
+
FIXTURE_SIMPLE_TABLE,
|
|
215
|
+
FIXTURE_COMPLEX_TABLE,
|
|
216
|
+
FIXTURE_VIEW_WITH_JOIN,
|
|
217
|
+
FIXTURE_STORED_PROCEDURE,
|
|
218
|
+
FIXTURE_FUNCTION_WITH_PARAMS,
|
|
219
|
+
FIXTURE_TRIGGER_BEFORE_INSERT,
|
|
220
|
+
FIXTURE_INDEX_UNIQUE,
|
|
221
|
+
FIXTURE_FUNCTION_WITH_SELECT,
|
|
222
|
+
FIXTURE_TRIGGER_WITH_DESCRIPTION,
|
|
223
|
+
FIXTURE_FUNCTION_WITH_AUTO_INCREMENT,
|
|
224
|
+
FIXTURE_VIEW_IN_ERROR_NODE,
|
|
225
|
+
FIXTURE_PHANTOM_TRIGGER,
|
|
226
|
+
FIXTURE_PROCEDURE_WITH_COMMENTS,
|
|
227
|
+
FIXTURE_INDEX_ON_EXPRESSION,
|
|
228
|
+
]
|