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
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from lintro.formatters.core.table_descriptor import TableDescriptor
|
|
6
5
|
from lintro.formatters.styles.csv import CsvStyle
|
|
7
6
|
from lintro.formatters.styles.grid import GridStyle
|
|
8
7
|
from lintro.formatters.styles.html import HtmlStyle
|
|
9
8
|
from lintro.formatters.styles.json import JsonStyle
|
|
10
9
|
from lintro.formatters.styles.markdown import MarkdownStyle
|
|
11
10
|
from lintro.formatters.styles.plain import PlainStyle
|
|
12
|
-
from lintro.parsers.black.black_issue import BlackIssue
|
|
13
11
|
from lintro.utils.path_utils import normalize_file_path_for_display
|
|
14
12
|
|
|
15
13
|
FORMAT_MAP = {
|
|
@@ -22,11 +20,26 @@ FORMAT_MAP = {
|
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
|
|
25
|
-
class BlackTableDescriptor
|
|
23
|
+
class BlackTableDescriptor:
|
|
24
|
+
"""Column layout for Black issues in tabular output."""
|
|
25
|
+
|
|
26
26
|
def get_columns(self) -> list[str]:
|
|
27
|
+
"""Return ordered column headers for Black output rows.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
list[str]: Column names for the formatted table.
|
|
31
|
+
"""
|
|
27
32
|
return ["File", "Message"]
|
|
28
33
|
|
|
29
|
-
def get_rows(self, issues: list
|
|
34
|
+
def get_rows(self, issues: list) -> list[list[str]]:
|
|
35
|
+
"""Return formatted rows for Black issues.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
issues: Parsed Black issues to render.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
list[list[str]]: Table rows with normalized file paths and messages.
|
|
42
|
+
"""
|
|
30
43
|
rows: list[list[str]] = []
|
|
31
44
|
for issue in issues:
|
|
32
45
|
rows.append(
|
|
@@ -38,7 +51,16 @@ class BlackTableDescriptor(TableDescriptor):
|
|
|
38
51
|
return rows
|
|
39
52
|
|
|
40
53
|
|
|
41
|
-
def format_black_issues(issues
|
|
54
|
+
def format_black_issues(issues, format: str) -> str:
|
|
55
|
+
"""Format Black issues according to the chosen style.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
issues: Parsed Black issues.
|
|
59
|
+
format: Output style identifier.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
str: Rendered table string.
|
|
63
|
+
"""
|
|
42
64
|
descriptor = BlackTableDescriptor()
|
|
43
65
|
formatter = FORMAT_MAP.get(format, GridStyle())
|
|
44
66
|
columns = descriptor.get_columns()
|
|
@@ -21,13 +21,28 @@ FORMAT_MAP = {
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class DarglintTableDescriptor(TableDescriptor):
|
|
24
|
+
"""Describe columns and rows for Darglint issues."""
|
|
25
|
+
|
|
24
26
|
def get_columns(self) -> list[str]:
|
|
27
|
+
"""Return column headers for the Darglint issues table.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
list[str]: Column names for the formatted table.
|
|
31
|
+
"""
|
|
25
32
|
return ["File", "Line", "Code", "Message"]
|
|
26
33
|
|
|
27
34
|
def get_rows(
|
|
28
35
|
self,
|
|
29
36
|
issues: list[DarglintIssue],
|
|
30
37
|
) -> list[list[str]]:
|
|
38
|
+
"""Return rows for the Darglint table.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
issues: Parsed Darglint issues to format.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
list[list[str]]: Table rows with normalized path, line, code, message.
|
|
45
|
+
"""
|
|
31
46
|
rows = []
|
|
32
47
|
for issue in issues:
|
|
33
48
|
rows.append(
|
|
@@ -52,7 +67,7 @@ def format_darglint_issues(
|
|
|
52
67
|
format: Output format (plain, grid, markdown, html, json, csv).
|
|
53
68
|
|
|
54
69
|
Returns:
|
|
55
|
-
Formatted string representation of the issues.
|
|
70
|
+
str: Formatted string representation of the issues.
|
|
56
71
|
"""
|
|
57
72
|
descriptor = DarglintTableDescriptor()
|
|
58
73
|
columns = descriptor.get_columns()
|
|
@@ -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)
|
|
@@ -19,6 +19,11 @@ class HadolintTableDescriptor(TableDescriptor):
|
|
|
19
19
|
"""Describe hadolint issue columns and row extraction."""
|
|
20
20
|
|
|
21
21
|
def get_columns(self) -> list[str]:
|
|
22
|
+
"""Return ordered column headers for the Hadolint table.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
list[str]: Column names for the formatted table.
|
|
26
|
+
"""
|
|
22
27
|
return [
|
|
23
28
|
"File",
|
|
24
29
|
"Line",
|
|
@@ -32,6 +37,14 @@ class HadolintTableDescriptor(TableDescriptor):
|
|
|
32
37
|
self,
|
|
33
38
|
issues: list[HadolintIssue],
|
|
34
39
|
) -> list[list[Any]]:
|
|
40
|
+
"""Return rows for the Hadolint issues table.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
issues: Parsed Hadolint issues to render.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
list[list[Any]]: Table rows with normalized file path and fields.
|
|
47
|
+
"""
|
|
35
48
|
rows: list[list[Any]] = []
|
|
36
49
|
for issue in issues:
|
|
37
50
|
rows.append(
|
|
@@ -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)
|
|
@@ -21,13 +21,28 @@ FORMAT_MAP = {
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class PrettierTableDescriptor(TableDescriptor):
|
|
24
|
+
"""Describe columns and rows for Prettier issues."""
|
|
25
|
+
|
|
24
26
|
def get_columns(self) -> list[str]:
|
|
27
|
+
"""Return ordered column headers for the Prettier table.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
list[str]: Column names for the formatted table.
|
|
31
|
+
"""
|
|
25
32
|
return ["File", "Line", "Column", "Code", "Message"]
|
|
26
33
|
|
|
27
34
|
def get_rows(
|
|
28
35
|
self,
|
|
29
36
|
issues: list[PrettierIssue],
|
|
30
37
|
) -> list[list[str]]:
|
|
38
|
+
"""Return rows for the Prettier issues table.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
issues: Parsed Prettier issues to render.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
list[list[str]]: Table rows with normalized file path and fields.
|
|
45
|
+
"""
|
|
31
46
|
rows = []
|
|
32
47
|
for issue in issues:
|
|
33
48
|
rows.append(
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Formatter for pytest 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.pytest.pytest_issue import PytestIssue
|
|
11
|
+
from lintro.utils.path_utils import normalize_file_path_for_display
|
|
12
|
+
|
|
13
|
+
# Maximum message length before truncation (reasonable for terminal widths)
|
|
14
|
+
MAX_MESSAGE_LENGTH: int = 100
|
|
15
|
+
|
|
16
|
+
FORMAT_MAP = {
|
|
17
|
+
"plain": PlainStyle(),
|
|
18
|
+
"grid": GridStyle(),
|
|
19
|
+
"markdown": MarkdownStyle(),
|
|
20
|
+
"html": HtmlStyle(),
|
|
21
|
+
"json": JsonStyle(),
|
|
22
|
+
"csv": CsvStyle(),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PytestFailuresTableDescriptor(TableDescriptor):
|
|
27
|
+
"""Describe columns and rows for pytest failed/error issues."""
|
|
28
|
+
|
|
29
|
+
def get_columns(self) -> list[str]:
|
|
30
|
+
"""Return ordered column headers for the pytest failures table.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
list[str]: Column names for the formatted table.
|
|
34
|
+
"""
|
|
35
|
+
return ["File", "Status", "Error"]
|
|
36
|
+
|
|
37
|
+
def get_rows(
|
|
38
|
+
self,
|
|
39
|
+
issues: list[PytestIssue],
|
|
40
|
+
) -> list[list[str]]:
|
|
41
|
+
"""Return rows for the pytest failures table.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
issues: Parsed pytest issues to render.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
list[list[str]]: Table rows with normalized file path and fields.
|
|
48
|
+
"""
|
|
49
|
+
rows = []
|
|
50
|
+
for issue in issues:
|
|
51
|
+
# Only show failed/error tests
|
|
52
|
+
if issue.test_status not in ("FAILED", "ERROR"):
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
message = str(issue.message) if issue.message is not None else ""
|
|
56
|
+
truncated_message = (
|
|
57
|
+
f"{message[:MAX_MESSAGE_LENGTH]}..."
|
|
58
|
+
if len(message) > MAX_MESSAGE_LENGTH + 3
|
|
59
|
+
else message
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
status_emoji = "❌ FAIL" if issue.test_status == "FAILED" else "⚠️ ERROR"
|
|
63
|
+
|
|
64
|
+
rows.append(
|
|
65
|
+
[
|
|
66
|
+
normalize_file_path_for_display(issue.file),
|
|
67
|
+
status_emoji,
|
|
68
|
+
truncated_message,
|
|
69
|
+
],
|
|
70
|
+
)
|
|
71
|
+
return rows
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PytestSkippedTableDescriptor(TableDescriptor):
|
|
75
|
+
"""Describe columns and rows for pytest skipped issues."""
|
|
76
|
+
|
|
77
|
+
def get_columns(self) -> list[str]:
|
|
78
|
+
"""Return ordered column headers for the pytest skipped table.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
list[str]: Column names for the formatted table.
|
|
82
|
+
"""
|
|
83
|
+
return ["File", "Test", "Skip Reason"]
|
|
84
|
+
|
|
85
|
+
def get_rows(
|
|
86
|
+
self,
|
|
87
|
+
issues: list[PytestIssue],
|
|
88
|
+
) -> list[list[str]]:
|
|
89
|
+
"""Return rows for the pytest skipped table.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
issues: Parsed pytest issues to render.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
list[list[str]]: Table rows with normalized file path and fields.
|
|
96
|
+
"""
|
|
97
|
+
rows = []
|
|
98
|
+
for issue in issues:
|
|
99
|
+
# Only show skipped tests
|
|
100
|
+
if issue.test_status != "SKIPPED":
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
message = str(issue.message) if issue.message is not None else ""
|
|
104
|
+
truncated_message = (
|
|
105
|
+
f"{message[:MAX_MESSAGE_LENGTH]}..."
|
|
106
|
+
if len(message) > MAX_MESSAGE_LENGTH + 3
|
|
107
|
+
else message
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
rows.append(
|
|
111
|
+
[
|
|
112
|
+
normalize_file_path_for_display(issue.file),
|
|
113
|
+
issue.test_name or "Unknown",
|
|
114
|
+
truncated_message,
|
|
115
|
+
],
|
|
116
|
+
)
|
|
117
|
+
return rows
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def format_pytest_failures(
|
|
121
|
+
issues: list[PytestIssue],
|
|
122
|
+
format: str = "grid",
|
|
123
|
+
) -> str:
|
|
124
|
+
"""Format pytest failures and errors into a table.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
issues: List of pytest issues to format.
|
|
128
|
+
format: Output format (plain, grid, markdown, html, json, csv).
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
str: Formatted string with pytest failures table.
|
|
132
|
+
"""
|
|
133
|
+
descriptor = PytestFailuresTableDescriptor()
|
|
134
|
+
formatter = FORMAT_MAP.get(format, GridStyle())
|
|
135
|
+
|
|
136
|
+
columns = descriptor.get_columns()
|
|
137
|
+
rows = descriptor.get_rows(issues)
|
|
138
|
+
|
|
139
|
+
# Always return a table structure, even if empty
|
|
140
|
+
return formatter.format(
|
|
141
|
+
columns=columns,
|
|
142
|
+
rows=rows,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def format_pytest_skipped(
|
|
147
|
+
issues: list[PytestIssue],
|
|
148
|
+
format: str = "grid",
|
|
149
|
+
) -> str:
|
|
150
|
+
"""Format pytest skipped tests into a table.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
issues: List of pytest issues to format.
|
|
154
|
+
format: Output format (plain, grid, markdown, html, json, csv).
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
str: Formatted string with pytest skipped tests table.
|
|
158
|
+
"""
|
|
159
|
+
descriptor = PytestSkippedTableDescriptor()
|
|
160
|
+
formatter = FORMAT_MAP.get(format, GridStyle())
|
|
161
|
+
|
|
162
|
+
columns = descriptor.get_columns()
|
|
163
|
+
rows = descriptor.get_rows(issues)
|
|
164
|
+
|
|
165
|
+
# Always return a table structure, even if empty
|
|
166
|
+
return formatter.format(
|
|
167
|
+
columns=columns,
|
|
168
|
+
rows=rows,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def format_pytest_issues(
|
|
173
|
+
issues: list[PytestIssue],
|
|
174
|
+
format: str = "grid",
|
|
175
|
+
) -> str:
|
|
176
|
+
"""Format pytest issues into tables for failures and skipped tests.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
issues: List of pytest issues to format.
|
|
180
|
+
format: Output format (plain, grid, markdown, html, json, csv).
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
str: Formatted string with pytest issues tables.
|
|
184
|
+
"""
|
|
185
|
+
output_parts = []
|
|
186
|
+
|
|
187
|
+
# Format failures table
|
|
188
|
+
failures_table = format_pytest_failures(issues, format)
|
|
189
|
+
if failures_table.strip():
|
|
190
|
+
output_parts.append("Test Failures:")
|
|
191
|
+
output_parts.append(failures_table)
|
|
192
|
+
|
|
193
|
+
# Format skipped tests table
|
|
194
|
+
skipped_table = format_pytest_skipped(issues, format)
|
|
195
|
+
if skipped_table.strip():
|
|
196
|
+
if output_parts:
|
|
197
|
+
output_parts.append("") # Add blank line between tables
|
|
198
|
+
output_parts.append("Skipped Tests:")
|
|
199
|
+
output_parts.append(skipped_table)
|
|
200
|
+
|
|
201
|
+
return "\n".join(output_parts)
|
|
@@ -21,13 +21,28 @@ FORMAT_MAP = {
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class RuffTableDescriptor(TableDescriptor):
|
|
24
|
+
"""Describe columns and rows for Ruff issues."""
|
|
25
|
+
|
|
24
26
|
def get_columns(self) -> list[str]:
|
|
27
|
+
"""Return ordered column headers for the Ruff table.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
list[str]: Column names for the formatted table.
|
|
31
|
+
"""
|
|
25
32
|
return ["File", "Line", "Column", "Code", "Message"]
|
|
26
33
|
|
|
27
34
|
def get_rows(
|
|
28
35
|
self,
|
|
29
36
|
issues: list[RuffIssue | RuffFormatIssue],
|
|
30
37
|
) -> list[list[str]]:
|
|
38
|
+
"""Return rows for the Ruff issues table.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
issues: Parsed Ruff issues to render.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
list[list[str]]: Table rows with normalized file path and fields.
|
|
45
|
+
"""
|
|
31
46
|
rows = []
|
|
32
47
|
for issue in issues:
|
|
33
48
|
if isinstance(issue, RuffIssue):
|
|
@@ -69,7 +84,7 @@ def format_ruff_issues(
|
|
|
69
84
|
format: Output format (plain, grid, markdown, html, json, csv).
|
|
70
85
|
|
|
71
86
|
Returns:
|
|
72
|
-
Formatted string (one or two tables depending on format).
|
|
87
|
+
str: Formatted string (one or two tables depending on format).
|
|
73
88
|
"""
|
|
74
89
|
descriptor = RuffTableDescriptor()
|
|
75
90
|
formatter = FORMAT_MAP.get(format, GridStyle())
|
|
@@ -79,9 +94,11 @@ def format_ruff_issues(
|
|
|
79
94
|
non_fixable_issues: list[RuffIssue] = []
|
|
80
95
|
|
|
81
96
|
for issue in issues:
|
|
82
|
-
if
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
if (
|
|
98
|
+
isinstance(issue, RuffFormatIssue)
|
|
99
|
+
or isinstance(issue, RuffIssue)
|
|
100
|
+
and issue.fixable
|
|
101
|
+
):
|
|
85
102
|
fixable_issues.append(issue)
|
|
86
103
|
elif isinstance(issue, RuffIssue):
|
|
87
104
|
non_fixable_issues.append(issue)
|
|
@@ -90,7 +107,11 @@ def format_ruff_issues(
|
|
|
90
107
|
if format == "json":
|
|
91
108
|
columns = descriptor.get_columns()
|
|
92
109
|
rows = descriptor.get_rows(issues)
|
|
93
|
-
return formatter.format(
|
|
110
|
+
return formatter.format(
|
|
111
|
+
columns=columns,
|
|
112
|
+
rows=rows,
|
|
113
|
+
tool_name="ruff",
|
|
114
|
+
)
|
|
94
115
|
|
|
95
116
|
sections: list[str] = []
|
|
96
117
|
|
|
@@ -19,6 +19,11 @@ class YamllintTableDescriptor(TableDescriptor):
|
|
|
19
19
|
"""Describe yamllint issue columns and row extraction."""
|
|
20
20
|
|
|
21
21
|
def get_columns(self) -> list[str]:
|
|
22
|
+
"""Return ordered column headers for the Yamllint table.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
list[str]: Column names for the formatted table.
|
|
26
|
+
"""
|
|
22
27
|
return [
|
|
23
28
|
"File",
|
|
24
29
|
"Line",
|
|
@@ -32,6 +37,14 @@ class YamllintTableDescriptor(TableDescriptor):
|
|
|
32
37
|
self,
|
|
33
38
|
issues: list[YamllintIssue],
|
|
34
39
|
) -> list[list[Any]]:
|
|
40
|
+
"""Return rows for the Yamllint issues table.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
issues: Parsed Yamllint issues to render.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
list[list[Any]]: Table rows with normalized file path and fields.
|
|
47
|
+
"""
|
|
35
48
|
rows: list[list[Any]] = []
|
|
36
49
|
for issue in issues:
|
|
37
50
|
rows.append(
|
|
@@ -61,7 +74,7 @@ def format_yamllint_issues(
|
|
|
61
74
|
tool_name: Tool name for JSON metadata.
|
|
62
75
|
|
|
63
76
|
Returns:
|
|
64
|
-
Rendered string for the issues table.
|
|
77
|
+
str: Rendered string for the issues table.
|
|
65
78
|
"""
|
|
66
79
|
descriptor = YamllintTableDescriptor()
|
|
67
80
|
columns = descriptor.get_columns()
|
lintro/models/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Model package containing issue types and configuration structures."""
|
lintro/models/core/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core data models used across tool integrations."""
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Core model for tool configuration used by Lintro tools."""
|
|
2
|
+
|
|
1
3
|
from dataclasses import dataclass, field
|
|
2
4
|
|
|
3
5
|
from lintro.enums.tool_type import ToolType
|
|
@@ -5,15 +7,17 @@ from lintro.enums.tool_type import ToolType
|
|
|
5
7
|
|
|
6
8
|
@dataclass
|
|
7
9
|
class ToolConfig:
|
|
8
|
-
"""Configuration for a
|
|
10
|
+
"""Configuration container for a tool.
|
|
11
|
+
|
|
12
|
+
This dataclass defines the static configuration associated with a tool,
|
|
13
|
+
including its priority, file targeting, type flags, and default options.
|
|
9
14
|
|
|
10
15
|
Attributes:
|
|
11
|
-
priority: int:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
options: dict[str, object]: Tool-specific configuration options
|
|
16
|
+
priority: int: Priority used when ordering tools.
|
|
17
|
+
conflicts_with: list[str]: Names of tools that conflict with this one.
|
|
18
|
+
file_patterns: list[str]: Glob patterns to select applicable files.
|
|
19
|
+
tool_type: ToolType: Bitmask describing tool capabilities.
|
|
20
|
+
options: dict[str, object]: Default tool options applied at runtime.
|
|
17
21
|
"""
|
|
18
22
|
|
|
19
23
|
priority: int = 0
|