113 lines
3.9 KiB
Python
113 lines
3.9 KiB
Python
# Copyright 2017-2020 Palantir Technologies, Inc.
|
|
# Copyright 2021- Python Language Server Contributors.
|
|
|
|
import logging
|
|
|
|
import pycodestyle
|
|
|
|
from pylsp import hookimpl, lsp
|
|
from pylsp._utils import get_eol_chars
|
|
|
|
try:
|
|
from autopep8 import continued_indentation as autopep8_c_i
|
|
except ImportError:
|
|
pass
|
|
else:
|
|
# Check if autopep8's continued_indentation implementation
|
|
# is overriding pycodestyle's and if so, re-register
|
|
# the check using pycodestyle's implementation as expected
|
|
if autopep8_c_i in pycodestyle._checks["logical_line"]:
|
|
del pycodestyle._checks["logical_line"][autopep8_c_i]
|
|
pycodestyle.register_check(pycodestyle.continued_indentation)
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
@hookimpl
|
|
def pylsp_lint(workspace, document):
|
|
with workspace.report_progress("lint: pycodestyle"):
|
|
config = workspace._config
|
|
settings = config.plugin_settings("pycodestyle", document_path=document.path)
|
|
log.debug("Got pycodestyle settings: %s", settings)
|
|
|
|
opts = {
|
|
"exclude": settings.get("exclude"),
|
|
"filename": settings.get("filename"),
|
|
"hang_closing": settings.get("hangClosing"),
|
|
"ignore": settings.get("ignore"),
|
|
"max_line_length": settings.get("maxLineLength"),
|
|
"indent_size": settings.get("indentSize"),
|
|
"select": settings.get("select"),
|
|
}
|
|
kwargs = {k: v for k, v in opts.items() if v}
|
|
styleguide = pycodestyle.StyleGuide(kwargs)
|
|
|
|
# Use LF to lint file because other line endings can give false positives.
|
|
# See spyder-ide/spyder#19565 for context.
|
|
source = document.source
|
|
eol_chars = get_eol_chars(source)
|
|
if eol_chars in ["\r", "\r\n"]:
|
|
source = source.replace(eol_chars, "\n")
|
|
lines = source.splitlines(keepends=True)
|
|
else:
|
|
lines = document.lines
|
|
|
|
c = pycodestyle.Checker(
|
|
filename=document.path,
|
|
lines=lines,
|
|
options=styleguide.options,
|
|
report=PyCodeStyleDiagnosticReport(styleguide.options),
|
|
)
|
|
c.check_all()
|
|
diagnostics = c.report.diagnostics
|
|
|
|
return diagnostics
|
|
|
|
|
|
class PyCodeStyleDiagnosticReport(pycodestyle.BaseReport):
|
|
def __init__(self, options) -> None:
|
|
self.diagnostics = []
|
|
super().__init__(options=options)
|
|
|
|
def error(self, line_number, offset, text, check):
|
|
code = text[:4]
|
|
if self._ignore_code(code):
|
|
return
|
|
|
|
# Don't care about expected errors or warnings
|
|
if code in self.expected:
|
|
return
|
|
|
|
# PyCodeStyle will sometimes give you an error the line after the end of the file
|
|
# e.g. no newline at end of file
|
|
# In that case, the end offset should just be some number ~100
|
|
# (because why not? There's nothing to underline anyways)
|
|
err_range = {
|
|
"start": {"line": line_number - 1, "character": offset},
|
|
"end": {
|
|
# FIXME: It's a little naiive to mark until the end of the line, can we not easily do better?
|
|
"line": line_number - 1,
|
|
"character": 100
|
|
if line_number > len(self.lines)
|
|
else len(self.lines[line_number - 1]),
|
|
},
|
|
}
|
|
diagnostic = {
|
|
"source": "pycodestyle",
|
|
"range": err_range,
|
|
"message": text,
|
|
"code": code,
|
|
# Are style errors really ever errors?
|
|
"severity": _get_severity(code),
|
|
}
|
|
if code.startswith("W6"):
|
|
diagnostic["tags"] = [lsp.DiagnosticTag.Deprecated]
|
|
self.diagnostics.append(diagnostic)
|
|
|
|
|
|
def _get_severity(code):
|
|
# Are style errors ever really errors?
|
|
if code[0] == "E" or code[0] == "W":
|
|
return lsp.DiagnosticSeverity.Warning
|
|
# If no severity is specified, why wouldn't this be informational only?
|
|
return lsp.DiagnosticSeverity.Information
|