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.
Files changed (52) hide show
  1. src/cli.py +646 -36
  2. src/config.py +6 -2
  3. src/core/base.py +90 -5
  4. src/core/config_parser.py +31 -4
  5. src/linters/dry/block_filter.py +5 -2
  6. src/linters/dry/cache.py +46 -92
  7. src/linters/dry/config.py +17 -13
  8. src/linters/dry/duplicate_storage.py +17 -80
  9. src/linters/dry/file_analyzer.py +11 -48
  10. src/linters/dry/linter.py +5 -12
  11. src/linters/dry/python_analyzer.py +188 -37
  12. src/linters/dry/storage_initializer.py +9 -18
  13. src/linters/dry/token_hasher.py +63 -9
  14. src/linters/dry/typescript_analyzer.py +7 -5
  15. src/linters/dry/violation_filter.py +4 -1
  16. src/linters/file_header/__init__.py +24 -0
  17. src/linters/file_header/atemporal_detector.py +87 -0
  18. src/linters/file_header/config.py +66 -0
  19. src/linters/file_header/field_validator.py +69 -0
  20. src/linters/file_header/linter.py +313 -0
  21. src/linters/file_header/python_parser.py +86 -0
  22. src/linters/file_header/violation_builder.py +78 -0
  23. src/linters/file_placement/linter.py +15 -4
  24. src/linters/magic_numbers/__init__.py +48 -0
  25. src/linters/magic_numbers/config.py +82 -0
  26. src/linters/magic_numbers/context_analyzer.py +247 -0
  27. src/linters/magic_numbers/linter.py +516 -0
  28. src/linters/magic_numbers/python_analyzer.py +76 -0
  29. src/linters/magic_numbers/typescript_analyzer.py +218 -0
  30. src/linters/magic_numbers/violation_builder.py +98 -0
  31. src/linters/nesting/__init__.py +6 -2
  32. src/linters/nesting/config.py +6 -3
  33. src/linters/nesting/linter.py +8 -19
  34. src/linters/nesting/typescript_analyzer.py +1 -0
  35. src/linters/print_statements/__init__.py +53 -0
  36. src/linters/print_statements/config.py +83 -0
  37. src/linters/print_statements/linter.py +430 -0
  38. src/linters/print_statements/python_analyzer.py +155 -0
  39. src/linters/print_statements/typescript_analyzer.py +135 -0
  40. src/linters/print_statements/violation_builder.py +98 -0
  41. src/linters/srp/__init__.py +3 -3
  42. src/linters/srp/config.py +12 -6
  43. src/linters/srp/linter.py +33 -24
  44. src/orchestrator/core.py +12 -2
  45. src/templates/thailint_config_template.yaml +158 -0
  46. src/utils/project_root.py +135 -16
  47. {thailint-0.2.0.dist-info → thailint-0.5.0.dist-info}/METADATA +387 -81
  48. thailint-0.5.0.dist-info/RECORD +96 -0
  49. {thailint-0.2.0.dist-info → thailint-0.5.0.dist-info}/WHEEL +1 -1
  50. thailint-0.2.0.dist-info/RECORD +0 -75
  51. {thailint-0.2.0.dist-info → thailint-0.5.0.dist-info}/entry_points.txt +0 -0
  52. {thailint-0.2.0.dist-info → thailint-0.5.0.dist-info/licenses}/LICENSE +0 -0
src/utils/project_root.py CHANGED
@@ -4,29 +4,52 @@ Purpose: Centralized project root detection for consistent file placement
4
4
  Scope: Single source of truth for finding project root directory
5
5
 
6
6
  Overview: Uses pyprojroot package to provide reliable project root detection across
7
- different environments (development, CI/CD, user installations). Delegates all
8
- project root detection logic to the industry-standard pyprojroot library which
9
- handles various project markers and edge cases that we cannot anticipate.
7
+ different environments (development, CI/CD, user installations). Falls back to
8
+ manual detection if pyprojroot is not available (e.g., in test environments).
9
+ Searches for standard project markers like .git, .thailint.yaml, and pyproject.toml.
10
10
 
11
- Dependencies: pyprojroot for robust project root detection
11
+ Dependencies: pyprojroot (optional, with manual fallback)
12
12
 
13
13
  Exports: is_project_root(), get_project_root()
14
14
 
15
15
  Interfaces: Path-based functions for checking and finding project roots
16
16
 
17
- Implementation: Pure delegation to pyprojroot with fallback to start_path when no root found
17
+ Implementation: pyprojroot delegation with manual fallback for test environments
18
18
  """
19
19
 
20
20
  from pathlib import Path
21
21
 
22
- from pyprojroot import find_root
22
+ # Try to import pyprojroot, but don't fail if it's not available
23
+ try:
24
+ from pyprojroot import find_root
25
+
26
+ HAS_PYPROJROOT = True
27
+ except ImportError:
28
+ HAS_PYPROJROOT = False
29
+
30
+
31
+ def _has_marker(path: Path, marker_name: str, is_dir: bool = False) -> bool:
32
+ """Check if a directory contains a specific marker.
33
+
34
+ Args:
35
+ path: Directory path to check
36
+ marker_name: Name of marker file or directory
37
+ is_dir: True if marker is a directory, False if it's a file
38
+
39
+ Returns:
40
+ True if marker exists, False otherwise
41
+ """
42
+ marker_path = path / marker_name
43
+ if is_dir:
44
+ return marker_path.is_dir()
45
+ return marker_path.is_file()
23
46
 
24
47
 
25
48
  def is_project_root(path: Path) -> bool:
26
49
  """Check if a directory is a project root.
27
50
 
28
- Uses pyprojroot to detect if the given path is a project root by checking
29
- if finding the root from this path returns the same path.
51
+ Uses pyprojroot if available, otherwise checks for common project markers
52
+ like .git, .thailint.yaml, or pyproject.toml.
30
53
 
31
54
  Args:
32
55
  path: Directory path to check
@@ -43,6 +66,21 @@ def is_project_root(path: Path) -> bool:
43
66
  if not path.exists() or not path.is_dir():
44
67
  return False
45
68
 
69
+ if HAS_PYPROJROOT:
70
+ return _check_root_with_pyprojroot(path)
71
+
72
+ return _check_root_with_markers(path)
73
+
74
+
75
+ def _check_root_with_pyprojroot(path: Path) -> bool:
76
+ """Check if path is project root using pyprojroot.
77
+
78
+ Args:
79
+ path: Directory path to check
80
+
81
+ Returns:
82
+ True if path is a project root, False otherwise
83
+ """
46
84
  try:
47
85
  # Find root from this path - if it equals this path, it's a root
48
86
  found_root = find_root(path)
@@ -52,14 +90,74 @@ def is_project_root(path: Path) -> bool:
52
90
  return False
53
91
 
54
92
 
93
+ def _check_root_with_markers(path: Path) -> bool:
94
+ """Check if path contains project root markers.
95
+
96
+ Args:
97
+ path: Directory path to check
98
+
99
+ Returns:
100
+ True if path contains .git, .thailint.yaml, or pyproject.toml
101
+ """
102
+ return (
103
+ _has_marker(path, ".git", is_dir=True)
104
+ or _has_marker(path, ".thailint.yaml", is_dir=False)
105
+ or _has_marker(path, "pyproject.toml", is_dir=False)
106
+ )
107
+
108
+
109
+ def _try_find_with_criterion(criterion: object, start_path: Path) -> Path | None:
110
+ """Try to find project root with a specific criterion.
111
+
112
+ Args:
113
+ criterion: pyprojroot criterion function (e.g., has_dir(".git"))
114
+ start_path: Path to start searching from
115
+
116
+ Returns:
117
+ Found project root or None if not found
118
+ """
119
+ try:
120
+ return find_root(criterion, start=start_path) # type: ignore[arg-type]
121
+ except (OSError, RuntimeError):
122
+ return None
123
+
124
+
125
+ def _find_root_manual(start_path: Path) -> Path:
126
+ """Manually find project root by walking up directory tree.
127
+
128
+ Fallback implementation when pyprojroot is not available.
129
+
130
+ Args:
131
+ start_path: Directory to start searching from
132
+
133
+ Returns:
134
+ Path to project root, or start_path if no markers found
135
+ """
136
+ current = start_path.resolve()
137
+
138
+ # Walk up the directory tree
139
+ for parent in [current] + list(current.parents):
140
+ # Check for project markers
141
+ if (
142
+ _has_marker(parent, ".git", is_dir=True)
143
+ or _has_marker(parent, ".thailint.yaml", is_dir=False)
144
+ or _has_marker(parent, "pyproject.toml", is_dir=False)
145
+ ):
146
+ return parent
147
+
148
+ # No markers found, return start path
149
+ return current
150
+
151
+
55
152
  def get_project_root(start_path: Path | None = None) -> Path:
56
153
  """Find project root by walking up the directory tree.
57
154
 
58
155
  This is the single source of truth for project root detection.
59
156
  All code that needs to find the project root should use this function.
60
157
 
61
- Uses pyprojroot which searches for standard project markers defined by the
62
- pyprojroot library (git repos, Python projects, etc).
158
+ Uses pyprojroot if available, otherwise uses manual detection searching for
159
+ standard project markers (.git directory, pyproject.toml, .thailint.yaml, etc)
160
+ starting from start_path and walking upward.
63
161
 
64
162
  Args:
65
163
  start_path: Directory to start searching from. If None, uses current working directory.
@@ -76,9 +174,30 @@ def get_project_root(start_path: Path | None = None) -> Path:
76
174
 
77
175
  current = start_path.resolve()
78
176
 
79
- try:
80
- # Use pyprojroot to find the project root
81
- return find_root(current)
82
- except (OSError, RuntimeError):
83
- # No project markers found, return the start path
84
- return current
177
+ if HAS_PYPROJROOT:
178
+ return _find_root_with_pyprojroot(current)
179
+
180
+ # Manual fallback for test environments
181
+ return _find_root_manual(current)
182
+
183
+
184
+ def _find_root_with_pyprojroot(current: Path) -> Path:
185
+ """Find project root using pyprojroot library.
186
+
187
+ Args:
188
+ current: Current path to start searching from
189
+
190
+ Returns:
191
+ Path to project root, or current if no markers found
192
+ """
193
+ from pyprojroot import has_dir, has_file
194
+
195
+ # Search for project root markers in priority order
196
+ # Try .git first (most reliable), then .thailint.yaml, then pyproject.toml
197
+ for criterion in [has_dir(".git"), has_file(".thailint.yaml"), has_file("pyproject.toml")]:
198
+ root = _try_find_with_criterion(criterion, current)
199
+ if root is not None:
200
+ return root
201
+
202
+ # No markers found, return start path
203
+ return current