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.

Files changed (85) hide show
  1. lintro/__init__.py +3 -0
  2. lintro/__main__.py +6 -0
  3. lintro/ascii-art/fail.txt +404 -0
  4. lintro/ascii-art/success.txt +484 -0
  5. lintro/cli.py +70 -0
  6. lintro/cli_utils/__init__.py +7 -0
  7. lintro/cli_utils/commands/__init__.py +7 -0
  8. lintro/cli_utils/commands/check.py +210 -0
  9. lintro/cli_utils/commands/format.py +167 -0
  10. lintro/cli_utils/commands/list_tools.py +114 -0
  11. lintro/enums/__init__.py +0 -0
  12. lintro/enums/action.py +29 -0
  13. lintro/enums/darglint_strictness.py +22 -0
  14. lintro/enums/group_by.py +31 -0
  15. lintro/enums/hadolint_enums.py +46 -0
  16. lintro/enums/output_format.py +40 -0
  17. lintro/enums/tool_name.py +36 -0
  18. lintro/enums/tool_type.py +27 -0
  19. lintro/enums/yamllint_format.py +22 -0
  20. lintro/exceptions/__init__.py +0 -0
  21. lintro/exceptions/errors.py +15 -0
  22. lintro/formatters/__init__.py +0 -0
  23. lintro/formatters/core/__init__.py +0 -0
  24. lintro/formatters/core/output_style.py +21 -0
  25. lintro/formatters/core/table_descriptor.py +24 -0
  26. lintro/formatters/styles/__init__.py +17 -0
  27. lintro/formatters/styles/csv.py +41 -0
  28. lintro/formatters/styles/grid.py +91 -0
  29. lintro/formatters/styles/html.py +48 -0
  30. lintro/formatters/styles/json.py +61 -0
  31. lintro/formatters/styles/markdown.py +41 -0
  32. lintro/formatters/styles/plain.py +39 -0
  33. lintro/formatters/tools/__init__.py +35 -0
  34. lintro/formatters/tools/darglint_formatter.py +72 -0
  35. lintro/formatters/tools/hadolint_formatter.py +84 -0
  36. lintro/formatters/tools/prettier_formatter.py +76 -0
  37. lintro/formatters/tools/ruff_formatter.py +116 -0
  38. lintro/formatters/tools/yamllint_formatter.py +87 -0
  39. lintro/models/__init__.py +0 -0
  40. lintro/models/core/__init__.py +0 -0
  41. lintro/models/core/tool.py +104 -0
  42. lintro/models/core/tool_config.py +23 -0
  43. lintro/models/core/tool_result.py +39 -0
  44. lintro/parsers/__init__.py +0 -0
  45. lintro/parsers/darglint/__init__.py +0 -0
  46. lintro/parsers/darglint/darglint_issue.py +9 -0
  47. lintro/parsers/darglint/darglint_parser.py +62 -0
  48. lintro/parsers/hadolint/__init__.py +1 -0
  49. lintro/parsers/hadolint/hadolint_issue.py +24 -0
  50. lintro/parsers/hadolint/hadolint_parser.py +65 -0
  51. lintro/parsers/prettier/__init__.py +0 -0
  52. lintro/parsers/prettier/prettier_issue.py +10 -0
  53. lintro/parsers/prettier/prettier_parser.py +60 -0
  54. lintro/parsers/ruff/__init__.py +1 -0
  55. lintro/parsers/ruff/ruff_issue.py +43 -0
  56. lintro/parsers/ruff/ruff_parser.py +89 -0
  57. lintro/parsers/yamllint/__init__.py +0 -0
  58. lintro/parsers/yamllint/yamllint_issue.py +24 -0
  59. lintro/parsers/yamllint/yamllint_parser.py +68 -0
  60. lintro/tools/__init__.py +40 -0
  61. lintro/tools/core/__init__.py +0 -0
  62. lintro/tools/core/tool_base.py +320 -0
  63. lintro/tools/core/tool_manager.py +167 -0
  64. lintro/tools/implementations/__init__.py +0 -0
  65. lintro/tools/implementations/tool_darglint.py +245 -0
  66. lintro/tools/implementations/tool_hadolint.py +302 -0
  67. lintro/tools/implementations/tool_prettier.py +270 -0
  68. lintro/tools/implementations/tool_ruff.py +618 -0
  69. lintro/tools/implementations/tool_yamllint.py +240 -0
  70. lintro/tools/tool_enum.py +17 -0
  71. lintro/utils/__init__.py +0 -0
  72. lintro/utils/ascii_normalize_cli.py +84 -0
  73. lintro/utils/config.py +39 -0
  74. lintro/utils/console_logger.py +783 -0
  75. lintro/utils/formatting.py +173 -0
  76. lintro/utils/output_manager.py +301 -0
  77. lintro/utils/path_utils.py +41 -0
  78. lintro/utils/tool_executor.py +443 -0
  79. lintro/utils/tool_utils.py +431 -0
  80. lintro-0.3.2.dist-info/METADATA +338 -0
  81. lintro-0.3.2.dist-info/RECORD +85 -0
  82. lintro-0.3.2.dist-info/WHEEL +5 -0
  83. lintro-0.3.2.dist-info/entry_points.txt +2 -0
  84. lintro-0.3.2.dist-info/licenses/LICENSE +21 -0
  85. lintro-0.3.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,270 @@
1
+ """Prettier code formatter integration."""
2
+
3
+ import os
4
+ from dataclasses import dataclass, field
5
+
6
+ from loguru import logger
7
+
8
+ from lintro.enums.tool_type import ToolType
9
+ from lintro.models.core.tool import Tool, ToolConfig, ToolResult
10
+ from lintro.parsers.prettier.prettier_parser import parse_prettier_output
11
+ from lintro.tools.core.tool_base import BaseTool
12
+ from lintro.utils.tool_utils import walk_files_with_excludes
13
+
14
+ # Constants for Prettier configuration
15
+ PRETTIER_DEFAULT_TIMEOUT: int = 30
16
+ PRETTIER_DEFAULT_PRIORITY: int = 80
17
+ PRETTIER_FILE_PATTERNS: list[str] = [
18
+ "*.js",
19
+ "*.jsx",
20
+ "*.ts",
21
+ "*.tsx",
22
+ "*.css",
23
+ "*.scss",
24
+ "*.less",
25
+ "*.html",
26
+ "*.json",
27
+ "*.yaml",
28
+ "*.yml",
29
+ "*.md",
30
+ "*.graphql",
31
+ "*.vue",
32
+ ]
33
+
34
+
35
+ @dataclass
36
+ class PrettierTool(BaseTool):
37
+ """Prettier code formatter integration.
38
+
39
+ A code formatter that supports multiple languages (JavaScript, TypeScript,
40
+ CSS, HTML, etc.).
41
+ """
42
+
43
+ name: str = "prettier"
44
+ description: str = (
45
+ "Code formatter that supports multiple languages (JavaScript, TypeScript, "
46
+ "CSS, HTML, etc.)"
47
+ )
48
+ can_fix: bool = True
49
+ config: ToolConfig = field(
50
+ default_factory=lambda: ToolConfig(
51
+ priority=PRETTIER_DEFAULT_PRIORITY, # High priority
52
+ conflicts_with=[], # No direct conflicts
53
+ file_patterns=PRETTIER_FILE_PATTERNS, # Applies to many file types
54
+ tool_type=ToolType.FORMATTER,
55
+ ),
56
+ )
57
+
58
+ def set_options(
59
+ self,
60
+ exclude_patterns: list[str] | None = None,
61
+ include_venv: bool = False,
62
+ timeout: int | None = None,
63
+ verbose_fix_output: bool | None = None,
64
+ ) -> None:
65
+ """Set options for the core.
66
+
67
+ Args:
68
+ exclude_patterns: List of patterns to exclude
69
+ include_venv: Whether to include virtual environment directories
70
+ timeout: Timeout in seconds per file (default: 30)
71
+ verbose_fix_output: If True, include raw Prettier output in fix()
72
+ """
73
+ self.exclude_patterns = exclude_patterns or []
74
+ self.include_venv = include_venv
75
+ if timeout is not None:
76
+ self.timeout = timeout
77
+ if verbose_fix_output is not None:
78
+ self.options["verbose_fix_output"] = verbose_fix_output
79
+
80
+ def _find_config(self) -> str | None:
81
+ """Locate a Prettier config if none is found by native discovery.
82
+
83
+ Wrapper-first default: rely on Prettier's native discovery via cwd. Only
84
+ return a config path if we later decide to ship a default config and the
85
+ user has no config present. For now, return None to avoid forcing config.
86
+
87
+ Returns:
88
+ str | None: Path to a discovered configuration file, or None if
89
+ no explicit configuration should be enforced.
90
+ """
91
+ return None
92
+
93
+ def check(
94
+ self,
95
+ paths: list[str],
96
+ ) -> ToolResult:
97
+ """Check files with Prettier without making changes.
98
+
99
+ Args:
100
+ paths: List of file or directory paths to check
101
+
102
+ Returns:
103
+ ToolResult instance
104
+ """
105
+ self._validate_paths(paths=paths)
106
+ prettier_files: list[str] = walk_files_with_excludes(
107
+ paths=paths,
108
+ file_patterns=self.config.file_patterns,
109
+ exclude_patterns=self.exclude_patterns,
110
+ include_venv=self.include_venv,
111
+ )
112
+ if not prettier_files:
113
+ return Tool.to_result(
114
+ name=self.name,
115
+ success=True,
116
+ output="No files to check.",
117
+ issues_count=0,
118
+ )
119
+ # Use relative paths and set cwd to the common parent
120
+ cwd: str = self.get_cwd(paths=prettier_files)
121
+ rel_files: list[str] = [
122
+ os.path.relpath(f, cwd) if cwd else f for f in prettier_files
123
+ ]
124
+ # Resolve executable in a manner consistent with other tools
125
+ cmd: list[str] = self._get_executable_command(tool_name="prettier") + [
126
+ "--check",
127
+ ]
128
+ # Do not force config; rely on native discovery via cwd
129
+ cmd.extend(rel_files)
130
+ logger.debug(f"[PrettierTool] Running: {' '.join(cmd)} (cwd={cwd})")
131
+ result = self._run_subprocess(
132
+ cmd=cmd,
133
+ timeout=self.options.get("timeout", self._default_timeout),
134
+ cwd=cwd,
135
+ )
136
+ output: str = result[1]
137
+ # Do not filter lines post-hoc; rely on discovery and ignore files
138
+ issues: list = parse_prettier_output(output=output)
139
+ issues_count: int = len(issues)
140
+ success: bool = issues_count == 0
141
+ # Standardize: suppress Prettier's informational output when no issues
142
+ # so the unified logger prints a single, consistent success line.
143
+ if success:
144
+ output = None
145
+ return Tool.to_result(
146
+ name=self.name,
147
+ success=success,
148
+ output=output,
149
+ issues_count=issues_count,
150
+ )
151
+
152
+ def fix(
153
+ self,
154
+ paths: list[str],
155
+ ) -> ToolResult:
156
+ """Format files with Prettier.
157
+
158
+ Args:
159
+ paths: List of file or directory paths to format
160
+
161
+ Returns:
162
+ ToolResult: Result object with counts and messages.
163
+ """
164
+ self._validate_paths(paths=paths)
165
+ prettier_files: list[str] = walk_files_with_excludes(
166
+ paths=paths,
167
+ file_patterns=self.config.file_patterns,
168
+ exclude_patterns=self.exclude_patterns,
169
+ include_venv=self.include_venv,
170
+ )
171
+ if not prettier_files:
172
+ return Tool.to_result(
173
+ name=self.name,
174
+ success=True,
175
+ output="No files to format.",
176
+ issues_count=0,
177
+ )
178
+
179
+ # First, check for issues before fixing
180
+ cwd: str = self.get_cwd(paths=prettier_files)
181
+ rel_files: list[str] = [
182
+ os.path.relpath(f, cwd) if cwd else f for f in prettier_files
183
+ ]
184
+
185
+ # Check for issues first
186
+ check_cmd: list[str] = self._get_executable_command(tool_name="prettier") + [
187
+ "--check",
188
+ ]
189
+ # Do not force config; rely on native discovery via cwd
190
+ check_cmd.extend(rel_files)
191
+ logger.debug(f"[PrettierTool] Checking: {' '.join(check_cmd)} (cwd={cwd})")
192
+ check_result = self._run_subprocess(
193
+ cmd=check_cmd,
194
+ timeout=self.options.get("timeout", self._default_timeout),
195
+ cwd=cwd,
196
+ )
197
+ check_output: str = check_result[1]
198
+
199
+ # Parse initial issues
200
+ initial_issues: list = parse_prettier_output(output=check_output)
201
+ initial_count: int = len(initial_issues)
202
+
203
+ # Now fix the issues
204
+ fix_cmd: list[str] = self._get_executable_command(tool_name="prettier") + [
205
+ "--write",
206
+ ]
207
+ fix_cmd.extend(rel_files)
208
+ logger.debug(f"[PrettierTool] Fixing: {' '.join(fix_cmd)} (cwd={cwd})")
209
+ fix_result = self._run_subprocess(
210
+ cmd=fix_cmd,
211
+ timeout=self.options.get("timeout", self._default_timeout),
212
+ cwd=cwd,
213
+ )
214
+ fix_output: str = fix_result[1]
215
+
216
+ # Check for remaining issues after fixing
217
+ final_check_result = self._run_subprocess(
218
+ cmd=check_cmd,
219
+ timeout=self.options.get("timeout", self._default_timeout),
220
+ cwd=cwd,
221
+ )
222
+ final_check_output: str = final_check_result[1]
223
+ remaining_issues: list = parse_prettier_output(output=final_check_output)
224
+ remaining_count: int = len(remaining_issues)
225
+
226
+ # Calculate fixed issues
227
+ fixed_count: int = max(0, initial_count - remaining_count)
228
+
229
+ # Build output message
230
+ output_lines: list[str] = []
231
+ if fixed_count > 0:
232
+ output_lines.append(f"Fixed {fixed_count} formatting issue(s)")
233
+
234
+ if remaining_count > 0:
235
+ output_lines.append(
236
+ f"Found {remaining_count} issue(s) that cannot be auto-fixed"
237
+ )
238
+ for issue in remaining_issues[:5]:
239
+ output_lines.append(f" {issue.file} - {issue.message}")
240
+ if len(remaining_issues) > 5:
241
+ output_lines.append(f" ... and {len(remaining_issues) - 5} more")
242
+
243
+ # If there were no initial issues, rely on the logger's unified
244
+ # success line (avoid duplicate "No issues found" lines here)
245
+ elif remaining_count == 0 and fixed_count > 0:
246
+ output_lines.append("All formatting issues were successfully auto-fixed")
247
+
248
+ # Add verbose raw formatting output only when explicitly requested
249
+ if (
250
+ self.options.get("verbose_fix_output", False)
251
+ and fix_output
252
+ and fix_output.strip()
253
+ ):
254
+ output_lines.append(f"Formatting output:\n{fix_output}")
255
+
256
+ final_output: str | None = "\n".join(output_lines) if output_lines else None
257
+
258
+ # Success means no remaining issues
259
+ success: bool = remaining_count == 0
260
+
261
+ return ToolResult(
262
+ name=self.name,
263
+ success=success,
264
+ output=final_output,
265
+ # For fix operations, issues_count represents remaining for summaries
266
+ issues_count=remaining_count,
267
+ initial_issues_count=initial_count,
268
+ fixed_issues_count=fixed_count,
269
+ remaining_issues_count=remaining_count,
270
+ )