From 7a1eca134bca3232346504b1f9ce820cee2934b2 Mon Sep 17 00:00:00 2001 From: Nicholas Gates Date: Fri, 22 Dec 2017 15:07:38 +0000 Subject: [PATCH] Catch and ignore parser errors for pydocstyle linting (#208) * Catch and ignore parser errors for pydocstyle linting * Fix pydocstyle empty file * Fix linting --- pyls/config/source.py | 2 +- pyls/plugins/mccabe_lint.py | 2 +- pyls/plugins/pydocstyle_lint.py | 51 ++++++++++++++------- pyls/plugins/rope_completion.py | 66 ++++++++++++++-------------- pyls/plugins/symbols.py | 4 +- pyls/server.py | 2 + test/plugins/test_pydocstyle_lint.py | 28 ++++++++++-- 7 files changed, 98 insertions(+), 57 deletions(-) diff --git a/pyls/config/source.py b/pyls/config/source.py index bcf64d52..4b6dc4ec 100644 --- a/pyls/config/source.py +++ b/pyls/config/source.py @@ -75,7 +75,7 @@ def _get_opt(config, key, option, opt_type): if opt_type == list: return _parse_list_opt(config.get(key, opt_key)) - raise ValueError("Unknown option type: %s", opt_type) + raise ValueError("Unknown option type: %s" % opt_type) def _parse_list_opt(string): diff --git a/pyls/plugins/mccabe_lint.py b/pyls/plugins/mccabe_lint.py index 6a532991..46e3ee72 100644 --- a/pyls/plugins/mccabe_lint.py +++ b/pyls/plugins/mccabe_lint.py @@ -19,7 +19,7 @@ def pyls_lint(config, document): tree = compile(document.source, document.path, "exec", ast.PyCF_ONLY_AST) except SyntaxError: # We'll let the other linters point this one out - return + return None visitor = mccabe.PathGraphingAstVisitor() visitor.preorder(tree, visitor) diff --git a/pyls/plugins/pydocstyle_lint.py b/pyls/plugins/pydocstyle_lint.py index 33c6c7f9..d8d396a1 100644 --- a/pyls/plugins/pydocstyle_lint.py +++ b/pyls/plugins/pydocstyle_lint.py @@ -29,22 +29,39 @@ def pyls_lint(document): document.source, filename, ignore_decorators=ignore_decorators ) - for err in errors: - if err.code not in checked_codes: - continue - - lineno = err.definition.start - 1 - line = document.lines[lineno] - character = len(line) - len(line.lstrip()) - diags.append({ - 'source': 'pydocstyle', - 'code': err.code, - 'message': err.message, - 'severity': lsp.DiagnosticSeverity.Warning, - 'range': { - 'start': {'line': lineno, 'character': character}, - 'end': {'line': lineno, 'character': len(document.lines[lineno])} - } - }) + try: + for error in errors: + if error.code not in checked_codes: + continue + diags.append(_parse_diagnostic(document, error)) + except pydocstyle.parser.ParseError: + # In the case we cannot parse the Python file, just continue + pass return diags + + +def _parse_diagnostic(document, error): + log.info("Got error: %s", error) + lineno = error.definition.start - 1 + line = document.lines[0] if document.lines else "" + + start_character = len(line) - len(line.lstrip()) + end_character = len(line) + + return { + 'source': 'pydocstyle', + 'code': error.code, + 'message': error.message, + 'severity': lsp.DiagnosticSeverity.Warning, + 'range': { + 'start': { + 'line': lineno, + 'character': start_character + }, + 'end': { + 'line': lineno, + 'character': end_character + } + } + } diff --git a/pyls/plugins/rope_completion.py b/pyls/plugins/rope_completion.py index d67040ca..e3b17dcf 100644 --- a/pyls/plugins/rope_completion.py +++ b/pyls/plugins/rope_completion.py @@ -1,10 +1,10 @@ # Copyright 2017 Palantir Technologies, Inc. import logging -from pyls.lsp import CompletionItemKind -from pyls import hookimpl - from rope.contrib.codeassist import code_assist, sorted_proposals +from pyls import hookimpl, lsp + + log = logging.getLogger(__name__) @@ -62,36 +62,36 @@ def _sort_text(definition): def _kind(d): """ Return the VSCode type """ MAP = { - 'none': CompletionItemKind.Value, - 'type': CompletionItemKind.Class, - 'tuple': CompletionItemKind.Class, - 'dict': CompletionItemKind.Class, - 'dictionary': CompletionItemKind.Class, - 'function': CompletionItemKind.Function, - 'lambda': CompletionItemKind.Function, - 'generator': CompletionItemKind.Function, - 'class': CompletionItemKind.Class, - 'instance': CompletionItemKind.Reference, - 'method': CompletionItemKind.Method, - 'builtin': CompletionItemKind.Class, - 'builtinfunction': CompletionItemKind.Function, - 'module': CompletionItemKind.Module, - 'file': CompletionItemKind.File, - 'xrange': CompletionItemKind.Class, - 'slice': CompletionItemKind.Class, - 'traceback': CompletionItemKind.Class, - 'frame': CompletionItemKind.Class, - 'buffer': CompletionItemKind.Class, - 'dictproxy': CompletionItemKind.Class, - 'funcdef': CompletionItemKind.Function, - 'property': CompletionItemKind.Property, - 'import': CompletionItemKind.Module, - 'keyword': CompletionItemKind.Keyword, - 'constant': CompletionItemKind.Variable, - 'variable': CompletionItemKind.Variable, - 'value': CompletionItemKind.Value, - 'param': CompletionItemKind.Variable, - 'statement': CompletionItemKind.Keyword, + 'none': lsp.CompletionItemKind.Value, + 'type': lsp.CompletionItemKind.Class, + 'tuple': lsp.CompletionItemKind.Class, + 'dict': lsp.CompletionItemKind.Class, + 'dictionary': lsp.CompletionItemKind.Class, + 'function': lsp.CompletionItemKind.Function, + 'lambda': lsp.CompletionItemKind.Function, + 'generator': lsp.CompletionItemKind.Function, + 'class': lsp.CompletionItemKind.Class, + 'instance': lsp.CompletionItemKind.Reference, + 'method': lsp.CompletionItemKind.Method, + 'builtin': lsp.CompletionItemKind.Class, + 'builtinfunction': lsp.CompletionItemKind.Function, + 'module': lsp.CompletionItemKind.Module, + 'file': lsp.CompletionItemKind.File, + 'xrange': lsp.CompletionItemKind.Class, + 'slice': lsp.CompletionItemKind.Class, + 'traceback': lsp.CompletionItemKind.Class, + 'frame': lsp.CompletionItemKind.Class, + 'buffer': lsp.CompletionItemKind.Class, + 'dictproxy': lsp.CompletionItemKind.Class, + 'funcdef': lsp.CompletionItemKind.Function, + 'property': lsp.CompletionItemKind.Property, + 'import': lsp.CompletionItemKind.Module, + 'keyword': lsp.CompletionItemKind.Keyword, + 'constant': lsp.CompletionItemKind.Variable, + 'variable': lsp.CompletionItemKind.Variable, + 'value': lsp.CompletionItemKind.Value, + 'param': lsp.CompletionItemKind.Variable, + 'statement': lsp.CompletionItemKind.Keyword, } return MAP.get(d.type) diff --git a/pyls/plugins/symbols.py b/pyls/plugins/symbols.py index c0f9747a..6de90ccf 100644 --- a/pyls/plugins/symbols.py +++ b/pyls/plugins/symbols.py @@ -40,7 +40,9 @@ def _container(definition): if parent.parent(): return parent.name except: # pylint: disable=bare-except - pass + return None + + return None def _range(definition): diff --git a/pyls/server.py b/pyls/server.py index 5b1ca6ac..fb7069c5 100644 --- a/pyls/server.py +++ b/pyls/server.py @@ -123,3 +123,5 @@ def _content_length(line): return int(value) except ValueError: raise ValueError("Invalid Content-Length header: {}".format(value)) + + return None diff --git a/test/plugins/test_pydocstyle_lint.py b/test/plugins/test_pydocstyle_lint.py index 40d03e0b..983a04ea 100644 --- a/test/plugins/test_pydocstyle_lint.py +++ b/test/plugins/test_pydocstyle_lint.py @@ -1,5 +1,5 @@ # Copyright 2017 Palantir Technologies, Inc. -from pyls import uris +from pyls import lsp, uris from pyls.workspace import Document from pyls.plugins import pydocstyle_lint @@ -20,7 +20,27 @@ def test_pydocstyle(): assert all([d['source'] == 'pydocstyle' for d in diags]) # One we're expecting is: - msg = 'D100: Missing docstring in public module' - unused_import = [d for d in diags if d['message'] == msg][0] + assert diags[0] == { + 'code': 'D100', + 'message': 'D100: Missing docstring in public module', + 'severity': lsp.DiagnosticSeverity.Warning, + 'range': { + 'start': {'line': 0, 'character': 0}, + 'end': {'line': 0, 'character': 11}, + }, + 'source': 'pydocstyle' + } + + +def test_pydocstyle_empty_source(): + doc = Document(DOC_URI, "") + diags = pydocstyle_lint.pyls_lint(doc) + assert diags[0]['message'] == 'D100: Missing docstring in public module' + assert len(diags) == 1 + - assert unused_import['range']['start'] == {'line': 0, 'character': 0} +def test_pydocstyle_invalid_source(): + doc = Document(DOC_URI, "bad syntax") + diags = pydocstyle_lint.pyls_lint(doc) + # We're unable to parse the file, so can't get any pydocstyle diagnostics + assert not diags