11# Copyright 2019 Palantir Technologies, Inc.
22"""Linter pluging for flake8"""
33import logging
4- from flake8 .api import legacy as flake8
4+ import re
5+ from subprocess import Popen , PIPE
56from pyls import hookimpl , lsp
67
78log = logging .getLogger (__name__ )
@@ -21,23 +22,64 @@ def pyls_lint(config, document):
2122 opts = {
2223 'exclude' : settings .get ('exclude' ),
2324 'filename' : settings .get ('filename' ),
24- 'hang_closing ' : settings .get ('hangClosing' ),
25+ 'hang-closing ' : settings .get ('hangClosing' ),
2526 'ignore' : settings .get ('ignore' ),
26- 'max_line_length ' : settings .get ('maxLineLength' ),
27+ 'max-line-length ' : settings .get ('maxLineLength' ),
2728 'select' : settings .get ('select' ),
2829 }
2930
30- # Build the flake8 checker and use it to generate a report from the document
31- kwargs = { k : v for k , v in opts . items () if v }
32- style_guide = flake8 . get_style_guide ( quiet = 4 , verbose = 0 , ** kwargs )
33- report = style_guide . check_files ([ document . path ] )
31+ # Call the flake8 utility then parse diagnostics from stdout
32+ args = build_args ( opts , document . path )
33+ output = run_flake8 ( args )
34+ return parse_stdout ( document , output )
3435
35- return parse_report (document , report )
3636
37+ def run_flake8 (args ):
38+ """Run flake8 with the provided arguments, logs errors
39+ from stderr if any.
40+ """
41+ log .debug ("Calling flake8 with args: '%s'" , args )
42+ try :
43+ cmd = ['flake8' ]
44+ cmd .extend (args )
45+ p = Popen (cmd , stdout = PIPE , stderr = PIPE )
46+ except IOError :
47+ log .debug ("Can't execute flake8. Trying with 'python -m flake8'" )
48+ cmd = ['python' , '-m' , 'flake8' ]
49+ cmd .extend (args )
50+ p = Popen (cmd , stdout = PIPE , stderr = PIPE )
51+ stderr = p .stderr .read ().decode ()
52+ if stderr :
53+ log .error ("Error while running flake8 '%s'" , stderr )
54+ stdout = p .stdout
55+ return stdout .read ().decode ()
56+
57+
58+ def build_args (options , doc_path ):
59+ """Build arguments for calling flake8.
60+
61+ Args:
62+ options: dictionary of argument names and their values.
63+ doc_path: path of the document to lint.
64+ """
65+ args = [doc_path ]
66+ for arg_name , arg_val in options .items ():
67+ arg = None
68+ if isinstance (arg_val , list ):
69+ arg = '--{}={}' .format (arg_name , ',' .join (arg_val ))
70+ elif isinstance (arg_val , bool ):
71+ if arg_val :
72+ arg = '--{}' .format (arg_name )
73+ elif isinstance (arg_val , int ):
74+ arg = '--{}={}' .format (arg_name , arg_val )
75+ if arg :
76+ args .append (arg )
77+ return args
3778
38- def parse_report (document , report ):
79+
80+ def parse_stdout (document , stdout ):
3981 """
40- Build a diagnostics from a report , it should extract every result and format
82+ Build a diagnostics from flake8's output , it should extract every result and format
4183 it into a dict that looks like this:
4284 {
4385 'source': 'flake8',
@@ -58,40 +100,37 @@ def parse_report(document, report):
58100
59101 Args:
60102 document: The document to be linted.
61- report: A Report object returned by checking the document.
103+ stdout: output from flake8
62104 Returns:
63105 A list of dictionaries.
64106 """
65107
66- file_checkers = report ._application .file_checker_manager .checkers
67- # No file have been checked
68- if not file_checkers :
69- return []
70- # There should be only a filechecker since we are parsing using a path and not a pattern
71- if len (file_checkers ) > 1 :
72- log .error ("Flake8 parsed more than a file for '%s'" , document .path )
73-
74108 diagnostics = []
75- file_checker = file_checkers [0 ]
76- for error in file_checker .results :
77- code , line , character , msg , physical_line = error
109+ lines = stdout .splitlines ()
110+ for raw_line in lines :
111+ parsed_line = re .match (r'(.*):(\d*):(\d*): (\w*) (.*)' , raw_line ).groups ()
112+ if not parsed_line or len (parsed_line ) != 5 :
113+ log .debug ("Flake8 output parser can't parse line '%s'" , raw_line )
114+ continue
115+ _ , line , character , code , msg = parsed_line
116+ line = int (line ) - 1
117+ character = int (character ) - 1
78118 diagnostics .append (
79119 {
80120 'source' : 'flake8' ,
81121 'code' : code ,
82122 'range' : {
83123 'start' : {
84- 'line' : line - 1 ,
124+ 'line' : line ,
85125 'character' : character
86126 },
87127 'end' : {
88- 'line' : line - 1 ,
128+ 'line' : line ,
89129 # no way to determine the column
90- 'character' : len (physical_line )
130+ 'character' : len (document . lines [ line ] )
91131 }
92132 },
93133 'message' : msg ,
94- # no way to determine the severity using the legacy api
95134 'severity' : lsp .DiagnosticSeverity .Warning ,
96135 }
97136 )
0 commit comments