thailint 0.9.0__py3-none-any.whl → 0.10.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.
@@ -30,7 +30,7 @@ SRP Exception: TypeScriptDuplicateAnalyzer has 20 methods and 324 lines (exceeds
30
30
  responsibility: accurately detecting duplicate TypeScript/JavaScript code while minimizing false positives.
31
31
  """
32
32
 
33
- from collections.abc import Generator
33
+ from collections.abc import Generator, Iterable
34
34
  from pathlib import Path
35
35
 
36
36
  from src.analyzers.typescript_base import TREE_SITTER_AVAILABLE
@@ -84,16 +84,33 @@ class TypeScriptDuplicateAnalyzer(BaseTokenAnalyzer): # thailint: ignore[srp.vi
84
84
  # Generate rolling hash windows
85
85
  windows = self._rolling_hash_with_tracking(lines_with_numbers, config.min_duplicate_lines)
86
86
 
87
- blocks = []
88
- for hash_val, start_line, end_line, snippet in windows:
89
- # Filter interface/type definitions
90
- if not self._should_include_block(content, start_line, end_line):
91
- continue
87
+ # Filter out interface/type definitions and single statement patterns
88
+ valid_windows = (
89
+ (hash_val, start_line, end_line, snippet)
90
+ for hash_val, start_line, end_line, snippet in windows
91
+ if self._should_include_block(content, start_line, end_line)
92
+ and not self._is_single_statement_in_source(content, start_line, end_line)
93
+ )
94
+ return self._build_blocks(valid_windows, file_path, content)
95
+
96
+ def _build_blocks(
97
+ self,
98
+ windows: Iterable[tuple[int, int, int, str]],
99
+ file_path: Path,
100
+ content: str,
101
+ ) -> list[CodeBlock]:
102
+ """Build CodeBlock objects from valid windows, applying filters.
92
103
 
93
- # Filter single statement patterns
94
- if self._is_single_statement_in_source(content, start_line, end_line):
95
- continue
104
+ Args:
105
+ windows: Iterable of (hash_val, start_line, end_line, snippet) tuples
106
+ file_path: Path to source file
107
+ content: File content
96
108
 
109
+ Returns:
110
+ List of CodeBlock instances that pass all filters
111
+ """
112
+ blocks = []
113
+ for hash_val, start_line, end_line, snippet in windows:
97
114
  block = CodeBlock(
98
115
  file_path=file_path,
99
116
  start_line=start_line,
@@ -101,13 +118,8 @@ class TypeScriptDuplicateAnalyzer(BaseTokenAnalyzer): # thailint: ignore[srp.vi
101
118
  snippet=snippet,
102
119
  hash_value=hash_val,
103
120
  )
104
-
105
- # Apply extensible filters (keyword arguments, imports, etc.)
106
- if self._filter_registry.should_filter_block(block, content):
107
- continue
108
-
109
- blocks.append(block)
110
-
121
+ if not self._filter_registry.should_filter_block(block, content):
122
+ blocks.append(block)
111
123
  return blocks
112
124
 
113
125
  def _get_jsdoc_ranges_from_content(self, content: str) -> set[int]:
@@ -188,26 +200,44 @@ class TypeScriptDuplicateAnalyzer(BaseTokenAnalyzer): # thailint: ignore[srp.vi
188
200
  lines_with_numbers = []
189
201
  in_multiline_import = False
190
202
 
191
- for line_num, line in enumerate(content.split("\n"), start=1):
192
- # Skip JSDoc comment lines
193
- if line_num in jsdoc_lines:
194
- continue
195
-
196
- line = self._hasher._normalize_line(line) # pylint: disable=protected-access
197
- if not line:
198
- continue
199
-
200
- # Update multi-line import state and check if line should be skipped
201
- in_multiline_import, should_skip = self._hasher._should_skip_import_line( # pylint: disable=protected-access
203
+ # Skip JSDoc comment lines
204
+ non_jsdoc_lines = (
205
+ (line_num, line)
206
+ for line_num, line in enumerate(content.split("\n"), start=1)
207
+ if line_num not in jsdoc_lines
208
+ )
209
+ for line_num, line in non_jsdoc_lines:
210
+ in_multiline_import, normalized = self._normalize_and_filter_line(
202
211
  line, in_multiline_import
203
212
  )
204
- if should_skip:
205
- continue
206
-
207
- lines_with_numbers.append((line_num, line))
213
+ if normalized is not None:
214
+ lines_with_numbers.append((line_num, normalized))
208
215
 
209
216
  return lines_with_numbers
210
217
 
218
+ def _normalize_and_filter_line(
219
+ self, line: str, in_multiline_import: bool
220
+ ) -> tuple[bool, str | None]:
221
+ """Normalize line and check if it should be included.
222
+
223
+ Args:
224
+ line: Raw source line
225
+ in_multiline_import: Current multi-line import state
226
+
227
+ Returns:
228
+ Tuple of (new_import_state, normalized_line or None if should skip)
229
+ """
230
+ normalized = self._hasher._normalize_line(line) # pylint: disable=protected-access
231
+ if not normalized:
232
+ return in_multiline_import, None
233
+
234
+ new_state, should_skip = self._hasher._should_skip_import_line( # pylint: disable=protected-access
235
+ normalized, in_multiline_import
236
+ )
237
+ if should_skip:
238
+ return new_state, None
239
+ return new_state, normalized
240
+
211
241
  def _rolling_hash_with_tracking(
212
242
  self, lines_with_numbers: list[tuple[int, str]], window_size: int
213
243
  ) -> list[tuple[int, int, int, str]]:
@@ -273,17 +273,13 @@ class FileHeaderRule(BaseLintRule): # thailint: ignore[srp]
273
273
  file_content = context.file_content or ""
274
274
  lines = file_content.splitlines()
275
275
 
276
- filtered = []
277
- for v in violations:
278
- if self._ignore_parser.should_ignore_violation(v, file_content):
279
- continue
280
-
281
- if self._has_line_level_ignore(lines, v):
282
- continue
283
-
284
- filtered.append(v)
285
-
286
- return filtered
276
+ non_ignored = (
277
+ v
278
+ for v in violations
279
+ if not self._ignore_parser.should_ignore_violation(v, file_content)
280
+ and not self._has_line_level_ignore(lines, v)
281
+ )
282
+ return list(non_ignored)
287
283
 
288
284
  def _has_line_level_ignore(self, lines: list[str], violation: Violation) -> bool:
289
285
  """Check for thailint-ignore-line directive."""
@@ -124,20 +124,40 @@ class FilePlacementLinter:
124
124
  Returns:
125
125
  List of all violations found
126
126
  """
127
+ valid_files = self._get_valid_files(dir_path, recursive)
128
+ return self._lint_files(valid_files)
129
+
130
+ def _get_valid_files(self, dir_path: Path, recursive: bool) -> list[Path]:
131
+ """Get list of valid files to lint from directory.
132
+
133
+ Args:
134
+ dir_path: Directory to scan
135
+ recursive: Scan recursively
136
+
137
+ Returns:
138
+ List of file paths to lint
139
+ """
127
140
  from src.linter_config.ignore import IgnoreDirectiveParser
128
141
 
129
142
  ignore_parser = IgnoreDirectiveParser(self.project_root)
130
143
  pattern = "**/*" if recursive else "*"
131
144
 
132
- violations = []
133
- for file_path in dir_path.glob(pattern):
134
- if not file_path.is_file():
135
- continue
136
- if ignore_parser.is_ignored(file_path):
137
- continue
138
- file_violations = self.lint_path(file_path)
139
- violations.extend(file_violations)
145
+ return [
146
+ f for f in dir_path.glob(pattern) if f.is_file() and not ignore_parser.is_ignored(f)
147
+ ]
148
+
149
+ def _lint_files(self, file_paths: list[Path]) -> list[Violation]:
150
+ """Lint multiple files and collect violations.
151
+
152
+ Args:
153
+ file_paths: List of file paths to lint
140
154
 
155
+ Returns:
156
+ List of all violations found
157
+ """
158
+ violations = []
159
+ for file_path in file_paths:
160
+ violations.extend(self.lint_path(file_path))
141
161
  return violations
142
162
 
143
163
 
@@ -33,9 +33,10 @@ def count_methods(class_node: ast.ClassDef) -> int:
33
33
  Number of methods in the class
34
34
  """
35
35
  methods = 0
36
- for node in class_node.body:
37
- if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
38
- continue
36
+ func_nodes = (
37
+ n for n in class_node.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))
38
+ )
39
+ for node in func_nodes:
39
40
  # Don't count @property decorators as methods
40
41
  if not has_property_decorator(node):
41
42
  methods += 1
src/linters/srp/linter.py CHANGED
@@ -171,9 +171,8 @@ class SRPRule(MultiLanguageLintRule):
171
171
  List of violations
172
172
  """
173
173
  violations = []
174
- for metrics in metrics_list:
175
- if not isinstance(metrics, dict):
176
- continue
174
+ valid_metrics = (m for m in metrics_list if isinstance(m, dict))
175
+ for metrics in valid_metrics:
177
176
  violation = self._create_violation_if_needed(metrics, config, context)
178
177
  if violation:
179
178
  violations.append(violation)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thailint
3
- Version: 0.9.0
3
+ Version: 0.10.0
4
4
  Summary: The AI Linter - Enterprise-grade linting and governance for AI-generated code across multiple languages
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -37,8 +37,8 @@ Description-Content-Type: text/markdown
37
37
 
38
38
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
39
39
  [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
40
- [![Tests](https://img.shields.io/badge/tests-728%2F728%20passing-brightgreen.svg)](tests/)
41
- [![Coverage](https://img.shields.io/badge/coverage-87%25-brightgreen.svg)](htmlcov/)
40
+ [![Tests](https://img.shields.io/badge/tests-795%2F795%20passing-brightgreen.svg)](tests/)
41
+ [![Coverage](https://img.shields.io/badge/coverage-88%25-brightgreen.svg)](htmlcov/)
42
42
  [![Documentation Status](https://readthedocs.org/projects/thai-lint/badge/?version=latest)](https://thai-lint.readthedocs.io/en/latest/?badge=latest)
43
43
  [![SARIF 2.1.0](https://img.shields.io/badge/SARIF-2.1.0-orange.svg)](docs/sarif-output.md)
44
44
 
@@ -98,6 +98,12 @@ thailint complements your existing linting stack by catching the patterns AI too
98
98
  - Configurable thresholds (lines, tokens, occurrences)
99
99
  - Language-specific detection (Python, TypeScript, JavaScript)
100
100
  - False positive filtering (keyword args, imports)
101
+ - **Collection Pipeline Linting** - Detect for loops with embedded filtering
102
+ - Based on Martin Fowler's "Replace Loop with Pipeline" refactoring
103
+ - Detects if/continue patterns that should use generator expressions
104
+ - Generates refactoring suggestions with generator syntax
105
+ - Configurable threshold (min_continues)
106
+ - Python support with AST analysis
101
107
  - **Method Property Linting** - Detect methods that should be @property decorators
102
108
  - Python AST-based detection
103
109
  - get_* prefix detection (Java-style getters)
@@ -741,6 +747,109 @@ Built-in filters automatically exclude common non-duplication patterns:
741
747
 
742
748
  See [DRY Linter Guide](https://thai-lint.readthedocs.io/en/latest/dry-linter/) for comprehensive documentation, storage modes, and refactoring patterns.
743
749
 
750
+ ## Collection Pipeline Linter
751
+
752
+ ### Overview
753
+
754
+ The collection-pipeline linter detects for loops with embedded filtering (if/continue patterns) that should be refactored to use generator expressions or other collection pipelines. Based on Martin Fowler's "Replace Loop with Pipeline" refactoring pattern.
755
+
756
+ ### The Anti-Pattern
757
+
758
+ ```python
759
+ # Anti-pattern: Embedded filtering in loop body
760
+ for file_path in dir_path.glob(pattern):
761
+ if not file_path.is_file():
762
+ continue
763
+ if ignore_parser.is_ignored(file_path):
764
+ continue
765
+ violations.extend(lint_file(file_path))
766
+ ```
767
+
768
+ ### The Solution
769
+
770
+ ```python
771
+ # Collection pipeline: Filtering separated from processing
772
+ valid_files = (
773
+ f for f in dir_path.glob(pattern)
774
+ if f.is_file() and not ignore_parser.is_ignored(f)
775
+ )
776
+ for file_path in valid_files:
777
+ violations.extend(lint_file(file_path))
778
+ ```
779
+
780
+ ### Quick Start
781
+
782
+ ```bash
783
+ # Check current directory
784
+ thailint pipeline .
785
+
786
+ # Check specific directory
787
+ thailint pipeline src/
788
+
789
+ # Only flag patterns with 2+ filter conditions
790
+ thailint pipeline --min-continues 2 src/
791
+
792
+ # JSON output
793
+ thailint pipeline --format json src/
794
+ ```
795
+
796
+ ### Configuration
797
+
798
+ ```yaml
799
+ # .thailint.yaml
800
+ collection-pipeline:
801
+ enabled: true
802
+ min_continues: 1 # Minimum if/continue patterns to flag
803
+ ignore:
804
+ - "tests/**"
805
+ - "**/legacy/**"
806
+ ```
807
+
808
+ ### Example Violation
809
+
810
+ **Detected Pattern:**
811
+ ```python
812
+ def process_files(paths):
813
+ for path in paths:
814
+ if not path.is_file():
815
+ continue
816
+ analyze(path)
817
+ ```
818
+
819
+ **Violation Message:**
820
+ ```
821
+ src/processor.py:3 - For loop over 'paths' has embedded filtering.
822
+ Consider using a generator expression:
823
+ for path in (path for path in paths if path.is_file()):
824
+ ```
825
+
826
+ **Refactored Code:**
827
+ ```python
828
+ def process_files(paths):
829
+ valid_paths = (p for p in paths if p.is_file())
830
+ for path in valid_paths:
831
+ analyze(path)
832
+ ```
833
+
834
+ ### Why This Matters
835
+
836
+ - **Separation of concerns**: Filtering logic is separate from processing logic
837
+ - **Readability**: Intent is clear at a glance
838
+ - **Testability**: Filtering can be tested independently
839
+ - **Based on**: Martin Fowler's "Replace Loop with Pipeline" refactoring
840
+
841
+ ### Ignoring Violations
842
+
843
+ ```python
844
+ # Line-level ignore
845
+ for item in items: # thailint: ignore[collection-pipeline]
846
+ if not item.valid:
847
+ continue
848
+ process(item)
849
+ ```
850
+
851
+ See [Collection Pipeline Linter Guide](docs/collection-pipeline-linter.md) for comprehensive documentation and refactoring patterns.
852
+
744
853
  ## Magic Numbers Linter
745
854
 
746
855
  ### Overview
@@ -1351,6 +1460,9 @@ docker run --rm -v $(pwd):/data washad/thailint:latest nesting /data/src/file1.p
1351
1460
  # Lint specific subdirectory
1352
1461
  docker run --rm -v $(pwd):/data washad/thailint:latest nesting /data/src
1353
1462
 
1463
+ # Collection pipeline linter
1464
+ docker run --rm -v $(pwd):/data washad/thailint:latest pipeline /data/src
1465
+
1354
1466
  # With custom config
1355
1467
  docker run --rm -v $(pwd):/data \
1356
1468
  washad/thailint:latest nesting --config /data/.thailint.yaml /data
@@ -1414,6 +1526,7 @@ docker run --rm -v /path/to/workspace:/workspace \
1414
1526
  - **[Nesting Depth Linter](https://thai-lint.readthedocs.io/en/latest/nesting-linter/)** - Nesting depth analysis guide
1415
1527
  - **[SRP Linter](https://thai-lint.readthedocs.io/en/latest/srp-linter/)** - Single Responsibility Principle guide
1416
1528
  - **[DRY Linter](https://thai-lint.readthedocs.io/en/latest/dry-linter/)** - Duplicate code detection guide
1529
+ - **[Collection Pipeline Linter](https://thai-lint.readthedocs.io/en/latest/collection-pipeline-linter/)** - Loop filtering refactoring guide
1417
1530
  - **[Method Property Linter](https://thai-lint.readthedocs.io/en/latest/method-property-linter/)** - Method-to-property conversion guide
1418
1531
  - **[Stateless Class Linter](https://thai-lint.readthedocs.io/en/latest/stateless-class-linter/)** - Stateless class detection guide
1419
1532
  - **[Pre-commit Hooks](https://thai-lint.readthedocs.io/en/latest/pre-commit-hooks/)** - Automated quality checks
@@ -2,15 +2,15 @@ src/__init__.py,sha256=f601zncODr2twrUHqTLS5wyOdZqZi9tMjAe2INhRKqU,2175
2
2
  src/analyzers/__init__.py,sha256=fFloZtjkBGwYbAhKTxS3Qy3yDr2_3i3WSfKTw1mAioo,972
3
3
  src/analyzers/typescript_base.py,sha256=4I7fAcMOAY9vY1AXh52QpohgFmguBECwOkvBRP4zCS4,5054
4
4
  src/api.py,sha256=pJ5l3qxccKBEY-BkANwzTgLAl1ZFq7OP6hx6LSxbhDw,4664
5
- src/cli.py,sha256=ABjxAdnQeyFb9a-d1B0yIidewCxVpCY7n9fYEmgsSUM,63240
6
- src/config.py,sha256=2ebAjIpAhw4bHbOxViEA5nCjfBlDEIrMR59DBrzcYzM,12460
5
+ src/cli.py,sha256=NoCEg1jLDkS7My4XhUfrssQmBGX-EaC2ULMwQs23Rt0,67580
6
+ src/config.py,sha256=gQ2DoBSZra4Dw3zUVd-t0lyD_pOY4iufx7sL_sXGBGU,12482
7
7
  src/core/__init__.py,sha256=5FtsDvhMt4SNRx3pbcGURrxn135XRbeRrjSUxiXwkNc,381
8
8
  src/core/base.py,sha256=7bb_CAEohgEFXDgIK6Qc_zt6Cz4U9K6uhLhunbMX8f0,7966
9
9
  src/core/cli_utils.py,sha256=vKw0jF1rZv_N7gbzvV5TeO9rV5VPfd89fneKrglQ2Hs,6502
10
10
  src/core/config_parser.py,sha256=zAY4bDptNlVID0a4JDXN0YlUKXLM92cFqTAwhp_8uGc,4183
11
11
  src/core/linter_utils.py,sha256=4jmC2YfpPvGhS_XHlHXa5SBIJh9CQlNj5zuW_GpdPKc,5273
12
12
  src/core/registry.py,sha256=yRA8mQLiZwjmgxl1wSTgdj1cuo_QXuRdrXt3NpCBUgE,3285
13
- src/core/rule_discovery.py,sha256=MWsyylfQqUBIayyQfd1Vu5RhQzGQ6IqxjYn7KsagCdk,4439
13
+ src/core/rule_discovery.py,sha256=GL6X-38qiqmPHpk96iOCrFOLjENIze-QamO0WwugZEg,5365
14
14
  src/core/types.py,sha256=dIYLaCDNtCAzVaplx5S5yxywkLIuX0BN9No_l2zCfV0,2887
15
15
  src/core/violation_builder.py,sha256=le9YCMcJt0hFji41-cCplpowys-jT44kGe-59_Du1ZM,6592
16
16
  src/formatters/__init__.py,sha256=yE1yIL8lplTMEjsmQm7F-kOMaYq7OjmbFuiwwK0D-gM,815
@@ -19,9 +19,15 @@ src/linter_config/__init__.py,sha256=_I2VVlZlfKyT-tKukuUA5-aVcHLOe3m6C2cev43AiEc
19
19
  src/linter_config/ignore.py,sha256=S2Ub0CCOOC-wpU5Y_EodMprciw18fgWcnp4z_h1MYNk,19638
20
20
  src/linter_config/loader.py,sha256=K6mKRkP2jgwar-pwBoJGWgwynLVjqdez-l3Nd6bUCMk,3363
21
21
  src/linters/__init__.py,sha256=-nnNsL8E5-2p9qlLKp_TaShHAjPH-NacOEU1sXmAR9k,77
22
+ src/linters/collection_pipeline/__init__.py,sha256=BcnbY3wgJB1XLfZ9J9qfUJQ1_yCo_THjGDTppxJEMZY,3231
23
+ src/linters/collection_pipeline/config.py,sha256=Z3V84X2rlj27x1eZfFBJwOMzoqFzOiwdjk-ze5sDop0,2240
24
+ src/linters/collection_pipeline/continue_analyzer.py,sha256=51DBvR92no01rlMOjukGhsLX8jtuLbrEU--fbehUW6A,2812
25
+ src/linters/collection_pipeline/detector.py,sha256=67M0LZwBHqZHNy5E6OS1CnBFVqis1LzkO0YP615BFr4,4291
26
+ src/linters/collection_pipeline/linter.py,sha256=jvLqKsBAFj_QRrQVBTTMdLzDOjn2H2YlD9cSrdfbJbk,13947
27
+ src/linters/collection_pipeline/suggestion_builder.py,sha256=xwMWFCTm8UWvn_6XAJerH68XayJJGWiPFmpe-NztUlc,2103
22
28
  src/linters/dry/__init__.py,sha256=p58tN3z_VbulfTkRm1kLZJ43Bemt66T2sro1teirUY8,826
23
29
  src/linters/dry/base_token_analyzer.py,sha256=SgnsX4a4kUzOsA9hTkLkg4yB8I77ewAyiUp6cAybUrg,2684
24
- src/linters/dry/block_filter.py,sha256=9hTPWbtO432qMPdgwHz4ZB-sFyV0kFI6hewBOW1mrB4,8382
30
+ src/linters/dry/block_filter.py,sha256=sSCaut6U8sZQUhpFKey4qxgdB0E43_8X2K_lkhIWIXE,8297
25
31
  src/linters/dry/block_grouper.py,sha256=NP66BlofaY7HVXcWwmK5lyiNXbaTlU1V3IbcZubIq_I,1937
26
32
  src/linters/dry/cache.py,sha256=dedkGU5SolTjmTTsmj-dwPEnxyhWZ2aBiHGkQy1JwXo,5881
27
33
  src/linters/dry/cache_query.py,sha256=qu_uHe360ZvKmFTBvfREjjPMGbJgLQsFTKPVIA2jQJ0,1949
@@ -32,10 +38,10 @@ src/linters/dry/duplicate_storage.py,sha256=9pIALnwAuz5BJUYNXrPbObbP932CE9x0vgUk
32
38
  src/linters/dry/file_analyzer.py,sha256=ufSQ85ddsGTqGnBHZNTdV_5DGfTpUmJOB58sIdJNV0I,2928
33
39
  src/linters/dry/inline_ignore.py,sha256=ASfA-fp_1aPpkakN2e0T6qdTh8S7Jqj89ovxXJLmFlc,4439
34
40
  src/linters/dry/linter.py,sha256=XMLwCgGrFX0l0dVUJs1jpsXOfgxeKKDbxOtN5h5Emhk,5835
35
- src/linters/dry/python_analyzer.py,sha256=jmMUw2YhwkRrAJJDHaHKffGUjCiz5OzWwv_ZJrzNXqw,27367
41
+ src/linters/dry/python_analyzer.py,sha256=S0eEpBcMI0ufyMKfK0Lep-VgJ9-xt0M9525o_4hXDaQ,28025
36
42
  src/linters/dry/storage_initializer.py,sha256=ykMALFs4uMUrN0_skEwySDl_t5Dm_LGHllF0OxDhiUI,1366
37
43
  src/linters/dry/token_hasher.py,sha256=VT7BkzYMIWadvGrnL8bFlH3lzRfgdZSj5lDU364tRNA,5589
38
- src/linters/dry/typescript_analyzer.py,sha256=ShNoB2KfPe010wKEZoFxn-ZKh0MnRUwgADDQKQtfedI,21627
44
+ src/linters/dry/typescript_analyzer.py,sha256=ecAJZjNRtzw_XKyvXX9v53Ewyfuex_obVxmq5aBk0h4,22854
39
45
  src/linters/dry/violation_builder.py,sha256=WkCibSNytoqMHGC-3GrVff4PD7-SOnVzzZgkMeqmzco,2952
40
46
  src/linters/dry/violation_filter.py,sha256=RJVZZ3RWqJhIdkRQ8YcXXG9XGhiQmaiZ4_IzHWpbCG8,3265
41
47
  src/linters/dry/violation_generator.py,sha256=hbMs8Fo2hS8JCXicZcZni6NEkv4fJRsyrsjzrqN6qVw,6122
@@ -46,7 +52,7 @@ src/linters/file_header/bash_parser.py,sha256=y74YLSQ6FKXnkl9RCu31N3llDxeAH9wRgm
46
52
  src/linters/file_header/config.py,sha256=Ewrln4W4QDnInTgWr8WgSlQEjAuDyMbUuh9GHAa9a4c,4030
47
53
  src/linters/file_header/css_parser.py,sha256=ijpGMixg2ZqNWWdiZjSNtMXCOhm6XDfSY7OU68B9fS8,2332
48
54
  src/linters/file_header/field_validator.py,sha256=uASqHj7ID4JJZzgc6X3SmRRLWV35NnX2iZElCt3HW1o,2830
49
- src/linters/file_header/linter.py,sha256=rbfpHBCCn0cRKUyadc2luZSQay_gJfVC79JcqkwUMb4,12192
55
+ src/linters/file_header/linter.py,sha256=ffh-gMF9IW7syqE3EXuZUxo94RMP6Dfd97hGsS3Rabg,12153
50
56
  src/linters/file_header/markdown_parser.py,sha256=dmrB8JCxKTHyw-qMU6S-UjKaFbqJ6ZQY1f23tND5_Jo,4964
51
57
  src/linters/file_header/python_parser.py,sha256=RTOeEt1b3tCvFWbZIt89awQA37CUOSBIGagEYnayn-M,1432
52
58
  src/linters/file_header/typescript_parser.py,sha256=R11Vkr6dUVaU8t90m8rrkMzODtBYk7u-TYFsMDRwzX8,2532
@@ -54,7 +60,7 @@ src/linters/file_header/violation_builder.py,sha256=HPYTmrcCmcO6Dx5dhmj85zZgEBM5
54
60
  src/linters/file_placement/__init__.py,sha256=vJ43GZujcbAk-K3DwfsQZ0J3yP_5G35CKssatLyntXk,862
55
61
  src/linters/file_placement/config_loader.py,sha256=Of5sTG2S-04efn3KOlXrSxpMcC1ipBpSvCjtJOMmWno,2640
56
62
  src/linters/file_placement/directory_matcher.py,sha256=1rxJtCEzqDYDQnscVX6pzk7gxCMD11pVIGaWcli-tHY,2742
57
- src/linters/file_placement/linter.py,sha256=A4mndpyIyxEzq64rLw2ILNA7APx_QmwzUfnhB0PyuCs,14190
63
+ src/linters/file_placement/linter.py,sha256=VCqQN4aeYhAEPmzSzJGcAEVRPyoVVT9w3o1n3368oPo,14789
58
64
  src/linters/file_placement/path_resolver.py,sha256=S6g7xOYsoSc0O_RDJh8j4Z2klcwzp16rSUfEAErGOTI,1972
59
65
  src/linters/file_placement/pattern_matcher.py,sha256=0TWozhKGqQk66oJpP66ewlmOI3bI8GpDf37PyImZcUA,2030
60
66
  src/linters/file_placement/pattern_validator.py,sha256=KW2Wv4owIGiLxxC3hhyVsKkIuzC95pGv3wQD3m4_eYo,3853
@@ -88,8 +94,8 @@ src/linters/print_statements/violation_builder.py,sha256=Vs5m3AnWjrQqQHf6JJDaPP5
88
94
  src/linters/srp/__init__.py,sha256=GbhaSB2_AYY-mWgG_ThbyAcDXoVZuB5eLzguoShf38w,3367
89
95
  src/linters/srp/class_analyzer.py,sha256=vSBKLDBc734Iy-p9Ux7ygnNYaUtkT3ivUUJS54tyKs0,3912
90
96
  src/linters/srp/config.py,sha256=hTxrM21HIOmg0sM6eJ_h3hRnuxqRZEgs13Ie97-PDr4,3397
91
- src/linters/srp/heuristics.py,sha256=Fwrro3I0qOV4o0epM0ZkqgQUU9bK1b2igxoxWjReljU,3185
92
- src/linters/srp/linter.py,sha256=aXeMKmMf5PC5P2E-mstys2xndkrvpZF6tAq9xRdzOYo,7624
97
+ src/linters/srp/heuristics.py,sha256=0JxKitWXJo0BxubLGLPoKhr39h4bxkfos2633ypOXcs,3203
98
+ src/linters/srp/linter.py,sha256=Sqh4QPz6G14A9osLHFO4xegnZSYaCWucKZPNjXzvUYA,7627
93
99
  src/linters/srp/metrics_evaluator.py,sha256=Prk_dPacas_dX7spAzV0g734srmzT5u0t5d4mTG9g2o,1606
94
100
  src/linters/srp/python_analyzer.py,sha256=PH27l38BFPNmj22Z10QDBioLDCZ4xpJFzBfTh_4XMZ4,3585
95
101
  src/linters/srp/typescript_analyzer.py,sha256=Wi0P_G1v5AnZYtMN3sNm1iHva84-8Kep2LZ5RmAS4c4,2885
@@ -105,8 +111,8 @@ src/orchestrator/language_detector.py,sha256=rHyVMApit80NTTNyDH1ObD1usKD8LjGmH3D
105
111
  src/templates/thailint_config_template.yaml,sha256=vxyhRRi25_xOnHDRx0jzz69dgPqKU2IU5-YFGUoX5lM,4953
106
112
  src/utils/__init__.py,sha256=NiBtKeQ09Y3kuUzeN4O1JNfUIYPQDS2AP1l5ODq-Dec,125
107
113
  src/utils/project_root.py,sha256=b3YTEGTa9RPcOeHn1IByMMWyRiUabfVlpnlektL0A0o,6156
108
- thailint-0.9.0.dist-info/METADATA,sha256=zcAW2ZVfxz5MPGZlcs3EasPdflQZrdhb3Ev7R5xZkXU,45453
109
- thailint-0.9.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
110
- thailint-0.9.0.dist-info/entry_points.txt,sha256=l7DQJgU18sVLDpSaXOXY3lLhnQHQIRrSJZTQjG1cEAk,62
111
- thailint-0.9.0.dist-info/licenses/LICENSE,sha256=kxh1J0Sb62XvhNJ6MZsVNe8PqNVJ7LHRn_EWa-T3djw,1070
112
- thailint-0.9.0.dist-info/RECORD,,
114
+ thailint-0.10.0.dist-info/METADATA,sha256=ZeNW9Hi0Fvu5BB0Sr9Dro1fopDYG6C6hobA5nd3qTQs,48517
115
+ thailint-0.10.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
116
+ thailint-0.10.0.dist-info/entry_points.txt,sha256=l7DQJgU18sVLDpSaXOXY3lLhnQHQIRrSJZTQjG1cEAk,62
117
+ thailint-0.10.0.dist-info/licenses/LICENSE,sha256=kxh1J0Sb62XvhNJ6MZsVNe8PqNVJ7LHRn_EWa-T3djw,1070
118
+ thailint-0.10.0.dist-info/RECORD,,