Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement real fixers #333

Merged
merged 1 commit into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
max-line-length = 100

ignore =
E731,
D1,
W503
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ npm install -g eslint
npm install -D eslint
```

## Quick Fixes

`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)

You may want to define a key binding:

```
// To trigger a quick action
{ "keys": ["ctrl+k", "ctrl+f"],
"command": "sublime_linter_quick_actions"
},
```

## Using eslint with plugins (e.g. vue)

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.
Expand Down
87 changes: 69 additions & 18 deletions linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,27 @@

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

from functools import partial
import json
import logging
import os
import re
import shutil
from SublimeLinter.lint import LintMatch, NodeLinter, PermanentError

import sublime

from SublimeLinter.lint import LintMatch, NodeLinter, PermanentError
from SublimeLinter.lint.base_linter.node_linter import read_json_file
from SublimeLinter.lint.quick_fix import (
TextRange, QuickAction, merge_actions_by_code_and_line, quick_actions_for)


MYPY = False
if MYPY:
from typing import List, Optional, Union
from typing import Iterator, List, Optional, Union
from SublimeLinter.lint import util
from SublimeLinter.lint.linter import VirtualView
from SublimeLinter.lint.persist import LintError


logger = logging.getLogger('SublimeLinter.plugin.eslint')
Expand Down Expand Up @@ -177,21 +185,27 @@ def on_stderr(self, stderr):
logger.error(stderr)
self.notify_failure()

def find_errors(self, output):
def parse_output(self, proc, virtual_view): # type: ignore[override]
# type: (util.popen_output, VirtualView) -> Iterator[LintError]
"""Parse errors from linter's output."""
assert proc.stdout is not None
assert proc.stderr is not None
if proc.stderr.strip():
self.on_stderr(proc.stderr)

try:
# It is possible that users output debug messages to stdout, so we
# only parse the last line, which is hopefully the actual eslint
# output.
# https://github.com/SublimeLinter/SublimeLinter-eslint/issues/251
last_line = output.rstrip().split('\n')[-1]
last_line = proc.stdout.rstrip().split('\n')[-1]
content = json.loads(last_line)
except ValueError:
logger.error(
"JSON Decode error: We expected JSON from 'eslint', "
"but instead got this:\n{}\n\n"
"Be aware that we only parse the last line of above "
"output.".format(output))
"output.".format(proc.stdout))
self.notify_failure()
return

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

for match in entry['messages']:
if match['message'].startswith('File ignored'):
for item in entry['messages']:
if item['message'].startswith('File ignored'):
continue

if 'line' not in match:
logger.error(match['message'])
if 'line' not in item:
logger.error(item['message'])
self.notify_failure()
continue

yield LintMatch(
match=match,
match = LintMatch(
match=item,
filename=filename,
line=match['line'] - 1, # apply line_col_base manually
col=_try(lambda: match['column'] - 1),
end_line=_try(lambda: match['endLine'] - 1),
end_col=_try(lambda: match['endColumn'] - 1),
error_type='error' if match['severity'] == 2 else 'warning',
code=match.get('ruleId', ''),
message=match['message'],
line=item['line'] - 1, # apply line_col_base manually
col=_try(lambda: item['column'] - 1),
end_line=_try(lambda: item['endLine'] - 1),
end_col=_try(lambda: item['endColumn'] - 1),
error_type='error' if item['severity'] == 2 else 'warning',
code=item.get('ruleId', ''),
message=item['message'],
)
error = self.process_match(match, virtual_view)
if error:
try:
fix_description = item["fix"]
except KeyError:
pass
else:
if fix_description:
error["fix"] = fix_description # type: ignore[typeddict-unknown-key]
yield error


@quick_actions_for("eslint")
def eslint_fixes_provider(errors, _view):
# type: (List[LintError], Optional[sublime.View]) -> Iterator[QuickAction]
def make_action(error):
# type: (LintError) -> QuickAction
return QuickAction(
"eslint: Fix {code}".format(**error),
partial(eslint_fix_error, error),
"{msg}".format(**error),
solves=[error]
)

except_ = lambda error: "fix" not in error
yield from merge_actions_by_code_and_line(make_action, except_, errors, _view)


def eslint_fix_error(error, view) -> "Iterator[TextRange]":
"""
'fix': {'text': '; ', 'range': [40, 44]}
"""
fix_description = error["fix"]
yield TextRange(
fix_description["text"],
sublime.Region(*fix_description["range"])
)


def _try(getter, otherwise=None, catch=Exception):
Expand Down
Loading