Skip to content

Commit 7609388

Browse files
committed
Add LintMatch yielding to comply with SublimeLinter standards
1 parent 26016d3 commit 7609388

File tree

1 file changed

+56
-106
lines changed

1 file changed

+56
-106
lines changed

linter.py

Lines changed: 56 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,36 @@
11
import os
22
import 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

Comments
 (0)