thailint 0.11.0__py3-none-any.whl → 0.12.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/code_smells.py +114 -7
- src/cli/utils.py +29 -9
- src/linters/stringly_typed/__init__.py +22 -9
- src/linters/stringly_typed/config.py +28 -3
- src/linters/stringly_typed/context_filter.py +451 -0
- src/linters/stringly_typed/function_call_violation_builder.py +137 -0
- src/linters/stringly_typed/ignore_checker.py +102 -0
- src/linters/stringly_typed/ignore_utils.py +51 -0
- src/linters/stringly_typed/linter.py +344 -0
- src/linters/stringly_typed/python/__init__.py +9 -5
- src/linters/stringly_typed/python/analyzer.py +155 -9
- src/linters/stringly_typed/python/call_tracker.py +172 -0
- src/linters/stringly_typed/python/comparison_tracker.py +252 -0
- src/linters/stringly_typed/storage.py +630 -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 +329 -0
- src/linters/stringly_typed/typescript/comparison_tracker.py +372 -0
- src/linters/stringly_typed/violation_generator.py +376 -0
- {thailint-0.11.0.dist-info → thailint-0.12.0.dist-info}/METADATA +9 -3
- {thailint-0.11.0.dist-info → thailint-0.12.0.dist-info}/RECORD +25 -11
- {thailint-0.11.0.dist-info → thailint-0.12.0.dist-info}/WHEEL +0 -0
- {thailint-0.11.0.dist-info → thailint-0.12.0.dist-info}/entry_points.txt +0 -0
- {thailint-0.11.0.dist-info → thailint-0.12.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Violation generation from cross-file stringly-typed patterns
|
|
3
|
+
|
|
4
|
+
Scope: Generates violations from duplicate pattern hashes, function call patterns, and
|
|
5
|
+
scattered string comparisons
|
|
6
|
+
|
|
7
|
+
Overview: Handles violation generation for stringly-typed patterns that appear across multiple
|
|
8
|
+
files. Queries storage for duplicate hashes, retrieves patterns for each hash, builds
|
|
9
|
+
violations with cross-references to other files, and filters patterns based on enum value
|
|
10
|
+
thresholds. Delegates function call violation generation to FunctionCallViolationBuilder.
|
|
11
|
+
Generates violations for scattered string comparisons (e.g., `if env == "production"`)
|
|
12
|
+
where a variable is compared to multiple unique string values across files.
|
|
13
|
+
Applies inline ignore directives via IgnoreChecker to filter suppressed violations.
|
|
14
|
+
Separates violation generation logic from main linter rule to maintain SRP compliance.
|
|
15
|
+
|
|
16
|
+
Dependencies: StringlyTypedStorage, StoredPattern, StoredComparison, StringlyTypedConfig,
|
|
17
|
+
Violation, Severity, FunctionCallViolationBuilder, IgnoreChecker
|
|
18
|
+
|
|
19
|
+
Exports: ViolationGenerator class
|
|
20
|
+
|
|
21
|
+
Interfaces: ViolationGenerator.generate_violations(storage, rule_id, config) -> list[Violation]
|
|
22
|
+
|
|
23
|
+
Implementation: Queries storage, validates pattern thresholds, builds violations with
|
|
24
|
+
cross-file references, delegates function call violations to builder, generates
|
|
25
|
+
comparison violations from scattered string comparisons, filters by ignore directives
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from src.core.types import Severity, Violation
|
|
29
|
+
|
|
30
|
+
from .config import StringlyTypedConfig
|
|
31
|
+
from .context_filter import FunctionCallFilter
|
|
32
|
+
from .function_call_violation_builder import FunctionCallViolationBuilder
|
|
33
|
+
from .ignore_checker import IgnoreChecker
|
|
34
|
+
from .ignore_utils import is_ignored
|
|
35
|
+
from .storage import StoredComparison, StoredPattern, StringlyTypedStorage
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _filter_by_ignore(violations: list[Violation], ignore: list[str]) -> list[Violation]:
|
|
39
|
+
"""Filter violations by ignore patterns."""
|
|
40
|
+
if not ignore:
|
|
41
|
+
return violations
|
|
42
|
+
return [v for v in violations if not is_ignored(v.file_path, ignore)]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _is_allowed_value_set(values: set[str], config: StringlyTypedConfig) -> bool:
|
|
46
|
+
"""Check if a set of values is in the allowed list."""
|
|
47
|
+
return any(values == set(allowed) for allowed in config.allowed_string_sets)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ViolationGenerator: # thailint: ignore srp
|
|
51
|
+
"""Generates violations from cross-file stringly-typed patterns."""
|
|
52
|
+
|
|
53
|
+
def __init__(self) -> None:
|
|
54
|
+
"""Initialize with helper builders and filters."""
|
|
55
|
+
self._call_builder = FunctionCallViolationBuilder()
|
|
56
|
+
self._call_filter = FunctionCallFilter()
|
|
57
|
+
self._ignore_checker = IgnoreChecker()
|
|
58
|
+
|
|
59
|
+
def generate_violations(
|
|
60
|
+
self,
|
|
61
|
+
storage: StringlyTypedStorage,
|
|
62
|
+
rule_id: str,
|
|
63
|
+
config: StringlyTypedConfig,
|
|
64
|
+
) -> list[Violation]:
|
|
65
|
+
"""Generate violations from storage.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
storage: Pattern storage instance
|
|
69
|
+
rule_id: Rule identifier for violations
|
|
70
|
+
config: Stringly-typed configuration with thresholds
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List of violations for patterns appearing in multiple files
|
|
74
|
+
"""
|
|
75
|
+
violations: list[Violation] = []
|
|
76
|
+
pattern_violations, covered_vars = self._generate_pattern_violations(
|
|
77
|
+
storage, rule_id, config
|
|
78
|
+
)
|
|
79
|
+
violations.extend(pattern_violations)
|
|
80
|
+
violations.extend(self._generate_function_call_violations(storage, config))
|
|
81
|
+
violations.extend(self._generate_comparison_violations(storage, config, covered_vars))
|
|
82
|
+
|
|
83
|
+
# Apply path-based ignore patterns from config
|
|
84
|
+
violations = _filter_by_ignore(violations, config.ignore)
|
|
85
|
+
|
|
86
|
+
# Apply inline ignore directives (# thailint: ignore[stringly-typed])
|
|
87
|
+
violations = self._ignore_checker.filter_violations(violations)
|
|
88
|
+
|
|
89
|
+
return violations
|
|
90
|
+
|
|
91
|
+
def _generate_pattern_violations(
|
|
92
|
+
self,
|
|
93
|
+
storage: StringlyTypedStorage,
|
|
94
|
+
rule_id: str,
|
|
95
|
+
config: StringlyTypedConfig,
|
|
96
|
+
) -> tuple[list[Violation], set[str]]:
|
|
97
|
+
"""Generate violations for duplicate validation patterns.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Tuple of (violations list, set of variable names covered by these violations)
|
|
101
|
+
"""
|
|
102
|
+
duplicate_hashes = storage.get_duplicate_hashes(min_files=config.min_occurrences)
|
|
103
|
+
violations: list[Violation] = []
|
|
104
|
+
covered_variables: set[str] = set()
|
|
105
|
+
|
|
106
|
+
for hash_value in duplicate_hashes:
|
|
107
|
+
patterns = storage.get_patterns_by_hash(hash_value)
|
|
108
|
+
self._process_pattern_group(patterns, config, rule_id, violations, covered_variables)
|
|
109
|
+
|
|
110
|
+
return violations, covered_variables
|
|
111
|
+
|
|
112
|
+
def _process_pattern_group( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
113
|
+
self,
|
|
114
|
+
patterns: list[StoredPattern],
|
|
115
|
+
config: StringlyTypedConfig,
|
|
116
|
+
rule_id: str,
|
|
117
|
+
violations: list[Violation],
|
|
118
|
+
covered_variables: set[str],
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Process a group of patterns with the same hash."""
|
|
121
|
+
if self._should_skip_patterns(patterns, config):
|
|
122
|
+
return
|
|
123
|
+
violations.extend(self._build_violation(p, patterns, rule_id) for p in patterns)
|
|
124
|
+
# Track variable names to avoid duplicate comparison violations
|
|
125
|
+
for pattern in patterns:
|
|
126
|
+
if pattern.variable_name:
|
|
127
|
+
covered_variables.add(pattern.variable_name)
|
|
128
|
+
|
|
129
|
+
def _generate_function_call_violations(
|
|
130
|
+
self,
|
|
131
|
+
storage: StringlyTypedStorage,
|
|
132
|
+
config: StringlyTypedConfig,
|
|
133
|
+
) -> list[Violation]:
|
|
134
|
+
"""Generate violations for function call patterns."""
|
|
135
|
+
min_files = config.min_occurrences if config.require_cross_file else 1
|
|
136
|
+
limited_funcs = storage.get_limited_value_functions(
|
|
137
|
+
min_values=config.min_values_for_enum,
|
|
138
|
+
max_values=config.max_values_for_enum,
|
|
139
|
+
min_files=min_files,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
violations: list[Violation] = []
|
|
143
|
+
for function_name, param_index, unique_values in limited_funcs:
|
|
144
|
+
if _is_allowed_value_set(unique_values, config):
|
|
145
|
+
continue
|
|
146
|
+
# Apply context-aware filtering to reduce false positives
|
|
147
|
+
if not self._call_filter.should_include(function_name, param_index, unique_values):
|
|
148
|
+
continue
|
|
149
|
+
calls = storage.get_calls_by_function(function_name, param_index)
|
|
150
|
+
violations.extend(self._call_builder.build_violations(calls, unique_values))
|
|
151
|
+
|
|
152
|
+
return violations
|
|
153
|
+
|
|
154
|
+
def _should_skip_patterns(
|
|
155
|
+
self, patterns: list[StoredPattern], config: StringlyTypedConfig
|
|
156
|
+
) -> bool:
|
|
157
|
+
"""Check if pattern group should be skipped based on config."""
|
|
158
|
+
if not patterns:
|
|
159
|
+
return True
|
|
160
|
+
first = patterns[0]
|
|
161
|
+
if not self._is_enum_candidate(first, config):
|
|
162
|
+
return True
|
|
163
|
+
if self._is_pattern_allowed(first, config):
|
|
164
|
+
return True
|
|
165
|
+
# Skip if all values match excluded patterns (numeric strings, etc.)
|
|
166
|
+
if self._call_filter.are_all_values_excluded(set(first.string_values)):
|
|
167
|
+
return True
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
def _is_enum_candidate(self, pattern: StoredPattern, config: StringlyTypedConfig) -> bool:
|
|
171
|
+
"""Check if pattern's value count is within enum range."""
|
|
172
|
+
value_count = len(pattern.string_values)
|
|
173
|
+
return config.min_values_for_enum <= value_count <= config.max_values_for_enum
|
|
174
|
+
|
|
175
|
+
def _is_pattern_allowed(self, pattern: StoredPattern, config: StringlyTypedConfig) -> bool:
|
|
176
|
+
"""Check if pattern's string set is in allowed list."""
|
|
177
|
+
return _is_allowed_value_set(set(pattern.string_values), config)
|
|
178
|
+
|
|
179
|
+
def _build_violation(
|
|
180
|
+
self, pattern: StoredPattern, all_patterns: list[StoredPattern], rule_id: str
|
|
181
|
+
) -> Violation:
|
|
182
|
+
"""Build a violation for a pattern with cross-references."""
|
|
183
|
+
message = self._build_message(pattern, all_patterns)
|
|
184
|
+
suggestion = self._build_suggestion(pattern)
|
|
185
|
+
|
|
186
|
+
return Violation(
|
|
187
|
+
rule_id=rule_id,
|
|
188
|
+
file_path=str(pattern.file_path),
|
|
189
|
+
line=pattern.line_number,
|
|
190
|
+
column=pattern.column,
|
|
191
|
+
message=message,
|
|
192
|
+
severity=Severity.ERROR,
|
|
193
|
+
suggestion=suggestion,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def _build_message(self, pattern: StoredPattern, all_patterns: list[StoredPattern]) -> str:
|
|
197
|
+
"""Build violation message with cross-file references."""
|
|
198
|
+
file_count = len({p.file_path for p in all_patterns})
|
|
199
|
+
values_str = ", ".join(f"'{v}'" for v in sorted(pattern.string_values))
|
|
200
|
+
other_refs = self._build_cross_references(pattern, all_patterns)
|
|
201
|
+
|
|
202
|
+
message = (
|
|
203
|
+
f"Stringly-typed pattern with values [{values_str}] appears in {file_count} files."
|
|
204
|
+
)
|
|
205
|
+
if other_refs:
|
|
206
|
+
message += f" Also found in: {other_refs}."
|
|
207
|
+
|
|
208
|
+
return message
|
|
209
|
+
|
|
210
|
+
def _build_cross_references(
|
|
211
|
+
self, pattern: StoredPattern, all_patterns: list[StoredPattern]
|
|
212
|
+
) -> str:
|
|
213
|
+
"""Build cross-reference string for other files."""
|
|
214
|
+
refs = [
|
|
215
|
+
f"{other.file_path.name}:{other.line_number}"
|
|
216
|
+
for other in all_patterns
|
|
217
|
+
if other.file_path != pattern.file_path
|
|
218
|
+
]
|
|
219
|
+
return ", ".join(refs)
|
|
220
|
+
|
|
221
|
+
def _build_suggestion(self, pattern: StoredPattern) -> str:
|
|
222
|
+
"""Build fix suggestion for the pattern."""
|
|
223
|
+
values_count = len(pattern.string_values)
|
|
224
|
+
var_info = f" for '{pattern.variable_name}'" if pattern.variable_name else ""
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
f"Consider defining an enum or type union{var_info} with the "
|
|
228
|
+
f"{values_count} possible values instead of using string literals."
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def _generate_comparison_violations(
|
|
232
|
+
self,
|
|
233
|
+
storage: StringlyTypedStorage,
|
|
234
|
+
config: StringlyTypedConfig,
|
|
235
|
+
covered_variables: set[str] | None = None,
|
|
236
|
+
) -> list[Violation]:
|
|
237
|
+
"""Generate violations for scattered string comparisons.
|
|
238
|
+
|
|
239
|
+
Finds variables that are compared to multiple unique string values across
|
|
240
|
+
files (e.g., `if env == "production"` in one file and `if env == "staging"`
|
|
241
|
+
in another), suggesting they should use enums instead.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
storage: Pattern storage instance
|
|
245
|
+
config: Stringly-typed configuration
|
|
246
|
+
covered_variables: Variable names already flagged by pattern violations (to deduplicate)
|
|
247
|
+
"""
|
|
248
|
+
covered_variables = covered_variables or set()
|
|
249
|
+
min_files = config.min_occurrences if config.require_cross_file else 1
|
|
250
|
+
variables = storage.get_variables_with_multiple_values(
|
|
251
|
+
min_values=config.min_values_for_enum,
|
|
252
|
+
min_files=min_files,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
violations: list[Violation] = []
|
|
256
|
+
for variable_name, unique_values in variables:
|
|
257
|
+
self._process_variable_comparisons(
|
|
258
|
+
variable_name, unique_values, storage, config, covered_variables, violations
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
return violations
|
|
262
|
+
|
|
263
|
+
def _process_variable_comparisons( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
264
|
+
self,
|
|
265
|
+
variable_name: str,
|
|
266
|
+
unique_values: set[str],
|
|
267
|
+
storage: StringlyTypedStorage,
|
|
268
|
+
config: StringlyTypedConfig,
|
|
269
|
+
covered_variables: set[str],
|
|
270
|
+
violations: list[Violation],
|
|
271
|
+
) -> None:
|
|
272
|
+
"""Process comparisons for a single variable."""
|
|
273
|
+
# Skip if already covered by equality chain or validation pattern
|
|
274
|
+
if variable_name in covered_variables:
|
|
275
|
+
return
|
|
276
|
+
# Skip false positive variable patterns (.value, .method, etc.)
|
|
277
|
+
if self._should_skip_variable(variable_name):
|
|
278
|
+
return
|
|
279
|
+
if self._should_skip_comparison(unique_values, config):
|
|
280
|
+
return
|
|
281
|
+
comparisons = storage.get_comparisons_by_variable(variable_name)
|
|
282
|
+
violations.extend(
|
|
283
|
+
self._build_comparison_violation(c, comparisons, unique_values) for c in comparisons
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def _should_skip_comparison(self, unique_values: set[str], config: StringlyTypedConfig) -> bool:
|
|
287
|
+
"""Check if a comparison pattern should be skipped based on config."""
|
|
288
|
+
if len(unique_values) > config.max_values_for_enum:
|
|
289
|
+
return True
|
|
290
|
+
if _is_allowed_value_set(unique_values, config):
|
|
291
|
+
return True
|
|
292
|
+
if self._call_filter.are_all_values_excluded(unique_values):
|
|
293
|
+
return True
|
|
294
|
+
return False
|
|
295
|
+
|
|
296
|
+
def _should_skip_variable(self, variable_name: str) -> bool:
|
|
297
|
+
"""Check if a variable name indicates a false positive comparison.
|
|
298
|
+
|
|
299
|
+
Excludes:
|
|
300
|
+
- Variables ending with .value (enum value access)
|
|
301
|
+
- HTTP method variables (request.method, etc.)
|
|
302
|
+
- Variables that are likely test fixtures (underscore prefix patterns)
|
|
303
|
+
"""
|
|
304
|
+
# Enum value access - already using an enum
|
|
305
|
+
if variable_name.endswith(".value"):
|
|
306
|
+
return True
|
|
307
|
+
# HTTP method - standard protocol strings
|
|
308
|
+
if variable_name.endswith(".method"):
|
|
309
|
+
return True
|
|
310
|
+
# Test assertion patterns (underscore prefix is common in comprehensions/lambdas)
|
|
311
|
+
if variable_name.startswith("_."):
|
|
312
|
+
return True
|
|
313
|
+
return False
|
|
314
|
+
|
|
315
|
+
def _build_comparison_violation(
|
|
316
|
+
self,
|
|
317
|
+
comparison: StoredComparison,
|
|
318
|
+
all_comparisons: list[StoredComparison],
|
|
319
|
+
unique_values: set[str],
|
|
320
|
+
) -> Violation:
|
|
321
|
+
"""Build a violation for a scattered string comparison."""
|
|
322
|
+
message = self._build_comparison_message(comparison, all_comparisons, unique_values)
|
|
323
|
+
suggestion = self._build_comparison_suggestion(comparison, unique_values)
|
|
324
|
+
|
|
325
|
+
return Violation(
|
|
326
|
+
rule_id="stringly-typed.scattered-comparison",
|
|
327
|
+
file_path=str(comparison.file_path),
|
|
328
|
+
line=comparison.line_number,
|
|
329
|
+
column=comparison.column,
|
|
330
|
+
message=message,
|
|
331
|
+
severity=Severity.ERROR,
|
|
332
|
+
suggestion=suggestion,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def _build_comparison_message(
|
|
336
|
+
self,
|
|
337
|
+
comparison: StoredComparison,
|
|
338
|
+
all_comparisons: list[StoredComparison],
|
|
339
|
+
unique_values: set[str],
|
|
340
|
+
) -> str:
|
|
341
|
+
"""Build violation message for scattered comparison."""
|
|
342
|
+
file_count = len({c.file_path for c in all_comparisons})
|
|
343
|
+
values_str = ", ".join(f"'{v}'" for v in sorted(unique_values))
|
|
344
|
+
other_refs = self._build_comparison_cross_references(comparison, all_comparisons)
|
|
345
|
+
|
|
346
|
+
message = (
|
|
347
|
+
f"Variable '{comparison.variable_name}' is compared to {len(unique_values)} "
|
|
348
|
+
f"different string values [{values_str}] across {file_count} file(s)."
|
|
349
|
+
)
|
|
350
|
+
if other_refs:
|
|
351
|
+
message += f" Also compared in: {other_refs}."
|
|
352
|
+
|
|
353
|
+
return message
|
|
354
|
+
|
|
355
|
+
def _build_comparison_cross_references(
|
|
356
|
+
self,
|
|
357
|
+
comparison: StoredComparison,
|
|
358
|
+
all_comparisons: list[StoredComparison],
|
|
359
|
+
) -> str:
|
|
360
|
+
"""Build cross-reference string for other comparison locations."""
|
|
361
|
+
refs = [
|
|
362
|
+
f"{other.file_path.name}:{other.line_number}"
|
|
363
|
+
for other in all_comparisons
|
|
364
|
+
if other.file_path != comparison.file_path
|
|
365
|
+
]
|
|
366
|
+
return ", ".join(refs)
|
|
367
|
+
|
|
368
|
+
def _build_comparison_suggestion(
|
|
369
|
+
self, comparison: StoredComparison, unique_values: set[str]
|
|
370
|
+
) -> str:
|
|
371
|
+
"""Build fix suggestion for scattered comparison."""
|
|
372
|
+
return (
|
|
373
|
+
f"Consider defining an enum for '{comparison.variable_name}' with the "
|
|
374
|
+
f"{len(unique_values)} possible values instead of using string literals "
|
|
375
|
+
f"in scattered comparisons."
|
|
376
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: thailint
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.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
|
[](https://opensource.org/licenses/MIT)
|
|
39
39
|
[](https://www.python.org/downloads/)
|
|
40
|
-
[](tests/)
|
|
41
|
+
[](htmlcov/)
|
|
42
42
|
[](https://thai-lint.readthedocs.io/en/latest/?badge=latest)
|
|
43
43
|
[](docs/sarif-output.md)
|
|
44
44
|
|
|
@@ -116,6 +116,12 @@ thailint complements your existing linting stack by catching the patterns AI too
|
|
|
116
116
|
- No instance state (self.attr) detection
|
|
117
117
|
- Excludes ABC, Protocol, and decorated classes
|
|
118
118
|
- Helpful refactoring suggestions
|
|
119
|
+
- **Stringly-Typed Linting** - Detect string patterns that should use enums
|
|
120
|
+
- Python and TypeScript support
|
|
121
|
+
- Cross-file detection with SQLite storage
|
|
122
|
+
- Detects membership validation, equality chains, function call patterns
|
|
123
|
+
- False positive filtering (200+ exclusion patterns)
|
|
124
|
+
- Inline ignore directive support
|
|
119
125
|
- **Pluggable Architecture** - Easy to extend with custom linters
|
|
120
126
|
- **Multi-Language Support** - Python, TypeScript, JavaScript, and more
|
|
121
127
|
- **Flexible Configuration** - YAML/JSON configs with pattern matching
|
|
@@ -7,13 +7,13 @@ src/cli/__main__.py,sha256=xIKI57yqB1NOw9eXnGXfU8rC2UwcAJYjDxlZbt9eP0w,671
|
|
|
7
7
|
src/cli/config.py,sha256=a_kMr3DRQ8t_HzVXmgemKnKMSS0tPAxTA33tQswpRkc,14328
|
|
8
8
|
src/cli/linters/__init__.py,sha256=zpPCtFyKIkq0t3wqfUsd5_VJepPyT3LvAg4JZ_ToEeA,2056
|
|
9
9
|
src/cli/linters/code_patterns.py,sha256=Cyu26_Y-dTBudSgLDcwH3-djjXCN83xqzgMkZV9oZ-U,12434
|
|
10
|
-
src/cli/linters/code_smells.py,sha256=
|
|
10
|
+
src/cli/linters/code_smells.py,sha256=xf9fSS5qwRGOMw4FvhJo0QsU5Et0gFcObriZk-BUIg8,14858
|
|
11
11
|
src/cli/linters/documentation.py,sha256=UaD2MMltgfSVlQAo8MCXepmQWGLiz-5Llg6uowrwvIA,5170
|
|
12
12
|
src/cli/linters/shared.py,sha256=tHOsLF_L-xwf1KE4kVR7oES4IMOj-1ADWRnDhW36QzM,3010
|
|
13
13
|
src/cli/linters/structure.py,sha256=xPhb-SqtPM9QMhB5un8pi74l9SYKN1LHQL9Eb0ZLVqI,10538
|
|
14
14
|
src/cli/linters/structure_quality.py,sha256=qjZn1zKAp_rnmg1vVwUJ-Ob057AoYV3uZtZqiW2qFsU,10585
|
|
15
15
|
src/cli/main.py,sha256=mRyRN_IQI2WyqDCxI8vuzdhbkCkVrK6INfJPjU8ayIU,3844
|
|
16
|
-
src/cli/utils.py,sha256=
|
|
16
|
+
src/cli/utils.py,sha256=L1q2i6NBkWoQZLYnkeNNdfzHYRhaUobbzqSr2w8JUFY,12595
|
|
17
17
|
src/cli_main.py,sha256=-3XVwLztHt9clehSxr0nrXh9mhAiZOt0gP73TgijPEI,1499
|
|
18
18
|
src/config.py,sha256=gQ2DoBSZra4Dw3zUVd-t0lyD_pOY4iufx7sL_sXGBGU,12482
|
|
19
19
|
src/core/__init__.py,sha256=5FtsDvhMt4SNRx3pbcGURrxn135XRbeRrjSUxiXwkNc,381
|
|
@@ -127,24 +127,38 @@ src/linters/stateless_class/__init__.py,sha256=8ePpinmCD27PCz7ukwUWcNwo-ZgyvhOqu
|
|
|
127
127
|
src/linters/stateless_class/config.py,sha256=u8Jt_xygIkuxZx2o0Uw_XFatOh11QhC9aN8lB_vfnLk,1993
|
|
128
128
|
src/linters/stateless_class/linter.py,sha256=mYGF7Qn-7RG8qCwbLMTFXcfbU5jDbi8qEZpx6TEk-Bs,11121
|
|
129
129
|
src/linters/stateless_class/python_analyzer.py,sha256=Nv8mTM7HKjqDA8Kyu-bw3GOo9eq9XA4zD3GYsjQmV9E,7789
|
|
130
|
-
src/linters/stringly_typed/__init__.py,sha256=
|
|
131
|
-
src/linters/stringly_typed/config.py,sha256
|
|
132
|
-
src/linters/stringly_typed/
|
|
133
|
-
src/linters/stringly_typed/
|
|
130
|
+
src/linters/stringly_typed/__init__.py,sha256=6r4IIykZ6mm551KQpRTSDp418EFqJQbuzjSfLHcwyBc,1511
|
|
131
|
+
src/linters/stringly_typed/config.py,sha256=-ckS5SG5DEo-G7ty9gB3BB09Xf24Pr4VHz3WylL4bAU,7738
|
|
132
|
+
src/linters/stringly_typed/context_filter.py,sha256=lpDQBC1Sh0nC_dnPBhHPXJ0oUH012_xs8loo8eYiXDU,13511
|
|
133
|
+
src/linters/stringly_typed/function_call_violation_builder.py,sha256=JpiPRvlarZcyX-v4jQeawLI2xN6r4OdEHTOCU8HEL8w,4694
|
|
134
|
+
src/linters/stringly_typed/ignore_checker.py,sha256=sFV9NzsIhUWZe59h2X9JJv8yE3PWQLWAbhOG7Sl1Cs8,3438
|
|
135
|
+
src/linters/stringly_typed/ignore_utils.py,sha256=hw0wfnGFJQkysr1qi_vmykZPr02SNBElwVHFu55tB6M,1531
|
|
136
|
+
src/linters/stringly_typed/linter.py,sha256=cqgQ_8SElEOzCtdi7I6sct281zheuQyo6gm1ZPyBHvU,12224
|
|
137
|
+
src/linters/stringly_typed/python/__init__.py,sha256=y1ELj3We0_VeA0ygXd1DxudSWrZE5OhLGtZNkKwuomA,1359
|
|
138
|
+
src/linters/stringly_typed/python/analyzer.py,sha256=j6ObrKexB6oQILZ_HhaULbBDiQg6MWjkpYUVMdWrIe4,12073
|
|
139
|
+
src/linters/stringly_typed/python/call_tracker.py,sha256=xSel_LVB6shZJsSCyK--pwH_5KQnw-S4hvAYWRdJHCk,5898
|
|
140
|
+
src/linters/stringly_typed/python/comparison_tracker.py,sha256=EGJRyC2e7Zj3_EvC2sHdTq2cgnvB1l7Im2ugBAOE69Y,8075
|
|
134
141
|
src/linters/stringly_typed/python/condition_extractor.py,sha256=R1-7TXu2_pBMSGf8RD8ygWh-BmKkXUlLA6ZbCZysbsA,4114
|
|
135
142
|
src/linters/stringly_typed/python/conditional_detector.py,sha256=umXVh-gWAybnnrqS2AHlcDy-Fdd3ty5KhJpbWTU_P_Q,5927
|
|
136
143
|
src/linters/stringly_typed/python/constants.py,sha256=IF3Y2W96hihHlr5HMenq5Q93uOo7KHzNazVVvhq3E58,671
|
|
137
144
|
src/linters/stringly_typed/python/match_analyzer.py,sha256=o7_XCIEggsS9NBPQ3aNBvu37cpQAEvWyXjfqNMMj2k8,2536
|
|
138
145
|
src/linters/stringly_typed/python/validation_detector.py,sha256=VKm3V5t2JhkyVJHF8C8iX7fcsk_5o5zGeZpSEnMJU8I,6190
|
|
139
146
|
src/linters/stringly_typed/python/variable_extractor.py,sha256=yYJQ5jTSMz94SD_0IMfCHMWcw1F57GmRuh9h51oiAEs,2769
|
|
147
|
+
src/linters/stringly_typed/storage.py,sha256=gbQwkmy7gXnE2ePqFJP0l4-tvZ8RRYOxb1-CXNLVSzc,22069
|
|
148
|
+
src/linters/stringly_typed/storage_initializer.py,sha256=3-4St1ieN8325Xkb0HTS27dVyjjluM_X-bkwOfJW1JM,1548
|
|
149
|
+
src/linters/stringly_typed/typescript/__init__.py,sha256=lOgclS9wxLNyszfwVGbVxKfCkbTLX1pvskHzcADi5Xg,1121
|
|
150
|
+
src/linters/stringly_typed/typescript/analyzer.py,sha256=iNEk6wQJJfmJoRTXx29GEeqTpKzQ5TcNIimSuQPb6UU,6376
|
|
151
|
+
src/linters/stringly_typed/typescript/call_tracker.py,sha256=W88M5FrTYlLTjavkY3Auqw4ynKweJRSd68MoV2H9lbI,10909
|
|
152
|
+
src/linters/stringly_typed/typescript/comparison_tracker.py,sha256=GTfAQAhDsHrUoiSy-L5AIOFLJCR_7PpVPi3yn19hVmw,12186
|
|
153
|
+
src/linters/stringly_typed/violation_generator.py,sha256=vBNT2ei9mOSbW-DuLNlgXLYHM-Vk5-7AevUwWyZ8w4Y,15637
|
|
140
154
|
src/orchestrator/__init__.py,sha256=XXLDJq2oaB-TpP2Y97GRnde9EkITGuFCmuLrDfxI9nY,245
|
|
141
155
|
src/orchestrator/core.py,sha256=exdSuPm9ly7xy3QUB9vjAlk2LlSMhkTHNifLZ-fgsS8,17369
|
|
142
156
|
src/orchestrator/language_detector.py,sha256=rHyVMApit80NTTNyDH1ObD1usKD8LjGmH3DwqNAWYGc,2736
|
|
143
157
|
src/templates/thailint_config_template.yaml,sha256=vxyhRRi25_xOnHDRx0jzz69dgPqKU2IU5-YFGUoX5lM,4953
|
|
144
158
|
src/utils/__init__.py,sha256=NiBtKeQ09Y3kuUzeN4O1JNfUIYPQDS2AP1l5ODq-Dec,125
|
|
145
159
|
src/utils/project_root.py,sha256=b3YTEGTa9RPcOeHn1IByMMWyRiUabfVlpnlektL0A0o,6156
|
|
146
|
-
thailint-0.
|
|
147
|
-
thailint-0.
|
|
148
|
-
thailint-0.
|
|
149
|
-
thailint-0.
|
|
150
|
-
thailint-0.
|
|
160
|
+
thailint-0.12.0.dist-info/METADATA,sha256=bqVguJOc21dxwRlIOmu73KSW3ELtCB5uJGW7OXT4tvo,48840
|
|
161
|
+
thailint-0.12.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
162
|
+
thailint-0.12.0.dist-info/entry_points.txt,sha256=DNoGUlxpaMFqxQDgHp1yeGqohOjdFR-kH19uHYi3OUY,72
|
|
163
|
+
thailint-0.12.0.dist-info/licenses/LICENSE,sha256=kxh1J0Sb62XvhNJ6MZsVNe8PqNVJ7LHRn_EWa-T3djw,1070
|
|
164
|
+
thailint-0.12.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|