lintro 0.13.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 +226 -16
- 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/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/tool_name.py +2 -0
- lintro/enums/tool_type.py +2 -0
- lintro/formatters/tools/__init__.py +12 -0
- lintro/formatters/tools/eslint_formatter.py +108 -0
- lintro/formatters/tools/markdownlint_formatter.py +88 -0
- lintro/formatters/tools/pytest_formatter.py +201 -0
- lintro/parsers/__init__.py +69 -9
- 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/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/pytest/__init__.py +21 -0
- lintro/parsers/pytest/pytest_issue.py +28 -0
- lintro/parsers/pytest/pytest_parser.py +483 -0
- lintro/tools/__init__.py +2 -0
- lintro/tools/core/timeout_utils.py +112 -0
- lintro/tools/core/tool_base.py +255 -45
- lintro/tools/core/tool_manager.py +77 -24
- lintro/tools/core/version_requirements.py +482 -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 +23 -7
- lintro/tools/implementations/tool_black.py +236 -29
- lintro/tools/implementations/tool_darglint.py +180 -21
- 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 +313 -26
- lintro/tools/implementations/tool_pytest.py +327 -0
- lintro/tools/implementations/tool_ruff.py +247 -70
- lintro/tools/implementations/tool_yamllint.py +448 -34
- lintro/tools/tool_enum.py +6 -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 +336 -39
- lintro/utils/tool_utils.py +38 -2
- lintro/utils/unified_config.py +926 -0
- {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/METADATA +131 -29
- lintro-0.17.2.dist-info/RECORD +134 -0
- lintro-0.13.2.dist-info/RECORD +0 -96
- {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/WHEEL +0 -0
- {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/entry_points.txt +0 -0
- {lintro-0.13.2.dist-info → lintro-0.17.2.dist-info}/licenses/LICENSE +0 -0
- {lintro-0.13.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,10 +73,12 @@ 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
83
|
with open(lintro_ignore_path, encoding="utf-8") as f:
|
|
63
84
|
for line in f:
|
|
@@ -128,29 +149,32 @@ class RuffTool(BaseTool):
|
|
|
128
149
|
"""Initialize the tool with default configuration."""
|
|
129
150
|
super().__post_init__()
|
|
130
151
|
|
|
131
|
-
#
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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"]
|
|
154
178
|
|
|
155
179
|
# Allow environment variable override for unsafe fixes
|
|
156
180
|
# Useful for development and CI environments
|
|
@@ -267,8 +291,16 @@ class RuffTool(BaseTool):
|
|
|
267
291
|
"""
|
|
268
292
|
cmd: list[str] = self._get_executable_command(tool_name="ruff") + ["check"]
|
|
269
293
|
|
|
270
|
-
#
|
|
271
|
-
|
|
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:
|
|
272
304
|
cmd.append("--isolated")
|
|
273
305
|
|
|
274
306
|
# Add configuration options
|
|
@@ -291,11 +323,15 @@ class RuffTool(BaseTool):
|
|
|
291
323
|
cmd.extend(["--ignore", ",".join(sorted(ignored_rules))])
|
|
292
324
|
if extend_selected_rules:
|
|
293
325
|
cmd.extend(["--extend-select", ",".join(extend_selected_rules)])
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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:
|
|
297
333
|
cmd.extend(["--line-length", str(self.options["line_length"])])
|
|
298
|
-
if self.options.get("target_version"):
|
|
334
|
+
if self.options.get("target_version") and "target_python" not in enforced:
|
|
299
335
|
cmd.extend(["--target-version", self.options["target_version"]])
|
|
300
336
|
|
|
301
337
|
# Fix options
|
|
@@ -335,11 +371,16 @@ class RuffTool(BaseTool):
|
|
|
335
371
|
if check_only:
|
|
336
372
|
cmd.append("--check")
|
|
337
373
|
|
|
338
|
-
# Add
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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"]])
|
|
343
384
|
|
|
344
385
|
# Add files
|
|
345
386
|
cmd.extend(files)
|
|
@@ -358,6 +399,11 @@ class RuffTool(BaseTool):
|
|
|
358
399
|
Returns:
|
|
359
400
|
ToolResult: ToolResult instance.
|
|
360
401
|
"""
|
|
402
|
+
# Check version requirements
|
|
403
|
+
version_result = self._verify_tool_version()
|
|
404
|
+
if version_result is not None:
|
|
405
|
+
return version_result
|
|
406
|
+
|
|
361
407
|
self._validate_paths(paths=paths)
|
|
362
408
|
if not paths:
|
|
363
409
|
return ToolResult(
|
|
@@ -393,16 +439,31 @@ class RuffTool(BaseTool):
|
|
|
393
439
|
os.path.relpath(f, cwd) if cwd else f for f in python_files
|
|
394
440
|
]
|
|
395
441
|
|
|
396
|
-
timeout: int = self
|
|
442
|
+
timeout: int = get_timeout_value(self, RUFF_DEFAULT_TIMEOUT)
|
|
397
443
|
# Lint check
|
|
398
444
|
cmd: list[str] = self._build_check_command(files=rel_files, fix=False)
|
|
399
445
|
success_lint: bool
|
|
400
446
|
output_lint: str
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
+
)
|
|
406
467
|
lint_issues = parse_ruff_output(output=output_lint)
|
|
407
468
|
lint_issues_count: int = len(lint_issues)
|
|
408
469
|
|
|
@@ -417,11 +478,26 @@ class RuffTool(BaseTool):
|
|
|
417
478
|
)
|
|
418
479
|
success_format: bool
|
|
419
480
|
output_format: str
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
+
)
|
|
425
501
|
format_files = parse_ruff_format_check_output(output=output_format)
|
|
426
502
|
# Normalize files to absolute paths to keep behavior consistent with
|
|
427
503
|
# direct CLI calls and stabilize tests that compare exact paths.
|
|
@@ -465,6 +541,11 @@ class RuffTool(BaseTool):
|
|
|
465
541
|
Returns:
|
|
466
542
|
ToolResult: ToolResult instance.
|
|
467
543
|
"""
|
|
544
|
+
# Check version requirements
|
|
545
|
+
version_result = self._verify_tool_version()
|
|
546
|
+
if version_result is not None:
|
|
547
|
+
return version_result
|
|
548
|
+
|
|
468
549
|
self._validate_paths(paths=paths)
|
|
469
550
|
if not paths:
|
|
470
551
|
return ToolResult(
|
|
@@ -491,7 +572,7 @@ class RuffTool(BaseTool):
|
|
|
491
572
|
)
|
|
492
573
|
|
|
493
574
|
logger.debug(f"Files to fix: {python_files}")
|
|
494
|
-
timeout: int = self
|
|
575
|
+
timeout: int = get_timeout_value(self, RUFF_DEFAULT_TIMEOUT)
|
|
495
576
|
all_outputs: list[str] = []
|
|
496
577
|
overall_success: bool = True
|
|
497
578
|
|
|
@@ -502,10 +583,28 @@ class RuffTool(BaseTool):
|
|
|
502
583
|
cmd_check: list[str] = self._build_check_command(files=python_files, fix=False)
|
|
503
584
|
success_check: bool
|
|
504
585
|
output_check: str
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
+
)
|
|
509
608
|
initial_issues = parse_ruff_output(output=output_check)
|
|
510
609
|
initial_count: int = len(initial_issues)
|
|
511
610
|
|
|
@@ -519,10 +618,29 @@ class RuffTool(BaseTool):
|
|
|
519
618
|
)
|
|
520
619
|
success_format_check: bool
|
|
521
620
|
output_format_check: str
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
+
)
|
|
526
644
|
format_files = parse_ruff_format_check_output(output=output_format_check)
|
|
527
645
|
initial_format_count = len(format_files)
|
|
528
646
|
|
|
@@ -536,7 +654,25 @@ class RuffTool(BaseTool):
|
|
|
536
654
|
if self.options.get("lint_fix", True):
|
|
537
655
|
cmd: list[str] = self._build_check_command(files=python_files, fix=True)
|
|
538
656
|
output: str
|
|
539
|
-
|
|
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
|
+
)
|
|
540
676
|
remaining_issues = parse_ruff_output(output=output)
|
|
541
677
|
remaining_count = len(remaining_issues)
|
|
542
678
|
|
|
@@ -567,11 +703,18 @@ class RuffTool(BaseTool):
|
|
|
567
703
|
# Only run if not already run with unsafe fixes
|
|
568
704
|
success_unsafe: bool
|
|
569
705
|
output_unsafe: str
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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)
|
|
575
718
|
if len(remaining_unsafe) < remaining_count:
|
|
576
719
|
all_outputs.append(
|
|
577
720
|
"Some remaining issues could be fixed by enabling unsafe "
|
|
@@ -613,10 +756,28 @@ class RuffTool(BaseTool):
|
|
|
613
756
|
)
|
|
614
757
|
format_success: bool
|
|
615
758
|
format_output: str
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
+
)
|
|
620
781
|
# Formatting fixes are counted separately from lint fixes
|
|
621
782
|
if initial_format_count > 0:
|
|
622
783
|
fixed_count = fixed_lint_count + initial_format_count
|
|
@@ -643,14 +804,30 @@ class RuffTool(BaseTool):
|
|
|
643
804
|
# If there are no initial issues, success should be True
|
|
644
805
|
overall_success = True if total_initial_count == 0 else remaining_count == 0
|
|
645
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
|
|
822
|
+
|
|
646
823
|
return ToolResult(
|
|
647
824
|
name=self.name,
|
|
648
825
|
success=overall_success,
|
|
649
826
|
output=final_output,
|
|
650
827
|
# For fix operations, issues_count represents remaining for summaries
|
|
651
828
|
issues_count=remaining_count,
|
|
652
|
-
# Display
|
|
653
|
-
issues=
|
|
829
|
+
# Display both fixed format issues and remaining lint issues
|
|
830
|
+
issues=all_issues,
|
|
654
831
|
initial_issues_count=total_initial_count,
|
|
655
832
|
fixed_issues_count=fixed_count,
|
|
656
833
|
remaining_issues_count=remaining_count,
|