thailint 0.2.0__py3-none-any.whl → 0.5.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.py +646 -36
- src/config.py +6 -2
- src/core/base.py +90 -5
- src/core/config_parser.py +31 -4
- src/linters/dry/block_filter.py +5 -2
- src/linters/dry/cache.py +46 -92
- src/linters/dry/config.py +17 -13
- src/linters/dry/duplicate_storage.py +17 -80
- src/linters/dry/file_analyzer.py +11 -48
- src/linters/dry/linter.py +5 -12
- src/linters/dry/python_analyzer.py +188 -37
- src/linters/dry/storage_initializer.py +9 -18
- src/linters/dry/token_hasher.py +63 -9
- src/linters/dry/typescript_analyzer.py +7 -5
- src/linters/dry/violation_filter.py +4 -1
- src/linters/file_header/__init__.py +24 -0
- src/linters/file_header/atemporal_detector.py +87 -0
- src/linters/file_header/config.py +66 -0
- src/linters/file_header/field_validator.py +69 -0
- src/linters/file_header/linter.py +313 -0
- src/linters/file_header/python_parser.py +86 -0
- src/linters/file_header/violation_builder.py +78 -0
- src/linters/file_placement/linter.py +15 -4
- src/linters/magic_numbers/__init__.py +48 -0
- src/linters/magic_numbers/config.py +82 -0
- src/linters/magic_numbers/context_analyzer.py +247 -0
- src/linters/magic_numbers/linter.py +516 -0
- src/linters/magic_numbers/python_analyzer.py +76 -0
- src/linters/magic_numbers/typescript_analyzer.py +218 -0
- src/linters/magic_numbers/violation_builder.py +98 -0
- src/linters/nesting/__init__.py +6 -2
- src/linters/nesting/config.py +6 -3
- src/linters/nesting/linter.py +8 -19
- src/linters/nesting/typescript_analyzer.py +1 -0
- src/linters/print_statements/__init__.py +53 -0
- src/linters/print_statements/config.py +83 -0
- src/linters/print_statements/linter.py +430 -0
- src/linters/print_statements/python_analyzer.py +155 -0
- src/linters/print_statements/typescript_analyzer.py +135 -0
- src/linters/print_statements/violation_builder.py +98 -0
- src/linters/srp/__init__.py +3 -3
- src/linters/srp/config.py +12 -6
- src/linters/srp/linter.py +33 -24
- src/orchestrator/core.py +12 -2
- src/templates/thailint_config_template.yaml +158 -0
- src/utils/project_root.py +135 -16
- {thailint-0.2.0.dist-info → thailint-0.5.0.dist-info}/METADATA +387 -81
- thailint-0.5.0.dist-info/RECORD +96 -0
- {thailint-0.2.0.dist-info → thailint-0.5.0.dist-info}/WHEEL +1 -1
- thailint-0.2.0.dist-info/RECORD +0 -75
- {thailint-0.2.0.dist-info → thailint-0.5.0.dist-info}/entry_points.txt +0 -0
- {thailint-0.2.0.dist-info → thailint-0.5.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: TypeScript/JavaScript magic number detection using Tree-sitter AST analysis
|
|
3
|
+
|
|
4
|
+
Scope: Tree-sitter based numeric literal detection for TypeScript and JavaScript code
|
|
5
|
+
|
|
6
|
+
Overview: Analyzes TypeScript and JavaScript code to detect numeric literals that should be
|
|
7
|
+
extracted to named constants. Uses Tree-sitter parser to traverse TypeScript AST and
|
|
8
|
+
identify numeric literal nodes with their line numbers and values. Detects acceptable
|
|
9
|
+
contexts such as enum definitions and UPPERCASE constant declarations to avoid false
|
|
10
|
+
positives. Supports both TypeScript and JavaScript files with shared detection logic.
|
|
11
|
+
Handles TypeScript-specific syntax including enums, const assertions, readonly properties,
|
|
12
|
+
arrow functions, async functions, and class methods.
|
|
13
|
+
|
|
14
|
+
Dependencies: TypeScriptBaseAnalyzer for tree-sitter parsing, tree-sitter Node type
|
|
15
|
+
|
|
16
|
+
Exports: TypeScriptMagicNumberAnalyzer class with find_numeric_literals and context detection
|
|
17
|
+
|
|
18
|
+
Interfaces: find_numeric_literals(root_node) -> list[tuple], is_enum_context(node),
|
|
19
|
+
is_constant_definition(node)
|
|
20
|
+
|
|
21
|
+
Implementation: Tree-sitter node traversal with visitor pattern, context-aware filtering
|
|
22
|
+
for acceptable numeric literal locations
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from src.analyzers.typescript_base import TypeScriptBaseAnalyzer
|
|
28
|
+
|
|
29
|
+
# dry: ignore-block - tree-sitter import pattern (common across TypeScript analyzers)
|
|
30
|
+
try:
|
|
31
|
+
from tree_sitter import Node
|
|
32
|
+
|
|
33
|
+
TREE_SITTER_AVAILABLE = True
|
|
34
|
+
except ImportError:
|
|
35
|
+
TREE_SITTER_AVAILABLE = False
|
|
36
|
+
Node = Any # type: ignore
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TypeScriptMagicNumberAnalyzer(TypeScriptBaseAnalyzer): # thailint: ignore[srp]
|
|
40
|
+
"""Analyzes TypeScript/JavaScript code for magic numbers using Tree-sitter.
|
|
41
|
+
|
|
42
|
+
Note: Method count (11) exceeds SRP limit (8) because refactoring for A-grade
|
|
43
|
+
complexity requires extracting helper methods. Class maintains single responsibility
|
|
44
|
+
of TypeScript magic number detection - all methods support this core purpose.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def find_numeric_literals(self, root_node: Node) -> list[tuple[Node, float | int, int]]:
|
|
48
|
+
"""Find all numeric literal nodes in TypeScript/JavaScript AST.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
root_node: Root tree-sitter node to search from
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
List of (node, value, line_number) tuples for each numeric literal
|
|
55
|
+
"""
|
|
56
|
+
if not TREE_SITTER_AVAILABLE or root_node is None:
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
literals: list[tuple[Node, float | int, int]] = []
|
|
60
|
+
self._collect_numeric_literals(root_node, literals)
|
|
61
|
+
return literals
|
|
62
|
+
|
|
63
|
+
def _collect_numeric_literals(
|
|
64
|
+
self, node: Node, literals: list[tuple[Node, float | int, int]]
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Recursively collect numeric literals from AST.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
node: Current tree-sitter node
|
|
70
|
+
literals: List to accumulate found literals
|
|
71
|
+
"""
|
|
72
|
+
if node.type == "number":
|
|
73
|
+
value = self._extract_numeric_value(node)
|
|
74
|
+
if value is not None:
|
|
75
|
+
line_number = node.start_point[0] + 1
|
|
76
|
+
literals.append((node, value, line_number))
|
|
77
|
+
|
|
78
|
+
for child in node.children:
|
|
79
|
+
self._collect_numeric_literals(child, literals)
|
|
80
|
+
|
|
81
|
+
def _extract_numeric_value(self, node: Node) -> float | int | None:
|
|
82
|
+
"""Extract numeric value from number node.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
node: Tree-sitter number node
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Numeric value (int or float) or None if parsing fails
|
|
89
|
+
"""
|
|
90
|
+
text = self.extract_node_text(node)
|
|
91
|
+
try:
|
|
92
|
+
# Try int first
|
|
93
|
+
if "." not in text and "e" not in text.lower():
|
|
94
|
+
return int(text, 0) # Handles hex, octal, binary
|
|
95
|
+
# Otherwise float
|
|
96
|
+
return float(text)
|
|
97
|
+
except (ValueError, TypeError):
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
def is_enum_context(self, node: Node) -> bool:
|
|
101
|
+
"""Check if numeric literal is in enum definition.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
node: Numeric literal node
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
True if node is within enum_declaration
|
|
108
|
+
"""
|
|
109
|
+
if not TREE_SITTER_AVAILABLE:
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
current = node.parent
|
|
113
|
+
while current is not None:
|
|
114
|
+
if current.type == "enum_declaration":
|
|
115
|
+
return True
|
|
116
|
+
current = current.parent
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
def is_constant_definition(self, node: Node, source_code: str) -> bool:
|
|
120
|
+
"""Check if numeric literal is in UPPERCASE constant definition.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
node: Numeric literal node
|
|
124
|
+
source_code: Full source code to extract variable names
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
True if assigned to UPPERCASE constant variable
|
|
128
|
+
"""
|
|
129
|
+
if not TREE_SITTER_AVAILABLE:
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
# Find the declaration parent
|
|
133
|
+
parent = self._find_declaration_parent(node)
|
|
134
|
+
if parent is None:
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
# Check if identifier is UPPERCASE constant
|
|
138
|
+
return self._has_uppercase_identifier(parent)
|
|
139
|
+
|
|
140
|
+
def _find_declaration_parent(self, node: Node) -> Node | None:
|
|
141
|
+
"""Find the declaration parent node.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
node: Starting node
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Declaration parent or None
|
|
148
|
+
"""
|
|
149
|
+
parent = node.parent
|
|
150
|
+
if self._is_declaration_type(parent):
|
|
151
|
+
return parent
|
|
152
|
+
|
|
153
|
+
# Try grandparent for nested cases
|
|
154
|
+
if parent is not None:
|
|
155
|
+
grandparent = parent.parent
|
|
156
|
+
if self._is_declaration_type(grandparent):
|
|
157
|
+
return grandparent
|
|
158
|
+
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
def _is_declaration_type(self, node: Node | None) -> bool:
|
|
162
|
+
"""Check if node is a declaration type."""
|
|
163
|
+
if node is None:
|
|
164
|
+
return False
|
|
165
|
+
return node.type in ("variable_declarator", "lexical_declaration", "pair")
|
|
166
|
+
|
|
167
|
+
def _has_uppercase_identifier(self, parent_node: Node) -> bool:
|
|
168
|
+
"""Check if declaration has UPPERCASE identifier.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
parent_node: Declaration parent node
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
True if identifier is UPPERCASE
|
|
175
|
+
"""
|
|
176
|
+
identifier_node = self._find_identifier_in_declaration(parent_node)
|
|
177
|
+
if identifier_node is None:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
identifier_text = self.extract_node_text(identifier_node)
|
|
181
|
+
return self._is_uppercase_constant(identifier_text)
|
|
182
|
+
|
|
183
|
+
def _find_identifier_in_declaration(self, node: Node) -> Node | None:
|
|
184
|
+
"""Find identifier node in variable declaration.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
node: Variable declarator or lexical declaration node
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Identifier node or None
|
|
191
|
+
"""
|
|
192
|
+
# Walk children looking for identifier
|
|
193
|
+
for child in node.children:
|
|
194
|
+
if child.type in ("identifier", "property_identifier"):
|
|
195
|
+
return child
|
|
196
|
+
# Recursively check children
|
|
197
|
+
result = self._find_identifier_in_declaration(child)
|
|
198
|
+
if result is not None:
|
|
199
|
+
return result
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
def _is_uppercase_constant(self, name: str) -> bool:
|
|
203
|
+
"""Check if identifier is UPPERCASE constant style.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
name: Identifier name
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
True if name is UPPERCASE with optional underscores
|
|
210
|
+
"""
|
|
211
|
+
if not name:
|
|
212
|
+
return False
|
|
213
|
+
# Must be at least one letter and all letters must be uppercase
|
|
214
|
+
# Allow underscores and numbers
|
|
215
|
+
letters_only = "".join(c for c in name if c.isalpha())
|
|
216
|
+
if not letters_only:
|
|
217
|
+
return False
|
|
218
|
+
return letters_only.isupper()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Builds Violation objects for magic number detection
|
|
3
|
+
|
|
4
|
+
Scope: Violation message construction for magic numbers linter
|
|
5
|
+
|
|
6
|
+
Overview: Provides ViolationBuilder class that creates Violation objects for magic number detections.
|
|
7
|
+
Generates helpful, descriptive messages suggesting constant extraction for numeric literals.
|
|
8
|
+
Constructs complete Violation instances with rule_id, file_path, line number, column, message,
|
|
9
|
+
and suggestions. Formats messages to mention the specific numeric value and encourage using
|
|
10
|
+
named constants for better code maintainability and readability. Provides consistent violation
|
|
11
|
+
structure across all magic number detections.
|
|
12
|
+
|
|
13
|
+
Dependencies: src.core.types for Violation dataclass, pathlib for Path handling, ast for node types
|
|
14
|
+
|
|
15
|
+
Exports: ViolationBuilder class
|
|
16
|
+
|
|
17
|
+
Interfaces: ViolationBuilder.create_violation(node, value, line, file_path) -> Violation,
|
|
18
|
+
builds complete Violation object with all required fields
|
|
19
|
+
|
|
20
|
+
Implementation: Message template with value interpolation, structured violation construction
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import ast
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from src.core.types import Violation
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ViolationBuilder:
|
|
30
|
+
"""Builds violations for magic number detections."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, rule_id: str) -> None:
|
|
33
|
+
"""Initialize the violation builder.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
rule_id: The rule ID to use in violations
|
|
37
|
+
"""
|
|
38
|
+
self.rule_id = rule_id
|
|
39
|
+
|
|
40
|
+
def create_violation(
|
|
41
|
+
self,
|
|
42
|
+
node: ast.Constant,
|
|
43
|
+
value: int | float,
|
|
44
|
+
line: int,
|
|
45
|
+
file_path: Path | None,
|
|
46
|
+
) -> Violation:
|
|
47
|
+
"""Create a violation for a magic number.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
node: The AST node containing the magic number
|
|
51
|
+
value: The numeric value
|
|
52
|
+
line: Line number where the violation occurs
|
|
53
|
+
file_path: Path to the file
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Violation object with details about the magic number
|
|
57
|
+
"""
|
|
58
|
+
message = f"Magic number {value} should be a named constant"
|
|
59
|
+
|
|
60
|
+
suggestion = f"Extract {value} to a named constant (e.g., CONSTANT_NAME = {value})"
|
|
61
|
+
|
|
62
|
+
return Violation(
|
|
63
|
+
rule_id=self.rule_id,
|
|
64
|
+
file_path=str(file_path) if file_path else "",
|
|
65
|
+
line=line,
|
|
66
|
+
column=node.col_offset if hasattr(node, "col_offset") else 0,
|
|
67
|
+
message=message,
|
|
68
|
+
suggestion=suggestion,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def create_typescript_violation(
|
|
72
|
+
self,
|
|
73
|
+
value: int | float,
|
|
74
|
+
line: int,
|
|
75
|
+
file_path: Path | None,
|
|
76
|
+
) -> Violation:
|
|
77
|
+
"""Create a violation for a TypeScript magic number.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
value: The numeric value
|
|
81
|
+
line: Line number where the violation occurs
|
|
82
|
+
file_path: Path to the file
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Violation object with details about the magic number
|
|
86
|
+
"""
|
|
87
|
+
message = f"Magic number {value} should be a named constant"
|
|
88
|
+
|
|
89
|
+
suggestion = f"Extract {value} to a named constant (e.g., const CONSTANT_NAME = {value})"
|
|
90
|
+
|
|
91
|
+
return Violation(
|
|
92
|
+
rule_id=self.rule_id,
|
|
93
|
+
file_path=str(file_path) if file_path else "",
|
|
94
|
+
line=line,
|
|
95
|
+
column=0, # Tree-sitter nodes don't have easy column access
|
|
96
|
+
message=message,
|
|
97
|
+
suggestion=suggestion,
|
|
98
|
+
)
|
src/linters/nesting/__init__.py
CHANGED
|
@@ -22,7 +22,7 @@ Implementation: Simple re-export pattern for package interface, convenience func
|
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
from typing import Any
|
|
24
24
|
|
|
25
|
-
from .config import NestingConfig
|
|
25
|
+
from .config import DEFAULT_MAX_NESTING_DEPTH, NestingConfig
|
|
26
26
|
from .linter import NestingDepthRule
|
|
27
27
|
from .python_analyzer import PythonNestingAnalyzer
|
|
28
28
|
from .typescript_analyzer import TypeScriptNestingAnalyzer
|
|
@@ -36,7 +36,11 @@ __all__ = [
|
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def lint(
|
|
39
|
+
def lint(
|
|
40
|
+
path: Path | str,
|
|
41
|
+
config: dict[str, Any] | None = None,
|
|
42
|
+
max_depth: int = DEFAULT_MAX_NESTING_DEPTH,
|
|
43
|
+
) -> list:
|
|
40
44
|
"""Lint a file or directory for nesting depth violations.
|
|
41
45
|
|
|
42
46
|
Args:
|
src/linters/nesting/config.py
CHANGED
|
@@ -21,12 +21,15 @@ Implementation: Dataclass with validation and defaults, matches reference implem
|
|
|
21
21
|
from dataclasses import dataclass
|
|
22
22
|
from typing import Any
|
|
23
23
|
|
|
24
|
+
# Default nesting threshold constant
|
|
25
|
+
DEFAULT_MAX_NESTING_DEPTH = 4
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
@dataclass
|
|
26
29
|
class NestingConfig:
|
|
27
30
|
"""Configuration for nesting depth linter."""
|
|
28
31
|
|
|
29
|
-
max_nesting_depth: int =
|
|
32
|
+
max_nesting_depth: int = DEFAULT_MAX_NESTING_DEPTH # Default from reference implementation
|
|
30
33
|
enabled: bool = True
|
|
31
34
|
|
|
32
35
|
def __post_init__(self) -> None:
|
|
@@ -49,10 +52,10 @@ class NestingConfig:
|
|
|
49
52
|
if language and language in config:
|
|
50
53
|
lang_config = config[language]
|
|
51
54
|
max_nesting_depth = lang_config.get(
|
|
52
|
-
"max_nesting_depth", config.get("max_nesting_depth",
|
|
55
|
+
"max_nesting_depth", config.get("max_nesting_depth", DEFAULT_MAX_NESTING_DEPTH)
|
|
53
56
|
)
|
|
54
57
|
else:
|
|
55
|
-
max_nesting_depth = config.get("max_nesting_depth",
|
|
58
|
+
max_nesting_depth = config.get("max_nesting_depth", DEFAULT_MAX_NESTING_DEPTH)
|
|
56
59
|
|
|
57
60
|
return cls(
|
|
58
61
|
max_nesting_depth=max_nesting_depth,
|
src/linters/nesting/linter.py
CHANGED
|
@@ -21,8 +21,8 @@ Implementation: Composition pattern with helper classes, AST-based analysis with
|
|
|
21
21
|
import ast
|
|
22
22
|
from typing import Any
|
|
23
23
|
|
|
24
|
-
from src.core.base import BaseLintContext,
|
|
25
|
-
from src.core.linter_utils import
|
|
24
|
+
from src.core.base import BaseLintContext, MultiLanguageLintRule
|
|
25
|
+
from src.core.linter_utils import load_linter_config
|
|
26
26
|
from src.core.types import Violation
|
|
27
27
|
from src.linter_config.ignore import IgnoreDirectiveParser
|
|
28
28
|
|
|
@@ -32,7 +32,7 @@ from .typescript_analyzer import TypeScriptNestingAnalyzer
|
|
|
32
32
|
from .violation_builder import NestingViolationBuilder
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
class NestingDepthRule(
|
|
35
|
+
class NestingDepthRule(MultiLanguageLintRule):
|
|
36
36
|
"""Detects excessive nesting depth in functions."""
|
|
37
37
|
|
|
38
38
|
def __init__(self) -> None:
|
|
@@ -55,27 +55,16 @@ class NestingDepthRule(BaseLintRule):
|
|
|
55
55
|
"""Description of what this rule checks."""
|
|
56
56
|
return "Functions should not have excessive nesting depth for better readability"
|
|
57
57
|
|
|
58
|
-
def
|
|
59
|
-
"""
|
|
58
|
+
def _load_config(self, context: BaseLintContext) -> NestingConfig:
|
|
59
|
+
"""Load configuration from context.
|
|
60
60
|
|
|
61
61
|
Args:
|
|
62
|
-
context: Lint context
|
|
62
|
+
context: Lint context
|
|
63
63
|
|
|
64
64
|
Returns:
|
|
65
|
-
|
|
65
|
+
NestingConfig instance
|
|
66
66
|
"""
|
|
67
|
-
|
|
68
|
-
return []
|
|
69
|
-
|
|
70
|
-
config = load_linter_config(context, "nesting", NestingConfig)
|
|
71
|
-
if not config.enabled:
|
|
72
|
-
return []
|
|
73
|
-
|
|
74
|
-
if context.language == "python":
|
|
75
|
-
return self._check_python(context, config)
|
|
76
|
-
if context.language in ("typescript", "javascript"):
|
|
77
|
-
return self._check_typescript(context, config)
|
|
78
|
-
return []
|
|
67
|
+
return load_linter_config(context, "nesting", NestingConfig)
|
|
79
68
|
|
|
80
69
|
def _process_python_functions(
|
|
81
70
|
self, functions: list, analyzer: Any, config: NestingConfig, context: BaseLintContext
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File: src/linters/print_statements/__init__.py
|
|
3
|
+
|
|
4
|
+
Purpose: Print statements linter package exports and convenience functions
|
|
5
|
+
|
|
6
|
+
Exports: PrintStatementRule class, PrintStatementConfig dataclass, lint() convenience function
|
|
7
|
+
|
|
8
|
+
Depends: .linter for PrintStatementRule, .config for PrintStatementConfig
|
|
9
|
+
|
|
10
|
+
Implements: lint(file_path, config) -> list[Violation] for simple linting operations
|
|
11
|
+
|
|
12
|
+
Related: src/linters/magic_numbers/__init__.py, src/core/base.py
|
|
13
|
+
|
|
14
|
+
Overview: Provides the public interface for the print statements linter package. Exports main
|
|
15
|
+
PrintStatementRule class for use by the orchestrator and PrintStatementConfig for configuration.
|
|
16
|
+
Includes lint() convenience function that provides a simple API for running the print statements
|
|
17
|
+
linter on a file without directly interacting with the orchestrator. This module serves as the
|
|
18
|
+
entry point for users of the print statements linter, hiding implementation details and exposing
|
|
19
|
+
only the essential components needed for linting operations.
|
|
20
|
+
|
|
21
|
+
Usage: from src.linters.print_statements import PrintStatementRule, lint
|
|
22
|
+
violations = lint("path/to/file.py")
|
|
23
|
+
|
|
24
|
+
Notes: Module-level exports with __all__ definition, convenience function wrapper
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from .config import PrintStatementConfig
|
|
28
|
+
from .linter import PrintStatementRule
|
|
29
|
+
|
|
30
|
+
__all__ = ["PrintStatementRule", "PrintStatementConfig", "lint"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def lint(file_path: str, config: dict | None = None) -> list:
|
|
34
|
+
"""Convenience function for linting a file for print statements.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
file_path: Path to the file to lint
|
|
38
|
+
config: Optional configuration dictionary
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
List of violations found
|
|
42
|
+
"""
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
|
|
45
|
+
from src.orchestrator.core import FileLintContext
|
|
46
|
+
|
|
47
|
+
rule = PrintStatementRule()
|
|
48
|
+
context = FileLintContext(
|
|
49
|
+
path=Path(file_path),
|
|
50
|
+
lang="python",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return rule.check(context)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
File: src/linters/print_statements/config.py
|
|
3
|
+
|
|
4
|
+
Purpose: Configuration schema for print statements linter
|
|
5
|
+
|
|
6
|
+
Exports: PrintStatementConfig dataclass
|
|
7
|
+
|
|
8
|
+
Depends: dataclasses, typing
|
|
9
|
+
|
|
10
|
+
Implements: PrintStatementConfig(enabled, ignore, allow_in_scripts, console_methods),
|
|
11
|
+
from_dict class method for loading configuration from dictionary
|
|
12
|
+
|
|
13
|
+
Related: src/linters/magic_numbers/config.py, src/core/types.py
|
|
14
|
+
|
|
15
|
+
Overview: Defines configuration schema for print statements linter. Provides PrintStatementConfig
|
|
16
|
+
dataclass with enabled flag, ignore patterns list, allow_in_scripts setting (default True to
|
|
17
|
+
allow print in __main__ blocks), and console_methods set (default includes log, warn, error,
|
|
18
|
+
debug, info) for TypeScript/JavaScript console method detection. Supports per-file and
|
|
19
|
+
per-directory config overrides through from_dict class method. Integrates with orchestrator's
|
|
20
|
+
configuration system to allow users to customize detection via .thailint.yaml configuration.
|
|
21
|
+
|
|
22
|
+
Usage: config = PrintStatementConfig.from_dict(yaml_config, language="python")
|
|
23
|
+
|
|
24
|
+
Notes: Dataclass with defaults matching common use cases, language-specific override support
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class PrintStatementConfig:
|
|
33
|
+
"""Configuration for print statements linter."""
|
|
34
|
+
|
|
35
|
+
enabled: bool = True
|
|
36
|
+
ignore: list[str] = field(default_factory=list)
|
|
37
|
+
allow_in_scripts: bool = True
|
|
38
|
+
console_methods: set[str] = field(
|
|
39
|
+
default_factory=lambda: {"log", "warn", "error", "debug", "info"}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def from_dict(
|
|
44
|
+
cls, config: dict[str, Any], language: str | None = None
|
|
45
|
+
) -> "PrintStatementConfig":
|
|
46
|
+
"""Load configuration from dictionary with language-specific overrides.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
config: Dictionary containing configuration values
|
|
50
|
+
language: Programming language (python, typescript, javascript)
|
|
51
|
+
for language-specific settings
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
PrintStatementConfig instance with values from dictionary
|
|
55
|
+
"""
|
|
56
|
+
# Get language-specific config if available
|
|
57
|
+
if language and language in config:
|
|
58
|
+
lang_config = config[language]
|
|
59
|
+
allow_in_scripts = lang_config.get(
|
|
60
|
+
"allow_in_scripts", config.get("allow_in_scripts", True)
|
|
61
|
+
)
|
|
62
|
+
console_methods = set(
|
|
63
|
+
lang_config.get(
|
|
64
|
+
"console_methods",
|
|
65
|
+
config.get("console_methods", ["log", "warn", "error", "debug", "info"]),
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
allow_in_scripts = config.get("allow_in_scripts", True)
|
|
70
|
+
console_methods = set(
|
|
71
|
+
config.get("console_methods", ["log", "warn", "error", "debug", "info"])
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
ignore_patterns = config.get("ignore", [])
|
|
75
|
+
if not isinstance(ignore_patterns, list):
|
|
76
|
+
ignore_patterns = []
|
|
77
|
+
|
|
78
|
+
return cls(
|
|
79
|
+
enabled=config.get("enabled", True),
|
|
80
|
+
ignore=ignore_patterns,
|
|
81
|
+
allow_in_scripts=allow_in_scripts,
|
|
82
|
+
console_methods=console_methods,
|
|
83
|
+
)
|