thailint 0.1.5__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/__init__.py +7 -2
- src/analyzers/__init__.py +23 -0
- src/analyzers/typescript_base.py +148 -0
- src/api.py +1 -1
- src/cli.py +1111 -144
- src/config.py +12 -33
- src/core/base.py +102 -5
- src/core/cli_utils.py +206 -0
- src/core/config_parser.py +126 -0
- src/core/linter_utils.py +168 -0
- src/core/registry.py +17 -92
- src/core/rule_discovery.py +132 -0
- src/core/violation_builder.py +122 -0
- src/linter_config/ignore.py +112 -40
- src/linter_config/loader.py +3 -13
- src/linters/dry/__init__.py +23 -0
- src/linters/dry/base_token_analyzer.py +76 -0
- src/linters/dry/block_filter.py +265 -0
- src/linters/dry/block_grouper.py +59 -0
- src/linters/dry/cache.py +172 -0
- src/linters/dry/cache_query.py +61 -0
- src/linters/dry/config.py +134 -0
- src/linters/dry/config_loader.py +44 -0
- src/linters/dry/deduplicator.py +120 -0
- src/linters/dry/duplicate_storage.py +63 -0
- src/linters/dry/file_analyzer.py +90 -0
- src/linters/dry/inline_ignore.py +140 -0
- src/linters/dry/linter.py +163 -0
- src/linters/dry/python_analyzer.py +668 -0
- src/linters/dry/storage_initializer.py +42 -0
- src/linters/dry/token_hasher.py +169 -0
- src/linters/dry/typescript_analyzer.py +592 -0
- src/linters/dry/violation_builder.py +74 -0
- src/linters/dry/violation_filter.py +94 -0
- src/linters/dry/violation_generator.py +174 -0
- 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/config_loader.py +86 -0
- src/linters/file_placement/directory_matcher.py +80 -0
- src/linters/file_placement/linter.py +262 -471
- src/linters/file_placement/path_resolver.py +61 -0
- src/linters/file_placement/pattern_matcher.py +55 -0
- src/linters/file_placement/pattern_validator.py +106 -0
- src/linters/file_placement/rule_checker.py +229 -0
- src/linters/file_placement/violation_factory.py +177 -0
- 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 +17 -4
- src/linters/nesting/linter.py +81 -168
- src/linters/nesting/typescript_analyzer.py +39 -102
- src/linters/nesting/typescript_function_extractor.py +130 -0
- src/linters/nesting/violation_builder.py +139 -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 +99 -0
- src/linters/srp/class_analyzer.py +113 -0
- src/linters/srp/config.py +82 -0
- src/linters/srp/heuristics.py +89 -0
- src/linters/srp/linter.py +234 -0
- src/linters/srp/metrics_evaluator.py +47 -0
- src/linters/srp/python_analyzer.py +72 -0
- src/linters/srp/typescript_analyzer.py +75 -0
- src/linters/srp/typescript_metrics_calculator.py +90 -0
- src/linters/srp/violation_builder.py +117 -0
- src/orchestrator/core.py +54 -9
- src/templates/thailint_config_template.yaml +158 -0
- src/utils/__init__.py +4 -0
- src/utils/project_root.py +203 -0
- thailint-0.5.0.dist-info/METADATA +1286 -0
- thailint-0.5.0.dist-info/RECORD +96 -0
- {thailint-0.1.5.dist-info → thailint-0.5.0.dist-info}/WHEEL +1 -1
- src/.ai/layout.yaml +0 -48
- thailint-0.1.5.dist-info/METADATA +0 -629
- thailint-0.1.5.dist-info/RECORD +0 -28
- {thailint-0.1.5.dist-info → thailint-0.5.0.dist-info}/entry_points.txt +0 -0
- {thailint-0.1.5.dist-info → thailint-0.5.0.dist-info/licenses}/LICENSE +0 -0
src/__init__.py
CHANGED
|
@@ -7,13 +7,14 @@ Overview: Initializes the CLI application package, defines version number using
|
|
|
7
7
|
and exports the public API. Provides single source of truth for version information used by
|
|
8
8
|
setup tools, CLI help text, and documentation. Exports main CLI entry point, high-level Linter
|
|
9
9
|
class for library usage, configuration utilities, and direct linter imports for advanced usage.
|
|
10
|
-
Includes nesting depth linter exports for convenient access to
|
|
10
|
+
Includes nesting depth linter and SRP linter exports for convenient access to code analysis.
|
|
11
11
|
Version is dynamically loaded from package metadata (pyproject.toml) using importlib.metadata.
|
|
12
12
|
|
|
13
13
|
Dependencies: importlib.metadata for dynamic version loading from installed package metadata
|
|
14
14
|
|
|
15
15
|
Exports: __version__, Linter (high-level API), cli (CLI entry point), load_config, save_config,
|
|
16
|
-
ConfigError, Orchestrator (advanced usage), file_placement_lint, nesting_lint, NestingDepthRule
|
|
16
|
+
ConfigError, Orchestrator (advanced usage), file_placement_lint, nesting_lint, NestingDepthRule,
|
|
17
|
+
srp_lint, SRPRule
|
|
17
18
|
|
|
18
19
|
Interfaces: Package version string, Linter class API, CLI command group, configuration functions
|
|
19
20
|
"""
|
|
@@ -37,6 +38,8 @@ from src.config import ConfigError, load_config, save_config
|
|
|
37
38
|
from src.linters.file_placement import lint as file_placement_lint
|
|
38
39
|
from src.linters.nesting import NestingDepthRule
|
|
39
40
|
from src.linters.nesting import lint as nesting_lint
|
|
41
|
+
from src.linters.srp import SRPRule
|
|
42
|
+
from src.linters.srp import lint as srp_lint
|
|
40
43
|
from src.orchestrator.core import Orchestrator
|
|
41
44
|
|
|
42
45
|
__all__ = [
|
|
@@ -53,4 +56,6 @@ __all__ = [
|
|
|
53
56
|
"file_placement_lint",
|
|
54
57
|
"nesting_lint",
|
|
55
58
|
"NestingDepthRule",
|
|
59
|
+
"srp_lint",
|
|
60
|
+
"SRPRule",
|
|
56
61
|
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Analyzers package for language-specific AST analysis base classes
|
|
3
|
+
|
|
4
|
+
Scope: Language analyzers (TypeScript, Python) providing shared parsing and traversal utilities
|
|
5
|
+
|
|
6
|
+
Overview: Package containing base analyzer classes for different programming languages.
|
|
7
|
+
Provides common tree-sitter initialization, AST parsing, and node traversal patterns
|
|
8
|
+
to eliminate duplicate code across linters. Each language has a base analyzer class
|
|
9
|
+
(TypeScriptBaseAnalyzer, etc.) that linter-specific analyzers extend. Centralizes
|
|
10
|
+
language parsing infrastructure to improve maintainability and consistency.
|
|
11
|
+
|
|
12
|
+
Dependencies: tree-sitter, language-specific tree-sitter bindings
|
|
13
|
+
|
|
14
|
+
Exports: TypeScriptBaseAnalyzer
|
|
15
|
+
|
|
16
|
+
Interfaces: Base analyzer classes with parse(), walk_tree(), and extract() methods
|
|
17
|
+
|
|
18
|
+
Implementation: Composition-based design for linter analyzers to use base utilities
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from .typescript_base import TypeScriptBaseAnalyzer
|
|
22
|
+
|
|
23
|
+
__all__ = ["TypeScriptBaseAnalyzer"]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Base class for TypeScript AST analysis with tree-sitter parsing
|
|
3
|
+
|
|
4
|
+
Scope: Common tree-sitter initialization, parsing, and traversal utilities for TypeScript
|
|
5
|
+
|
|
6
|
+
Overview: Provides shared infrastructure for TypeScript code analysis using tree-sitter parser.
|
|
7
|
+
Implements common tree-sitter initialization with language setup and parser configuration.
|
|
8
|
+
Provides reusable parsing methods that convert TypeScript source to AST nodes. Includes
|
|
9
|
+
shared traversal utilities for walking AST trees recursively and finding nodes by type.
|
|
10
|
+
Centralizes node extraction patterns including name extraction from identifiers and
|
|
11
|
+
type identifiers. Serves as foundation for specialized analyzers (SRP, nesting, DRY)
|
|
12
|
+
to eliminate duplicate tree-sitter boilerplate.
|
|
13
|
+
|
|
14
|
+
Dependencies: tree-sitter, tree-sitter-typescript
|
|
15
|
+
|
|
16
|
+
Exports: TypeScriptBaseAnalyzer class with parsing and traversal utilities
|
|
17
|
+
|
|
18
|
+
Interfaces: parse_typescript(code), walk_tree(node, node_type), extract_node_text(node)
|
|
19
|
+
|
|
20
|
+
Implementation: Tree-sitter parser singleton, recursive AST traversal, composition pattern
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
import tree_sitter_typescript as tstypescript
|
|
27
|
+
from tree_sitter import Language, Node, Parser
|
|
28
|
+
|
|
29
|
+
TS_LANGUAGE = Language(tstypescript.language_typescript())
|
|
30
|
+
TS_PARSER = Parser(TS_LANGUAGE)
|
|
31
|
+
TREE_SITTER_AVAILABLE = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
TREE_SITTER_AVAILABLE = False
|
|
34
|
+
TS_PARSER = None # type: ignore[assignment]
|
|
35
|
+
Node = Any # type: ignore[assignment,misc]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TypeScriptBaseAnalyzer:
|
|
39
|
+
"""Base analyzer for TypeScript code using tree-sitter."""
|
|
40
|
+
|
|
41
|
+
def __init__(self) -> None:
|
|
42
|
+
"""Initialize TypeScript base analyzer."""
|
|
43
|
+
self.tree_sitter_available = TREE_SITTER_AVAILABLE
|
|
44
|
+
|
|
45
|
+
def parse_typescript(self, code: str) -> Node | None:
|
|
46
|
+
"""Parse TypeScript code to AST using tree-sitter.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
code: TypeScript source code to parse
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Tree-sitter AST root node, or None if parsing fails or tree-sitter unavailable
|
|
53
|
+
"""
|
|
54
|
+
if not TREE_SITTER_AVAILABLE or TS_PARSER is None:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
tree = TS_PARSER.parse(bytes(code, "utf8"))
|
|
58
|
+
return tree.root_node
|
|
59
|
+
|
|
60
|
+
def walk_tree(self, node: Node, node_type: str) -> list[Node]:
|
|
61
|
+
"""Find all nodes of a specific type in the AST.
|
|
62
|
+
|
|
63
|
+
Recursively walks the tree and collects all nodes matching the given type.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
node: Root tree-sitter node to search from
|
|
67
|
+
node_type: Tree-sitter node type to find (e.g., "class_declaration")
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
List of all matching nodes
|
|
71
|
+
"""
|
|
72
|
+
if not TREE_SITTER_AVAILABLE or node is None:
|
|
73
|
+
return []
|
|
74
|
+
|
|
75
|
+
nodes: list[Node] = []
|
|
76
|
+
self._walk_tree_recursive(node, node_type, nodes)
|
|
77
|
+
return nodes
|
|
78
|
+
|
|
79
|
+
def _walk_tree_recursive(self, node: Node, node_type: str, nodes: list[Node]) -> None:
|
|
80
|
+
"""Recursively walk tree to find nodes of specific type.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
node: Current tree-sitter node
|
|
84
|
+
node_type: Node type to find
|
|
85
|
+
nodes: List to accumulate matching nodes
|
|
86
|
+
"""
|
|
87
|
+
if node.type == node_type:
|
|
88
|
+
nodes.append(node)
|
|
89
|
+
|
|
90
|
+
for child in node.children:
|
|
91
|
+
self._walk_tree_recursive(child, node_type, nodes)
|
|
92
|
+
|
|
93
|
+
def extract_node_text(self, node: Node) -> str:
|
|
94
|
+
"""Extract text content from a tree-sitter node.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
node: Tree-sitter node
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Decoded text content of the node
|
|
101
|
+
"""
|
|
102
|
+
text = node.text
|
|
103
|
+
if text is None:
|
|
104
|
+
return ""
|
|
105
|
+
return text.decode()
|
|
106
|
+
|
|
107
|
+
def find_child_by_type(self, node: Node, child_type: str) -> Node | None:
|
|
108
|
+
"""Find first child node of a specific type.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
node: Parent node to search
|
|
112
|
+
child_type: Child node type to find
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
First matching child node or None
|
|
116
|
+
"""
|
|
117
|
+
for child in node.children:
|
|
118
|
+
if child.type == child_type:
|
|
119
|
+
return child
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
def find_children_by_types(self, node: Node, child_types: set[str]) -> list[Node]:
|
|
123
|
+
"""Find all children matching any of the given types.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
node: Parent node to search
|
|
127
|
+
child_types: Set of child node types to find
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
List of matching child nodes
|
|
131
|
+
"""
|
|
132
|
+
return [child for child in node.children if child.type in child_types]
|
|
133
|
+
|
|
134
|
+
def extract_identifier_name(self, node: Node) -> str:
|
|
135
|
+
"""Extract identifier or type_identifier name from node children.
|
|
136
|
+
|
|
137
|
+
Common pattern for extracting names from class/function declarations.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
node: Node to extract identifier from
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Identifier name or default fallback
|
|
144
|
+
"""
|
|
145
|
+
for child in node.children:
|
|
146
|
+
if child.type in ("identifier", "type_identifier", "property_identifier"):
|
|
147
|
+
return self.extract_node_text(child)
|
|
148
|
+
return "anonymous"
|
src/api.py
CHANGED
|
@@ -60,7 +60,7 @@ class Linter:
|
|
|
60
60
|
|
|
61
61
|
config_path = self._resolve_config_path(config_file)
|
|
62
62
|
self.config = self.config_loader.load(config_path)
|
|
63
|
-
self.orchestrator = Orchestrator(project_root=self.project_root)
|
|
63
|
+
self.orchestrator = Orchestrator(project_root=self.project_root, config=self.config)
|
|
64
64
|
|
|
65
65
|
def _resolve_config_path(self, config_file: str | Path | None) -> Path:
|
|
66
66
|
"""Resolve configuration file path."""
|