1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
import dataclasses
from typing import List, Optional, Callable, Counter
from lsprotocol.types import Position, Range, Diagnostic, DiagnosticSeverity
from debputy.commands.debputy_cmd.output import OutputStylingBase
from debputy.util import _DEFAULT_LOGGER, _warn
LinterImpl = Callable[
[str, str, List[str], "LintCapablePositionCodec"], Optional[List[Diagnostic]]
]
@dataclasses.dataclass(slots=True)
class LintReport:
diagnostics_count: Counter[DiagnosticSeverity] = dataclasses.field(
default_factory=Counter
)
diagnostics_without_severity: int = 0
diagnostic_errors: int = 0
fixed: int = 0
fixable: int = 0
class LinterPositionCodec:
def client_num_units(self, chars: str):
return len(chars)
def position_from_client_units(
self, lines: List[str], position: Position
) -> Position:
if len(lines) == 0:
return Position(0, 0)
if position.line >= len(lines):
return Position(len(lines) - 1, self.client_num_units(lines[-1]))
return position
def position_to_client_units(
self, _lines: List[str], position: Position
) -> Position:
return position
def range_from_client_units(self, _lines: List[str], range: Range) -> Range:
return range
def range_to_client_units(self, _lines: List[str], range: Range) -> Range:
return range
LINTER_POSITION_CODEC = LinterPositionCodec()
_SEVERITY2TAG = {
DiagnosticSeverity.Error: lambda fo: fo.colored(
"error",
fg="red",
bg="black",
style="bold",
),
DiagnosticSeverity.Warning: lambda fo: fo.colored(
"warning",
fg="yellow",
bg="black",
style="bold",
),
DiagnosticSeverity.Information: lambda fo: fo.colored(
"informational",
fg="blue",
bg="black",
style="bold",
),
DiagnosticSeverity.Hint: lambda fo: fo.colored(
"pedantic",
fg="green",
bg="black",
style="bold",
),
}
def _lines_to_print(range_: Range) -> int:
count = range_.end.line - range_.start.line
if range_.end.character > 0:
count += 1
return count
def _highlight_range(
fo: OutputStylingBase, line: str, line_no: int, range_: Range
) -> str:
line_wo_nl = line.rstrip("\r\n")
start_pos = 0
prefix = ""
suffix = ""
if line_no == range_.start.line:
start_pos = range_.start.character
prefix = line_wo_nl[0:start_pos]
if line_no == range_.end.line:
end_pos = range_.end.character
suffix = line_wo_nl[end_pos:]
else:
end_pos = len(line_wo_nl)
marked_part = fo.colored(line_wo_nl[start_pos:end_pos], fg="red", style="bold")
return prefix + marked_part + suffix
def report_diagnostic(
fo: OutputStylingBase,
filename: str,
diagnostic: Diagnostic,
lines: List[str],
auto_fixable: bool,
auto_fixed: bool,
lint_report: LintReport,
) -> None:
logger = _DEFAULT_LOGGER
assert logger is not None
severity = diagnostic.severity
missing_severity = False
if severity is None:
severity = DiagnosticSeverity.Warning
missing_severity = True
if not auto_fixed:
tag_unresolved = _SEVERITY2TAG.get(severity)
if tag_unresolved is None:
tag_unresolved = _SEVERITY2TAG[DiagnosticSeverity.Warning]
lint_report.diagnostics_without_severity += 1
else:
lint_report.diagnostics_count[severity] += 1
tag = tag_unresolved(fo)
else:
tag = fo.colored(
"auto-fixing",
fg="magenta",
bg="black",
style="bold",
)
start_line = diagnostic.range.start.line
start_position = diagnostic.range.start.character
end_line = diagnostic.range.end.line
end_position = diagnostic.range.end.character
has_fixit = ""
line_no_width = len(str(len(lines)))
if not auto_fixed and auto_fixable:
has_fixit = " [Correctable via --auto-fix]"
lint_report.fixable += 1
print(
f"{tag}: File: {filename}:{start_line+1}:{start_position}:{end_line+1}:{end_position}: {diagnostic.message}{has_fixit}",
)
if missing_severity:
_warn(
" This warning did not have an explicit severity; Used Warning as a fallback!"
)
if auto_fixed:
# If it is fixed, there is no reason to show additional context.
lint_report.fixed += 1
return
lines_to_print = _lines_to_print(diagnostic.range)
if diagnostic.range.end.line >= len(lines) or diagnostic.range.start.line < 1:
lint_report.diagnostic_errors += 1
_warn(
"Bug in the underlying linter: The line numbers of the warning does not fit in the file..."
)
return
if lines_to_print == 1:
line = _highlight_range(fo, lines[start_line], start_line, diagnostic.range)
print(f" {start_line+1:{line_no_width}}: {line}")
else:
for line_no in range(start_line, end_line):
line = _highlight_range(fo, lines[line_no], line_no, diagnostic.range)
print(f" {line_no+1:{line_no_width}}: {line}")
|