summaryrefslogtreecommitdiffstats
path: root/src/debputy/lsp/lsp_debian_debputy_manifest.py
blob: 97fffccab9d20737bd872e2acac248803a5f4a3a (plain)
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
import re
from typing import (
    Optional,
    List,
)

from lsprotocol.types import (
    Diagnostic,
    TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL,
    Position,
    Range,
    DiagnosticSeverity,
)
from ruamel.yaml.error import MarkedYAMLError, YAMLError

from debputy.highlevel_manifest import MANIFEST_YAML
from debputy.lsp.lsp_features import (
    lint_diagnostics,
    lsp_standard_handler,
)
from debputy.lsp.text_util import (
    LintCapablePositionCodec,
)

try:
    from pygls.server import LanguageServer
except ImportError:
    pass


_CONTAINS_TAB_OR_COLON = re.compile(r"[\t:]")
_WORDS_RE = re.compile("([a-zA-Z0-9_-]+)")
_MAKE_ERROR_RE = re.compile(r"^[^:]+:(\d+):\s*(\S.+)")


_LANGUAGE_IDS = [
    "debian/debputy.manifest",
    "debputy.manifest",
    # LSP's official language ID for YAML files
    "yaml",
]


# lsp_standard_handler(_LANGUAGE_IDS, TEXT_DOCUMENT_CODE_ACTION)
lsp_standard_handler(_LANGUAGE_IDS, TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL)


def is_valid_file(path: str) -> bool:
    # For debian/debputy.manifest, the language ID is often set to makefile meaning we get random
    # "non-debian/debputy.manifest" YAML files here. Skip those.
    return path.endswith("debian/debputy.manifest")


def _word_range_at_position(
    lines: List[str],
    line_no: int,
    char_offset: int,
) -> Range:
    line = lines[line_no]
    line_len = len(line)
    start_idx = char_offset
    end_idx = char_offset
    while end_idx + 1 < line_len and not line[end_idx + 1].isspace():
        end_idx += 1

    while start_idx - 1 >= 0 and not line[start_idx - 1].isspace():
        start_idx -= 1

    return Range(
        Position(line_no, start_idx),
        Position(line_no, end_idx),
    )


@lint_diagnostics(_LANGUAGE_IDS)
def _lint_debian_debputy_manifest(
    _doc_reference: str,
    path: str,
    lines: List[str],
    position_codec: LintCapablePositionCodec,
) -> Optional[List[Diagnostic]]:
    if not is_valid_file(path):
        return None
    diagnostics = []
    try:
        MANIFEST_YAML.load("".join(lines))
    except MarkedYAMLError as e:
        error_range = position_codec.range_to_client_units(
            lines,
            _word_range_at_position(
                lines,
                e.problem_mark.line,
                e.problem_mark.column,
            ),
        )
        diagnostics.append(
            Diagnostic(
                error_range,
                f"YAML parse error: {e}",
                DiagnosticSeverity.Error,
            ),
        )
    except YAMLError as e:
        error_range = position_codec.range_to_client_units(
            lines,
            Range(
                Position(0, 0),
                Position(0, len(lines[0])),
            ),
        )
        diagnostics.append(
            Diagnostic(
                error_range,
                f"Unknown YAML parse error: {e} [{e!r}]",
                DiagnosticSeverity.Error,
            ),
        )

    return diagnostics