1
1
# Copyright 2019 Palantir Technologies, Inc.
2
2
"""Linter pluging for flake8"""
3
3
import logging
4
- from flake8 .api import legacy as flake8
4
+ import re
5
+ from subprocess import Popen , PIPE
5
6
from pyls import hookimpl , lsp
6
7
7
8
log = logging .getLogger (__name__ )
@@ -21,23 +22,64 @@ def pyls_lint(config, document):
21
22
opts = {
22
23
'exclude' : settings .get ('exclude' ),
23
24
'filename' : settings .get ('filename' ),
24
- 'hang_closing ' : settings .get ('hangClosing' ),
25
+ 'hang-closing ' : settings .get ('hangClosing' ),
25
26
'ignore' : settings .get ('ignore' ),
26
- 'max_line_length ' : settings .get ('maxLineLength' ),
27
+ 'max-line-length ' : settings .get ('maxLineLength' ),
27
28
'select' : settings .get ('select' ),
28
29
}
29
30
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 )
34
35
35
- return parse_report (document , report )
36
36
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
37
78
38
- def parse_report (document , report ):
79
+
80
+ def parse_stdout (document , stdout ):
39
81
"""
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
41
83
it into a dict that looks like this:
42
84
{
43
85
'source': 'flake8',
@@ -58,40 +100,37 @@ def parse_report(document, report):
58
100
59
101
Args:
60
102
document: The document to be linted.
61
- report: A Report object returned by checking the document.
103
+ stdout: output from flake8
62
104
Returns:
63
105
A list of dictionaries.
64
106
"""
65
107
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
-
74
108
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
78
118
diagnostics .append (
79
119
{
80
120
'source' : 'flake8' ,
81
121
'code' : code ,
82
122
'range' : {
83
123
'start' : {
84
- 'line' : line - 1 ,
124
+ 'line' : line ,
85
125
'character' : character
86
126
},
87
127
'end' : {
88
- 'line' : line - 1 ,
128
+ 'line' : line ,
89
129
# no way to determine the column
90
- 'character' : len (physical_line )
130
+ 'character' : len (document . lines [ line ] )
91
131
}
92
132
},
93
133
'message' : msg ,
94
- # no way to determine the severity using the legacy api
95
134
'severity' : lsp .DiagnosticSeverity .Warning ,
96
135
}
97
136
)
0 commit comments