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
|
import dataclasses
from bisect import bisect_left, bisect_right
from collections.abc import Mapping
from typing import (
TypedDict,
NotRequired,
List,
Any,
Literal,
Optional,
TYPE_CHECKING,
get_args,
FrozenSet,
cast,
Tuple,
TypeVar,
)
from collections.abc import Sequence
if TYPE_CHECKING:
import lsprotocol.types as types
else:
import debputy.lsprotocol.types as types
# These are in order of severity (most important to least important).
#
# Special cases:
# - "spelling" is a specialized version of "pedantic" for textual spelling mistakes
# (LSP uses the same severity for both; only `debputy lint` shows a difference
# between them)
#
LintSeverity = Literal["error", "warning", "informational", "pedantic", "spelling"]
LINT_SEVERITY2LSP_SEVERITY: Mapping[LintSeverity, types.DiagnosticSeverity] = {
"error": types.DiagnosticSeverity.Error,
"warning": types.DiagnosticSeverity.Warning,
"informational": types.DiagnosticSeverity.Information,
"pedantic": types.DiagnosticSeverity.Hint,
"spelling": types.DiagnosticSeverity.Hint,
}
NATIVELY_LSP_SUPPORTED_SEVERITIES: frozenset[LintSeverity] = cast(
"FrozenSet[LintSeverity]",
frozenset(
{
"error",
"warning",
"informational",
"pedantic",
}
),
)
_delta = set(get_args(LintSeverity)).symmetric_difference(
LINT_SEVERITY2LSP_SEVERITY.keys()
)
assert (
not _delta
), f"LintSeverity and LINT_SEVERITY2LSP_SEVERITY are not aligned. Delta: {_delta}"
del _delta
class DiagnosticData(TypedDict):
quickfixes: NotRequired[list[Any] | None]
lint_severity: NotRequired[LintSeverity | None]
report_for_related_file: NotRequired[str]
enable_non_interactive_auto_fix: bool
@dataclasses.dataclass(slots=True)
class DiagnosticReport:
doc_uri: str
doc_version: int
diagnostic_report_id: str
is_in_progress: bool
diagnostics: list[types.Diagnostic]
_diagnostic_range_helper: Optional["DiagnosticRangeHelper"] = None
def diagnostics_in_range(self, text_range: types.Range) -> list[types.Diagnostic]:
if not self.diagnostics:
return []
helper = self._diagnostic_range_helper
if helper is None:
helper = DiagnosticRangeHelper(self.diagnostics)
self._diagnostic_range_helper = helper
return helper.diagnostics_in_range(text_range)
def _pos_as_tuple(pos: types.Position) -> tuple[int, int]:
return pos.line, pos.character
class DiagnosticRangeHelper:
__slots__ = ("diagnostics", "by_start_index", "by_end_index")
def __init__(self, diagnostics: list[types.Diagnostic]) -> None:
self.diagnostics = diagnostics
self.by_start_index = sorted(
(
(_pos_as_tuple(diagnostics[i].range.start), i)
for i in range(len(diagnostics))
),
)
self.by_end_index = sorted(
(
(_pos_as_tuple(diagnostics[i].range.end), i)
for i in range(len(diagnostics))
),
)
def diagnostics_in_range(self, text_range: types.Range) -> list[types.Diagnostic]:
start_pos = _pos_as_tuple(text_range.start)
end_pos = _pos_as_tuple(text_range.end)
try:
lower_index_limit = _find_gt(
self.by_end_index,
start_pos,
key=lambda t: t[0],
)[1]
except NoSuchElementError:
lower_index_limit = len(self.diagnostics)
try:
upper_index_limit = _find_lt(
self.by_start_index,
end_pos,
key=lambda t: t[0],
)[1]
upper_index_limit += 1
except NoSuchElementError:
upper_index_limit = 0
return self.diagnostics[lower_index_limit:upper_index_limit]
T = TypeVar("T")
class NoSuchElementError(ValueError):
pass
def _find_lt(a: Sequence[Any], x: Any, *, key: Any = None):
"Find rightmost value less than x"
i = bisect_left(a, x, key=key)
if i:
return a[i - 1]
raise NoSuchElementError
def _find_gt(a: Sequence[Any], x: Any, *, key: Any = None):
"Find leftmost value greater than x"
i = bisect_right(a, x, key=key)
if i != len(a):
return a[i]
raise NoSuchElementError
|