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.
Files changed (204) hide show
  1. src/__init__.py +1 -0
  2. src/analyzers/__init__.py +4 -3
  3. src/analyzers/ast_utils.py +54 -0
  4. src/analyzers/rust_base.py +155 -0
  5. src/analyzers/rust_context.py +141 -0
  6. src/analyzers/typescript_base.py +4 -0
  7. src/cli/__init__.py +30 -0
  8. src/cli/__main__.py +22 -0
  9. src/cli/config.py +480 -0
  10. src/cli/config_merge.py +241 -0
  11. src/cli/linters/__init__.py +67 -0
  12. src/cli/linters/code_patterns.py +270 -0
  13. src/cli/linters/code_smells.py +342 -0
  14. src/cli/linters/documentation.py +83 -0
  15. src/cli/linters/performance.py +287 -0
  16. src/cli/linters/shared.py +331 -0
  17. src/cli/linters/structure.py +327 -0
  18. src/cli/linters/structure_quality.py +328 -0
  19. src/cli/main.py +120 -0
  20. src/cli/utils.py +395 -0
  21. src/cli_main.py +37 -0
  22. src/config.py +38 -25
  23. src/core/base.py +7 -2
  24. src/core/cli_utils.py +19 -2
  25. src/core/config_parser.py +5 -2
  26. src/core/constants.py +54 -0
  27. src/core/linter_utils.py +95 -6
  28. src/core/python_lint_rule.py +101 -0
  29. src/core/registry.py +1 -1
  30. src/core/rule_discovery.py +147 -84
  31. src/core/types.py +13 -0
  32. src/core/violation_builder.py +78 -15
  33. src/core/violation_utils.py +69 -0
  34. src/formatters/__init__.py +22 -0
  35. src/formatters/sarif.py +202 -0
  36. src/linter_config/directive_markers.py +109 -0
  37. src/linter_config/ignore.py +254 -395
  38. src/linter_config/loader.py +45 -12
  39. src/linter_config/pattern_utils.py +65 -0
  40. src/linter_config/rule_matcher.py +89 -0
  41. src/linters/collection_pipeline/__init__.py +90 -0
  42. src/linters/collection_pipeline/any_all_analyzer.py +281 -0
  43. src/linters/collection_pipeline/ast_utils.py +40 -0
  44. src/linters/collection_pipeline/config.py +75 -0
  45. src/linters/collection_pipeline/continue_analyzer.py +94 -0
  46. src/linters/collection_pipeline/detector.py +360 -0
  47. src/linters/collection_pipeline/filter_map_analyzer.py +402 -0
  48. src/linters/collection_pipeline/linter.py +420 -0
  49. src/linters/collection_pipeline/suggestion_builder.py +130 -0
  50. src/linters/cqs/__init__.py +54 -0
  51. src/linters/cqs/config.py +55 -0
  52. src/linters/cqs/function_analyzer.py +201 -0
  53. src/linters/cqs/input_detector.py +139 -0
  54. src/linters/cqs/linter.py +159 -0
  55. src/linters/cqs/output_detector.py +84 -0
  56. src/linters/cqs/python_analyzer.py +54 -0
  57. src/linters/cqs/types.py +82 -0
  58. src/linters/cqs/typescript_cqs_analyzer.py +61 -0
  59. src/linters/cqs/typescript_function_analyzer.py +192 -0
  60. src/linters/cqs/typescript_input_detector.py +203 -0
  61. src/linters/cqs/typescript_output_detector.py +117 -0
  62. src/linters/cqs/violation_builder.py +94 -0
  63. src/linters/dry/base_token_analyzer.py +16 -9
  64. src/linters/dry/block_filter.py +120 -20
  65. src/linters/dry/block_grouper.py +4 -0
  66. src/linters/dry/cache.py +104 -10
  67. src/linters/dry/cache_query.py +4 -0
  68. src/linters/dry/config.py +54 -11
  69. src/linters/dry/constant.py +92 -0
  70. src/linters/dry/constant_matcher.py +223 -0
  71. src/linters/dry/constant_violation_builder.py +98 -0
  72. src/linters/dry/duplicate_storage.py +5 -4
  73. src/linters/dry/file_analyzer.py +4 -2
  74. src/linters/dry/inline_ignore.py +7 -16
  75. src/linters/dry/linter.py +183 -48
  76. src/linters/dry/python_analyzer.py +60 -439
  77. src/linters/dry/python_constant_extractor.py +100 -0
  78. src/linters/dry/single_statement_detector.py +417 -0
  79. src/linters/dry/token_hasher.py +116 -112
  80. src/linters/dry/typescript_analyzer.py +68 -382
  81. src/linters/dry/typescript_constant_extractor.py +138 -0
  82. src/linters/dry/typescript_statement_detector.py +255 -0
  83. src/linters/dry/typescript_value_extractor.py +70 -0
  84. src/linters/dry/violation_builder.py +4 -0
  85. src/linters/dry/violation_filter.py +5 -4
  86. src/linters/dry/violation_generator.py +71 -14
  87. src/linters/file_header/atemporal_detector.py +68 -50
  88. src/linters/file_header/base_parser.py +93 -0
  89. src/linters/file_header/bash_parser.py +66 -0
  90. src/linters/file_header/config.py +90 -16
  91. src/linters/file_header/css_parser.py +70 -0
  92. src/linters/file_header/field_validator.py +36 -33
  93. src/linters/file_header/linter.py +140 -144
  94. src/linters/file_header/markdown_parser.py +130 -0
  95. src/linters/file_header/python_parser.py +14 -58
  96. src/linters/file_header/typescript_parser.py +73 -0
  97. src/linters/file_header/violation_builder.py +13 -12
  98. src/linters/file_placement/config_loader.py +3 -1
  99. src/linters/file_placement/directory_matcher.py +4 -0
  100. src/linters/file_placement/linter.py +66 -34
  101. src/linters/file_placement/pattern_matcher.py +41 -6
  102. src/linters/file_placement/pattern_validator.py +31 -12
  103. src/linters/file_placement/rule_checker.py +12 -7
  104. src/linters/lazy_ignores/__init__.py +43 -0
  105. src/linters/lazy_ignores/config.py +74 -0
  106. src/linters/lazy_ignores/directive_utils.py +164 -0
  107. src/linters/lazy_ignores/header_parser.py +177 -0
  108. src/linters/lazy_ignores/linter.py +158 -0
  109. src/linters/lazy_ignores/matcher.py +168 -0
  110. src/linters/lazy_ignores/python_analyzer.py +209 -0
  111. src/linters/lazy_ignores/rule_id_utils.py +180 -0
  112. src/linters/lazy_ignores/skip_detector.py +298 -0
  113. src/linters/lazy_ignores/types.py +71 -0
  114. src/linters/lazy_ignores/typescript_analyzer.py +146 -0
  115. src/linters/lazy_ignores/violation_builder.py +135 -0
  116. src/linters/lbyl/__init__.py +31 -0
  117. src/linters/lbyl/config.py +63 -0
  118. src/linters/lbyl/linter.py +67 -0
  119. src/linters/lbyl/pattern_detectors/__init__.py +53 -0
  120. src/linters/lbyl/pattern_detectors/base.py +63 -0
  121. src/linters/lbyl/pattern_detectors/dict_key_detector.py +107 -0
  122. src/linters/lbyl/pattern_detectors/division_check_detector.py +232 -0
  123. src/linters/lbyl/pattern_detectors/file_exists_detector.py +220 -0
  124. src/linters/lbyl/pattern_detectors/hasattr_detector.py +119 -0
  125. src/linters/lbyl/pattern_detectors/isinstance_detector.py +119 -0
  126. src/linters/lbyl/pattern_detectors/len_check_detector.py +173 -0
  127. src/linters/lbyl/pattern_detectors/none_check_detector.py +146 -0
  128. src/linters/lbyl/pattern_detectors/string_validator_detector.py +145 -0
  129. src/linters/lbyl/python_analyzer.py +215 -0
  130. src/linters/lbyl/violation_builder.py +354 -0
  131. src/linters/magic_numbers/context_analyzer.py +227 -225
  132. src/linters/magic_numbers/linter.py +28 -82
  133. src/linters/magic_numbers/python_analyzer.py +4 -16
  134. src/linters/magic_numbers/typescript_analyzer.py +9 -12
  135. src/linters/magic_numbers/typescript_ignore_checker.py +81 -0
  136. src/linters/method_property/__init__.py +49 -0
  137. src/linters/method_property/config.py +138 -0
  138. src/linters/method_property/linter.py +414 -0
  139. src/linters/method_property/python_analyzer.py +473 -0
  140. src/linters/method_property/violation_builder.py +119 -0
  141. src/linters/nesting/linter.py +24 -16
  142. src/linters/nesting/python_analyzer.py +4 -0
  143. src/linters/nesting/typescript_analyzer.py +6 -12
  144. src/linters/nesting/violation_builder.py +1 -0
  145. src/linters/performance/__init__.py +91 -0
  146. src/linters/performance/config.py +43 -0
  147. src/linters/performance/constants.py +49 -0
  148. src/linters/performance/linter.py +149 -0
  149. src/linters/performance/python_analyzer.py +365 -0
  150. src/linters/performance/regex_analyzer.py +312 -0
  151. src/linters/performance/regex_linter.py +139 -0
  152. src/linters/performance/typescript_analyzer.py +236 -0
  153. src/linters/performance/violation_builder.py +160 -0
  154. src/linters/print_statements/config.py +7 -12
  155. src/linters/print_statements/linter.py +26 -43
  156. src/linters/print_statements/python_analyzer.py +91 -93
  157. src/linters/print_statements/typescript_analyzer.py +15 -25
  158. src/linters/print_statements/violation_builder.py +12 -14
  159. src/linters/srp/class_analyzer.py +11 -7
  160. src/linters/srp/heuristics.py +56 -22
  161. src/linters/srp/linter.py +15 -16
  162. src/linters/srp/python_analyzer.py +55 -20
  163. src/linters/srp/typescript_metrics_calculator.py +110 -50
  164. src/linters/stateless_class/__init__.py +25 -0
  165. src/linters/stateless_class/config.py +58 -0
  166. src/linters/stateless_class/linter.py +349 -0
  167. src/linters/stateless_class/python_analyzer.py +290 -0
  168. src/linters/stringly_typed/__init__.py +36 -0
  169. src/linters/stringly_typed/config.py +189 -0
  170. src/linters/stringly_typed/context_filter.py +451 -0
  171. src/linters/stringly_typed/function_call_violation_builder.py +135 -0
  172. src/linters/stringly_typed/ignore_checker.py +100 -0
  173. src/linters/stringly_typed/ignore_utils.py +51 -0
  174. src/linters/stringly_typed/linter.py +376 -0
  175. src/linters/stringly_typed/python/__init__.py +33 -0
  176. src/linters/stringly_typed/python/analyzer.py +348 -0
  177. src/linters/stringly_typed/python/call_tracker.py +175 -0
  178. src/linters/stringly_typed/python/comparison_tracker.py +257 -0
  179. src/linters/stringly_typed/python/condition_extractor.py +134 -0
  180. src/linters/stringly_typed/python/conditional_detector.py +179 -0
  181. src/linters/stringly_typed/python/constants.py +21 -0
  182. src/linters/stringly_typed/python/match_analyzer.py +94 -0
  183. src/linters/stringly_typed/python/validation_detector.py +189 -0
  184. src/linters/stringly_typed/python/variable_extractor.py +96 -0
  185. src/linters/stringly_typed/storage.py +620 -0
  186. src/linters/stringly_typed/storage_initializer.py +45 -0
  187. src/linters/stringly_typed/typescript/__init__.py +28 -0
  188. src/linters/stringly_typed/typescript/analyzer.py +157 -0
  189. src/linters/stringly_typed/typescript/call_tracker.py +335 -0
  190. src/linters/stringly_typed/typescript/comparison_tracker.py +378 -0
  191. src/linters/stringly_typed/violation_generator.py +419 -0
  192. src/orchestrator/core.py +252 -14
  193. src/orchestrator/language_detector.py +5 -3
  194. src/templates/thailint_config_template.yaml +196 -0
  195. src/utils/project_root.py +3 -0
  196. thailint-0.15.3.dist-info/METADATA +187 -0
  197. thailint-0.15.3.dist-info/RECORD +226 -0
  198. thailint-0.15.3.dist-info/entry_points.txt +4 -0
  199. src/cli.py +0 -1665
  200. thailint-0.5.0.dist-info/METADATA +0 -1286
  201. thailint-0.5.0.dist-info/RECORD +0 -96
  202. thailint-0.5.0.dist-info/entry_points.txt +0 -4
  203. {thailint-0.5.0.dist-info → thailint-0.15.3.dist-info}/WHEEL +0 -0
  204. {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
- from src.analyzers.typescript_base import TypeScriptBaseAnalyzer
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
- TREE_SITTER_AVAILABLE = True
30
- except ImportError:
31
- TREE_SITTER_AVAILABLE = False
32
- Node = Any # type: ignore
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 "")