11import os
22import json
3- import tempfile
4- from SublimeLinter .lint import Linter , util
5- from SublimeLinter .lint .persist import settings
3+ import logging
4+ from SublimeLinter .lint import NodeLinter
5+ from SublimeLinter .lint .linter import LintMatch
66
7+ logger = logging .getLogger ('SublimeLinter.plugin.golangcilint' )
78
8- class Golangcilint (Linter ):
9- cmd = "golangci-lint run --fast --out-format json --enable typecheck"
10- regex = r"(?:[^:]+):(?P<line>\d+):(?P<col>\d+):(?:(?P<warning>warning)|(?P<error>error)):(?P<message>.*)"
11- defaults = {"selector" : "source.go" }
12- error_stream = util .STREAM_STDOUT
13- shortname = "unknown.go"
14- multiline = False
15-
16- def run (self , cmd , code ):
17- dir = os .path .dirname (self .filename )
18- if not dir :
19- print ("golangcilint: skipped linting of unsaved file" )
20- return
21- self .shortname = os .path .basename (self .filename )
22- if settings .get ("lint_mode" ) == "background" :
23- return self ._live_lint (cmd , code )
24- else :
25- return self ._in_place_lint (cmd )
26-
27- def finalize_cmd (self , cmd , context , at_value = '' , auto_append = False ):
28- """prevents SublimeLinter to append filename at the end of cmd"""
29- return cmd
309
31- def _live_lint (self , cmd , code ):
32- dir = os .path .dirname (self .filename )
33- files = [file for file in os .listdir (dir ) if file .endswith (".go" )]
34- if len (files ) > 100 :
35- print ("golangcilint: too many files ({}), live linting skipped" .format (len (files )))
36- return ""
37- return self .tmpdir (cmd , dir , files , self .filename , code )
38-
39- def _in_place_lint (self , cmd ):
40- return self .execute (cmd )
41-
42- def tmpdir (self , cmd , dir , files , filename , code ):
43- """Run an external executable using a temp dir filled with files and return its output."""
44- try :
45- with tempfile .TemporaryDirectory (dir = dir , prefix = ".golangcilint-" ) as tmpdir :
46- for filepath in files :
47- target = os .path .join (tmpdir , filepath )
48- filepath = os .path .join (dir , filepath )
49- if os .path .basename (target ) != os .path .basename (filename ):
50- os .link (filepath , target )
51- continue
52- # source file hasn't been saved since change
53- # so update it from our live buffer for now
54- with open (target , 'wb' ) as w :
55- if isinstance (code , str ):
56- code = code .encode ('utf8' )
57- w .write (code )
58- return self .execute (cmd + [tmpdir ])
59- except FileNotFoundError :
60- print ("golangcilint file not found error on `{}`" .format (dir ))
61- return ""
62- except PermissionError :
63- print ("golangcilint permission error on `{}`" .format (dir ))
64- return ""
10+ class Golangcilint (NodeLinter ):
11+ cmd = "golangci-lint run --fast --out-format json"
12+ defaults = {"selector" : "source.go" }
13+ axis_base = (1 , 1 )
6514
66- def issue_level (self , issue ):
15+ def severity (self , issue ):
6716 """consider /dev/stderr as errors and /dev/stdout as warnings"""
6817 return "error" if issue ["FromLinter" ] == "typecheck" else "warning"
6918
70- def canonical_error (self , issue ):
19+ def shortname (self , issue ):
20+ """find and return short filename"""
21+ return os .path .basename (issue ["Pos" ]["Filename" ])
22+
23+ def lintissue (self , issue ):
24+ return LintMatch (
25+ match = issue ,
26+ message = issue ["Text" ],
27+ error_type = self .severity (issue ),
28+ line = issue ["Pos" ]["Line" ] - self .axis_base [0 ],
29+ col = issue ["Pos" ]["Column" ] - self .axis_base [1 ],
30+ code = issue ["FromLinter" ]
31+ )
32+
33+ def canonical (self , issue ):
7134 mark = issue ["Text" ].rfind ("/" )
7235 package = issue ["Text" ][mark + 1 :- 1 ]
7336 # Go 1.4 introduces an annotation for package clauses in Go source that
@@ -98,40 +61,31 @@ def canonical_error(self, issue):
9861 "Level" : "error" ,
9962 "Pos" : {
10063 "Filename" : self .filename ,
101- "Shortname" : self .shortname ,
10264 "Offset" : 0 ,
10365 "Column" : 0 ,
10466 "Line" : 1
10567 }
10668 }
10769
108- def formalize (self , issues ):
109- lines = []
110- for issue in issues :
111- lines .append (
112- "{}:{}:{}:{}:{}" .format (
113- issue ["Pos" ]["Shortname" ],
114- issue ["Pos" ]["Line" ],
115- issue ["Pos" ]["Column" ],
116- issue ["Level" ],
117- issue ["Text" ]
118- )
119- )
120- return "\n " .join (lines )
70+ def find_errors (self , output ):
71+ current = os .path .basename (self .filename )
72+ exclude = False
12173
122- def execute ( self , cmd ) :
123- issues = []
124- ignore = False
125- output = self . communicate ( cmd )
126- report = json . loads ( output )
74+ try :
75+ data = json . loads ( output )
76+ except Exception as e :
77+ logger . warning ( e )
78+ self . notify_failure ( )
12779
12880 """merge possible stderr with issues"""
129- if "Error" in report ["Report" ]:
130- for line in report ["Report" ]["Error" ].splitlines ():
81+ if (data
82+ and "Report" in data
83+ and "Error" in data ["Report" ]):
84+ for line in data ["Report" ]["Error" ].splitlines ():
13185 if line .count (":" ) < 3 :
13286 continue
13387 parts = line .split (":" )
134- report ["Issues" ].append ({
88+ data ["Issues" ].append ({
13589 "FromLinter" : "typecheck" ,
13690 "Text" : parts [3 ].strip (),
13791 "Pos" : {
@@ -141,29 +95,25 @@ def execute(self, cmd):
14195 }
14296 })
14397
144- """format issues into formal pattern"""
145- for issue in report ["Issues" ]:
146- name = issue ["Pos" ]["Filename" ]
147- mark = name .rfind ("/" )
148- mark = 0 if mark == - 1 else mark + 1
149- issue ["Pos" ]["Shortname" ] = name [mark :]
150- issue ["Level" ] = self .issue_level (issue )
151-
152- """detect broken canonical imports"""
153- if ("code in directory" in issue ["Text" ]
154- and "expects import" in issue ["Text" ]):
155- issues .append (self .canonical_error (issue ))
156- ignore = True
157- continue
98+ """find relevant issues and yield a LintMatch"""
99+ if data and "Issues" in data :
100+ for issue in data ["Issues" ]:
101+ """detect broken canonical imports"""
102+ if ("code in directory" in issue ["Text" ]
103+ and "expects import" in issue ["Text" ]):
104+ issue = self .canonical (issue )
105+ yield self .lintissue (issue )
106+ exclude = True
107+ continue
158108
159- """ignore false positive warnings"""
160- if (ignore
161- and "could not import" in issue ["Text" ]
162- and "missing package:" in issue ["Text" ]):
163- continue
109+ """ignore false positive warnings"""
110+ if (exclude
111+ and "could not import" in issue ["Text" ]
112+ and "missing package:" in issue ["Text" ]):
113+ continue
164114
165- """report issues relevant to this file"""
166- if issue [ "Pos" ][ "Shortname" ] == self .shortname :
167- issues . append ( issue )
115+ """issues found in the current file are relevant """
116+ if self .shortname ( issue ) != current :
117+ continue
168118
169- return self .formalize ( issues )
119+ yield self .lintissue ( issue )
0 commit comments