Skip to content

Commit 2d563c9

Browse files
committed
Implement real fixers
Using the quick action API expose the fixes eslint reports on JSON.
1 parent 2d54479 commit 2d563c9

File tree

3 files changed

+83
-18
lines changed

3 files changed

+83
-18
lines changed

.flake8

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
max-line-length = 100
33

44
ignore =
5+
E731,
56
D1,
67
W503

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ npm install -g eslint
2323
npm install -D eslint
2424
```
2525

26+
## Quick Fixes
27+
28+
`eslint` provides fixes for some errors. These fixes are available in SublimeLinter as quick actions. See the Command Palette: `SublimeLinter: Quick Action`. (Also: https://github.com/SublimeLinter/SublimeLinter#quick-actionsfixers)
29+
30+
You may want to define a key binding:
31+
32+
```
33+
// To trigger a quick action
34+
{ "keys": ["ctrl+k", "ctrl+f"],
35+
"command": "sublime_linter_quick_actions"
36+
},
37+
```
38+
2639
## Using eslint with plugins (e.g. vue)
2740

2841
SublimeLinter will detect _some_ installed **local** plugins, and thus it should work automatically for e.g. `.vue` or `.ts` files. If it works on the command line, there is a chance it works in Sublime without further ado.

linter.py

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,27 @@
1010

1111
"""This module exports the ESLint plugin class."""
1212

13+
from functools import partial
1314
import json
1415
import logging
1516
import os
1617
import re
1718
import shutil
18-
from SublimeLinter.lint import LintMatch, NodeLinter, PermanentError
1919

20+
import sublime
21+
22+
from SublimeLinter.lint import LintMatch, NodeLinter, PermanentError
2023
from SublimeLinter.lint.base_linter.node_linter import read_json_file
24+
from SublimeLinter.lint.quick_fix import (
25+
TextRange, QuickAction, merge_actions_by_code_and_line, quick_actions_for)
2126

2227

2328
MYPY = False
2429
if MYPY:
25-
from typing import List, Optional, Union
30+
from typing import Iterator, List, Optional, Union
31+
from SublimeLinter.lint import util
32+
from SublimeLinter.lint.linter import VirtualView
33+
from SublimeLinter.lint.persist import LintError
2634

2735

2836
logger = logging.getLogger('SublimeLinter.plugin.eslint')
@@ -177,21 +185,27 @@ def on_stderr(self, stderr):
177185
logger.error(stderr)
178186
self.notify_failure()
179187

180-
def find_errors(self, output):
188+
def parse_output(self, proc, virtual_view): # type: ignore[override]
189+
# type: (util.popen_output, VirtualView) -> Iterator[LintError]
181190
"""Parse errors from linter's output."""
191+
assert proc.stdout is not None
192+
assert proc.stderr is not None
193+
if proc.stderr.strip():
194+
self.on_stderr(proc.stderr)
195+
182196
try:
183197
# It is possible that users output debug messages to stdout, so we
184198
# only parse the last line, which is hopefully the actual eslint
185199
# output.
186200
# https://github.com/SublimeLinter/SublimeLinter-eslint/issues/251
187-
last_line = output.rstrip().split('\n')[-1]
201+
last_line = proc.stdout.rstrip().split('\n')[-1]
188202
content = json.loads(last_line)
189203
except ValueError:
190204
logger.error(
191205
"JSON Decode error: We expected JSON from 'eslint', "
192206
"but instead got this:\n{}\n\n"
193207
"Be aware that we only parse the last line of above "
194-
"output.".format(output))
208+
"output.".format(proc.stdout))
195209
self.notify_failure()
196210
return
197211

@@ -207,26 +221,63 @@ def find_errors(self, output):
207221
elif filename and os.path.basename(filename).startswith(BUFFER_FILE_STEM + '.'):
208222
filename = 'stdin'
209223

210-
for match in entry['messages']:
211-
if match['message'].startswith('File ignored'):
224+
for item in entry['messages']:
225+
if item['message'].startswith('File ignored'):
212226
continue
213227

214-
if 'line' not in match:
215-
logger.error(match['message'])
228+
if 'line' not in item:
229+
logger.error(item['message'])
216230
self.notify_failure()
217231
continue
218232

219-
yield LintMatch(
220-
match=match,
233+
match = LintMatch(
234+
match=item,
221235
filename=filename,
222-
line=match['line'] - 1, # apply line_col_base manually
223-
col=_try(lambda: match['column'] - 1),
224-
end_line=_try(lambda: match['endLine'] - 1),
225-
end_col=_try(lambda: match['endColumn'] - 1),
226-
error_type='error' if match['severity'] == 2 else 'warning',
227-
code=match.get('ruleId', ''),
228-
message=match['message'],
236+
line=item['line'] - 1, # apply line_col_base manually
237+
col=_try(lambda: item['column'] - 1),
238+
end_line=_try(lambda: item['endLine'] - 1),
239+
end_col=_try(lambda: item['endColumn'] - 1),
240+
error_type='error' if item['severity'] == 2 else 'warning',
241+
code=item.get('ruleId', ''),
242+
message=item['message'],
229243
)
244+
error = self.process_match(match, virtual_view)
245+
if error:
246+
try:
247+
fix_description = item["fix"]
248+
except KeyError:
249+
pass
250+
else:
251+
if fix_description:
252+
error["fix"] = fix_description # type: ignore[typeddict-unknown-key]
253+
yield error
254+
255+
256+
@quick_actions_for("eslint")
257+
def eslint_fixes_provider(errors, _view):
258+
# type: (List[LintError], Optional[sublime.View]) -> Iterator[QuickAction]
259+
def make_action(error):
260+
# type: (LintError) -> QuickAction
261+
return QuickAction(
262+
"eslint: Fix {code}".format(**error),
263+
partial(eslint_fix_error, error),
264+
"{msg}".format(**error),
265+
solves=[error]
266+
)
267+
268+
except_ = lambda error: "fix" not in error
269+
yield from merge_actions_by_code_and_line(make_action, except_, errors, _view)
270+
271+
272+
def eslint_fix_error(error, view) -> "Iterator[TextRange]":
273+
"""
274+
'fix': {'text': '; ', 'range': [40, 44]}
275+
"""
276+
fix_description = error["fix"]
277+
yield TextRange(
278+
fix_description["text"],
279+
sublime.Region(*fix_description["range"])
280+
)
230281

231282

232283
def _try(getter, otherwise=None, catch=Exception):

0 commit comments

Comments
 (0)