thailint 0.13.0__py3-none-any.whl → 0.15.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.
- src/cli/linters/__init__.py +6 -0
- src/cli/linters/code_patterns.py +75 -333
- src/cli/linters/code_smells.py +47 -168
- src/cli/linters/documentation.py +21 -98
- src/cli/linters/performance.py +274 -0
- src/cli/linters/shared.py +232 -6
- src/cli/linters/structure.py +23 -21
- src/cli/linters/structure_quality.py +25 -21
- src/core/linter_utils.py +91 -6
- src/linters/file_header/atemporal_detector.py +54 -40
- src/linters/file_header/config.py +14 -0
- src/linters/lazy_ignores/python_analyzer.py +5 -1
- src/linters/lazy_ignores/types.py +2 -0
- src/linters/method_property/config.py +0 -1
- src/linters/method_property/linter.py +0 -6
- src/linters/nesting/linter.py +11 -6
- 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/templates/thailint_config_template.yaml +30 -0
- {thailint-0.13.0.dist-info → thailint-0.15.0.dist-info}/METADATA +3 -2
- {thailint-0.13.0.dist-info → thailint-0.15.0.dist-info}/RECORD +32 -22
- {thailint-0.13.0.dist-info → thailint-0.15.0.dist-info}/WHEEL +0 -0
- {thailint-0.13.0.dist-info → thailint-0.15.0.dist-info}/entry_points.txt +0 -0
- {thailint-0.13.0.dist-info → thailint-0.15.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -111,6 +111,20 @@ class FileHeaderConfig:
|
|
|
111
111
|
defaults = cls()
|
|
112
112
|
required_fields = config_dict.get("required_fields", {})
|
|
113
113
|
|
|
114
|
+
# Handle both list format (applies to all languages) and dict format (language-specific)
|
|
115
|
+
if isinstance(required_fields, list):
|
|
116
|
+
# Simple list format: apply same fields to all languages
|
|
117
|
+
return cls(
|
|
118
|
+
required_fields_python=required_fields,
|
|
119
|
+
required_fields_typescript=required_fields,
|
|
120
|
+
required_fields_bash=required_fields,
|
|
121
|
+
required_fields_markdown=required_fields,
|
|
122
|
+
required_fields_css=required_fields,
|
|
123
|
+
enforce_atemporal=config_dict.get("enforce_atemporal", True),
|
|
124
|
+
ignore=config_dict.get("ignore", defaults.ignore),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Dict format: language-specific fields
|
|
114
128
|
return cls(
|
|
115
129
|
required_fields_python=required_fields.get("python", defaults.required_fields_python),
|
|
116
130
|
required_fields_typescript=required_fields.get(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Purpose: Detect Python linting ignore directives in source code
|
|
3
3
|
|
|
4
|
-
Scope: noqa, type:ignore, pylint:disable, nosec pattern detection
|
|
4
|
+
Scope: noqa, type:ignore, pylint:disable, nosec, dry:ignore-block pattern detection
|
|
5
5
|
|
|
6
6
|
Overview: Provides PythonIgnoreDetector class that scans Python source code for common
|
|
7
7
|
linting ignore patterns. Detects bare patterns (e.g., # noqa) and rule-specific
|
|
@@ -122,6 +122,10 @@ class PythonIgnoreDetector:
|
|
|
122
122
|
r"#\s*thailint:\s*ignore-start(?:\[([^\]]+)\])?",
|
|
123
123
|
re.IGNORECASE,
|
|
124
124
|
),
|
|
125
|
+
IgnoreType.DRY_IGNORE_BLOCK: re.compile(
|
|
126
|
+
r"#\s*dry:\s*ignore-block\b",
|
|
127
|
+
re.IGNORECASE,
|
|
128
|
+
),
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
def find_ignores(self, code: str, file_path: Path | None = None) -> list[IgnoreDirective]:
|
|
@@ -39,6 +39,8 @@ class IgnoreType(Enum):
|
|
|
39
39
|
THAILINT_IGNORE_FILE = "thailint:ignore-file"
|
|
40
40
|
THAILINT_IGNORE_NEXT = "thailint:ignore-next-line"
|
|
41
41
|
THAILINT_IGNORE_BLOCK = "thailint:ignore-start"
|
|
42
|
+
# DRY ignore patterns
|
|
43
|
+
DRY_IGNORE_BLOCK = "dry:ignore-block"
|
|
42
44
|
# Test skip patterns
|
|
43
45
|
PYTEST_SKIP = "pytest:skip"
|
|
44
46
|
PYTEST_SKIPIF = "pytest:skipif"
|
|
@@ -100,7 +100,6 @@ class MethodPropertyConfig: # thailint: ignore[dry]
|
|
|
100
100
|
exclude_prefixes: tuple[str, ...] = DEFAULT_EXCLUDE_PREFIXES
|
|
101
101
|
exclude_names: frozenset[str] = DEFAULT_EXCLUDE_NAMES
|
|
102
102
|
|
|
103
|
-
# dry: ignore-block
|
|
104
103
|
@classmethod
|
|
105
104
|
def from_dict(
|
|
106
105
|
cls, config: dict[str, Any] | None, language: str | None = None
|
|
@@ -75,7 +75,6 @@ class MethodPropertyRule(MultiLanguageLintRule): # thailint: ignore[srp,dry]
|
|
|
75
75
|
|
|
76
76
|
return MethodPropertyConfig()
|
|
77
77
|
|
|
78
|
-
# dry: ignore-block
|
|
79
78
|
def _try_load_test_config(self, context: BaseLintContext) -> MethodPropertyConfig | None:
|
|
80
79
|
"""Try to load test-style configuration.
|
|
81
80
|
|
|
@@ -95,7 +94,6 @@ class MethodPropertyRule(MultiLanguageLintRule): # thailint: ignore[srp,dry]
|
|
|
95
94
|
linter_config = config_attr.get("method-property", config_attr)
|
|
96
95
|
return MethodPropertyConfig.from_dict(linter_config)
|
|
97
96
|
|
|
98
|
-
# dry: ignore-block
|
|
99
97
|
def _is_file_ignored(self, context: BaseLintContext, config: MethodPropertyConfig) -> bool:
|
|
100
98
|
"""Check if file matches ignore patterns.
|
|
101
99
|
|
|
@@ -115,7 +113,6 @@ class MethodPropertyRule(MultiLanguageLintRule): # thailint: ignore[srp,dry]
|
|
|
115
113
|
file_path = Path(context.file_path)
|
|
116
114
|
return any(self._matches_pattern(file_path, pattern) for pattern in config.ignore)
|
|
117
115
|
|
|
118
|
-
# dry: ignore-block
|
|
119
116
|
def _matches_pattern(self, file_path: Path, pattern: str) -> bool:
|
|
120
117
|
"""Check if file path matches a glob pattern.
|
|
121
118
|
|
|
@@ -132,7 +129,6 @@ class MethodPropertyRule(MultiLanguageLintRule): # thailint: ignore[srp,dry]
|
|
|
132
129
|
return True
|
|
133
130
|
return False
|
|
134
131
|
|
|
135
|
-
# dry: ignore-block
|
|
136
132
|
def _is_test_file(self, file_path: object) -> bool:
|
|
137
133
|
"""Check if file is a test file.
|
|
138
134
|
|
|
@@ -204,7 +200,6 @@ class MethodPropertyRule(MultiLanguageLintRule): # thailint: ignore[srp,dry]
|
|
|
204
200
|
return candidates
|
|
205
201
|
return [c for c in candidates if c.method_name not in config.ignore_methods]
|
|
206
202
|
|
|
207
|
-
# dry: ignore-block
|
|
208
203
|
def _parse_python_code(self, code: str | None) -> ast.AST | None:
|
|
209
204
|
"""Parse Python code into AST.
|
|
210
205
|
|
|
@@ -285,7 +280,6 @@ class MethodPropertyRule(MultiLanguageLintRule): # thailint: ignore[srp,dry]
|
|
|
285
280
|
return True
|
|
286
281
|
return False
|
|
287
282
|
|
|
288
|
-
# dry: ignore-block
|
|
289
283
|
def _has_inline_ignore(self, violation: Violation, context: BaseLintContext) -> bool:
|
|
290
284
|
"""Check for inline ignore directive on method line.
|
|
291
285
|
|
src/linters/nesting/linter.py
CHANGED
|
@@ -16,13 +16,13 @@ Exports: NestingDepthRule class
|
|
|
16
16
|
Interfaces: NestingDepthRule.check(context) -> list[Violation], properties for rule metadata
|
|
17
17
|
|
|
18
18
|
Implementation: Composition pattern with helper classes, AST-based analysis with configurable limits
|
|
19
|
+
|
|
19
20
|
"""
|
|
20
21
|
|
|
21
|
-
import ast
|
|
22
22
|
from typing import Any
|
|
23
23
|
|
|
24
24
|
from src.core.base import BaseLintContext, MultiLanguageLintRule
|
|
25
|
-
from src.core.linter_utils import load_linter_config
|
|
25
|
+
from src.core.linter_utils import load_linter_config, with_parsed_python
|
|
26
26
|
from src.core.types import Violation
|
|
27
27
|
from src.linter_config.ignore import get_ignore_parser
|
|
28
28
|
|
|
@@ -106,11 +106,16 @@ class NestingDepthRule(MultiLanguageLintRule):
|
|
|
106
106
|
Returns:
|
|
107
107
|
List of violations found in Python code
|
|
108
108
|
"""
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
return with_parsed_python(
|
|
110
|
+
context,
|
|
111
|
+
self._violation_builder,
|
|
112
|
+
lambda tree: self._analyze_python_tree(tree, config, context),
|
|
113
|
+
)
|
|
113
114
|
|
|
115
|
+
def _analyze_python_tree(
|
|
116
|
+
self, tree: Any, config: NestingConfig, context: BaseLintContext
|
|
117
|
+
) -> list[Violation]:
|
|
118
|
+
"""Analyze parsed Python AST for nesting violations."""
|
|
114
119
|
functions = self._python_analyzer.find_all_functions(tree)
|
|
115
120
|
return self._process_python_functions(functions, self._python_analyzer, config, context)
|
|
116
121
|
|
|
@@ -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 "")
|