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
|
import collections
from typing import List, Optional, Mapping, Any, Callable, Sequence
import pytest
from debputy.filesystem_scan import VirtualPathBase
from debputy.linting.lint_util import (
LinterPositionCodec,
LintStateImpl,
LintState,
)
from debputy.lsp.maint_prefs import (
MaintainerPreferenceTable,
EffectiveFormattingPreference,
)
from debputy.packages import DctrlParser
from debputy.plugin.api.feature_set import PluginProvidedFeatureSet
from debputy.lsprotocol.types import Diagnostic, DiagnosticSeverity, Range
try:
from Levenshtein import distance
HAS_LEVENSHTEIN = True
except ImportError:
HAS_LEVENSHTEIN = False
LINTER_POSITION_CODEC = LinterPositionCodec()
class LintWrapper:
def __init__(
self,
path: str,
handler: Callable[[LintState], Optional[List[Diagnostic]]],
debputy_plugin_feature_set: PluginProvidedFeatureSet,
dctrl_parser: DctrlParser,
) -> None:
self._debputy_plugin_feature_set = debputy_plugin_feature_set
self._handler = handler
self.dctrl_lines: Optional[List[str]] = None
self.path = path
self._dctrl_parser = dctrl_parser
self.source_root: Optional[VirtualPathBase] = None
self.lint_maint_preference_table = MaintainerPreferenceTable({}, {})
self.effective_preference: Optional[EffectiveFormattingPreference] = None
def __call__(self, lines: List[str]) -> List["Diagnostic"]:
source_package = None
binary_packages = None
dctrl_lines = self.dctrl_lines
if dctrl_lines is not None:
_, source_package, binary_packages = (
self._dctrl_parser.parse_source_debian_control(
dctrl_lines, ignore_errors=True
)
)
source_root = self.source_root
debian_dir = source_root.get("debian") if source_root is not None else None
state = LintStateImpl(
self._debputy_plugin_feature_set,
self.lint_maint_preference_table,
source_root,
debian_dir,
self.path,
"".join(dctrl_lines) if dctrl_lines is not None else "",
lines,
source_package,
binary_packages,
self.effective_preference,
self._handler,
)
return check_diagnostics(state.gather_diagnostics())
def requires_levenshtein(func: Any) -> Any:
return pytest.mark.skipif(
not HAS_LEVENSHTEIN, reason="Missing python3-levenshtein"
)(func)
def check_diagnostics(
diagnostics: Optional[List["Diagnostic"]],
) -> List["Diagnostic"]:
if diagnostics:
for diagnostic in diagnostics:
assert diagnostic.severity is not None
assert diagnostic.source is not None
elif diagnostics is None:
diagnostics = []
return diagnostics
def by_range_sort_key(diagnostic: Diagnostic) -> Any:
start_pos = diagnostic.range.start
end_pos = diagnostic.range.end
return start_pos.line, start_pos.character, end_pos.line, end_pos.character
def group_diagnostics_by_severity(
diagnostics: Optional[List["Diagnostic"]],
) -> Mapping["DiagnosticSeverity", List["Diagnostic"]]:
if not diagnostics:
return {}
by_severity = collections.defaultdict(list)
for diagnostic in sorted(diagnostics, key=by_range_sort_key):
severity = diagnostic.severity
assert severity is not None
by_severity[severity].append(diagnostic)
return by_severity
def diag_range_to_text(lines: Sequence[str], range_: "Range") -> str:
parts = []
for line_no in range(range_.start.line, range_.end.line + 1):
line = lines[line_no]
chunk = line
if line_no == range_.start.line and line_no == range_.end.line:
chunk = line[range_.start.character : range_.end.character]
elif line_no == range_.start.line:
chunk = line[range_.start.character :]
elif line_no == range_.end.line:
chunk = line[: range_.end.character]
parts.append(chunk)
return "".join(parts)
|