Skip to content

Commit

Permalink
Flake8 plugin for linting (#656)
Browse files Browse the repository at this point in the history
  • Loading branch information
youben11 authored and gatesn committed Oct 12, 2019
1 parent e6421d9 commit fecffb2
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 0 deletions.
7 changes: 7 additions & 0 deletions pyls/config/flake8_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
('ignore', 'plugins.pycodestyle.ignore', list),
('max-line-length', 'plugins.pycodestyle.maxLineLength', int),
('select', 'plugins.pycodestyle.select', list),
# flake8
('exclude', 'plugins.flake8.exclude', list),
('filename', 'plugins.flake8.filename', list),
('hang-closing', 'plugins.flake8.hangClosing', bool),
('ignore', 'plugins.flake8.ignore', list),
('max-line-length', 'plugins.flake8.maxLineLength', int),
('select', 'plugins.flake8.select', list),
]


Expand Down
99 changes: 99 additions & 0 deletions pyls/plugins/flake8_lint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Copyright 2019 Palantir Technologies, Inc.
"""Linter pluging for flake8"""
import logging
from flake8.api import legacy as flake8
from pyls import hookimpl, lsp

log = logging.getLogger(__name__)


@hookimpl
def pyls_settings():
# Default flake8 to disabled
return {'plugins': {'flake8': {'enabled': False}}}


@hookimpl
def pyls_lint(config, document):
settings = config.plugin_settings('flake8')
log.debug("Got flake8 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'),
'select': settings.get('select'),
}

# Build the flake8 checker and use it to generate a report from the document
kwargs = {k: v for k, v in opts.items() if v}
style_guide = flake8.get_style_guide(quiet=4, verbose=0, **kwargs)
report = style_guide.check_files([document.path])

return parse_report(document, report)


def parse_report(document, report):
"""
Build a diagnostics from a report, it should extract every result and format
it into a dict that looks like this:
{
'source': 'flake8',
'code': code, # 'E501'
'range': {
'start': {
'line': start_line,
'character': start_column,
},
'end': {
'line': end_line,
'character': end_column,
},
},
'message': msg,
'severity': lsp.DiagnosticSeverity.*,
}
Args:
document: The document to be linted.
report: A Report object returned by checking the document.
Returns:
A list of dictionaries.
"""

file_checkers = report._application.file_checker_manager.checkers
# No file have been checked
if not file_checkers:
return []
# There should be only a filechecker since we are parsing using a path and not a pattern
if len(file_checkers) > 1:
log.error("Flake8 parsed more than a file for '%s'", document.path)

diagnostics = []
file_checker = file_checkers[0]
for error in file_checker.results:
code, line, character, msg, physical_line = error
diagnostics.append(
{
'source': 'flake8',
'code': code,
'range': {
'start': {
'line': line,
'character': character
},
'end': {
'line': line,
# no way to determine the column
'character': len(physical_line)
}
},
'message': msg,
# no way to determine the severity using the legacy api
'severity': lsp.DiagnosticSeverity.Warning,
}
)

return diagnostics
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
extras_require={
'all': [
'autopep8',
'flake8',
'mccabe',
'pycodestyle',
'pydocstyle>=2.0.0',
Expand All @@ -57,6 +58,7 @@
'yapf',
],
'autopep8': ['autopep8'],
'flake8': ['flake8'],
'mccabe': ['mccabe'],
'pycodestyle': ['pycodestyle'],
'pydocstyle': ['pydocstyle>=2.0.0'],
Expand All @@ -76,6 +78,7 @@
],
'pyls': [
'autopep8 = pyls.plugins.autopep8_format',
'flake8 = pyls.plugins.flake8_lint',
'jedi_completion = pyls.plugins.jedi_completion',
'jedi_definition = pyls.plugins.definition',
'jedi_hover = pyls.plugins.hover',
Expand Down
52 changes: 52 additions & 0 deletions test/plugins/test_flake8_lint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copyright 2019 Palantir Technologies, Inc.
import tempfile
import os
from pyls import lsp, uris
from pyls.plugins import flake8_lint
from pyls.workspace import Document

DOC_URI = uris.from_fs_path(__file__)
DOC = """import pyls
t = "TEST"
def using_const():
\ta = 8 + 9
\treturn t
"""


def temp_document(doc_text):
temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False)
name = temp_file.name
temp_file.write(doc_text)
temp_file.close()
doc = Document(uris.from_fs_path(name))

return name, doc


def test_flake8_no_checked_file(config):
# A bad uri or a non-saved file may cause the flake8 linter to do nothing.
# In this situtation, the linter will return an empty list.

doc = Document('', DOC)
diags = flake8_lint.pyls_lint(config, doc)
assert diags == []


def test_flake8_lint(config):
try:
name, doc = temp_document(DOC)
diags = flake8_lint.pyls_lint(config, doc)
msg = 'local variable \'a\' is assigned to but never used'
unused_var = [d for d in diags if d['message'] == msg][0]

assert unused_var['source'] == 'flake8'
assert unused_var['code'] == 'F841'
assert unused_var['range']['start'] == {'line': 6, 'character': 1}
assert unused_var['range']['end'] == {'line': 6, 'character': 11}
assert unused_var['severity'] == lsp.DiagnosticSeverity.Warning

finally:
os.remove(name)

0 comments on commit fecffb2

Please sign in to comment.