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,6 +1,7 @@
|
|
|
1
1
|
"""Ruff Python linter and formatter integration."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import subprocess # nosec B404 - used safely with shell disabled
|
|
4
5
|
import tomllib
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
7
|
from pathlib import Path
|
|
@@ -14,6 +15,11 @@ from lintro.parsers.ruff.ruff_parser import (
|
|
|
14
15
|
parse_ruff_format_check_output,
|
|
15
16
|
parse_ruff_output,
|
|
16
17
|
)
|
|
18
|
+
from lintro.tools.core.timeout_utils import (
|
|
19
|
+
create_timeout_result,
|
|
20
|
+
get_timeout_value,
|
|
21
|
+
run_subprocess_with_timeout,
|
|
22
|
+
)
|
|
17
23
|
from lintro.tools.core.tool_base import BaseTool
|
|
18
24
|
from lintro.utils.tool_utils import walk_files_with_excludes
|
|
19
25
|
|
|
@@ -31,7 +37,7 @@ def _load_ruff_config() -> dict:
|
|
|
31
37
|
"""Load ruff configuration from pyproject.toml.
|
|
32
38
|
|
|
33
39
|
Returns:
|
|
34
|
-
dict: Ruff configuration dictionary.
|
|
40
|
+
dict: Ruff configuration dictionary with flattened lint settings.
|
|
35
41
|
"""
|
|
36
42
|
config: dict = {}
|
|
37
43
|
pyproject_path = Path("pyproject.toml")
|
|
@@ -41,7 +47,20 @@ def _load_ruff_config() -> dict:
|
|
|
41
47
|
with open(pyproject_path, "rb") as f:
|
|
42
48
|
pyproject_data = tomllib.load(f)
|
|
43
49
|
if "tool" in pyproject_data and "ruff" in pyproject_data["tool"]:
|
|
44
|
-
|
|
50
|
+
ruff_config = pyproject_data["tool"]["ruff"]
|
|
51
|
+
# Copy top-level settings
|
|
52
|
+
config = dict(ruff_config)
|
|
53
|
+
# Flatten nested lint section to top level for easy access
|
|
54
|
+
if "lint" in ruff_config:
|
|
55
|
+
lint_config = ruff_config["lint"]
|
|
56
|
+
if "select" in lint_config:
|
|
57
|
+
config["select"] = lint_config["select"]
|
|
58
|
+
if "ignore" in lint_config:
|
|
59
|
+
config["ignore"] = lint_config["ignore"]
|
|
60
|
+
if "extend-select" in lint_config:
|
|
61
|
+
config["extend_select"] = lint_config["extend-select"]
|
|
62
|
+
if "extend-ignore" in lint_config:
|
|
63
|
+
config["extend_ignore"] = lint_config["extend-ignore"]
|
|
45
64
|
except Exception as e:
|
|
46
65
|
logger.warning(f"Failed to load ruff configuration: {e}")
|
|
47
66
|
|
|
@@ -54,12 +73,14 @@ def _load_lintro_ignore() -> list[str]:
|
|
|
54
73
|
Returns:
|
|
55
74
|
list[str]: List of ignore patterns.
|
|
56
75
|
"""
|
|
76
|
+
from lintro.utils.path_utils import find_lintro_ignore
|
|
77
|
+
|
|
57
78
|
ignore_patterns: list[str] = []
|
|
58
|
-
lintro_ignore_path =
|
|
79
|
+
lintro_ignore_path = find_lintro_ignore()
|
|
59
80
|
|
|
60
|
-
if lintro_ignore_path.exists():
|
|
81
|
+
if lintro_ignore_path and lintro_ignore_path.exists():
|
|
61
82
|
try:
|
|
62
|
-
with open(lintro_ignore_path,
|
|
83
|
+
with open(lintro_ignore_path, encoding="utf-8") as f:
|
|
63
84
|
for line in f:
|
|
64
85
|
line = line.strip()
|
|
65
86
|
# Skip empty lines and comments
|
|
@@ -127,29 +148,40 @@ class RuffTool(BaseTool):
|
|
|
127
148
|
def __post_init__(self) -> None:
|
|
128
149
|
"""Initialize the tool with default configuration."""
|
|
129
150
|
super().__post_init__()
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
|
|
152
|
+
# Skip config loading in test mode to allow tests to set specific options
|
|
153
|
+
# without interference from pyproject.toml settings
|
|
154
|
+
if os.environ.get(RUFF_TEST_MODE_ENV) != RUFF_TEST_MODE_VALUE:
|
|
155
|
+
# Load ruff configuration from pyproject.toml
|
|
156
|
+
ruff_config = _load_ruff_config()
|
|
157
|
+
|
|
158
|
+
# Load .lintro-ignore patterns
|
|
159
|
+
lintro_ignore_patterns = _load_lintro_ignore()
|
|
160
|
+
|
|
161
|
+
# Update exclude patterns from configuration and .lintro-ignore
|
|
162
|
+
if "exclude" in ruff_config:
|
|
163
|
+
self.exclude_patterns.extend(ruff_config["exclude"])
|
|
164
|
+
if lintro_ignore_patterns:
|
|
165
|
+
self.exclude_patterns.extend(lintro_ignore_patterns)
|
|
166
|
+
|
|
167
|
+
# Update other options from configuration
|
|
168
|
+
if "line_length" in ruff_config:
|
|
169
|
+
self.options["line_length"] = ruff_config["line_length"]
|
|
170
|
+
if "target_version" in ruff_config:
|
|
171
|
+
self.options["target_version"] = ruff_config["target_version"]
|
|
172
|
+
if "select" in ruff_config:
|
|
173
|
+
self.options["select"] = ruff_config["select"]
|
|
174
|
+
if "ignore" in ruff_config:
|
|
175
|
+
self.options["ignore"] = ruff_config["ignore"]
|
|
176
|
+
if "unsafe_fixes" in ruff_config:
|
|
177
|
+
self.options["unsafe_fixes"] = ruff_config["unsafe_fixes"]
|
|
178
|
+
|
|
179
|
+
# Allow environment variable override for unsafe fixes
|
|
180
|
+
# Useful for development and CI environments
|
|
181
|
+
# This must come after config loading to override config values
|
|
182
|
+
env_unsafe_fixes = os.environ.get("RUFF_UNSAFE_FIXES", "").lower()
|
|
183
|
+
if env_unsafe_fixes in ("true", "1", "yes", "on"):
|
|
184
|
+
self.options["unsafe_fixes"] = True
|
|
153
185
|
|
|
154
186
|
def set_options(
|
|
155
187
|
self,
|
|
@@ -187,14 +219,26 @@ class RuffTool(BaseTool):
|
|
|
187
219
|
Raises:
|
|
188
220
|
ValueError: If an option value is invalid.
|
|
189
221
|
"""
|
|
190
|
-
if select is not None
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
222
|
+
if select is not None:
|
|
223
|
+
if isinstance(select, str):
|
|
224
|
+
select = [select]
|
|
225
|
+
elif not isinstance(select, list):
|
|
226
|
+
raise ValueError("select must be a string or list of rule codes")
|
|
227
|
+
if ignore is not None:
|
|
228
|
+
if isinstance(ignore, str):
|
|
229
|
+
ignore = [ignore]
|
|
230
|
+
elif not isinstance(ignore, list):
|
|
231
|
+
raise ValueError("ignore must be a string or list of rule codes")
|
|
232
|
+
if extend_select is not None:
|
|
233
|
+
if isinstance(extend_select, str):
|
|
234
|
+
extend_select = [extend_select]
|
|
235
|
+
elif not isinstance(extend_select, list):
|
|
236
|
+
raise ValueError("extend_select must be a string or list of rule codes")
|
|
237
|
+
if extend_ignore is not None:
|
|
238
|
+
if isinstance(extend_ignore, str):
|
|
239
|
+
extend_ignore = [extend_ignore]
|
|
240
|
+
elif not isinstance(extend_ignore, list):
|
|
241
|
+
raise ValueError("extend_ignore must be a string or list of rule codes")
|
|
198
242
|
if line_length is not None:
|
|
199
243
|
if not isinstance(line_length, int):
|
|
200
244
|
raise ValueError("line_length must be an integer")
|
|
@@ -247,8 +291,16 @@ class RuffTool(BaseTool):
|
|
|
247
291
|
"""
|
|
248
292
|
cmd: list[str] = self._get_executable_command(tool_name="ruff") + ["check"]
|
|
249
293
|
|
|
250
|
-
#
|
|
251
|
-
|
|
294
|
+
# Get enforced settings to avoid duplicate CLI args
|
|
295
|
+
enforced = self._get_enforced_settings()
|
|
296
|
+
|
|
297
|
+
# Add Lintro config injection args (--line-length, --target-version)
|
|
298
|
+
# from enforce tier. This takes precedence over native config and options
|
|
299
|
+
config_args = self._build_config_args()
|
|
300
|
+
if config_args:
|
|
301
|
+
cmd.extend(config_args)
|
|
302
|
+
# Add --isolated if in test mode (fallback when no Lintro config)
|
|
303
|
+
elif os.environ.get(RUFF_TEST_MODE_ENV) == RUFF_TEST_MODE_VALUE:
|
|
252
304
|
cmd.append("--isolated")
|
|
253
305
|
|
|
254
306
|
# Add configuration options
|
|
@@ -271,11 +323,15 @@ class RuffTool(BaseTool):
|
|
|
271
323
|
cmd.extend(["--ignore", ",".join(sorted(ignored_rules))])
|
|
272
324
|
if extend_selected_rules:
|
|
273
325
|
cmd.extend(["--extend-select", ",".join(extend_selected_rules)])
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
326
|
+
extend_ignored_rules = list(self.options.get("extend_ignore") or [])
|
|
327
|
+
if extend_ignored_rules:
|
|
328
|
+
cmd.extend(["--extend-ignore", ",".join(extend_ignored_rules)])
|
|
329
|
+
# Only add line_length/target_version from options if not enforced.
|
|
330
|
+
# Note: enforced uses Lintro's generic names (line_length, target_python)
|
|
331
|
+
# while options use tool-specific names (line_length, target_version).
|
|
332
|
+
if self.options.get("line_length") and "line_length" not in enforced:
|
|
277
333
|
cmd.extend(["--line-length", str(self.options["line_length"])])
|
|
278
|
-
if self.options.get("target_version"):
|
|
334
|
+
if self.options.get("target_version") and "target_python" not in enforced:
|
|
279
335
|
cmd.extend(["--target-version", self.options["target_version"]])
|
|
280
336
|
|
|
281
337
|
# Fix options
|
|
@@ -315,11 +371,16 @@ class RuffTool(BaseTool):
|
|
|
315
371
|
if check_only:
|
|
316
372
|
cmd.append("--check")
|
|
317
373
|
|
|
318
|
-
# Add
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
374
|
+
# Add Lintro config injection args (--isolated, --config)
|
|
375
|
+
config_args = self._build_config_args()
|
|
376
|
+
if config_args:
|
|
377
|
+
cmd.extend(config_args)
|
|
378
|
+
else:
|
|
379
|
+
# Fallback to options-based configuration
|
|
380
|
+
if self.options.get("line_length"):
|
|
381
|
+
cmd.extend(["--line-length", str(self.options["line_length"])])
|
|
382
|
+
if self.options.get("target_version"):
|
|
383
|
+
cmd.extend(["--target-version", self.options["target_version"]])
|
|
323
384
|
|
|
324
385
|
# Add files
|
|
325
386
|
cmd.extend(files)
|
|
@@ -338,6 +399,11 @@ class RuffTool(BaseTool):
|
|
|
338
399
|
Returns:
|
|
339
400
|
ToolResult: ToolResult instance.
|
|
340
401
|
"""
|
|
402
|
+
# Check version requirements
|
|
403
|
+
version_result = self._verify_tool_version()
|
|
404
|
+
if version_result is not None:
|
|
405
|
+
return version_result
|
|
406
|
+
|
|
341
407
|
self._validate_paths(paths=paths)
|
|
342
408
|
if not paths:
|
|
343
409
|
return ToolResult(
|
|
@@ -373,16 +439,31 @@ class RuffTool(BaseTool):
|
|
|
373
439
|
os.path.relpath(f, cwd) if cwd else f for f in python_files
|
|
374
440
|
]
|
|
375
441
|
|
|
376
|
-
timeout: int = self
|
|
442
|
+
timeout: int = get_timeout_value(self, RUFF_DEFAULT_TIMEOUT)
|
|
377
443
|
# Lint check
|
|
378
444
|
cmd: list[str] = self._build_check_command(files=rel_files, fix=False)
|
|
379
445
|
success_lint: bool
|
|
380
446
|
output_lint: str
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
447
|
+
try:
|
|
448
|
+
success_lint, output_lint = run_subprocess_with_timeout(
|
|
449
|
+
tool=self,
|
|
450
|
+
cmd=cmd,
|
|
451
|
+
timeout=timeout,
|
|
452
|
+
cwd=cwd,
|
|
453
|
+
)
|
|
454
|
+
except subprocess.TimeoutExpired:
|
|
455
|
+
timeout_result = create_timeout_result(
|
|
456
|
+
tool=self,
|
|
457
|
+
timeout=timeout,
|
|
458
|
+
cmd=cmd,
|
|
459
|
+
)
|
|
460
|
+
return ToolResult(
|
|
461
|
+
name=self.name,
|
|
462
|
+
success=timeout_result["success"],
|
|
463
|
+
output=timeout_result["output"],
|
|
464
|
+
issues_count=timeout_result["issues_count"],
|
|
465
|
+
issues=timeout_result["issues"],
|
|
466
|
+
)
|
|
386
467
|
lint_issues = parse_ruff_output(output=output_lint)
|
|
387
468
|
lint_issues_count: int = len(lint_issues)
|
|
388
469
|
|
|
@@ -397,11 +478,26 @@ class RuffTool(BaseTool):
|
|
|
397
478
|
)
|
|
398
479
|
success_format: bool
|
|
399
480
|
output_format: str
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
481
|
+
try:
|
|
482
|
+
success_format, output_format = self._run_subprocess(
|
|
483
|
+
cmd=format_cmd,
|
|
484
|
+
timeout=timeout,
|
|
485
|
+
cwd=cwd,
|
|
486
|
+
)
|
|
487
|
+
except subprocess.TimeoutExpired:
|
|
488
|
+
timeout_msg = (
|
|
489
|
+
f"Ruff execution timed out ({timeout}s limit exceeded).\n\n"
|
|
490
|
+
"This may indicate:\n"
|
|
491
|
+
" - Large codebase taking too long to process\n"
|
|
492
|
+
" - Need to increase timeout via --tool-options ruff:timeout=N"
|
|
493
|
+
)
|
|
494
|
+
return ToolResult(
|
|
495
|
+
name=self.name,
|
|
496
|
+
success=False,
|
|
497
|
+
output=timeout_msg,
|
|
498
|
+
issues_count=1, # Count timeout as execution failure
|
|
499
|
+
issues=lint_issues, # Include any lint issues found before timeout
|
|
500
|
+
)
|
|
405
501
|
format_files = parse_ruff_format_check_output(output=output_format)
|
|
406
502
|
# Normalize files to absolute paths to keep behavior consistent with
|
|
407
503
|
# direct CLI calls and stabilize tests that compare exact paths.
|
|
@@ -445,6 +541,11 @@ class RuffTool(BaseTool):
|
|
|
445
541
|
Returns:
|
|
446
542
|
ToolResult: ToolResult instance.
|
|
447
543
|
"""
|
|
544
|
+
# Check version requirements
|
|
545
|
+
version_result = self._verify_tool_version()
|
|
546
|
+
if version_result is not None:
|
|
547
|
+
return version_result
|
|
548
|
+
|
|
448
549
|
self._validate_paths(paths=paths)
|
|
449
550
|
if not paths:
|
|
450
551
|
return ToolResult(
|
|
@@ -471,7 +572,7 @@ class RuffTool(BaseTool):
|
|
|
471
572
|
)
|
|
472
573
|
|
|
473
574
|
logger.debug(f"Files to fix: {python_files}")
|
|
474
|
-
timeout: int = self
|
|
575
|
+
timeout: int = get_timeout_value(self, RUFF_DEFAULT_TIMEOUT)
|
|
475
576
|
all_outputs: list[str] = []
|
|
476
577
|
overall_success: bool = True
|
|
477
578
|
|
|
@@ -482,10 +583,28 @@ class RuffTool(BaseTool):
|
|
|
482
583
|
cmd_check: list[str] = self._build_check_command(files=python_files, fix=False)
|
|
483
584
|
success_check: bool
|
|
484
585
|
output_check: str
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
586
|
+
try:
|
|
587
|
+
success_check, output_check = run_subprocess_with_timeout(
|
|
588
|
+
tool=self,
|
|
589
|
+
cmd=cmd_check,
|
|
590
|
+
timeout=timeout,
|
|
591
|
+
)
|
|
592
|
+
except subprocess.TimeoutExpired:
|
|
593
|
+
timeout_result = create_timeout_result(
|
|
594
|
+
tool=self,
|
|
595
|
+
timeout=timeout,
|
|
596
|
+
cmd=cmd_check,
|
|
597
|
+
)
|
|
598
|
+
return ToolResult(
|
|
599
|
+
name=self.name,
|
|
600
|
+
success=timeout_result["success"],
|
|
601
|
+
output=timeout_result["output"],
|
|
602
|
+
issues_count=timeout_result["issues_count"],
|
|
603
|
+
issues=timeout_result["issues"],
|
|
604
|
+
initial_issues_count=0,
|
|
605
|
+
fixed_issues_count=0,
|
|
606
|
+
remaining_issues_count=1,
|
|
607
|
+
)
|
|
489
608
|
initial_issues = parse_ruff_output(output=output_check)
|
|
490
609
|
initial_count: int = len(initial_issues)
|
|
491
610
|
|
|
@@ -499,10 +618,29 @@ class RuffTool(BaseTool):
|
|
|
499
618
|
)
|
|
500
619
|
success_format_check: bool
|
|
501
620
|
output_format_check: str
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
621
|
+
try:
|
|
622
|
+
success_format_check, output_format_check = self._run_subprocess(
|
|
623
|
+
cmd=format_cmd_check,
|
|
624
|
+
timeout=timeout,
|
|
625
|
+
)
|
|
626
|
+
except subprocess.TimeoutExpired:
|
|
627
|
+
timeout_msg = (
|
|
628
|
+
f"Ruff execution timed out ({timeout}s limit exceeded).\n\n"
|
|
629
|
+
"This may indicate:\n"
|
|
630
|
+
" - Large codebase taking too long to process\n"
|
|
631
|
+
" - Need to increase timeout via --tool-options ruff:timeout=N"
|
|
632
|
+
)
|
|
633
|
+
return ToolResult(
|
|
634
|
+
name=self.name,
|
|
635
|
+
success=False,
|
|
636
|
+
output=timeout_msg,
|
|
637
|
+
issues_count=1, # Count timeout as execution failure
|
|
638
|
+
# Include any lint issues found before timeout
|
|
639
|
+
issues=initial_issues,
|
|
640
|
+
initial_issues_count=initial_count,
|
|
641
|
+
fixed_issues_count=0,
|
|
642
|
+
remaining_issues_count=1,
|
|
643
|
+
)
|
|
506
644
|
format_files = parse_ruff_format_check_output(output=output_format_check)
|
|
507
645
|
initial_format_count = len(format_files)
|
|
508
646
|
|
|
@@ -512,11 +650,29 @@ class RuffTool(BaseTool):
|
|
|
512
650
|
# Optionally run ruff check --fix (lint fixes)
|
|
513
651
|
remaining_issues = []
|
|
514
652
|
remaining_count = 0
|
|
653
|
+
success: bool = True # Default to True when lint_fix is disabled
|
|
515
654
|
if self.options.get("lint_fix", True):
|
|
516
655
|
cmd: list[str] = self._build_check_command(files=python_files, fix=True)
|
|
517
|
-
success: bool
|
|
518
656
|
output: str
|
|
519
|
-
|
|
657
|
+
try:
|
|
658
|
+
success, output = self._run_subprocess(cmd=cmd, timeout=timeout)
|
|
659
|
+
except subprocess.TimeoutExpired:
|
|
660
|
+
timeout_msg = (
|
|
661
|
+
f"Ruff execution timed out ({timeout}s limit exceeded).\n\n"
|
|
662
|
+
"This may indicate:\n"
|
|
663
|
+
" - Large codebase taking too long to process\n"
|
|
664
|
+
" - Need to increase timeout via --tool-options ruff:timeout=N"
|
|
665
|
+
)
|
|
666
|
+
return ToolResult(
|
|
667
|
+
name=self.name,
|
|
668
|
+
success=False,
|
|
669
|
+
output=timeout_msg,
|
|
670
|
+
issues_count=1, # Count timeout as execution failure
|
|
671
|
+
issues=initial_issues, # Include initial issues found
|
|
672
|
+
initial_issues_count=total_initial_count,
|
|
673
|
+
fixed_issues_count=0,
|
|
674
|
+
remaining_issues_count=1,
|
|
675
|
+
)
|
|
520
676
|
remaining_issues = parse_ruff_output(output=output)
|
|
521
677
|
remaining_count = len(remaining_issues)
|
|
522
678
|
|
|
@@ -547,11 +703,18 @@ class RuffTool(BaseTool):
|
|
|
547
703
|
# Only run if not already run with unsafe fixes
|
|
548
704
|
success_unsafe: bool
|
|
549
705
|
output_unsafe: str
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
706
|
+
try:
|
|
707
|
+
success_unsafe, output_unsafe = self._run_subprocess(
|
|
708
|
+
cmd=cmd_unsafe,
|
|
709
|
+
timeout=timeout,
|
|
710
|
+
)
|
|
711
|
+
except subprocess.TimeoutExpired:
|
|
712
|
+
# If unsafe check times out, just continue with current results
|
|
713
|
+
# Don't fail the entire operation for this optional check
|
|
714
|
+
logger.debug("Unsafe fixes check timed out, skipping")
|
|
715
|
+
remaining_unsafe = remaining_issues
|
|
716
|
+
else:
|
|
717
|
+
remaining_unsafe = parse_ruff_output(output=output_unsafe)
|
|
555
718
|
if len(remaining_unsafe) < remaining_count:
|
|
556
719
|
all_outputs.append(
|
|
557
720
|
"Some remaining issues could be fixed by enabling unsafe "
|
|
@@ -593,10 +756,28 @@ class RuffTool(BaseTool):
|
|
|
593
756
|
)
|
|
594
757
|
format_success: bool
|
|
595
758
|
format_output: str
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
759
|
+
try:
|
|
760
|
+
format_success, format_output = self._run_subprocess(
|
|
761
|
+
cmd=format_cmd,
|
|
762
|
+
timeout=timeout,
|
|
763
|
+
)
|
|
764
|
+
except subprocess.TimeoutExpired:
|
|
765
|
+
timeout_msg = (
|
|
766
|
+
f"Ruff execution timed out ({timeout}s limit exceeded).\n\n"
|
|
767
|
+
"This may indicate:\n"
|
|
768
|
+
" - Large codebase taking too long to process\n"
|
|
769
|
+
" - Need to increase timeout via --tool-options ruff:timeout=N"
|
|
770
|
+
)
|
|
771
|
+
return ToolResult(
|
|
772
|
+
name=self.name,
|
|
773
|
+
success=False,
|
|
774
|
+
output=timeout_msg,
|
|
775
|
+
issues_count=1, # Count timeout as execution failure
|
|
776
|
+
issues=remaining_issues, # Include any issues found before timeout
|
|
777
|
+
initial_issues_count=total_initial_count,
|
|
778
|
+
fixed_issues_count=fixed_lint_count,
|
|
779
|
+
remaining_issues_count=1,
|
|
780
|
+
)
|
|
600
781
|
# Formatting fixes are counted separately from lint fixes
|
|
601
782
|
if initial_format_count > 0:
|
|
602
783
|
fixed_count = fixed_lint_count + initial_format_count
|
|
@@ -621,10 +802,23 @@ class RuffTool(BaseTool):
|
|
|
621
802
|
|
|
622
803
|
# Success should be based on whether there are remaining issues after fixing
|
|
623
804
|
# If there are no initial issues, success should be True
|
|
624
|
-
if total_initial_count == 0
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
805
|
+
overall_success = True if total_initial_count == 0 else remaining_count == 0
|
|
806
|
+
|
|
807
|
+
# Convert initial format files to RuffFormatIssue objects (these were fixed)
|
|
808
|
+
# and combine with remaining issues so formatter can split them by fixability
|
|
809
|
+
fixed_format_issues: list[RuffFormatIssue] = []
|
|
810
|
+
if format_files:
|
|
811
|
+
# Normalize files to absolute paths to keep behavior consistent
|
|
812
|
+
cwd: str | None = self.get_cwd(paths=python_files)
|
|
813
|
+
for file_path in format_files:
|
|
814
|
+
if cwd and not os.path.isabs(file_path):
|
|
815
|
+
absolute_path = os.path.abspath(os.path.join(cwd, file_path))
|
|
816
|
+
fixed_format_issues.append(RuffFormatIssue(file=absolute_path))
|
|
817
|
+
else:
|
|
818
|
+
fixed_format_issues.append(RuffFormatIssue(file=file_path))
|
|
819
|
+
|
|
820
|
+
# Combine fixed format issues with remaining lint issues
|
|
821
|
+
all_issues = fixed_format_issues + remaining_issues
|
|
628
822
|
|
|
629
823
|
return ToolResult(
|
|
630
824
|
name=self.name,
|
|
@@ -632,8 +826,8 @@ class RuffTool(BaseTool):
|
|
|
632
826
|
output=final_output,
|
|
633
827
|
# For fix operations, issues_count represents remaining for summaries
|
|
634
828
|
issues_count=remaining_count,
|
|
635
|
-
# Display
|
|
636
|
-
issues=
|
|
829
|
+
# Display both fixed format issues and remaining lint issues
|
|
830
|
+
issues=all_issues,
|
|
637
831
|
initial_issues_count=total_initial_count,
|
|
638
832
|
fixed_issues_count=fixed_count,
|
|
639
833
|
remaining_issues_count=remaining_count,
|