thailint 0.5.0__py3-none-any.whl → 0.15.3__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.
- src/__init__.py +1 -0
- src/analyzers/__init__.py +4 -3
- src/analyzers/ast_utils.py +54 -0
- src/analyzers/rust_base.py +155 -0
- src/analyzers/rust_context.py +141 -0
- src/analyzers/typescript_base.py +4 -0
- src/cli/__init__.py +30 -0
- src/cli/__main__.py +22 -0
- src/cli/config.py +480 -0
- src/cli/config_merge.py +241 -0
- src/cli/linters/__init__.py +67 -0
- src/cli/linters/code_patterns.py +270 -0
- src/cli/linters/code_smells.py +342 -0
- src/cli/linters/documentation.py +83 -0
- src/cli/linters/performance.py +287 -0
- src/cli/linters/shared.py +331 -0
- src/cli/linters/structure.py +327 -0
- src/cli/linters/structure_quality.py +328 -0
- src/cli/main.py +120 -0
- src/cli/utils.py +395 -0
- src/cli_main.py +37 -0
- src/config.py +38 -25
- src/core/base.py +7 -2
- src/core/cli_utils.py +19 -2
- src/core/config_parser.py +5 -2
- src/core/constants.py +54 -0
- src/core/linter_utils.py +95 -6
- src/core/python_lint_rule.py +101 -0
- src/core/registry.py +1 -1
- src/core/rule_discovery.py +147 -84
- src/core/types.py +13 -0
- src/core/violation_builder.py +78 -15
- src/core/violation_utils.py +69 -0
- src/formatters/__init__.py +22 -0
- src/formatters/sarif.py +202 -0
- src/linter_config/directive_markers.py +109 -0
- src/linter_config/ignore.py +254 -395
- src/linter_config/loader.py +45 -12
- src/linter_config/pattern_utils.py +65 -0
- src/linter_config/rule_matcher.py +89 -0
- src/linters/collection_pipeline/__init__.py +90 -0
- src/linters/collection_pipeline/any_all_analyzer.py +281 -0
- src/linters/collection_pipeline/ast_utils.py +40 -0
- src/linters/collection_pipeline/config.py +75 -0
- src/linters/collection_pipeline/continue_analyzer.py +94 -0
- src/linters/collection_pipeline/detector.py +360 -0
- src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
- src/linters/collection_pipeline/linter.py +420 -0
- src/linters/collection_pipeline/suggestion_builder.py +130 -0
- src/linters/cqs/__init__.py +54 -0
- src/linters/cqs/config.py +55 -0
- src/linters/cqs/function_analyzer.py +201 -0
- src/linters/cqs/input_detector.py +139 -0
- src/linters/cqs/linter.py +159 -0
- src/linters/cqs/output_detector.py +84 -0
- src/linters/cqs/python_analyzer.py +54 -0
- src/linters/cqs/types.py +82 -0
- src/linters/cqs/typescript_cqs_analyzer.py +61 -0
- src/linters/cqs/typescript_function_analyzer.py +192 -0
- src/linters/cqs/typescript_input_detector.py +203 -0
- src/linters/cqs/typescript_output_detector.py +117 -0
- src/linters/cqs/violation_builder.py +94 -0
- src/linters/dry/base_token_analyzer.py +16 -9
- src/linters/dry/block_filter.py +120 -20
- src/linters/dry/block_grouper.py +4 -0
- src/linters/dry/cache.py +104 -10
- src/linters/dry/cache_query.py +4 -0
- src/linters/dry/config.py +54 -11
- src/linters/dry/constant.py +92 -0
- src/linters/dry/constant_matcher.py +223 -0
- src/linters/dry/constant_violation_builder.py +98 -0
- src/linters/dry/duplicate_storage.py +5 -4
- src/linters/dry/file_analyzer.py +4 -2
- src/linters/dry/inline_ignore.py +7 -16
- src/linters/dry/linter.py +183 -48
- src/linters/dry/python_analyzer.py +60 -439
- src/linters/dry/python_constant_extractor.py +100 -0
- src/linters/dry/single_statement_detector.py +417 -0
- src/linters/dry/token_hasher.py +116 -112
- src/linters/dry/typescript_analyzer.py +68 -382
- src/linters/dry/typescript_constant_extractor.py +138 -0
- src/linters/dry/typescript_statement_detector.py +255 -0
- src/linters/dry/typescript_value_extractor.py +70 -0
- src/linters/dry/violation_builder.py +4 -0
- src/linters/dry/violation_filter.py +5 -4
- src/linters/dry/violation_generator.py +71 -14
- src/linters/file_header/atemporal_detector.py +68 -50
- src/linters/file_header/base_parser.py +93 -0
- src/linters/file_header/bash_parser.py +66 -0
- src/linters/file_header/config.py +90 -16
- src/linters/file_header/css_parser.py +70 -0
- src/linters/file_header/field_validator.py +36 -33
- src/linters/file_header/linter.py +140 -144
- src/linters/file_header/markdown_parser.py +130 -0
- src/linters/file_header/python_parser.py +14 -58
- src/linters/file_header/typescript_parser.py +73 -0
- src/linters/file_header/violation_builder.py +13 -12
- src/linters/file_placement/config_loader.py +3 -1
- src/linters/file_placement/directory_matcher.py +4 -0
- src/linters/file_placement/linter.py +66 -34
- src/linters/file_placement/pattern_matcher.py +41 -6
- src/linters/file_placement/pattern_validator.py +31 -12
- src/linters/file_placement/rule_checker.py +12 -7
- src/linters/lazy_ignores/__init__.py +43 -0
- src/linters/lazy_ignores/config.py +74 -0
- src/linters/lazy_ignores/directive_utils.py +164 -0
- src/linters/lazy_ignores/header_parser.py +177 -0
- src/linters/lazy_ignores/linter.py +158 -0
- src/linters/lazy_ignores/matcher.py +168 -0
- src/linters/lazy_ignores/python_analyzer.py +209 -0
- src/linters/lazy_ignores/rule_id_utils.py +180 -0
- src/linters/lazy_ignores/skip_detector.py +298 -0
- src/linters/lazy_ignores/types.py +71 -0
- src/linters/lazy_ignores/typescript_analyzer.py +146 -0
- src/linters/lazy_ignores/violation_builder.py +135 -0
- src/linters/lbyl/__init__.py +31 -0
- src/linters/lbyl/config.py +63 -0
- src/linters/lbyl/linter.py +67 -0
- src/linters/lbyl/pattern_detectors/__init__.py +53 -0
- src/linters/lbyl/pattern_detectors/base.py +63 -0
- src/linters/lbyl/pattern_detectors/dict_key_detector.py +107 -0
- src/linters/lbyl/pattern_detectors/division_check_detector.py +232 -0
- src/linters/lbyl/pattern_detectors/file_exists_detector.py +220 -0
- src/linters/lbyl/pattern_detectors/hasattr_detector.py +119 -0
- src/linters/lbyl/pattern_detectors/isinstance_detector.py +119 -0
- src/linters/lbyl/pattern_detectors/len_check_detector.py +173 -0
- src/linters/lbyl/pattern_detectors/none_check_detector.py +146 -0
- src/linters/lbyl/pattern_detectors/string_validator_detector.py +145 -0
- src/linters/lbyl/python_analyzer.py +215 -0
- src/linters/lbyl/violation_builder.py +354 -0
- src/linters/magic_numbers/context_analyzer.py +227 -225
- src/linters/magic_numbers/linter.py +28 -82
- src/linters/magic_numbers/python_analyzer.py +4 -16
- src/linters/magic_numbers/typescript_analyzer.py +9 -12
- src/linters/magic_numbers/typescript_ignore_checker.py +81 -0
- src/linters/method_property/__init__.py +49 -0
- src/linters/method_property/config.py +138 -0
- src/linters/method_property/linter.py +414 -0
- src/linters/method_property/python_analyzer.py +473 -0
- src/linters/method_property/violation_builder.py +119 -0
- src/linters/nesting/linter.py +24 -16
- src/linters/nesting/python_analyzer.py +4 -0
- src/linters/nesting/typescript_analyzer.py +6 -12
- src/linters/nesting/violation_builder.py +1 -0
- src/linters/performance/__init__.py +91 -0
- src/linters/performance/config.py +43 -0
- src/linters/performance/constants.py +49 -0
- src/linters/performance/linter.py +149 -0
- src/linters/performance/python_analyzer.py +365 -0
- src/linters/performance/regex_analyzer.py +312 -0
- src/linters/performance/regex_linter.py +139 -0
- src/linters/performance/typescript_analyzer.py +236 -0
- src/linters/performance/violation_builder.py +160 -0
- src/linters/print_statements/config.py +7 -12
- src/linters/print_statements/linter.py +26 -43
- src/linters/print_statements/python_analyzer.py +91 -93
- src/linters/print_statements/typescript_analyzer.py +15 -25
- src/linters/print_statements/violation_builder.py +12 -14
- src/linters/srp/class_analyzer.py +11 -7
- src/linters/srp/heuristics.py +56 -22
- src/linters/srp/linter.py +15 -16
- src/linters/srp/python_analyzer.py +55 -20
- src/linters/srp/typescript_metrics_calculator.py +110 -50
- src/linters/stateless_class/__init__.py +25 -0
- src/linters/stateless_class/config.py +58 -0
- src/linters/stateless_class/linter.py +349 -0
- src/linters/stateless_class/python_analyzer.py +290 -0
- src/linters/stringly_typed/__init__.py +36 -0
- src/linters/stringly_typed/config.py +189 -0
- src/linters/stringly_typed/context_filter.py +451 -0
- src/linters/stringly_typed/function_call_violation_builder.py +135 -0
- src/linters/stringly_typed/ignore_checker.py +100 -0
- src/linters/stringly_typed/ignore_utils.py +51 -0
- src/linters/stringly_typed/linter.py +376 -0
- src/linters/stringly_typed/python/__init__.py +33 -0
- src/linters/stringly_typed/python/analyzer.py +348 -0
- src/linters/stringly_typed/python/call_tracker.py +175 -0
- src/linters/stringly_typed/python/comparison_tracker.py +257 -0
- src/linters/stringly_typed/python/condition_extractor.py +134 -0
- src/linters/stringly_typed/python/conditional_detector.py +179 -0
- src/linters/stringly_typed/python/constants.py +21 -0
- src/linters/stringly_typed/python/match_analyzer.py +94 -0
- src/linters/stringly_typed/python/validation_detector.py +189 -0
- src/linters/stringly_typed/python/variable_extractor.py +96 -0
- src/linters/stringly_typed/storage.py +620 -0
- src/linters/stringly_typed/storage_initializer.py +45 -0
- src/linters/stringly_typed/typescript/__init__.py +28 -0
- src/linters/stringly_typed/typescript/analyzer.py +157 -0
- src/linters/stringly_typed/typescript/call_tracker.py +335 -0
- src/linters/stringly_typed/typescript/comparison_tracker.py +378 -0
- src/linters/stringly_typed/violation_generator.py +419 -0
- src/orchestrator/core.py +252 -14
- src/orchestrator/language_detector.py +5 -3
- src/templates/thailint_config_template.yaml +196 -0
- src/utils/project_root.py +3 -0
- thailint-0.15.3.dist-info/METADATA +187 -0
- thailint-0.15.3.dist-info/RECORD +226 -0
- thailint-0.15.3.dist-info/entry_points.txt +4 -0
- src/cli.py +0 -1665
- thailint-0.5.0.dist-info/METADATA +0 -1286
- thailint-0.5.0.dist-info/RECORD +0 -96
- thailint-0.5.0.dist-info/entry_points.txt +0 -4
- {thailint-0.5.0.dist-info → thailint-0.15.3.dist-info}/WHEEL +0 -0
- {thailint-0.5.0.dist-info → thailint-0.15.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -16,20 +16,14 @@ Exports: TypeScriptNestingAnalyzer class with calculate_max_depth methods
|
|
|
16
16
|
Interfaces: calculate_max_depth(func_node) -> tuple[int, int], find_all_functions(root_node)
|
|
17
17
|
|
|
18
18
|
Implementation: Inherits tree-sitter parsing from base, visitor pattern with depth tracking
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
from typing import Any
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# dry: ignore-block - tree-sitter import pattern (common across TypeScript analyzers)
|
|
26
|
-
try:
|
|
27
|
-
from tree_sitter import Node
|
|
20
|
+
"""
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
from src.analyzers.typescript_base import (
|
|
23
|
+
TREE_SITTER_AVAILABLE,
|
|
24
|
+
Node,
|
|
25
|
+
TypeScriptBaseAnalyzer,
|
|
26
|
+
)
|
|
33
27
|
|
|
34
28
|
from .typescript_function_extractor import TypeScriptFunctionExtractor
|
|
35
29
|
|
|
@@ -17,6 +17,7 @@ Interfaces: create_nesting_violation, create_typescript_nesting_violation, creat
|
|
|
17
17
|
|
|
18
18
|
Implementation: Formats messages with depth information, provides targeted refactoring suggestions,
|
|
19
19
|
extends BaseViolationBuilder for consistent violation construction
|
|
20
|
+
|
|
20
21
|
"""
|
|
21
22
|
|
|
22
23
|
import ast
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Performance linter package initialization
|
|
3
|
+
|
|
4
|
+
Scope: Exports for performance linter module
|
|
5
|
+
|
|
6
|
+
Overview: Initializes the performance linter package and exposes the main rule classes for
|
|
7
|
+
external use. Exports StringConcatLoopRule as the primary interface for the performance
|
|
8
|
+
linter, allowing the orchestrator to discover and instantiate the rule. Also exports
|
|
9
|
+
configuration and analyzer classes for advanced use cases. Provides a convenience lint()
|
|
10
|
+
function for direct usage without orchestrator setup. This module serves as the entry
|
|
11
|
+
point for the performance linter functionality within the thai-lint framework.
|
|
12
|
+
|
|
13
|
+
Dependencies: StringConcatLoopRule, PerformanceConfig, analyzers
|
|
14
|
+
|
|
15
|
+
Exports: StringConcatLoopRule (primary), PerformanceConfig, analyzers, lint
|
|
16
|
+
|
|
17
|
+
Interfaces: Standard Python package initialization with __all__ for explicit exports
|
|
18
|
+
|
|
19
|
+
Implementation: Simple re-export pattern for package interface, convenience function
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from .config import PerformanceConfig
|
|
26
|
+
from .linter import StringConcatLoopRule
|
|
27
|
+
from .python_analyzer import PythonStringConcatAnalyzer
|
|
28
|
+
from .regex_analyzer import PythonRegexInLoopAnalyzer
|
|
29
|
+
from .regex_linter import RegexInLoopRule
|
|
30
|
+
from .typescript_analyzer import TypeScriptStringConcatAnalyzer
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"StringConcatLoopRule",
|
|
34
|
+
"RegexInLoopRule",
|
|
35
|
+
"PerformanceConfig",
|
|
36
|
+
"PythonStringConcatAnalyzer",
|
|
37
|
+
"PythonRegexInLoopAnalyzer",
|
|
38
|
+
"TypeScriptStringConcatAnalyzer",
|
|
39
|
+
"lint",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def lint(
|
|
44
|
+
path: Path | str,
|
|
45
|
+
config: dict[str, Any] | None = None,
|
|
46
|
+
) -> list:
|
|
47
|
+
"""Lint a file or directory for performance issues.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
path: Path to file or directory to lint
|
|
51
|
+
config: Configuration dict (optional, uses defaults if not provided)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
List of violations found
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
>>> from src.linters.performance import lint
|
|
58
|
+
>>> violations = lint('src/my_module.py')
|
|
59
|
+
>>> for v in violations:
|
|
60
|
+
... print(f"{v.file_path}:{v.line} - {v.message}")
|
|
61
|
+
"""
|
|
62
|
+
path_obj = Path(path) if isinstance(path, str) else path
|
|
63
|
+
project_root = path_obj if path_obj.is_dir() else path_obj.parent
|
|
64
|
+
|
|
65
|
+
orchestrator = _setup_performance_orchestrator(project_root, config)
|
|
66
|
+
violations = _execute_performance_lint(orchestrator, path_obj)
|
|
67
|
+
|
|
68
|
+
return [v for v in violations if "performance" in v.rule_id]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _setup_performance_orchestrator(project_root: Path, config: dict[str, Any] | None) -> Any:
|
|
72
|
+
"""Set up orchestrator with performance config."""
|
|
73
|
+
from src.orchestrator.core import Orchestrator
|
|
74
|
+
|
|
75
|
+
orchestrator = Orchestrator(project_root=project_root)
|
|
76
|
+
|
|
77
|
+
if config:
|
|
78
|
+
orchestrator.config["performance"] = config
|
|
79
|
+
else:
|
|
80
|
+
orchestrator.config["performance"] = {"enabled": True}
|
|
81
|
+
|
|
82
|
+
return orchestrator
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _execute_performance_lint(orchestrator: Any, path_obj: Path) -> list:
|
|
86
|
+
"""Execute linting on file or directory."""
|
|
87
|
+
if path_obj.is_file():
|
|
88
|
+
return orchestrator.lint_file(path_obj)
|
|
89
|
+
if path_obj.is_dir():
|
|
90
|
+
return orchestrator.lint_directory(path_obj)
|
|
91
|
+
return []
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Configuration schema for performance linter rules
|
|
3
|
+
|
|
4
|
+
Scope: PerformanceConfig dataclass with settings for string-concat-loop and regex-in-loop
|
|
5
|
+
|
|
6
|
+
Overview: Defines configuration schema for performance linter rules. Provides PerformanceConfig
|
|
7
|
+
dataclass with enabled flag and optional rule-specific settings. Supports loading from
|
|
8
|
+
YAML/JSON configuration files. Integrates with the orchestrator's configuration system
|
|
9
|
+
to allow users to customize performance rule settings via .thailint.yaml files.
|
|
10
|
+
|
|
11
|
+
Dependencies: dataclasses, typing
|
|
12
|
+
|
|
13
|
+
Exports: PerformanceConfig dataclass
|
|
14
|
+
|
|
15
|
+
Interfaces: PerformanceConfig(enabled: bool = True), from_dict class method for loading config
|
|
16
|
+
|
|
17
|
+
Implementation: Dataclass with validation and defaults, simple enabled flag (extensible)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class PerformanceConfig:
|
|
26
|
+
"""Configuration for performance linter rules."""
|
|
27
|
+
|
|
28
|
+
enabled: bool = True
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_dict(cls, config: dict[str, Any], language: str | None = None) -> "PerformanceConfig":
|
|
32
|
+
"""Load configuration from dictionary.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
config: Dictionary containing configuration values
|
|
36
|
+
language: Programming language (reserved for language-specific config)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
PerformanceConfig instance with values from dictionary
|
|
40
|
+
"""
|
|
41
|
+
return cls(
|
|
42
|
+
enabled=config.get("enabled", True),
|
|
43
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Shared constants for performance linter rules
|
|
3
|
+
|
|
4
|
+
Scope: Common patterns and constants used across Python and TypeScript analyzers
|
|
5
|
+
|
|
6
|
+
Overview: Provides shared constants for the performance linter, including variable
|
|
7
|
+
name patterns that suggest string building. Used by both PythonStringConcatAnalyzer
|
|
8
|
+
and TypeScriptStringConcatAnalyzer to detect likely string concatenation.
|
|
9
|
+
|
|
10
|
+
Dependencies: None
|
|
11
|
+
|
|
12
|
+
Exports: STRING_VARIABLE_PATTERNS, LOOP_NODE_TYPES_TS
|
|
13
|
+
|
|
14
|
+
Interfaces: Frozen sets of patterns
|
|
15
|
+
|
|
16
|
+
Implementation: Constants shared between analyzers
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Variable names that suggest string building
|
|
20
|
+
STRING_VARIABLE_PATTERNS = frozenset(
|
|
21
|
+
{
|
|
22
|
+
"result",
|
|
23
|
+
"output",
|
|
24
|
+
"message",
|
|
25
|
+
"msg",
|
|
26
|
+
"text",
|
|
27
|
+
"html",
|
|
28
|
+
"content",
|
|
29
|
+
"body",
|
|
30
|
+
"buffer",
|
|
31
|
+
"response",
|
|
32
|
+
"data",
|
|
33
|
+
"line",
|
|
34
|
+
"lines",
|
|
35
|
+
"string",
|
|
36
|
+
"str",
|
|
37
|
+
"s",
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Tree-sitter node types for loops (TypeScript)
|
|
42
|
+
LOOP_NODE_TYPES_TS = frozenset(
|
|
43
|
+
{
|
|
44
|
+
"for_statement",
|
|
45
|
+
"for_in_statement",
|
|
46
|
+
"while_statement",
|
|
47
|
+
"do_statement",
|
|
48
|
+
}
|
|
49
|
+
)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Main string concatenation in loop linter rule implementation
|
|
3
|
+
|
|
4
|
+
Scope: StringConcatLoopRule class implementing MultiLanguageLintRule interface
|
|
5
|
+
|
|
6
|
+
Overview: Implements string-concat-loop linter rule following MultiLanguageLintRule interface.
|
|
7
|
+
Orchestrates configuration loading, Python/TypeScript analysis, and violation building
|
|
8
|
+
through focused helper classes. Detects O(n²) string building patterns using += in loops.
|
|
9
|
+
Supports configurable enabled flag and ignore directives. Main rule class acts as
|
|
10
|
+
coordinator for string concatenation detection workflow.
|
|
11
|
+
|
|
12
|
+
Dependencies: MultiLanguageLintRule, BaseLintContext, PythonStringConcatAnalyzer,
|
|
13
|
+
TypeScriptStringConcatAnalyzer, PerformanceViolationBuilder
|
|
14
|
+
|
|
15
|
+
Exports: StringConcatLoopRule class
|
|
16
|
+
|
|
17
|
+
Interfaces: StringConcatLoopRule.check(context) -> list[Violation], properties for rule metadata
|
|
18
|
+
|
|
19
|
+
Implementation: Composition pattern with analyzer classes, AST-based analysis
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from src.core.base import BaseLintContext, MultiLanguageLintRule
|
|
26
|
+
from src.core.linter_utils import load_linter_config, with_parsed_python
|
|
27
|
+
from src.core.types import Violation
|
|
28
|
+
from src.linter_config.ignore import get_ignore_parser
|
|
29
|
+
|
|
30
|
+
from .config import PerformanceConfig
|
|
31
|
+
from .python_analyzer import PythonStringConcatAnalyzer
|
|
32
|
+
from .typescript_analyzer import TypeScriptStringConcatAnalyzer
|
|
33
|
+
from .violation_builder import PerformanceViolationBuilder
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class StringConcatLoopRule(MultiLanguageLintRule):
|
|
37
|
+
"""Detects O(n²) string concatenation in loops."""
|
|
38
|
+
|
|
39
|
+
def __init__(self) -> None:
|
|
40
|
+
"""Initialize the string concat loop rule."""
|
|
41
|
+
self._ignore_parser = get_ignore_parser()
|
|
42
|
+
self._violation_builder = PerformanceViolationBuilder(self.rule_id)
|
|
43
|
+
# Singleton analyzers for performance
|
|
44
|
+
self._python_analyzer = PythonStringConcatAnalyzer()
|
|
45
|
+
self._typescript_analyzer = TypeScriptStringConcatAnalyzer()
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def rule_id(self) -> str:
|
|
49
|
+
"""Unique identifier for this rule."""
|
|
50
|
+
return "performance.string-concat-loop"
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def rule_name(self) -> str:
|
|
54
|
+
"""Human-readable name for this rule."""
|
|
55
|
+
return "String Concatenation in Loop"
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def description(self) -> str:
|
|
59
|
+
"""Description of what this rule checks."""
|
|
60
|
+
return "String += in loops creates O(n²) complexity; use join() instead"
|
|
61
|
+
|
|
62
|
+
def _load_config(self, context: BaseLintContext) -> PerformanceConfig:
|
|
63
|
+
"""Load configuration from context.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
context: Lint context
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
PerformanceConfig instance
|
|
70
|
+
"""
|
|
71
|
+
return load_linter_config(context, "performance", PerformanceConfig)
|
|
72
|
+
|
|
73
|
+
def _check_python(self, context: BaseLintContext, config: PerformanceConfig) -> list[Violation]:
|
|
74
|
+
"""Check Python code for string concatenation in loops.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
context: Lint context with Python file information
|
|
78
|
+
config: Performance configuration
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
List of violations found in Python code
|
|
82
|
+
"""
|
|
83
|
+
return with_parsed_python(
|
|
84
|
+
context,
|
|
85
|
+
self._violation_builder,
|
|
86
|
+
lambda tree: self._analyze_python_string_concat(tree, context),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def _analyze_python_string_concat(self, tree: Any, context: BaseLintContext) -> list[Violation]:
|
|
90
|
+
"""Analyze parsed Python AST for string concatenation in loops."""
|
|
91
|
+
violations_raw = self._python_analyzer.find_violations(tree)
|
|
92
|
+
violations_deduped = self._python_analyzer.deduplicate_violations(violations_raw)
|
|
93
|
+
return self._build_violations(violations_deduped, context)
|
|
94
|
+
|
|
95
|
+
def _check_typescript(
|
|
96
|
+
self, context: BaseLintContext, config: PerformanceConfig
|
|
97
|
+
) -> list[Violation]:
|
|
98
|
+
"""Check TypeScript code for string concatenation in loops.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
context: Lint context with TypeScript file information
|
|
102
|
+
config: Performance configuration
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
List of violations found in TypeScript code
|
|
106
|
+
"""
|
|
107
|
+
root_node = self._typescript_analyzer.parse_typescript(context.file_content or "")
|
|
108
|
+
if root_node is None:
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
violations_raw = self._typescript_analyzer.find_violations(root_node)
|
|
112
|
+
violations_deduped = self._typescript_analyzer.deduplicate_violations(violations_raw)
|
|
113
|
+
|
|
114
|
+
return self._build_violations(violations_deduped, context)
|
|
115
|
+
|
|
116
|
+
def _build_violations(self, raw_violations: list, context: BaseLintContext) -> list[Violation]:
|
|
117
|
+
"""Build Violation objects from analyzer results.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
raw_violations: List of StringConcatViolation dataclass instances
|
|
121
|
+
context: Lint context
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
List of Violation objects
|
|
125
|
+
"""
|
|
126
|
+
violations = []
|
|
127
|
+
for v in raw_violations:
|
|
128
|
+
violation = self._violation_builder.create_string_concat_violation(
|
|
129
|
+
variable_name=v.variable_name,
|
|
130
|
+
line_number=v.line_number,
|
|
131
|
+
column=v.column,
|
|
132
|
+
loop_type=v.loop_type,
|
|
133
|
+
context=context,
|
|
134
|
+
)
|
|
135
|
+
if not self._should_ignore(violation, context):
|
|
136
|
+
violations.append(violation)
|
|
137
|
+
return violations
|
|
138
|
+
|
|
139
|
+
def _should_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
|
|
140
|
+
"""Check if violation should be ignored based on inline directives.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
violation: Violation to check
|
|
144
|
+
context: Lint context with file content
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
True if violation should be ignored
|
|
148
|
+
"""
|
|
149
|
+
return self._ignore_parser.should_ignore_violation(violation, context.file_content or "")
|