lintro 0.3.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.
Potentially problematic release.
This version of lintro might be problematic. Click here for more details.
- lintro/__init__.py +3 -0
- lintro/__main__.py +6 -0
- lintro/ascii-art/fail.txt +404 -0
- lintro/ascii-art/success.txt +484 -0
- lintro/cli.py +70 -0
- lintro/cli_utils/__init__.py +7 -0
- lintro/cli_utils/commands/__init__.py +7 -0
- lintro/cli_utils/commands/check.py +210 -0
- lintro/cli_utils/commands/format.py +167 -0
- lintro/cli_utils/commands/list_tools.py +114 -0
- lintro/enums/__init__.py +0 -0
- lintro/enums/action.py +29 -0
- lintro/enums/darglint_strictness.py +22 -0
- lintro/enums/group_by.py +31 -0
- lintro/enums/hadolint_enums.py +46 -0
- lintro/enums/output_format.py +40 -0
- lintro/enums/tool_name.py +36 -0
- lintro/enums/tool_type.py +27 -0
- lintro/enums/yamllint_format.py +22 -0
- lintro/exceptions/__init__.py +0 -0
- lintro/exceptions/errors.py +15 -0
- lintro/formatters/__init__.py +0 -0
- lintro/formatters/core/__init__.py +0 -0
- lintro/formatters/core/output_style.py +21 -0
- lintro/formatters/core/table_descriptor.py +24 -0
- lintro/formatters/styles/__init__.py +17 -0
- lintro/formatters/styles/csv.py +41 -0
- lintro/formatters/styles/grid.py +91 -0
- lintro/formatters/styles/html.py +48 -0
- lintro/formatters/styles/json.py +61 -0
- lintro/formatters/styles/markdown.py +41 -0
- lintro/formatters/styles/plain.py +39 -0
- lintro/formatters/tools/__init__.py +35 -0
- lintro/formatters/tools/darglint_formatter.py +72 -0
- lintro/formatters/tools/hadolint_formatter.py +84 -0
- lintro/formatters/tools/prettier_formatter.py +76 -0
- lintro/formatters/tools/ruff_formatter.py +116 -0
- lintro/formatters/tools/yamllint_formatter.py +87 -0
- lintro/models/__init__.py +0 -0
- lintro/models/core/__init__.py +0 -0
- lintro/models/core/tool.py +104 -0
- lintro/models/core/tool_config.py +23 -0
- lintro/models/core/tool_result.py +39 -0
- lintro/parsers/__init__.py +0 -0
- lintro/parsers/darglint/__init__.py +0 -0
- lintro/parsers/darglint/darglint_issue.py +9 -0
- lintro/parsers/darglint/darglint_parser.py +62 -0
- lintro/parsers/hadolint/__init__.py +1 -0
- lintro/parsers/hadolint/hadolint_issue.py +24 -0
- lintro/parsers/hadolint/hadolint_parser.py +65 -0
- lintro/parsers/prettier/__init__.py +0 -0
- lintro/parsers/prettier/prettier_issue.py +10 -0
- lintro/parsers/prettier/prettier_parser.py +60 -0
- lintro/parsers/ruff/__init__.py +1 -0
- lintro/parsers/ruff/ruff_issue.py +43 -0
- lintro/parsers/ruff/ruff_parser.py +89 -0
- lintro/parsers/yamllint/__init__.py +0 -0
- lintro/parsers/yamllint/yamllint_issue.py +24 -0
- lintro/parsers/yamllint/yamllint_parser.py +68 -0
- lintro/tools/__init__.py +40 -0
- lintro/tools/core/__init__.py +0 -0
- lintro/tools/core/tool_base.py +320 -0
- lintro/tools/core/tool_manager.py +167 -0
- lintro/tools/implementations/__init__.py +0 -0
- lintro/tools/implementations/tool_darglint.py +245 -0
- lintro/tools/implementations/tool_hadolint.py +302 -0
- lintro/tools/implementations/tool_prettier.py +270 -0
- lintro/tools/implementations/tool_ruff.py +618 -0
- lintro/tools/implementations/tool_yamllint.py +240 -0
- lintro/tools/tool_enum.py +17 -0
- lintro/utils/__init__.py +0 -0
- lintro/utils/ascii_normalize_cli.py +84 -0
- lintro/utils/config.py +39 -0
- lintro/utils/console_logger.py +783 -0
- lintro/utils/formatting.py +173 -0
- lintro/utils/output_manager.py +301 -0
- lintro/utils/path_utils.py +41 -0
- lintro/utils/tool_executor.py +443 -0
- lintro/utils/tool_utils.py +431 -0
- lintro-0.3.2.dist-info/METADATA +338 -0
- lintro-0.3.2.dist-info/RECORD +85 -0
- lintro-0.3.2.dist-info/WHEEL +5 -0
- lintro-0.3.2.dist-info/entry_points.txt +2 -0
- lintro-0.3.2.dist-info/licenses/LICENSE +21 -0
- lintro-0.3.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Formatter for darglint 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.darglint.darglint_issue import DarglintIssue
|
|
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 DarglintTableDescriptor(TableDescriptor):
|
|
24
|
+
def get_columns(self) -> list[str]:
|
|
25
|
+
return ["File", "Line", "Code", "Message"]
|
|
26
|
+
|
|
27
|
+
def get_rows(
|
|
28
|
+
self,
|
|
29
|
+
issues: list[DarglintIssue],
|
|
30
|
+
) -> list[list[str]]:
|
|
31
|
+
rows = []
|
|
32
|
+
for issue in issues:
|
|
33
|
+
rows.append(
|
|
34
|
+
[
|
|
35
|
+
normalize_file_path_for_display(issue.file),
|
|
36
|
+
str(issue.line),
|
|
37
|
+
issue.code,
|
|
38
|
+
issue.message,
|
|
39
|
+
],
|
|
40
|
+
)
|
|
41
|
+
return rows
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def format_darglint_issues(
|
|
45
|
+
issues: list[DarglintIssue],
|
|
46
|
+
format: str = "grid",
|
|
47
|
+
) -> str:
|
|
48
|
+
"""Format a list of Darglint issues using the specified format.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
issues: List of Darglint issues to format.
|
|
52
|
+
format: Output format (plain, grid, markdown, html, json, csv).
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Formatted string representation of the issues.
|
|
56
|
+
"""
|
|
57
|
+
descriptor = DarglintTableDescriptor()
|
|
58
|
+
columns = descriptor.get_columns()
|
|
59
|
+
rows = descriptor.get_rows(issues)
|
|
60
|
+
|
|
61
|
+
formatter = FORMAT_MAP.get(format, GridStyle())
|
|
62
|
+
|
|
63
|
+
# For JSON format, pass tool name
|
|
64
|
+
if format == "json":
|
|
65
|
+
formatted_table = formatter.format(
|
|
66
|
+
columns=columns, rows=rows, tool_name="darglint"
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
# For other formats, use standard formatting
|
|
70
|
+
formatted_table = formatter.format(columns=columns, rows=rows)
|
|
71
|
+
|
|
72
|
+
return formatted_table
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Hadolint table formatting utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from lintro.formatters.core.table_descriptor import TableDescriptor
|
|
8
|
+
from lintro.formatters.styles.csv import CsvStyle
|
|
9
|
+
from lintro.formatters.styles.grid import GridStyle
|
|
10
|
+
from lintro.formatters.styles.html import HtmlStyle
|
|
11
|
+
from lintro.formatters.styles.json import JsonStyle
|
|
12
|
+
from lintro.formatters.styles.markdown import MarkdownStyle
|
|
13
|
+
from lintro.formatters.styles.plain import PlainStyle
|
|
14
|
+
from lintro.parsers.hadolint.hadolint_issue import HadolintIssue
|
|
15
|
+
from lintro.utils.path_utils import normalize_file_path_for_display
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HadolintTableDescriptor(TableDescriptor):
|
|
19
|
+
"""Describe hadolint issue columns and row extraction."""
|
|
20
|
+
|
|
21
|
+
def get_columns(self) -> list[str]:
|
|
22
|
+
return [
|
|
23
|
+
"File",
|
|
24
|
+
"Line",
|
|
25
|
+
"Column",
|
|
26
|
+
"Level",
|
|
27
|
+
"Code",
|
|
28
|
+
"Message",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
def get_rows(
|
|
32
|
+
self,
|
|
33
|
+
issues: list[HadolintIssue],
|
|
34
|
+
) -> list[list[Any]]:
|
|
35
|
+
rows: list[list[Any]] = []
|
|
36
|
+
for issue in issues:
|
|
37
|
+
rows.append(
|
|
38
|
+
[
|
|
39
|
+
normalize_file_path_for_display(issue.file),
|
|
40
|
+
issue.line,
|
|
41
|
+
issue.column if getattr(issue, "column", None) is not None else "",
|
|
42
|
+
issue.level,
|
|
43
|
+
issue.code,
|
|
44
|
+
issue.message,
|
|
45
|
+
],
|
|
46
|
+
)
|
|
47
|
+
return rows
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def format_hadolint_issues(
|
|
51
|
+
issues: list[HadolintIssue],
|
|
52
|
+
format: str = "grid",
|
|
53
|
+
) -> str:
|
|
54
|
+
"""Format hadolint issues to the given style.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
issues: List of HadolintIssue instances.
|
|
58
|
+
format: Output style identifier.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Rendered string for the issues table.
|
|
62
|
+
"""
|
|
63
|
+
descriptor = HadolintTableDescriptor()
|
|
64
|
+
columns = descriptor.get_columns()
|
|
65
|
+
rows = descriptor.get_rows(issues)
|
|
66
|
+
|
|
67
|
+
if not rows:
|
|
68
|
+
return "No issues found."
|
|
69
|
+
|
|
70
|
+
style = (format or "grid").lower()
|
|
71
|
+
if style == "grid":
|
|
72
|
+
return GridStyle().format(columns=columns, rows=rows)
|
|
73
|
+
if style == "plain":
|
|
74
|
+
return PlainStyle().format(columns=columns, rows=rows)
|
|
75
|
+
if style == "markdown":
|
|
76
|
+
return MarkdownStyle().format(columns=columns, rows=rows)
|
|
77
|
+
if style == "html":
|
|
78
|
+
return HtmlStyle().format(columns=columns, rows=rows)
|
|
79
|
+
if style == "json":
|
|
80
|
+
return JsonStyle().format(columns=columns, rows=rows, tool_name="hadolint")
|
|
81
|
+
if style == "csv":
|
|
82
|
+
return CsvStyle().format(columns=columns, rows=rows)
|
|
83
|
+
|
|
84
|
+
return GridStyle().format(columns=columns, rows=rows)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Formatter for Prettier 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.prettier.prettier_issue import PrettierIssue
|
|
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 PrettierTableDescriptor(TableDescriptor):
|
|
24
|
+
def get_columns(self) -> list[str]:
|
|
25
|
+
return ["File", "Line", "Column", "Code", "Message"]
|
|
26
|
+
|
|
27
|
+
def get_rows(
|
|
28
|
+
self,
|
|
29
|
+
issues: list[PrettierIssue],
|
|
30
|
+
) -> list[list[str]]:
|
|
31
|
+
rows = []
|
|
32
|
+
for issue in issues:
|
|
33
|
+
rows.append(
|
|
34
|
+
[
|
|
35
|
+
normalize_file_path_for_display(issue.file),
|
|
36
|
+
str(issue.line) if issue.line is not None else "-",
|
|
37
|
+
str(issue.column) if issue.column is not None else "-",
|
|
38
|
+
issue.code,
|
|
39
|
+
issue.message,
|
|
40
|
+
],
|
|
41
|
+
)
|
|
42
|
+
return rows
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def format_prettier_issues(
|
|
46
|
+
issues: list[PrettierIssue],
|
|
47
|
+
format: str = "grid",
|
|
48
|
+
) -> str:
|
|
49
|
+
"""Format Prettier issues with auto-fixable labeling.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
issues: List of PrettierIssue objects.
|
|
53
|
+
format: Output format identifier (e.g., "grid", "json").
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
str: Formatted output string.
|
|
57
|
+
|
|
58
|
+
Notes:
|
|
59
|
+
All Prettier issues are auto-fixable by running `lintro format`.
|
|
60
|
+
For non-JSON formats, a single section is labeled as auto-fixable.
|
|
61
|
+
JSON returns the combined table for compatibility.
|
|
62
|
+
"""
|
|
63
|
+
descriptor = PrettierTableDescriptor()
|
|
64
|
+
formatter = FORMAT_MAP.get(format, GridStyle())
|
|
65
|
+
|
|
66
|
+
if format == "json":
|
|
67
|
+
columns = descriptor.get_columns()
|
|
68
|
+
rows = descriptor.get_rows(issues)
|
|
69
|
+
return formatter.format(columns=columns, rows=rows, tool_name="prettier")
|
|
70
|
+
|
|
71
|
+
columns = descriptor.get_columns()
|
|
72
|
+
rows = descriptor.get_rows(issues)
|
|
73
|
+
table = formatter.format(columns=columns, rows=rows)
|
|
74
|
+
if not rows:
|
|
75
|
+
return table
|
|
76
|
+
return "Auto-fixable issues\n" + table
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Formatter for ruff 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.ruff.ruff_issue import RuffFormatIssue, RuffIssue
|
|
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 RuffTableDescriptor(TableDescriptor):
|
|
24
|
+
def get_columns(self) -> list[str]:
|
|
25
|
+
return ["File", "Line", "Column", "Code", "Message"]
|
|
26
|
+
|
|
27
|
+
def get_rows(
|
|
28
|
+
self,
|
|
29
|
+
issues: list[RuffIssue | RuffFormatIssue],
|
|
30
|
+
) -> list[list[str]]:
|
|
31
|
+
rows = []
|
|
32
|
+
for issue in issues:
|
|
33
|
+
if isinstance(issue, RuffIssue):
|
|
34
|
+
# Linting issue
|
|
35
|
+
rows.append(
|
|
36
|
+
[
|
|
37
|
+
normalize_file_path_for_display(issue.file),
|
|
38
|
+
str(issue.line),
|
|
39
|
+
str(issue.column),
|
|
40
|
+
issue.code,
|
|
41
|
+
issue.message,
|
|
42
|
+
],
|
|
43
|
+
)
|
|
44
|
+
elif isinstance(issue, RuffFormatIssue):
|
|
45
|
+
# From Lintro's perspective, fmt applies both lint fixes and
|
|
46
|
+
# formatting, so formatting entries are auto-fixable by fmt.
|
|
47
|
+
rows.append(
|
|
48
|
+
[
|
|
49
|
+
normalize_file_path_for_display(issue.file),
|
|
50
|
+
"-",
|
|
51
|
+
"-",
|
|
52
|
+
"FORMAT",
|
|
53
|
+
"Would reformat file",
|
|
54
|
+
],
|
|
55
|
+
)
|
|
56
|
+
return rows
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def format_ruff_issues(
|
|
60
|
+
issues: list[RuffIssue | RuffFormatIssue],
|
|
61
|
+
format: str = "grid",
|
|
62
|
+
) -> str:
|
|
63
|
+
"""Format Ruff issues, split into auto-fixable and not auto-fixable tables.
|
|
64
|
+
|
|
65
|
+
For JSON format, return a single combined table (backwards compatible).
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
issues: List of Ruff issues to format.
|
|
69
|
+
format: Output format (plain, grid, markdown, html, json, csv).
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Formatted string (one or two tables depending on format).
|
|
73
|
+
"""
|
|
74
|
+
descriptor = RuffTableDescriptor()
|
|
75
|
+
formatter = FORMAT_MAP.get(format, GridStyle())
|
|
76
|
+
|
|
77
|
+
# Partition issues
|
|
78
|
+
fixable_issues: list[RuffIssue | RuffFormatIssue] = []
|
|
79
|
+
non_fixable_issues: list[RuffIssue] = []
|
|
80
|
+
|
|
81
|
+
for issue in issues:
|
|
82
|
+
if isinstance(issue, RuffFormatIssue):
|
|
83
|
+
fixable_issues.append(issue)
|
|
84
|
+
elif isinstance(issue, RuffIssue) and issue.fixable:
|
|
85
|
+
fixable_issues.append(issue)
|
|
86
|
+
elif isinstance(issue, RuffIssue):
|
|
87
|
+
non_fixable_issues.append(issue)
|
|
88
|
+
|
|
89
|
+
# JSON: keep a single table for compatibility
|
|
90
|
+
if format == "json":
|
|
91
|
+
columns = descriptor.get_columns()
|
|
92
|
+
rows = descriptor.get_rows(issues)
|
|
93
|
+
return formatter.format(columns=columns, rows=rows, tool_name="ruff")
|
|
94
|
+
|
|
95
|
+
sections: list[str] = []
|
|
96
|
+
|
|
97
|
+
# Auto-fixable section
|
|
98
|
+
if fixable_issues:
|
|
99
|
+
columns_f = descriptor.get_columns()
|
|
100
|
+
rows_f = descriptor.get_rows(fixable_issues)
|
|
101
|
+
table_f = formatter.format(columns=columns_f, rows=rows_f)
|
|
102
|
+
sections.append("Auto-fixable issues\n" + table_f)
|
|
103
|
+
|
|
104
|
+
# Not auto-fixable section
|
|
105
|
+
if non_fixable_issues:
|
|
106
|
+
columns_u = descriptor.get_columns()
|
|
107
|
+
rows_u = descriptor.get_rows(non_fixable_issues)
|
|
108
|
+
table_u = formatter.format(columns=columns_u, rows=rows_u)
|
|
109
|
+
sections.append("Not auto-fixable issues\n" + table_u)
|
|
110
|
+
|
|
111
|
+
# If neither, return empty table structure
|
|
112
|
+
if not sections:
|
|
113
|
+
columns = descriptor.get_columns()
|
|
114
|
+
return formatter.format(columns=columns, rows=[])
|
|
115
|
+
|
|
116
|
+
return "\n\n".join(sections)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Yamllint table formatting utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from lintro.formatters.core.table_descriptor import TableDescriptor
|
|
8
|
+
from lintro.formatters.styles.csv import CsvStyle
|
|
9
|
+
from lintro.formatters.styles.grid import GridStyle
|
|
10
|
+
from lintro.formatters.styles.html import HtmlStyle
|
|
11
|
+
from lintro.formatters.styles.json import JsonStyle
|
|
12
|
+
from lintro.formatters.styles.markdown import MarkdownStyle
|
|
13
|
+
from lintro.formatters.styles.plain import PlainStyle
|
|
14
|
+
from lintro.parsers.yamllint.yamllint_issue import YamllintIssue
|
|
15
|
+
from lintro.utils.path_utils import normalize_file_path_for_display
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class YamllintTableDescriptor(TableDescriptor):
|
|
19
|
+
"""Describe yamllint issue columns and row extraction."""
|
|
20
|
+
|
|
21
|
+
def get_columns(self) -> list[str]:
|
|
22
|
+
return [
|
|
23
|
+
"File",
|
|
24
|
+
"Line",
|
|
25
|
+
"Column",
|
|
26
|
+
"Level",
|
|
27
|
+
"Rule",
|
|
28
|
+
"Message",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
def get_rows(
|
|
32
|
+
self,
|
|
33
|
+
issues: list[YamllintIssue],
|
|
34
|
+
) -> list[list[Any]]:
|
|
35
|
+
rows: list[list[Any]] = []
|
|
36
|
+
for issue in issues:
|
|
37
|
+
rows.append(
|
|
38
|
+
[
|
|
39
|
+
normalize_file_path_for_display(issue.file),
|
|
40
|
+
issue.line,
|
|
41
|
+
issue.column if getattr(issue, "column", None) is not None else "",
|
|
42
|
+
issue.level,
|
|
43
|
+
issue.rule or "",
|
|
44
|
+
issue.message,
|
|
45
|
+
],
|
|
46
|
+
)
|
|
47
|
+
return rows
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def format_yamllint_issues(
|
|
51
|
+
issues: list[YamllintIssue],
|
|
52
|
+
format: str = "grid",
|
|
53
|
+
*,
|
|
54
|
+
tool_name: str = "yamllint",
|
|
55
|
+
) -> str:
|
|
56
|
+
"""Format yamllint issues to the given style.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
issues: List of YamllintIssue instances.
|
|
60
|
+
format: Output style identifier.
|
|
61
|
+
tool_name: Tool name for JSON metadata.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Rendered string for the issues table.
|
|
65
|
+
"""
|
|
66
|
+
descriptor = YamllintTableDescriptor()
|
|
67
|
+
columns = descriptor.get_columns()
|
|
68
|
+
rows = descriptor.get_rows(issues)
|
|
69
|
+
|
|
70
|
+
if not rows:
|
|
71
|
+
return "No issues found."
|
|
72
|
+
|
|
73
|
+
style = (format or "grid").lower()
|
|
74
|
+
if style == "grid":
|
|
75
|
+
return GridStyle().format(columns=columns, rows=rows)
|
|
76
|
+
if style == "plain":
|
|
77
|
+
return PlainStyle().format(columns=columns, rows=rows)
|
|
78
|
+
if style == "markdown":
|
|
79
|
+
return MarkdownStyle().format(columns=columns, rows=rows)
|
|
80
|
+
if style == "html":
|
|
81
|
+
return HtmlStyle().format(columns=columns, rows=rows)
|
|
82
|
+
if style == "json":
|
|
83
|
+
return JsonStyle().format(columns=columns, rows=rows, tool_name=tool_name)
|
|
84
|
+
if style == "csv":
|
|
85
|
+
return CsvStyle().format(columns=columns, rows=rows)
|
|
86
|
+
|
|
87
|
+
return GridStyle().format(columns=columns, rows=rows)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Tool-related models for Lintro."""
|
|
2
|
+
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from typing import Protocol, runtime_checkable
|
|
5
|
+
|
|
6
|
+
from lintro.models.core.tool_config import ToolConfig
|
|
7
|
+
from lintro.models.core.tool_result import ToolResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@runtime_checkable
|
|
11
|
+
class Tool(Protocol):
|
|
12
|
+
"""Protocol for all linting and formatting tools.
|
|
13
|
+
|
|
14
|
+
This protocol defines the interface that all tools must implement.
|
|
15
|
+
Tools can be implemented as classes that inherit from this protocol.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
name: Tool name
|
|
19
|
+
description: Tool description
|
|
20
|
+
can_fix: Whether the core can fix issues
|
|
21
|
+
config: Tool configuration
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
name: str
|
|
25
|
+
description: str
|
|
26
|
+
can_fix: bool
|
|
27
|
+
config: ToolConfig
|
|
28
|
+
|
|
29
|
+
def set_options(
|
|
30
|
+
self,
|
|
31
|
+
**kwargs,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""Set core options.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
**kwargs: Tool-specific options
|
|
37
|
+
"""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def check(
|
|
42
|
+
self,
|
|
43
|
+
paths: list[str],
|
|
44
|
+
) -> ToolResult:
|
|
45
|
+
"""Check files for issues.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
paths: List of file paths to check.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Result of the check operation.
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
FileNotFoundError: If the core executable is not found
|
|
55
|
+
subprocess.TimeoutExpired: If the core execution times out
|
|
56
|
+
subprocess.CalledProcessError: If the core execution fails
|
|
57
|
+
"""
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def fix(
|
|
62
|
+
self,
|
|
63
|
+
paths: list[str],
|
|
64
|
+
) -> ToolResult:
|
|
65
|
+
"""Fix issues in files.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
paths: List of file paths to fix.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Result of the fix operation.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
FileNotFoundError: If the core executable is not found
|
|
75
|
+
subprocess.TimeoutExpired: If the core execution times out
|
|
76
|
+
subprocess.CalledProcessError: If the core execution fails
|
|
77
|
+
NotImplementedError: If the core does not support fixing issues
|
|
78
|
+
"""
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def to_result(
|
|
83
|
+
name: str,
|
|
84
|
+
success: bool,
|
|
85
|
+
output: str,
|
|
86
|
+
issues_count: int,
|
|
87
|
+
) -> ToolResult:
|
|
88
|
+
"""Convert core operation result to a ToolResult.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
name: Tool name
|
|
92
|
+
success: Whether the operation was successful
|
|
93
|
+
output: Output from the core
|
|
94
|
+
issues_count: Number of issues found or fixed
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Result object
|
|
98
|
+
"""
|
|
99
|
+
return ToolResult(
|
|
100
|
+
name=name,
|
|
101
|
+
success=success,
|
|
102
|
+
output=output,
|
|
103
|
+
issues_count=issues_count,
|
|
104
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
from lintro.enums.tool_type import ToolType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class ToolConfig:
|
|
8
|
+
"""Configuration for a core.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
priority: int:
|
|
12
|
+
Priority level (higher number = higher priority when resolving conflicts)
|
|
13
|
+
conflicts_with: list[str]: List of tools this core conflicts with
|
|
14
|
+
file_patterns: list[str]: List of file patterns this core should be applied to
|
|
15
|
+
tool_type: ToolType: Type of core
|
|
16
|
+
options: dict[str, object]: Tool-specific configuration options
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
priority: int = 0
|
|
20
|
+
conflicts_with: list[str] = field(default_factory=list)
|
|
21
|
+
file_patterns: list[str] = field(default_factory=list)
|
|
22
|
+
tool_type: ToolType = ToolType.LINTER
|
|
23
|
+
options: dict[str, object] = field(default_factory=dict)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Models for core tool execution results.
|
|
2
|
+
|
|
3
|
+
This module defines the canonical result object returned by all tools. It
|
|
4
|
+
supports both check and fix flows and includes standardized fields to report
|
|
5
|
+
fixed vs remaining counts for fix-capable tools.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ToolResult:
|
|
13
|
+
"""Result of running a tool.
|
|
14
|
+
|
|
15
|
+
For check operations:
|
|
16
|
+
- ``issues_count`` represents the number of issues found.
|
|
17
|
+
|
|
18
|
+
For fix/format operations:
|
|
19
|
+
- ``initial_issues_count`` is the number of issues detected before fixes
|
|
20
|
+
- ``fixed_issues_count`` is the number of issues the tool auto-fixed
|
|
21
|
+
- ``remaining_issues_count`` is the number of issues still remaining
|
|
22
|
+
- ``issues_count`` should mirror ``remaining_issues_count`` for
|
|
23
|
+
backward compatibility in format-mode summaries
|
|
24
|
+
|
|
25
|
+
The ``issues`` field can contain parsed issue objects (tool-specific) to
|
|
26
|
+
support unified table formatting.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
name: str
|
|
30
|
+
success: bool
|
|
31
|
+
output: str | None = None
|
|
32
|
+
issues_count: int = 0
|
|
33
|
+
formatted_output: str | None = None
|
|
34
|
+
issues: list[object] | None = None
|
|
35
|
+
|
|
36
|
+
# Optional standardized counts for fix-capable tools
|
|
37
|
+
initial_issues_count: int | None = None
|
|
38
|
+
fixed_issues_count: int | None = None
|
|
39
|
+
remaining_issues_count: int | None = None
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Parser for darglint output."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from lintro.parsers.darglint.darglint_issue import DarglintIssue
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse_darglint_output(output: str) -> list[DarglintIssue]:
|
|
9
|
+
"""Parse darglint output into a list of DarglintIssue objects.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
output: The raw output from darglint
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
List of DarglintIssue objects
|
|
16
|
+
"""
|
|
17
|
+
issues: list[DarglintIssue] = []
|
|
18
|
+
# Patterns:
|
|
19
|
+
# 1. filename:function:line: CODE message
|
|
20
|
+
# 2. filename:line: CODE message (for module-level errors)
|
|
21
|
+
# Accept both "CODE message" and "CODE: message" variants from darglint
|
|
22
|
+
pattern: re.Pattern[str] = re.compile(r"^(.*?):(.*?):(\d+): (D[A-Z]*\d+):? (.*)$")
|
|
23
|
+
alt_pattern: re.Pattern[str] = re.compile(r"^(.*?):(\d+): (D[A-Z]*\d+):? (.*)$")
|
|
24
|
+
lines: list[str] = output.splitlines()
|
|
25
|
+
i: int = 0
|
|
26
|
+
while i < len(lines):
|
|
27
|
+
line: str = lines[i]
|
|
28
|
+
match: re.Match[str] | None = pattern.match(line)
|
|
29
|
+
if match:
|
|
30
|
+
file: str
|
|
31
|
+
obj: str
|
|
32
|
+
line_num: str
|
|
33
|
+
code: str
|
|
34
|
+
message: str
|
|
35
|
+
file, obj, line_num, code, message = match.groups()
|
|
36
|
+
else:
|
|
37
|
+
match = alt_pattern.match(line)
|
|
38
|
+
if match:
|
|
39
|
+
file, line_num, code, message = match.groups()
|
|
40
|
+
else:
|
|
41
|
+
i += 1
|
|
42
|
+
continue
|
|
43
|
+
# Capture all subsequent indented or colon-prefixed lines as part of the message
|
|
44
|
+
message_lines: list[str] = [message]
|
|
45
|
+
j: int = i + 1
|
|
46
|
+
while j < len(lines) and (
|
|
47
|
+
lines[j].strip().startswith(":") or lines[j].startswith(" ")
|
|
48
|
+
):
|
|
49
|
+
# Remove leading colon and whitespace
|
|
50
|
+
message_lines.append(lines[j].strip().lstrip(": "))
|
|
51
|
+
j += 1
|
|
52
|
+
full_message: str = " ".join(message_lines)
|
|
53
|
+
issues.append(
|
|
54
|
+
DarglintIssue(
|
|
55
|
+
file=file,
|
|
56
|
+
line=int(line_num),
|
|
57
|
+
code=code,
|
|
58
|
+
message=full_message,
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
i = j
|
|
62
|
+
return issues
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Hadolint parser module."""
|