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
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""Tool configuration generator for Lintro.
|
|
2
|
+
|
|
3
|
+
This module provides CLI argument injection for enforced settings and
|
|
4
|
+
default config generation for tools without native configs.
|
|
5
|
+
|
|
6
|
+
The tiered configuration model:
|
|
7
|
+
1. EXECUTION: What tools run and how
|
|
8
|
+
2. ENFORCE: Cross-cutting settings injected via CLI flags
|
|
9
|
+
3. DEFAULTS: Fallback config when no native config exists
|
|
10
|
+
4. TOOLS: Per-tool enable/disable and config source
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import atexit
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import tempfile
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from loguru import logger
|
|
23
|
+
|
|
24
|
+
from lintro.config.lintro_config import LintroConfig
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
import yaml
|
|
28
|
+
except ImportError:
|
|
29
|
+
yaml = None # type: ignore[assignment]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# CLI flags for enforced settings: setting -> {tool: flag}
|
|
33
|
+
ENFORCE_CLI_FLAGS: dict[str, dict[str, str]] = {
|
|
34
|
+
"line_length": {
|
|
35
|
+
"ruff": "--line-length",
|
|
36
|
+
"black": "--line-length",
|
|
37
|
+
"prettier": "--print-width",
|
|
38
|
+
},
|
|
39
|
+
"target_python": {
|
|
40
|
+
"ruff": "--target-version",
|
|
41
|
+
"black": "--target-version",
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Tool config format for defaults generation
|
|
46
|
+
TOOL_CONFIG_FORMATS: dict[str, str] = {
|
|
47
|
+
"prettier": "json",
|
|
48
|
+
"yamllint": "yaml",
|
|
49
|
+
"markdownlint": "json",
|
|
50
|
+
"hadolint": "yaml",
|
|
51
|
+
"bandit": "yaml",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Native config file patterns for checking if tool has native config
|
|
55
|
+
NATIVE_CONFIG_PATTERNS: dict[str, list[str]] = {
|
|
56
|
+
"prettier": [
|
|
57
|
+
".prettierrc",
|
|
58
|
+
".prettierrc.json",
|
|
59
|
+
".prettierrc.yaml",
|
|
60
|
+
".prettierrc.yml",
|
|
61
|
+
".prettierrc.js",
|
|
62
|
+
".prettierrc.cjs",
|
|
63
|
+
".prettierrc.toml",
|
|
64
|
+
"prettier.config.js",
|
|
65
|
+
"prettier.config.cjs",
|
|
66
|
+
],
|
|
67
|
+
"markdownlint": [
|
|
68
|
+
".markdownlint-cli2.jsonc",
|
|
69
|
+
".markdownlint-cli2.yaml",
|
|
70
|
+
".markdownlint-cli2.cjs",
|
|
71
|
+
".markdownlint.jsonc",
|
|
72
|
+
".markdownlint.json",
|
|
73
|
+
".markdownlint.yaml",
|
|
74
|
+
".markdownlint.yml",
|
|
75
|
+
".markdownlint.cjs",
|
|
76
|
+
],
|
|
77
|
+
"yamllint": [
|
|
78
|
+
".yamllint",
|
|
79
|
+
".yamllint.yaml",
|
|
80
|
+
".yamllint.yml",
|
|
81
|
+
],
|
|
82
|
+
"hadolint": [
|
|
83
|
+
".hadolint.yaml",
|
|
84
|
+
".hadolint.yml",
|
|
85
|
+
],
|
|
86
|
+
"bandit": [
|
|
87
|
+
".bandit",
|
|
88
|
+
".bandit.yaml",
|
|
89
|
+
".bandit.yml",
|
|
90
|
+
"bandit.yaml",
|
|
91
|
+
"bandit.yml",
|
|
92
|
+
],
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Track temporary files for cleanup
|
|
96
|
+
_temp_files: list[Path] = []
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _cleanup_temp_files() -> None:
|
|
100
|
+
"""Clean up temporary config files on exit."""
|
|
101
|
+
for temp_file in _temp_files:
|
|
102
|
+
try:
|
|
103
|
+
if temp_file.exists():
|
|
104
|
+
temp_file.unlink()
|
|
105
|
+
logger.debug(f"Cleaned up temp config: {temp_file}")
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.debug(f"Failed to clean up {temp_file}: {e}")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Register cleanup on exit
|
|
111
|
+
atexit.register(_cleanup_temp_files)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_enforce_cli_args(
|
|
115
|
+
tool_name: str,
|
|
116
|
+
lintro_config: LintroConfig,
|
|
117
|
+
) -> list[str]:
|
|
118
|
+
"""Get CLI arguments for enforced settings.
|
|
119
|
+
|
|
120
|
+
These settings override native tool configs to ensure consistency
|
|
121
|
+
across different tools for shared concerns like line length.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
tool_name: Name of the tool (e.g., "ruff", "prettier").
|
|
125
|
+
lintro_config: Lintro configuration.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
list[str]: CLI arguments to inject (e.g., ["--line-length", "88"]).
|
|
129
|
+
"""
|
|
130
|
+
args: list[str] = []
|
|
131
|
+
tool_lower = tool_name.lower()
|
|
132
|
+
enforce = lintro_config.enforce
|
|
133
|
+
|
|
134
|
+
# Inject line_length if set
|
|
135
|
+
if enforce.line_length is not None:
|
|
136
|
+
flag = ENFORCE_CLI_FLAGS.get("line_length", {}).get(tool_lower)
|
|
137
|
+
if flag:
|
|
138
|
+
args.extend([flag, str(enforce.line_length)])
|
|
139
|
+
logger.debug(
|
|
140
|
+
f"Injecting enforce.line_length={enforce.line_length} "
|
|
141
|
+
f"to {tool_name} as {flag}",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Inject target_python if set
|
|
145
|
+
if enforce.target_python is not None:
|
|
146
|
+
flag = ENFORCE_CLI_FLAGS.get("target_python", {}).get(tool_lower)
|
|
147
|
+
if flag:
|
|
148
|
+
args.extend([flag, enforce.target_python])
|
|
149
|
+
logger.debug(
|
|
150
|
+
f"Injecting enforce.target_python={enforce.target_python} "
|
|
151
|
+
f"to {tool_name} as {flag}",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return args
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def has_native_config(tool_name: str) -> bool:
|
|
158
|
+
"""Check if a tool has a native config file in the project.
|
|
159
|
+
|
|
160
|
+
Searches for known native config file patterns starting from the
|
|
161
|
+
current working directory and moving upward to find the project root.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
tool_name: Name of the tool (e.g., "prettier", "markdownlint").
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
bool: True if a native config file exists.
|
|
168
|
+
"""
|
|
169
|
+
tool_lower = tool_name.lower()
|
|
170
|
+
patterns = NATIVE_CONFIG_PATTERNS.get(tool_lower, [])
|
|
171
|
+
|
|
172
|
+
if not patterns:
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
# Search from current directory upward
|
|
176
|
+
current = Path.cwd().resolve()
|
|
177
|
+
|
|
178
|
+
while True:
|
|
179
|
+
for pattern in patterns:
|
|
180
|
+
config_path = current / pattern
|
|
181
|
+
if config_path.exists():
|
|
182
|
+
logger.debug(
|
|
183
|
+
f"Found native config for {tool_name}: {config_path}",
|
|
184
|
+
)
|
|
185
|
+
return True
|
|
186
|
+
|
|
187
|
+
# Move up one directory
|
|
188
|
+
parent = current.parent
|
|
189
|
+
if parent == current:
|
|
190
|
+
# Reached filesystem root
|
|
191
|
+
break
|
|
192
|
+
current = parent
|
|
193
|
+
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def generate_defaults_config(
|
|
198
|
+
tool_name: str,
|
|
199
|
+
lintro_config: LintroConfig,
|
|
200
|
+
) -> Path | None:
|
|
201
|
+
"""Generate a temporary config file from defaults.
|
|
202
|
+
|
|
203
|
+
Only used when a tool has no native config file and defaults
|
|
204
|
+
are specified in the Lintro config.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
tool_name: Name of the tool.
|
|
208
|
+
lintro_config: Lintro configuration.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Path | None: Path to generated config file, or None if not needed.
|
|
212
|
+
"""
|
|
213
|
+
tool_lower = tool_name.lower()
|
|
214
|
+
|
|
215
|
+
# Check if tool has native config - if so, don't generate defaults
|
|
216
|
+
if has_native_config(tool_lower):
|
|
217
|
+
logger.debug(
|
|
218
|
+
f"Tool {tool_name} has native config, skipping defaults generation",
|
|
219
|
+
)
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
# Get defaults for this tool
|
|
223
|
+
defaults = lintro_config.get_tool_defaults(tool_lower)
|
|
224
|
+
if not defaults:
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
# Get config format for this tool
|
|
228
|
+
config_format = TOOL_CONFIG_FORMATS.get(tool_lower, "json")
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
return _write_defaults_config(
|
|
232
|
+
defaults=defaults,
|
|
233
|
+
tool_name=tool_lower,
|
|
234
|
+
config_format=config_format,
|
|
235
|
+
)
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error(f"Failed to generate defaults config for {tool_name}: {e}")
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _write_defaults_config(
|
|
242
|
+
defaults: dict[str, Any],
|
|
243
|
+
tool_name: str,
|
|
244
|
+
config_format: str,
|
|
245
|
+
) -> Path:
|
|
246
|
+
"""Write defaults configuration to a temporary file.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
defaults: Default configuration dictionary.
|
|
250
|
+
tool_name: Name of the tool.
|
|
251
|
+
config_format: Output format (json, yaml).
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
Path: Path to temporary config file.
|
|
255
|
+
|
|
256
|
+
Raises:
|
|
257
|
+
ImportError: If PyYAML is not installed and YAML format is requested.
|
|
258
|
+
"""
|
|
259
|
+
suffix_map = {"json": ".json", "yaml": ".yaml"}
|
|
260
|
+
suffix = suffix_map.get(config_format, ".json")
|
|
261
|
+
|
|
262
|
+
temp_fd, temp_path_str = tempfile.mkstemp(
|
|
263
|
+
prefix=f"lintro-{tool_name}-defaults-",
|
|
264
|
+
suffix=suffix,
|
|
265
|
+
)
|
|
266
|
+
os.close(temp_fd)
|
|
267
|
+
temp_path = Path(temp_path_str)
|
|
268
|
+
_temp_files.append(temp_path)
|
|
269
|
+
|
|
270
|
+
if config_format == "yaml":
|
|
271
|
+
if yaml is None:
|
|
272
|
+
raise ImportError("PyYAML required for YAML output")
|
|
273
|
+
content = yaml.dump(defaults, default_flow_style=False)
|
|
274
|
+
else:
|
|
275
|
+
content = json.dumps(defaults, indent=2)
|
|
276
|
+
|
|
277
|
+
temp_path.write_text(content, encoding="utf-8")
|
|
278
|
+
logger.debug(f"Generated defaults config for {tool_name}: {temp_path}")
|
|
279
|
+
|
|
280
|
+
return temp_path
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def get_defaults_injection_args(
|
|
284
|
+
tool_name: str,
|
|
285
|
+
config_path: Path | None,
|
|
286
|
+
) -> list[str]:
|
|
287
|
+
"""Get CLI arguments to inject defaults config file into a tool.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
tool_name: Name of the tool.
|
|
291
|
+
config_path: Path to defaults config file (or None).
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
list[str]: CLI arguments to pass to the tool.
|
|
295
|
+
"""
|
|
296
|
+
if config_path is None:
|
|
297
|
+
return []
|
|
298
|
+
|
|
299
|
+
tool_lower = tool_name.lower()
|
|
300
|
+
config_str = str(config_path)
|
|
301
|
+
|
|
302
|
+
# Tool-specific config flags
|
|
303
|
+
config_flags: dict[str, list[str]] = {
|
|
304
|
+
"prettier": ["--config", config_str],
|
|
305
|
+
"yamllint": ["-c", config_str],
|
|
306
|
+
"markdownlint": ["--config", config_str],
|
|
307
|
+
"hadolint": ["--config", config_str],
|
|
308
|
+
"bandit": ["-c", config_str],
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return config_flags.get(tool_lower, [])
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def cleanup_temp_config(config_path: Path) -> None:
|
|
315
|
+
"""Explicitly clean up a temporary config file.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
config_path: Path to temporary config file.
|
|
319
|
+
"""
|
|
320
|
+
try:
|
|
321
|
+
if config_path in _temp_files:
|
|
322
|
+
_temp_files.remove(config_path)
|
|
323
|
+
if config_path.exists():
|
|
324
|
+
config_path.unlink()
|
|
325
|
+
logger.debug(f"Cleaned up temp config: {config_path}")
|
|
326
|
+
except Exception as e:
|
|
327
|
+
logger.debug(f"Failed to clean up {config_path}: {e}")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
# =============================================================================
|
|
331
|
+
# DEPRECATED: Legacy functions for backward compatibility
|
|
332
|
+
# These will be removed in a future version.
|
|
333
|
+
# =============================================================================
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def generate_tool_config(
|
|
337
|
+
tool_name: str,
|
|
338
|
+
lintro_config: LintroConfig,
|
|
339
|
+
) -> Path | None:
|
|
340
|
+
"""Generate a temporary configuration file for a tool.
|
|
341
|
+
|
|
342
|
+
DEPRECATED: This function is deprecated. Use get_enforce_cli_args() for
|
|
343
|
+
CLI flag injection and generate_defaults_config() for defaults.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
tool_name: Name of the tool.
|
|
347
|
+
lintro_config: Lintro configuration.
|
|
348
|
+
|
|
349
|
+
Returns:
|
|
350
|
+
Path | None: Path to generated config file, or None.
|
|
351
|
+
"""
|
|
352
|
+
logger.warning(
|
|
353
|
+
f"generate_tool_config() is deprecated for {tool_name}. "
|
|
354
|
+
"Use get_enforce_cli_args() instead.",
|
|
355
|
+
)
|
|
356
|
+
return generate_defaults_config(
|
|
357
|
+
tool_name=tool_name,
|
|
358
|
+
lintro_config=lintro_config,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def get_config_injection_args(
|
|
363
|
+
tool_name: str,
|
|
364
|
+
config_path: Path | None,
|
|
365
|
+
) -> list[str]:
|
|
366
|
+
"""Get CLI arguments to inject config file into a tool.
|
|
367
|
+
|
|
368
|
+
DEPRECATED: Use get_defaults_injection_args() instead.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
tool_name: Name of the tool.
|
|
372
|
+
config_path: Path to config file (or None).
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
list[str]: CLI arguments to pass to the tool.
|
|
376
|
+
"""
|
|
377
|
+
logger.warning(
|
|
378
|
+
f"get_config_injection_args() is deprecated for {tool_name}. "
|
|
379
|
+
"Use get_defaults_injection_args() instead.",
|
|
380
|
+
)
|
|
381
|
+
return get_defaults_injection_args(
|
|
382
|
+
tool_name=tool_name,
|
|
383
|
+
config_path=config_path,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def get_no_auto_config_args(tool_name: str) -> list[str]:
|
|
388
|
+
"""Get CLI arguments to disable auto-config discovery.
|
|
389
|
+
|
|
390
|
+
DEPRECATED: No longer needed with the tiered model.
|
|
391
|
+
Tools use their native configs by default.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
tool_name: Name of the tool.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
list[str]: Empty list (no longer used).
|
|
398
|
+
"""
|
|
399
|
+
logger.warning(
|
|
400
|
+
f"get_no_auto_config_args() is deprecated for {tool_name}. "
|
|
401
|
+
"No longer needed with the tiered config model.",
|
|
402
|
+
)
|
|
403
|
+
return []
|
lintro/enums/tool_name.py
CHANGED
lintro/enums/tool_type.py
CHANGED
|
@@ -17,6 +17,7 @@ class ToolType(Flag):
|
|
|
17
17
|
DOCUMENTATION = Tool that checks documentation
|
|
18
18
|
SECURITY = Tool that checks for security issues
|
|
19
19
|
INFRASTRUCTURE = Tool that checks infrastructure code
|
|
20
|
+
TEST_RUNNER = Tool that runs tests
|
|
20
21
|
"""
|
|
21
22
|
|
|
22
23
|
LINTER = auto()
|
|
@@ -25,3 +26,4 @@ class ToolType(Flag):
|
|
|
25
26
|
DOCUMENTATION = auto()
|
|
26
27
|
SECURITY = auto()
|
|
27
28
|
INFRASTRUCTURE = auto()
|
|
29
|
+
TEST_RUNNER = auto()
|
|
@@ -12,10 +12,18 @@ from lintro.formatters.tools.darglint_formatter import (
|
|
|
12
12
|
DarglintTableDescriptor,
|
|
13
13
|
format_darglint_issues,
|
|
14
14
|
)
|
|
15
|
+
from lintro.formatters.tools.eslint_formatter import (
|
|
16
|
+
EslintTableDescriptor,
|
|
17
|
+
format_eslint_issues,
|
|
18
|
+
)
|
|
15
19
|
from lintro.formatters.tools.hadolint_formatter import (
|
|
16
20
|
HadolintTableDescriptor,
|
|
17
21
|
format_hadolint_issues,
|
|
18
22
|
)
|
|
23
|
+
from lintro.formatters.tools.markdownlint_formatter import (
|
|
24
|
+
MarkdownlintTableDescriptor,
|
|
25
|
+
format_markdownlint_issues,
|
|
26
|
+
)
|
|
19
27
|
from lintro.formatters.tools.prettier_formatter import (
|
|
20
28
|
PrettierTableDescriptor,
|
|
21
29
|
format_prettier_issues,
|
|
@@ -36,8 +44,12 @@ __all__ = [
|
|
|
36
44
|
"format_bandit_issues",
|
|
37
45
|
"DarglintTableDescriptor",
|
|
38
46
|
"format_darglint_issues",
|
|
47
|
+
"EslintTableDescriptor",
|
|
48
|
+
"format_eslint_issues",
|
|
39
49
|
"HadolintTableDescriptor",
|
|
40
50
|
"format_hadolint_issues",
|
|
51
|
+
"MarkdownlintTableDescriptor",
|
|
52
|
+
"format_markdownlint_issues",
|
|
41
53
|
"PrettierTableDescriptor",
|
|
42
54
|
"format_prettier_issues",
|
|
43
55
|
"RuffTableDescriptor",
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Formatter for ESLint issues."""
|
|
2
|
+
|
|
3
|
+
from lintro.formatters.core.table_descriptor import TableDescriptor
|
|
4
|
+
from lintro.formatters.styles.csv import CsvStyle
|
|
5
|
+
from lintro.formatters.styles.grid import GridStyle
|
|
6
|
+
from lintro.formatters.styles.html import HtmlStyle
|
|
7
|
+
from lintro.formatters.styles.json import JsonStyle
|
|
8
|
+
from lintro.formatters.styles.markdown import MarkdownStyle
|
|
9
|
+
from lintro.formatters.styles.plain import PlainStyle
|
|
10
|
+
from lintro.parsers.eslint.eslint_issue import EslintIssue
|
|
11
|
+
from lintro.utils.path_utils import normalize_file_path_for_display
|
|
12
|
+
|
|
13
|
+
FORMAT_MAP = {
|
|
14
|
+
"plain": PlainStyle(),
|
|
15
|
+
"grid": GridStyle(),
|
|
16
|
+
"markdown": MarkdownStyle(),
|
|
17
|
+
"html": HtmlStyle(),
|
|
18
|
+
"json": JsonStyle(),
|
|
19
|
+
"csv": CsvStyle(),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class EslintTableDescriptor(TableDescriptor):
|
|
24
|
+
"""Describe columns and rows for ESLint issues."""
|
|
25
|
+
|
|
26
|
+
def get_columns(self) -> list[str]:
|
|
27
|
+
"""Return ordered column headers for the ESLint table.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
list[str]: Column names for the formatted table.
|
|
31
|
+
"""
|
|
32
|
+
return ["File", "Line", "Column", "Code", "Severity", "Message"]
|
|
33
|
+
|
|
34
|
+
def get_rows(
|
|
35
|
+
self,
|
|
36
|
+
issues: list[EslintIssue],
|
|
37
|
+
) -> list[list[str]]:
|
|
38
|
+
"""Return rows for the ESLint issues table.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
issues: Parsed ESLint issues to render.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
list[list[str]]: Table rows with normalized file path and fields.
|
|
45
|
+
"""
|
|
46
|
+
rows = []
|
|
47
|
+
for issue in issues:
|
|
48
|
+
severity_str = "error" if issue.severity == 2 else "warning"
|
|
49
|
+
rows.append(
|
|
50
|
+
[
|
|
51
|
+
normalize_file_path_for_display(issue.file),
|
|
52
|
+
str(issue.line),
|
|
53
|
+
str(issue.column),
|
|
54
|
+
issue.code,
|
|
55
|
+
severity_str,
|
|
56
|
+
issue.message,
|
|
57
|
+
],
|
|
58
|
+
)
|
|
59
|
+
return rows
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def format_eslint_issues(
|
|
63
|
+
issues: list[EslintIssue],
|
|
64
|
+
format: str = "grid",
|
|
65
|
+
) -> str:
|
|
66
|
+
"""Format ESLint issues with auto-fixable labeling.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
issues: List of EslintIssue objects.
|
|
70
|
+
format: Output format identifier (e.g., "grid", "json").
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
str: Formatted output string.
|
|
74
|
+
|
|
75
|
+
Notes:
|
|
76
|
+
ESLint issues can be auto-fixable if the fixable flag is True.
|
|
77
|
+
For non-JSON formats, issues are split into auto-fixable and
|
|
78
|
+
not auto-fixable sections.
|
|
79
|
+
JSON returns the combined table for compatibility.
|
|
80
|
+
"""
|
|
81
|
+
descriptor = EslintTableDescriptor()
|
|
82
|
+
formatter = FORMAT_MAP.get(format, GridStyle())
|
|
83
|
+
|
|
84
|
+
if format == "json":
|
|
85
|
+
columns = descriptor.get_columns()
|
|
86
|
+
rows = descriptor.get_rows(issues)
|
|
87
|
+
return formatter.format(columns=columns, rows=rows, tool_name="eslint")
|
|
88
|
+
|
|
89
|
+
# Split issues by fixability
|
|
90
|
+
fixable_issues = [i for i in issues if i.fixable]
|
|
91
|
+
non_fixable_issues = [i for i in issues if not i.fixable]
|
|
92
|
+
|
|
93
|
+
sections: list[str] = []
|
|
94
|
+
if fixable_issues:
|
|
95
|
+
columns = descriptor.get_columns()
|
|
96
|
+
rows = descriptor.get_rows(fixable_issues)
|
|
97
|
+
table = formatter.format(columns=columns, rows=rows)
|
|
98
|
+
sections.append("Auto-fixable issues\n" + table)
|
|
99
|
+
if non_fixable_issues:
|
|
100
|
+
columns = descriptor.get_columns()
|
|
101
|
+
rows = descriptor.get_rows(non_fixable_issues)
|
|
102
|
+
table = formatter.format(columns=columns, rows=rows)
|
|
103
|
+
sections.append("Not auto-fixable issues\n" + table)
|
|
104
|
+
|
|
105
|
+
if not sections:
|
|
106
|
+
return "No issues found."
|
|
107
|
+
|
|
108
|
+
return "\n\n".join(sections)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Formatter for Markdownlint issues."""
|
|
2
|
+
|
|
3
|
+
from lintro.formatters.core.table_descriptor import TableDescriptor
|
|
4
|
+
from lintro.formatters.styles.csv import CsvStyle
|
|
5
|
+
from lintro.formatters.styles.grid import GridStyle
|
|
6
|
+
from lintro.formatters.styles.html import HtmlStyle
|
|
7
|
+
from lintro.formatters.styles.json import JsonStyle
|
|
8
|
+
from lintro.formatters.styles.markdown import MarkdownStyle
|
|
9
|
+
from lintro.formatters.styles.plain import PlainStyle
|
|
10
|
+
from lintro.parsers.markdownlint.markdownlint_issue import MarkdownlintIssue
|
|
11
|
+
from lintro.utils.path_utils import normalize_file_path_for_display
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MarkdownlintTableDescriptor(TableDescriptor):
|
|
15
|
+
"""Describe columns and rows for Markdownlint issues."""
|
|
16
|
+
|
|
17
|
+
def get_columns(self) -> list[str]:
|
|
18
|
+
"""Return ordered column headers for the Markdownlint table.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
list[str]: Column names for the formatted table.
|
|
22
|
+
"""
|
|
23
|
+
return ["File", "Line", "Column", "Code", "Message"]
|
|
24
|
+
|
|
25
|
+
def get_rows(
|
|
26
|
+
self,
|
|
27
|
+
issues: list[MarkdownlintIssue],
|
|
28
|
+
) -> list[list[str]]:
|
|
29
|
+
"""Return rows for the Markdownlint issues table.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
issues: Parsed Markdownlint issues to render.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
list[list[str]]: Table rows with normalized file path and fields.
|
|
36
|
+
"""
|
|
37
|
+
rows = []
|
|
38
|
+
for issue in issues:
|
|
39
|
+
rows.append(
|
|
40
|
+
[
|
|
41
|
+
normalize_file_path_for_display(issue.file),
|
|
42
|
+
str(issue.line),
|
|
43
|
+
str(issue.column) if issue.column is not None else "-",
|
|
44
|
+
issue.code,
|
|
45
|
+
issue.message,
|
|
46
|
+
],
|
|
47
|
+
)
|
|
48
|
+
return rows
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def format_markdownlint_issues(
|
|
52
|
+
issues: list[MarkdownlintIssue],
|
|
53
|
+
format: str = "grid",
|
|
54
|
+
*,
|
|
55
|
+
tool_name: str = "markdownlint",
|
|
56
|
+
) -> str:
|
|
57
|
+
"""Format Markdownlint issues to the given style.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
issues: List of MarkdownlintIssue instances.
|
|
61
|
+
format: Output style identifier.
|
|
62
|
+
tool_name: Tool name for JSON metadata.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
str: Rendered string for the issues table.
|
|
66
|
+
"""
|
|
67
|
+
descriptor = MarkdownlintTableDescriptor()
|
|
68
|
+
columns = descriptor.get_columns()
|
|
69
|
+
rows = descriptor.get_rows(issues)
|
|
70
|
+
|
|
71
|
+
if not rows:
|
|
72
|
+
return "No issues found."
|
|
73
|
+
|
|
74
|
+
style = (format or "grid").lower()
|
|
75
|
+
if style == "grid":
|
|
76
|
+
return GridStyle().format(columns=columns, rows=rows)
|
|
77
|
+
if style == "plain":
|
|
78
|
+
return PlainStyle().format(columns=columns, rows=rows)
|
|
79
|
+
if style == "markdown":
|
|
80
|
+
return MarkdownStyle().format(columns=columns, rows=rows)
|
|
81
|
+
if style == "html":
|
|
82
|
+
return HtmlStyle().format(columns=columns, rows=rows)
|
|
83
|
+
if style == "json":
|
|
84
|
+
return JsonStyle().format(columns=columns, rows=rows, tool_name=tool_name)
|
|
85
|
+
if style == "csv":
|
|
86
|
+
return CsvStyle().format(columns=columns, rows=rows)
|
|
87
|
+
|
|
88
|
+
return GridStyle().format(columns=columns, rows=rows)
|