Skip to content

Commit be27747

Browse files
authored
Merge pull request #52 from python-lsp/pr_v160
Support new ruff version, fix wrong call to ruff through PATH
2 parents b71fcd3 + 3f17ceb commit be27747

File tree

7 files changed

+89
-20
lines changed

7 files changed

+89
-20
lines changed

README.md

+12-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ pip install python-lsp-ruff
1717

1818
There also exists an [AUR package](https://aur.archlinux.org/packages/python-lsp-ruff).
1919

20-
# Usage
20+
### When using ruff before version 0.1.0
21+
Ruff version `0.1.0` introduced API changes that are fixed in Python LSP Ruff `v1.6.0`. To continue with `ruff<0.1.0` please use `v1.5.3`, e.g. using `pip`:
22+
23+
```sh
24+
pip install "ruff<0.1.0" "python-lsp-ruff==1.5.3"
25+
```
26+
27+
## Usage
2128

2229
This plugin will disable `pycodestyle`, `pyflakes`, `mccabe` and `pyls_isort` by default, unless they are explicitly enabled in the client configuration.
2330
When enabled, all linting diagnostics will be provided by `ruff`.
@@ -43,7 +50,7 @@ lspconfig.pylsp.setup {
4350
}
4451
```
4552

46-
# Configuration
53+
## Configuration
4754

4855
Configuration options can be passed to the python-language-server. If a `pyproject.toml`
4956
file is present in the project, `python-lsp-ruff` will use these configuration options.
@@ -58,19 +65,20 @@ the valid configuration keys:
5865
- `pylsp.plugins.ruff.enabled`: boolean to enable/disable the plugin. `true` by default.
5966
- `pylsp.plugins.ruff.config`: Path to optional `pyproject.toml` file.
6067
- `pylsp.plugins.ruff.exclude`: Exclude files from being checked by `ruff`.
61-
- `pylsp.plugins.ruff.executable`: Path to the `ruff` executable. Assumed to be in PATH by default.
68+
- `pylsp.plugins.ruff.executable`: Path to the `ruff` executable. Uses `os.executable -m "ruff"` by default.
6269
- `pylsp.plugins.ruff.ignore`: Error codes to ignore.
6370
- `pylsp.plugins.ruff.extendIgnore`: Same as ignore, but append to existing ignores.
6471
- `pylsp.plugins.ruff.lineLength`: Set the line-length for length checks.
6572
- `pylsp.plugins.ruff.perFileIgnores`: File-specific error codes to be ignored.
6673
- `pylsp.plugins.ruff.select`: List of error codes to enable.
6774
- `pylsp.plugins.ruff.extendSelect`: Same as select, but append to existing error codes.
6875
- `pylsp.plugins.ruff.format`: List of error codes to fix during formatting. The default is `["I"]`, any additional codes are appended to this list.
76+
- `pylsp.plugins.ruff.unsafeFixes`: boolean that enables/disables fixes that are marked "unsafe" by `ruff`. `false` by default.
6977
- `pylsp.plugins.ruff.severities`: Dictionary of custom severity levels for specific codes, see [below](#custom-severities).
7078

7179
For more information on the configuration visit [Ruff's homepage](https://beta.ruff.rs/docs/configuration/).
7280

73-
## Custom severities
81+
### Custom severities
7482

7583
By default, all diagnostics are marked as warning, except for `"E999"` and all error codes starting with `"F"`, which are displayed as errors.
7684
This default can be changed through the `pylsp.plugins.ruff.severities` option, which takes the error code as a key and any of

pylsp_ruff/plugin.py

+37-10
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,20 @@ def pylsp_lint(workspace: Workspace, document: Document) -> List[Dict]:
139139

140140

141141
def create_diagnostic(check: RuffCheck, settings: PluginSettings) -> Diagnostic:
142+
"""
143+
Create a LSP diagnostic based on the given RuffCheck object.
144+
145+
Parameters
146+
----------
147+
check : RuffCheck
148+
RuffCheck object to convert.
149+
settings : PluginSettings
150+
Current settings.
151+
152+
Returns
153+
-------
154+
Diagnostic
155+
"""
142156
# Adapt range to LSP specification (zero-based)
143157
range = Range(
144158
start=Position(
@@ -214,6 +228,8 @@ def pylsp_code_actions(
214228
code_actions = []
215229
has_organize_imports = False
216230

231+
settings = load_settings(workspace=workspace, document_path=document.path)
232+
217233
for diagnostic in diagnostics:
218234
code_actions.append(
219235
create_disable_code_action(document=document, diagnostic=diagnostic)
@@ -222,6 +238,10 @@ def pylsp_code_actions(
222238
if diagnostic.data: # Has fix
223239
fix = converter.structure(diagnostic.data, RuffFix)
224240

241+
# Ignore fix if marked as unsafe and unsafe_fixes are disabled
242+
if fix.applicability != "safe" and not settings.unsafe_fixes:
243+
continue
244+
225245
if diagnostic.code == "I001":
226246
code_actions.append(
227247
create_organize_imports_code_action(
@@ -236,7 +256,6 @@ def pylsp_code_actions(
236256
),
237257
)
238258

239-
settings = load_settings(workspace=workspace, document_path=document.path)
240259
checks = run_ruff_check(document=document, settings=settings)
241260
checks_with_fixes = [c for c in checks if c.fix]
242261
checks_organize_imports = [c for c in checks_with_fixes if c.code == "I001"]
@@ -446,19 +465,21 @@ def run_ruff(
446465
executable = settings.executable
447466
arguments = build_arguments(document_path, settings, fix, extra_arguments)
448467

449-
log.debug(f"Calling {executable} with args: {arguments} on '{document_path}'")
450-
try:
451-
cmd = [executable]
452-
cmd.extend(arguments)
453-
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
454-
except Exception:
455-
log.debug(f"Can't execute {executable}. Trying with '{sys.executable} -m ruff'")
468+
if executable is not None:
469+
log.debug(f"Calling {executable} with args: {arguments} on '{document_path}'")
470+
try:
471+
cmd = [executable]
472+
cmd.extend(arguments)
473+
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
474+
except Exception:
475+
log.error(f"Can't execute ruff with given executable '{executable}'.")
476+
else:
456477
cmd = [sys.executable, "-m", "ruff"]
457478
cmd.extend(arguments)
458479
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
459480
(stdout, stderr) = p.communicate(document_source.encode())
460481

461-
if stderr:
482+
if p.returncode != 0:
462483
log.error(f"Error running ruff: {stderr.decode()}")
463484

464485
return stdout.decode()
@@ -491,8 +512,10 @@ def build_arguments(
491512
args = []
492513
# Suppress update announcements
493514
args.append("--quiet")
515+
# Suppress exit 1 when violations were found
516+
args.append("--exit-zero")
494517
# Use the json formatting for easier evaluation
495-
args.append("--format=json")
518+
args.append("--output-format=json")
496519
if fix:
497520
args.append("--fix")
498521
else:
@@ -510,6 +533,9 @@ def build_arguments(
510533
if settings.line_length:
511534
args.append(f"--line-length={settings.line_length}")
512535

536+
if settings.unsafe_fixes:
537+
args.append("--unsafe-fixes")
538+
513539
if settings.exclude:
514540
args.append(f"--exclude={','.join(settings.exclude)}")
515541

@@ -583,6 +609,7 @@ def load_settings(workspace: Workspace, document_path: str) -> PluginSettings:
583609
return PluginSettings(
584610
enabled=plugin_settings.enabled,
585611
executable=plugin_settings.executable,
612+
unsafe_fixes=plugin_settings.unsafe_fixes,
586613
extend_ignore=plugin_settings.extend_ignore,
587614
extend_select=plugin_settings.extend_select,
588615
format=plugin_settings.format,

pylsp_ruff/ruff.py

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class Edit:
1919
class Fix:
2020
edits: List[Edit]
2121
message: str
22+
applicability: str
2223

2324

2425
@dataclass

pylsp_ruff/settings.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
@dataclass
99
class PluginSettings:
1010
enabled: bool = True
11-
executable: str = "ruff"
12-
11+
executable: Optional[str] = None
1312
config: Optional[str] = None
1413
line_length: Optional[int] = None
1514

@@ -24,6 +23,8 @@ class PluginSettings:
2423

2524
format: Optional[List[str]] = None
2625

26+
unsafe_fixes: bool = False
27+
2728
severities: Optional[Dict[str, str]] = None
2829

2930

pyproject.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ name = "python-lsp-ruff"
77
authors = [
88
{name = "Julian Hossbach", email = "[email protected]"}
99
]
10-
version = "1.5.3"
10+
version = "1.6.0"
1111
description = "Ruff linting plugin for pylsp"
1212
readme = "README.md"
1313
requires-python = ">=3.7"
1414
license = {text = "MIT"}
1515
dependencies = [
16-
"ruff>=0.0.267,<0.1.0",
16+
"ruff>=0.1.0, <0.2.0",
1717
"python-lsp-server",
1818
"lsprotocol>=2022.0.0a1",
1919
"tomli>=1.1.0; python_version < '3.11'",

tests/test_code_actions.py

+29
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,40 @@ def f():
115115
pass
116116
"""
117117
)
118+
expected_str_safe = dedent(
119+
"""
120+
def f():
121+
a = 2
122+
"""
123+
)
124+
workspace._config.update(
125+
{
126+
"plugins": {
127+
"ruff": {
128+
"unsafeFixes": True,
129+
}
130+
}
131+
}
132+
)
118133
_, doc = temp_document(codeaction_str, workspace)
119134
settings = ruff_lint.load_settings(workspace, doc.path)
120135
fixed_str = ruff_lint.run_ruff_fix(doc, settings)
121136
assert fixed_str == expected_str
122137

138+
workspace._config.update(
139+
{
140+
"plugins": {
141+
"ruff": {
142+
"unsafeFixes": False,
143+
}
144+
}
145+
}
146+
)
147+
_, doc = temp_document(codeaction_str, workspace)
148+
settings = ruff_lint.load_settings(workspace, doc.path)
149+
fixed_str = ruff_lint.run_ruff_fix(doc, settings)
150+
assert fixed_str == expected_str_safe
151+
123152

124153
def test_format_document_default_settings(workspace):
125154
_, doc = temp_document(import_str, workspace)

tests/test_ruff_lint.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Copyright 2021- Python Language Server Contributors.
33

44
import os
5+
import sys
56
import tempfile
67
from unittest.mock import Mock, patch
78

@@ -154,7 +155,6 @@ def f():
154155
)
155156

156157
# Check that user config is ignored
157-
assert ruff_settings.executable == "ruff"
158158
empty_keys = [
159159
"config",
160160
"line_length",
@@ -175,9 +175,12 @@ def f():
175175

176176
call_args = popen_mock.call_args[0][0]
177177
assert call_args == [
178+
str(sys.executable),
179+
"-m",
178180
"ruff",
179181
"--quiet",
180-
"--format=json",
182+
"--exit-zero",
183+
"--output-format=json",
181184
"--no-fix",
182185
"--force-exclude",
183186
f"--stdin-filename={os.path.join(workspace.root_path, '__init__.py')}",

0 commit comments

Comments
 (0)