lintro 0.6.2__py3-none-any.whl → 0.17.2__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.
- lintro/__init__.py +1 -1
- lintro/cli.py +230 -14
- lintro/cli_utils/commands/__init__.py +8 -1
- lintro/cli_utils/commands/check.py +1 -0
- lintro/cli_utils/commands/config.py +325 -0
- lintro/cli_utils/commands/format.py +2 -2
- lintro/cli_utils/commands/init.py +361 -0
- lintro/cli_utils/commands/list_tools.py +180 -42
- lintro/cli_utils/commands/test.py +316 -0
- lintro/cli_utils/commands/versions.py +81 -0
- lintro/config/__init__.py +62 -0
- lintro/config/config_loader.py +420 -0
- lintro/config/lintro_config.py +189 -0
- lintro/config/tool_config_generator.py +403 -0
- lintro/enums/__init__.py +1 -0
- lintro/enums/darglint_strictness.py +10 -0
- lintro/enums/hadolint_enums.py +22 -0
- lintro/enums/tool_name.py +2 -0
- lintro/enums/tool_type.py +2 -0
- lintro/enums/yamllint_format.py +11 -0
- lintro/exceptions/__init__.py +1 -0
- lintro/formatters/__init__.py +1 -0
- lintro/formatters/core/__init__.py +1 -0
- lintro/formatters/core/output_style.py +11 -0
- lintro/formatters/core/table_descriptor.py +8 -0
- lintro/formatters/styles/csv.py +2 -0
- lintro/formatters/styles/grid.py +2 -0
- lintro/formatters/styles/html.py +2 -0
- lintro/formatters/styles/json.py +2 -0
- lintro/formatters/styles/markdown.py +2 -0
- lintro/formatters/styles/plain.py +2 -0
- lintro/formatters/tools/__init__.py +12 -0
- lintro/formatters/tools/black_formatter.py +27 -5
- lintro/formatters/tools/darglint_formatter.py +16 -1
- lintro/formatters/tools/eslint_formatter.py +108 -0
- lintro/formatters/tools/hadolint_formatter.py +13 -0
- lintro/formatters/tools/markdownlint_formatter.py +88 -0
- lintro/formatters/tools/prettier_formatter.py +15 -0
- lintro/formatters/tools/pytest_formatter.py +201 -0
- lintro/formatters/tools/ruff_formatter.py +26 -5
- lintro/formatters/tools/yamllint_formatter.py +14 -1
- lintro/models/__init__.py +1 -0
- lintro/models/core/__init__.py +1 -0
- lintro/models/core/tool_config.py +11 -7
- lintro/parsers/__init__.py +69 -9
- lintro/parsers/actionlint/actionlint_parser.py +1 -1
- lintro/parsers/bandit/__init__.py +6 -0
- lintro/parsers/bandit/bandit_issue.py +49 -0
- lintro/parsers/bandit/bandit_parser.py +99 -0
- lintro/parsers/black/black_issue.py +4 -0
- lintro/parsers/darglint/__init__.py +1 -0
- lintro/parsers/darglint/darglint_issue.py +11 -0
- lintro/parsers/eslint/__init__.py +6 -0
- lintro/parsers/eslint/eslint_issue.py +26 -0
- lintro/parsers/eslint/eslint_parser.py +63 -0
- lintro/parsers/markdownlint/__init__.py +6 -0
- lintro/parsers/markdownlint/markdownlint_issue.py +22 -0
- lintro/parsers/markdownlint/markdownlint_parser.py +113 -0
- lintro/parsers/prettier/__init__.py +1 -0
- lintro/parsers/prettier/prettier_issue.py +12 -0
- lintro/parsers/prettier/prettier_parser.py +1 -1
- lintro/parsers/pytest/__init__.py +21 -0
- lintro/parsers/pytest/pytest_issue.py +28 -0
- lintro/parsers/pytest/pytest_parser.py +483 -0
- lintro/parsers/ruff/ruff_parser.py +6 -2
- lintro/parsers/yamllint/__init__.py +1 -0
- lintro/tools/__init__.py +3 -1
- lintro/tools/core/__init__.py +1 -0
- lintro/tools/core/timeout_utils.py +112 -0
- lintro/tools/core/tool_base.py +286 -50
- lintro/tools/core/tool_manager.py +77 -24
- lintro/tools/core/version_requirements.py +482 -0
- lintro/tools/implementations/__init__.py +1 -0
- lintro/tools/implementations/pytest/pytest_command_builder.py +311 -0
- lintro/tools/implementations/pytest/pytest_config.py +200 -0
- lintro/tools/implementations/pytest/pytest_error_handler.py +128 -0
- lintro/tools/implementations/pytest/pytest_executor.py +122 -0
- lintro/tools/implementations/pytest/pytest_handlers.py +375 -0
- lintro/tools/implementations/pytest/pytest_option_validators.py +212 -0
- lintro/tools/implementations/pytest/pytest_output_processor.py +408 -0
- lintro/tools/implementations/pytest/pytest_result_processor.py +113 -0
- lintro/tools/implementations/pytest/pytest_utils.py +697 -0
- lintro/tools/implementations/tool_actionlint.py +106 -16
- lintro/tools/implementations/tool_bandit.py +34 -29
- lintro/tools/implementations/tool_black.py +236 -29
- lintro/tools/implementations/tool_darglint.py +183 -22
- lintro/tools/implementations/tool_eslint.py +374 -0
- lintro/tools/implementations/tool_hadolint.py +94 -25
- lintro/tools/implementations/tool_markdownlint.py +354 -0
- lintro/tools/implementations/tool_prettier.py +317 -24
- lintro/tools/implementations/tool_pytest.py +327 -0
- lintro/tools/implementations/tool_ruff.py +278 -84
- lintro/tools/implementations/tool_yamllint.py +448 -34
- lintro/tools/tool_enum.py +8 -0
- lintro/utils/__init__.py +1 -0
- lintro/utils/ascii_normalize_cli.py +5 -0
- lintro/utils/config.py +41 -18
- lintro/utils/console_logger.py +211 -25
- lintro/utils/path_utils.py +42 -0
- lintro/utils/tool_executor.py +339 -45
- lintro/utils/tool_utils.py +51 -24
- lintro/utils/unified_config.py +926 -0
- {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/METADATA +172 -30
- lintro-0.17.2.dist-info/RECORD +134 -0
- lintro-0.6.2.dist-info/RECORD +0 -96
- {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/WHEEL +0 -0
- {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/entry_points.txt +0 -0
- {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/licenses/LICENSE +0 -0
- {lintro-0.6.2.dist-info → lintro-0.17.2.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Prettier code formatter integration."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import subprocess # nosec B404 - used safely with shell disabled
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
6
|
|
|
6
7
|
from loguru import logger
|
|
7
8
|
|
|
8
9
|
from lintro.enums.tool_type import ToolType
|
|
9
10
|
from lintro.models.core.tool import Tool, ToolConfig, ToolResult
|
|
11
|
+
from lintro.parsers.prettier.prettier_issue import PrettierIssue
|
|
10
12
|
from lintro.parsers.prettier.prettier_parser import parse_prettier_output
|
|
11
13
|
from lintro.tools.core.tool_base import BaseTool
|
|
12
14
|
from lintro.utils.tool_utils import walk_files_with_excludes
|
|
@@ -55,12 +57,21 @@ class PrettierTool(BaseTool):
|
|
|
55
57
|
),
|
|
56
58
|
)
|
|
57
59
|
|
|
60
|
+
def __post_init__(self) -> None:
|
|
61
|
+
"""Initialize prettier tool."""
|
|
62
|
+
super().__post_init__()
|
|
63
|
+
# Note: .prettierignore is handled by passing --ignore-path to prettier
|
|
64
|
+
# rather than loading into lintro's exclude patterns, to ensure prettier's
|
|
65
|
+
# native ignore logic is used consistently
|
|
66
|
+
|
|
58
67
|
def set_options(
|
|
59
68
|
self,
|
|
60
69
|
exclude_patterns: list[str] | None = None,
|
|
61
70
|
include_venv: bool = False,
|
|
62
71
|
timeout: int | None = None,
|
|
63
72
|
verbose_fix_output: bool | None = None,
|
|
73
|
+
line_length: int | None = None,
|
|
74
|
+
**kwargs,
|
|
64
75
|
) -> None:
|
|
65
76
|
"""Set options for the core.
|
|
66
77
|
|
|
@@ -69,13 +80,26 @@ class PrettierTool(BaseTool):
|
|
|
69
80
|
include_venv: Whether to include virtual environment directories
|
|
70
81
|
timeout: Timeout in seconds per file (default: 30)
|
|
71
82
|
verbose_fix_output: If True, include raw Prettier output in fix()
|
|
83
|
+
line_length: Print width for prettier (maps to --print-width).
|
|
84
|
+
If provided, this will be stored and used in CLI args.
|
|
85
|
+
**kwargs: Additional options (ignored for compatibility)
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
ValueError: If line_length is not a positive integer.
|
|
72
89
|
"""
|
|
73
|
-
|
|
90
|
+
if exclude_patterns is not None:
|
|
91
|
+
self.exclude_patterns = exclude_patterns.copy()
|
|
74
92
|
self.include_venv = include_venv
|
|
75
93
|
if timeout is not None:
|
|
76
94
|
self.timeout = timeout
|
|
77
95
|
if verbose_fix_output is not None:
|
|
78
96
|
self.options["verbose_fix_output"] = verbose_fix_output
|
|
97
|
+
if line_length is not None:
|
|
98
|
+
if not isinstance(line_length, int):
|
|
99
|
+
raise ValueError("line_length must be an integer")
|
|
100
|
+
if line_length <= 0:
|
|
101
|
+
raise ValueError("line_length must be positive")
|
|
102
|
+
self.options["line_length"] = line_length
|
|
79
103
|
|
|
80
104
|
def _find_config(self) -> str | None:
|
|
81
105
|
"""Locate a Prettier config if none is found by native discovery.
|
|
@@ -90,6 +114,150 @@ class PrettierTool(BaseTool):
|
|
|
90
114
|
"""
|
|
91
115
|
return None
|
|
92
116
|
|
|
117
|
+
def _find_prettier_config(self, search_dir: str | None = None) -> str | None:
|
|
118
|
+
"""Locate prettier config file by walking up the directory tree.
|
|
119
|
+
|
|
120
|
+
Prettier searches upward from the file's directory to find config files,
|
|
121
|
+
so we do the same to match native behavior and ensure config is found
|
|
122
|
+
even when cwd is a subdirectory.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
search_dir: Directory to start searching from. If None, searches from
|
|
126
|
+
current working directory.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
str | None: Path to config file if found, None otherwise.
|
|
130
|
+
"""
|
|
131
|
+
config_paths = [
|
|
132
|
+
".prettierrc",
|
|
133
|
+
".prettierrc.json",
|
|
134
|
+
".prettierrc.js",
|
|
135
|
+
".prettierrc.yaml",
|
|
136
|
+
".prettierrc.yml",
|
|
137
|
+
"prettier.config.js",
|
|
138
|
+
"package.json",
|
|
139
|
+
]
|
|
140
|
+
# Search upward from search_dir (or cwd) to find config, just like prettier does
|
|
141
|
+
start_dir = os.path.abspath(search_dir) if search_dir else os.getcwd()
|
|
142
|
+
current_dir = start_dir
|
|
143
|
+
|
|
144
|
+
# Walk upward from the directory to find config
|
|
145
|
+
# Stop at filesystem root to avoid infinite loop
|
|
146
|
+
while True:
|
|
147
|
+
for config_name in config_paths:
|
|
148
|
+
config_path = os.path.join(current_dir, config_name)
|
|
149
|
+
if os.path.exists(config_path):
|
|
150
|
+
# For package.json, check if it contains prettier config
|
|
151
|
+
if config_name == "package.json":
|
|
152
|
+
try:
|
|
153
|
+
import json
|
|
154
|
+
|
|
155
|
+
with open(config_path, encoding="utf-8") as f:
|
|
156
|
+
pkg_data = json.load(f)
|
|
157
|
+
if "prettier" not in pkg_data:
|
|
158
|
+
continue
|
|
159
|
+
except (
|
|
160
|
+
json.JSONDecodeError,
|
|
161
|
+
FileNotFoundError,
|
|
162
|
+
PermissionError,
|
|
163
|
+
):
|
|
164
|
+
# Skip invalid or unreadable package.json files
|
|
165
|
+
continue
|
|
166
|
+
logger.debug(
|
|
167
|
+
f"[PrettierTool] Found config file: {config_path} "
|
|
168
|
+
f"(searched from {start_dir})",
|
|
169
|
+
)
|
|
170
|
+
return config_path
|
|
171
|
+
|
|
172
|
+
# Move up one directory
|
|
173
|
+
parent_dir = os.path.dirname(current_dir)
|
|
174
|
+
# Stop if we've reached the filesystem root (parent == current)
|
|
175
|
+
if parent_dir == current_dir:
|
|
176
|
+
break
|
|
177
|
+
current_dir = parent_dir
|
|
178
|
+
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def _find_prettierignore(self, search_dir: str | None = None) -> str | None:
|
|
182
|
+
"""Locate .prettierignore file by walking up the directory tree.
|
|
183
|
+
|
|
184
|
+
Prettier searches upward from the file's directory to find .prettierignore,
|
|
185
|
+
so we do the same to match native behavior and ensure ignore file is found
|
|
186
|
+
even when cwd is a subdirectory.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
search_dir: Directory to start searching from. If None, searches from
|
|
190
|
+
current working directory.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
str | None: Path to .prettierignore file if found, None otherwise.
|
|
194
|
+
"""
|
|
195
|
+
ignore_filename = ".prettierignore"
|
|
196
|
+
# Search upward from search_dir (or cwd) to find ignore file
|
|
197
|
+
start_dir = os.path.abspath(search_dir) if search_dir else os.getcwd()
|
|
198
|
+
current_dir = start_dir
|
|
199
|
+
|
|
200
|
+
# Walk upward from the directory to find ignore file
|
|
201
|
+
# Stop at filesystem root to avoid infinite loop
|
|
202
|
+
while True:
|
|
203
|
+
ignore_path = os.path.join(current_dir, ignore_filename)
|
|
204
|
+
if os.path.exists(ignore_path):
|
|
205
|
+
logger.debug(
|
|
206
|
+
f"[PrettierTool] Found .prettierignore: {ignore_path} "
|
|
207
|
+
f"(searched from {start_dir})",
|
|
208
|
+
)
|
|
209
|
+
return ignore_path
|
|
210
|
+
|
|
211
|
+
# Move up one directory
|
|
212
|
+
parent_dir = os.path.dirname(current_dir)
|
|
213
|
+
# Stop if we've reached the filesystem root (parent == current)
|
|
214
|
+
if parent_dir == current_dir:
|
|
215
|
+
break
|
|
216
|
+
current_dir = parent_dir
|
|
217
|
+
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
def _create_timeout_result(
|
|
221
|
+
self,
|
|
222
|
+
timeout_val: int,
|
|
223
|
+
initial_issues: list | None = None,
|
|
224
|
+
initial_count: int = 0,
|
|
225
|
+
) -> ToolResult:
|
|
226
|
+
"""Create a ToolResult for timeout scenarios.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
timeout_val: The timeout value that was exceeded.
|
|
230
|
+
initial_issues: Optional list of issues found before timeout.
|
|
231
|
+
initial_count: Optional count of initial issues.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
ToolResult: ToolResult instance representing timeout failure.
|
|
235
|
+
"""
|
|
236
|
+
timeout_msg = (
|
|
237
|
+
f"Prettier execution timed out ({timeout_val}s limit exceeded).\n\n"
|
|
238
|
+
"This may indicate:\n"
|
|
239
|
+
" - Large codebase taking too long to process\n"
|
|
240
|
+
" - Need to increase timeout via --tool-options prettier:timeout=N"
|
|
241
|
+
)
|
|
242
|
+
timeout_issue = PrettierIssue(
|
|
243
|
+
file="execution",
|
|
244
|
+
line=None,
|
|
245
|
+
code="TIMEOUT",
|
|
246
|
+
message=timeout_msg,
|
|
247
|
+
column=None,
|
|
248
|
+
)
|
|
249
|
+
combined_issues = (initial_issues or []) + [timeout_issue]
|
|
250
|
+
return ToolResult(
|
|
251
|
+
name=self.name,
|
|
252
|
+
success=False,
|
|
253
|
+
output=timeout_msg,
|
|
254
|
+
issues_count=len(combined_issues),
|
|
255
|
+
issues=combined_issues,
|
|
256
|
+
initial_issues_count=initial_count,
|
|
257
|
+
fixed_issues_count=0,
|
|
258
|
+
remaining_issues_count=len(combined_issues),
|
|
259
|
+
)
|
|
260
|
+
|
|
93
261
|
def check(
|
|
94
262
|
self,
|
|
95
263
|
paths: list[str],
|
|
@@ -102,6 +270,11 @@ class PrettierTool(BaseTool):
|
|
|
102
270
|
Returns:
|
|
103
271
|
ToolResult instance
|
|
104
272
|
"""
|
|
273
|
+
# Check version requirements
|
|
274
|
+
version_result = self._verify_tool_version()
|
|
275
|
+
if version_result is not None:
|
|
276
|
+
return version_result
|
|
277
|
+
|
|
105
278
|
self._validate_paths(paths=paths)
|
|
106
279
|
prettier_files: list[str] = walk_files_with_excludes(
|
|
107
280
|
paths=paths,
|
|
@@ -109,6 +282,17 @@ class PrettierTool(BaseTool):
|
|
|
109
282
|
exclude_patterns=self.exclude_patterns,
|
|
110
283
|
include_venv=self.include_venv,
|
|
111
284
|
)
|
|
285
|
+
logger.debug(
|
|
286
|
+
f"[PrettierTool] Discovered {len(prettier_files)} files matching patterns: "
|
|
287
|
+
f"{self.config.file_patterns}",
|
|
288
|
+
)
|
|
289
|
+
logger.debug(
|
|
290
|
+
f"[PrettierTool] Exclude patterns applied: {self.exclude_patterns}",
|
|
291
|
+
)
|
|
292
|
+
if prettier_files:
|
|
293
|
+
logger.debug(
|
|
294
|
+
f"[PrettierTool] Files to check (first 10): " f"{prettier_files[:10]}",
|
|
295
|
+
)
|
|
112
296
|
if not prettier_files:
|
|
113
297
|
return Tool.to_result(
|
|
114
298
|
name=self.name,
|
|
@@ -118,6 +302,7 @@ class PrettierTool(BaseTool):
|
|
|
118
302
|
)
|
|
119
303
|
# Use relative paths and set cwd to the common parent
|
|
120
304
|
cwd: str = self.get_cwd(paths=prettier_files)
|
|
305
|
+
logger.debug(f"[PrettierTool] Working directory: {cwd}")
|
|
121
306
|
rel_files: list[str] = [
|
|
122
307
|
os.path.relpath(f, cwd) if cwd else f for f in prettier_files
|
|
123
308
|
]
|
|
@@ -125,14 +310,53 @@ class PrettierTool(BaseTool):
|
|
|
125
310
|
cmd: list[str] = self._get_executable_command(tool_name="prettier") + [
|
|
126
311
|
"--check",
|
|
127
312
|
]
|
|
128
|
-
|
|
313
|
+
|
|
314
|
+
# Add Lintro config injection args (--no-config, --config)
|
|
315
|
+
# This takes precedence over native config auto-discovery
|
|
316
|
+
config_args = self._build_config_args()
|
|
317
|
+
if config_args:
|
|
318
|
+
cmd.extend(config_args)
|
|
319
|
+
logger.debug(
|
|
320
|
+
"[PrettierTool] Using Lintro config injection",
|
|
321
|
+
)
|
|
322
|
+
else:
|
|
323
|
+
# Fallback: Find config and ignore files by walking up from cwd
|
|
324
|
+
found_config = self._find_prettier_config(search_dir=cwd)
|
|
325
|
+
if found_config:
|
|
326
|
+
logger.debug(
|
|
327
|
+
f"[PrettierTool] Found config: {found_config} (auto-detecting)",
|
|
328
|
+
)
|
|
329
|
+
else:
|
|
330
|
+
logger.debug(
|
|
331
|
+
"[PrettierTool] No prettier config file found (using defaults)",
|
|
332
|
+
)
|
|
333
|
+
# Apply line_length as --print-width if set and no config found
|
|
334
|
+
line_length = self.options.get("line_length")
|
|
335
|
+
if line_length:
|
|
336
|
+
cmd.extend(["--print-width", str(line_length)])
|
|
337
|
+
logger.debug(
|
|
338
|
+
"[PrettierTool] Using --print-width=%s from options",
|
|
339
|
+
line_length,
|
|
340
|
+
)
|
|
341
|
+
# Find .prettierignore by walking up from cwd
|
|
342
|
+
prettierignore_path = self._find_prettierignore(search_dir=cwd)
|
|
343
|
+
if prettierignore_path:
|
|
344
|
+
logger.debug(
|
|
345
|
+
f"[PrettierTool] Found .prettierignore: {prettierignore_path} "
|
|
346
|
+
"(auto-detecting)",
|
|
347
|
+
)
|
|
348
|
+
|
|
129
349
|
cmd.extend(rel_files)
|
|
130
350
|
logger.debug(f"[PrettierTool] Running: {' '.join(cmd)} (cwd={cwd})")
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
351
|
+
timeout_val: int = self.options.get("timeout", self._default_timeout)
|
|
352
|
+
try:
|
|
353
|
+
result = self._run_subprocess(
|
|
354
|
+
cmd=cmd,
|
|
355
|
+
timeout=timeout_val,
|
|
356
|
+
cwd=cwd,
|
|
357
|
+
)
|
|
358
|
+
except subprocess.TimeoutExpired:
|
|
359
|
+
return self._create_timeout_result(timeout_val=timeout_val)
|
|
136
360
|
output: str = result[1]
|
|
137
361
|
# Do not filter lines post-hoc; rely on discovery and ignore files
|
|
138
362
|
issues: list = parse_prettier_output(output=output)
|
|
@@ -142,11 +366,14 @@ class PrettierTool(BaseTool):
|
|
|
142
366
|
# so the unified logger prints a single, consistent success line.
|
|
143
367
|
if success:
|
|
144
368
|
output = None
|
|
145
|
-
|
|
369
|
+
|
|
370
|
+
# Return full ToolResult so table rendering can use parsed issues
|
|
371
|
+
return ToolResult(
|
|
146
372
|
name=self.name,
|
|
147
373
|
success=success,
|
|
148
374
|
output=output,
|
|
149
375
|
issues_count=issues_count,
|
|
376
|
+
issues=issues,
|
|
150
377
|
)
|
|
151
378
|
|
|
152
379
|
def fix(
|
|
@@ -161,6 +388,11 @@ class PrettierTool(BaseTool):
|
|
|
161
388
|
Returns:
|
|
162
389
|
ToolResult: Result object with counts and messages.
|
|
163
390
|
"""
|
|
391
|
+
# Check version requirements
|
|
392
|
+
version_result = self._verify_tool_version()
|
|
393
|
+
if version_result is not None:
|
|
394
|
+
return version_result
|
|
395
|
+
|
|
164
396
|
self._validate_paths(paths=paths)
|
|
165
397
|
prettier_files: list[str] = walk_files_with_excludes(
|
|
166
398
|
paths=paths,
|
|
@@ -182,18 +414,55 @@ class PrettierTool(BaseTool):
|
|
|
182
414
|
os.path.relpath(f, cwd) if cwd else f for f in prettier_files
|
|
183
415
|
]
|
|
184
416
|
|
|
417
|
+
# Get Lintro config injection args (--no-config, --config)
|
|
418
|
+
config_args = self._build_config_args()
|
|
419
|
+
fallback_args: list[str] = []
|
|
420
|
+
if not config_args:
|
|
421
|
+
# Fallback: Find config and ignore files by walking up from cwd
|
|
422
|
+
found_config = self._find_prettier_config(search_dir=cwd)
|
|
423
|
+
if found_config:
|
|
424
|
+
logger.debug(
|
|
425
|
+
f"[PrettierTool] Found config: {found_config} (auto-detecting)",
|
|
426
|
+
)
|
|
427
|
+
else:
|
|
428
|
+
logger.debug(
|
|
429
|
+
"[PrettierTool] No prettier config file found (using defaults)",
|
|
430
|
+
)
|
|
431
|
+
# Apply line_length as --print-width if set and no config found
|
|
432
|
+
line_length = self.options.get("line_length")
|
|
433
|
+
if line_length:
|
|
434
|
+
fallback_args.extend(["--print-width", str(line_length)])
|
|
435
|
+
logger.debug(
|
|
436
|
+
"[PrettierTool] Using --print-width=%s from options",
|
|
437
|
+
line_length,
|
|
438
|
+
)
|
|
439
|
+
prettierignore_path = self._find_prettierignore(search_dir=cwd)
|
|
440
|
+
if prettierignore_path:
|
|
441
|
+
logger.debug(
|
|
442
|
+
f"[PrettierTool] Found .prettierignore: {prettierignore_path} "
|
|
443
|
+
"(auto-detecting)",
|
|
444
|
+
)
|
|
445
|
+
|
|
185
446
|
# Check for issues first
|
|
186
447
|
check_cmd: list[str] = self._get_executable_command(tool_name="prettier") + [
|
|
187
448
|
"--check",
|
|
188
449
|
]
|
|
189
|
-
#
|
|
450
|
+
# Add Lintro config injection if available, otherwise use fallback args
|
|
451
|
+
if config_args:
|
|
452
|
+
check_cmd.extend(config_args)
|
|
453
|
+
elif fallback_args:
|
|
454
|
+
check_cmd.extend(fallback_args)
|
|
190
455
|
check_cmd.extend(rel_files)
|
|
191
456
|
logger.debug(f"[PrettierTool] Checking: {' '.join(check_cmd)} (cwd={cwd})")
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
457
|
+
timeout_val: int = self.options.get("timeout", self._default_timeout)
|
|
458
|
+
try:
|
|
459
|
+
check_result = self._run_subprocess(
|
|
460
|
+
cmd=check_cmd,
|
|
461
|
+
timeout=timeout_val,
|
|
462
|
+
cwd=cwd,
|
|
463
|
+
)
|
|
464
|
+
except subprocess.TimeoutExpired:
|
|
465
|
+
return self._create_timeout_result(timeout_val=timeout_val)
|
|
197
466
|
check_output: str = check_result[1]
|
|
198
467
|
|
|
199
468
|
# Parse initial issues
|
|
@@ -204,21 +473,40 @@ class PrettierTool(BaseTool):
|
|
|
204
473
|
fix_cmd: list[str] = self._get_executable_command(tool_name="prettier") + [
|
|
205
474
|
"--write",
|
|
206
475
|
]
|
|
476
|
+
# Add Lintro config injection if available, otherwise use fallback args
|
|
477
|
+
if config_args:
|
|
478
|
+
fix_cmd.extend(config_args)
|
|
479
|
+
elif fallback_args:
|
|
480
|
+
fix_cmd.extend(fallback_args)
|
|
207
481
|
fix_cmd.extend(rel_files)
|
|
208
482
|
logger.debug(f"[PrettierTool] Fixing: {' '.join(fix_cmd)} (cwd={cwd})")
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
483
|
+
try:
|
|
484
|
+
fix_result = self._run_subprocess(
|
|
485
|
+
cmd=fix_cmd,
|
|
486
|
+
timeout=timeout_val,
|
|
487
|
+
cwd=cwd,
|
|
488
|
+
)
|
|
489
|
+
except subprocess.TimeoutExpired:
|
|
490
|
+
return self._create_timeout_result(
|
|
491
|
+
timeout_val=timeout_val,
|
|
492
|
+
initial_issues=initial_issues,
|
|
493
|
+
initial_count=initial_count,
|
|
494
|
+
)
|
|
214
495
|
fix_output: str = fix_result[1]
|
|
215
496
|
|
|
216
497
|
# Check for remaining issues after fixing
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
498
|
+
try:
|
|
499
|
+
final_check_result = self._run_subprocess(
|
|
500
|
+
cmd=check_cmd,
|
|
501
|
+
timeout=timeout_val,
|
|
502
|
+
cwd=cwd,
|
|
503
|
+
)
|
|
504
|
+
except subprocess.TimeoutExpired:
|
|
505
|
+
return self._create_timeout_result(
|
|
506
|
+
timeout_val=timeout_val,
|
|
507
|
+
initial_issues=initial_issues,
|
|
508
|
+
initial_count=initial_count,
|
|
509
|
+
)
|
|
222
510
|
final_check_output: str = final_check_result[1]
|
|
223
511
|
remaining_issues: list = parse_prettier_output(output=final_check_output)
|
|
224
512
|
remaining_count: int = len(remaining_issues)
|
|
@@ -258,12 +546,17 @@ class PrettierTool(BaseTool):
|
|
|
258
546
|
# Success means no remaining issues
|
|
259
547
|
success: bool = remaining_count == 0
|
|
260
548
|
|
|
549
|
+
# Combine initial and remaining issues so formatter can split them by fixability
|
|
550
|
+
all_issues = (initial_issues or []) + (remaining_issues or [])
|
|
551
|
+
|
|
261
552
|
return ToolResult(
|
|
262
553
|
name=self.name,
|
|
263
554
|
success=success,
|
|
264
555
|
output=final_output,
|
|
265
556
|
# For fix operations, issues_count represents remaining for summaries
|
|
266
557
|
issues_count=remaining_count,
|
|
558
|
+
# Provide both initial (fixed) and remaining issues for display
|
|
559
|
+
issues=all_issues,
|
|
267
560
|
initial_issues_count=initial_count,
|
|
268
561
|
fixed_issues_count=fixed_count,
|
|
269
562
|
remaining_issues_count=remaining_count,
|