File: test_pylint_lint.py

package info (click to toggle)
python-lsp-server 1.12.0-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 796 kB
  • sloc: python: 7,791; sh: 12; makefile: 4
file content (139 lines) | stat: -rw-r--r-- 5,044 bytes parent folder | download
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
# Copyright 2018 Google LLC.
# Copyright 2017-2020 Palantir Technologies, Inc.
# Copyright 2021- Python Language Server Contributors.

import contextlib
import os
import tempfile
from pathlib import Path

from pylsp import lsp, uris
from pylsp.plugins import pylint_lint
from pylsp.workspace import Document, Workspace

DOC_URI = uris.from_fs_path(__file__)
DOC = """import sys

def hello():
\tpass

import json
"""

DOC_SYNTAX_ERR = """def hello()
    pass
"""


@contextlib.contextmanager
def temp_document(doc_text, workspace) -> None:
    try:
        with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
            name = temp_file.name
            temp_file.write(doc_text)
        yield Document(uris.from_fs_path(name), workspace)
    finally:
        os.remove(name)


def write_temp_doc(document, contents) -> None:
    with open(document.path, "w", encoding="utf-8") as temp_file:
        temp_file.write(contents)


def test_pylint(config, workspace) -> None:
    with temp_document(DOC, workspace) as doc:
        diags = pylint_lint.pylsp_lint(config, workspace, doc, True)

        msg = "[unused-import] Unused import sys"
        unused_import = [d for d in diags if d["message"] == msg][0]

        assert unused_import["range"]["start"] == {"line": 0, "character": 0}
        assert unused_import["severity"] == lsp.DiagnosticSeverity.Warning
        assert unused_import["tags"] == [lsp.DiagnosticTag.Unnecessary]

        # test running pylint in stdin
        config.plugin_settings("pylint")["executable"] = "pylint"
        diags = pylint_lint.pylsp_lint(config, workspace, doc, True)

        msg = "Unused import sys (unused-import)"
        unused_import = [d for d in diags if d["message"] == msg][0]

        assert unused_import["range"]["start"] == {
            "line": 0,
            "character": 0,
        }
        assert unused_import["severity"] == lsp.DiagnosticSeverity.Warning


def test_syntax_error_pylint(config, workspace) -> None:
    with temp_document(DOC_SYNTAX_ERR, workspace) as doc:
        diag = pylint_lint.pylsp_lint(config, workspace, doc, True)[0]

        assert diag["message"].startswith("[syntax-error]")
        assert diag["message"].count("expected ':'") or diag["message"].count(
            "invalid syntax"
        )
        # Pylint doesn't give column numbers for invalid syntax.
        assert diag["range"]["start"] == {"line": 0, "character": 12}
        assert diag["severity"] == lsp.DiagnosticSeverity.Error
        assert "tags" not in diag

        # test running pylint in stdin
        config.plugin_settings("pylint")["executable"] = "pylint"
        diag = pylint_lint.pylsp_lint(config, workspace, doc, True)[0]

        assert diag["message"].count("expected ':'") or diag["message"].count(
            "invalid syntax"
        )
        # Pylint doesn't give column numbers for invalid syntax.
        assert diag["range"]["start"] == {"line": 0, "character": 12}
        assert diag["severity"] == lsp.DiagnosticSeverity.Error


def test_lint_free_pylint(config, workspace) -> None:
    # Can't use temp_document because it might give us a file that doesn't
    # match pylint's naming requirements. We should be keeping this file clean
    # though, so it works for a test of an empty lint.
    ws = Workspace(str(Path(__file__).absolute().parents[2]), workspace._endpoint)
    assert not pylint_lint.pylsp_lint(
        config, ws, Document(uris.from_fs_path(__file__), ws), True
    )


def test_lint_caching(workspace) -> None:
    # Pylint can only operate on files, not in-memory contents. We cache the
    # diagnostics after a run so we can continue displaying them until the file
    # is saved again.
    #
    # We use PylintLinter.lint directly here rather than pylsp_lint so we can
    # pass --disable=invalid-name to pylint, since we want a temporary file but
    # need to ensure that pylint doesn't give us invalid-name when our temp
    # file has capital letters in its name.

    flags = "--disable=invalid-name"
    with temp_document(DOC, workspace) as doc:
        # Start with a file with errors.
        diags = pylint_lint.PylintLinter.lint(doc, True, flags)
        assert diags

        # Fix lint errors and write the changes to disk. Run the linter in the
        # in-memory mode to check the cached diagnostic behavior.
        write_temp_doc(doc, "")
        assert pylint_lint.PylintLinter.lint(doc, False, flags) == diags

        # Now check the on-disk behavior.
        assert not pylint_lint.PylintLinter.lint(doc, True, flags)

        # Make sure the cache was properly cleared.
        assert not pylint_lint.PylintLinter.lint(doc, False, flags)


def test_per_file_caching(config, workspace) -> None:
    # Ensure that diagnostics are cached per-file.
    with temp_document(DOC, workspace) as doc:
        assert pylint_lint.pylsp_lint(config, workspace, doc, True)

    assert not pylint_lint.pylsp_lint(
        config, workspace, Document(uris.from_fs_path(__file__), workspace), False
    )