Skip to content

Commit 0fa8cca

Browse files
committed
add custom severity codes
1 parent 07fa9ac commit 0fa8cca

File tree

5 files changed

+84
-30
lines changed

5 files changed

+84
-30
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,17 @@ the valid configuration keys:
6666
- `pylsp.plugins.ruff.select`: List of error codes to enable.
6767
- `pylsp.plugins.ruff.extendSelect`: Same as select, but append to existing error codes.
6868
- `pylsp.plugins.ruff.format`: List of error codes to fix during formatting. The default is `["I"]`, any additional codes are appended to this list.
69+
- `pylsp.plugins.ruff.severities`: Dictionary of custom severity levels for specific codes, see [below](#custom-severities).
6970

7071
For more information on the configuration visit [Ruff's homepage](https://beta.ruff.rs/docs/configuration/).
72+
73+
## Custom severities
74+
75+
By default all diagnostics are marked as warning, except for `"E999"` and all error codes starting with `"F"`, which are displayed as errors.
76+
This default can be changed through the `pylsp.plugins.ruff.severities` option, which takes the error code as a key and any of
77+
`"E"`, `"W"`, `"I"` and `"H"` to be displayed as errors, warnings, information and hints, respectively.
78+
For more information on the diagnostic severities please refer to
79+
[the official LSP reference](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticSeverity).
80+
81+
Note that `python-lsp-ruff` does *not* accept regex, and it will *not* check whether the error code exists. If the custom severity level is not displayed,
82+
please check first that the error code is correct and that the given value is one of the possible keys from above.

pylsp_ruff/plugin.py

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@
5353
"F841", # local variable `name` is assigned to but never used
5454
}
5555

56+
DIAGNOSTIC_SEVERITIES = {
57+
"E": DiagnosticSeverity.Error,
58+
"W": DiagnosticSeverity.Warning,
59+
"I": DiagnosticSeverity.Information,
60+
"H": DiagnosticSeverity.Hint,
61+
}
62+
5663

5764
@hookimpl
5865
def pylsp_settings():
@@ -91,7 +98,10 @@ def pylsp_format_document(workspace: Workspace, document: Document) -> Generator
9198
else:
9299
source = document.source
93100

94-
new_text = run_ruff_format(workspace, document.path, document_source=source)
101+
settings = load_settings(workspace=workspace, document_path=document.path)
102+
new_text = run_ruff_format(
103+
settings=settings, document_path=document.path, document_source=source
104+
)
95105

96106
# Avoid applying empty text edit
97107
if new_text == source:
@@ -122,12 +132,13 @@ def pylsp_lint(workspace: Workspace, document: Document) -> List[Dict]:
122132
-------
123133
List of dicts containing the diagnostics.
124134
"""
125-
checks = run_ruff_check(workspace, document)
126-
diagnostics = [create_diagnostic(c) for c in checks]
135+
settings = load_settings(workspace, document.path)
136+
checks = run_ruff_check(document=document, settings=settings)
137+
diagnostics = [create_diagnostic(check=c, settings=settings) for c in checks]
127138
return converter.unstructure(diagnostics)
128139

129140

130-
def create_diagnostic(check: RuffCheck) -> Diagnostic:
141+
def create_diagnostic(check: RuffCheck, settings: PluginSettings) -> Diagnostic:
131142
# Adapt range to LSP specification (zero-based)
132143
range = Range(
133144
start=Position(
@@ -146,6 +157,12 @@ def create_diagnostic(check: RuffCheck) -> Diagnostic:
146157
if check.code == "E999" or check.code[0] == "F":
147158
severity = DiagnosticSeverity.Error
148159

160+
# Override severity with custom severity if possible, use default otherwise
161+
if settings.severities is not None:
162+
custom_sev = settings.severities.get(check.code, None)
163+
if custom_sev is not None:
164+
severity = DIAGNOSTIC_SEVERITIES.get(custom_sev, severity)
165+
149166
tags = []
150167
if check.code in UNNECESSITY_CODES:
151168
tags = [DiagnosticTag.Unnecessary]
@@ -198,39 +215,48 @@ def pylsp_code_actions(
198215
has_organize_imports = False
199216

200217
for diagnostic in diagnostics:
201-
code_actions.append(create_disable_code_action(document, diagnostic))
218+
code_actions.append(
219+
create_disable_code_action(document=document, diagnostic=diagnostic)
220+
)
202221

203222
if diagnostic.data: # Has fix
204223
fix = converter.structure(diagnostic.data, RuffFix)
205224

206225
if diagnostic.code == "I001":
207226
code_actions.append(
208-
create_organize_imports_code_action(document, diagnostic, fix)
227+
create_organize_imports_code_action(
228+
document=document, diagnostic=diagnostic, fix=fix
229+
)
209230
)
210231
has_organize_imports = True
211232
else:
212233
code_actions.append(
213-
create_fix_code_action(document, diagnostic, fix),
234+
create_fix_code_action(
235+
document=document, diagnostic=diagnostic, fix=fix
236+
),
214237
)
215238

216-
checks = run_ruff_check(workspace, document)
239+
settings = load_settings(workspace=workspace, document_path=document.path)
240+
checks = run_ruff_check(document=document, settings=settings)
217241
checks_with_fixes = [c for c in checks if c.fix]
218242
checks_organize_imports = [c for c in checks_with_fixes if c.code == "I001"]
219243

220244
if not has_organize_imports and checks_organize_imports:
221245
check = checks_organize_imports[0]
222246
fix = check.fix # type: ignore
223-
diagnostic = create_diagnostic(check)
247+
diagnostic = create_diagnostic(check=check, settings=settings)
224248
code_actions.extend(
225249
[
226-
create_organize_imports_code_action(document, diagnostic, fix),
227-
create_disable_code_action(document, diagnostic),
250+
create_organize_imports_code_action(
251+
document=document, diagnostic=diagnostic, fix=fix
252+
),
253+
create_disable_code_action(document=document, diagnostic=diagnostic),
228254
]
229255
)
230256

231257
if checks_with_fixes:
232258
code_actions.append(
233-
create_fix_all_code_action(workspace, document),
259+
create_fix_all_code_action(document=document, settings=settings),
234260
)
235261

236262
return converter.unstructure(code_actions)
@@ -308,13 +334,13 @@ def create_organize_imports_code_action(
308334

309335

310336
def create_fix_all_code_action(
311-
workspace: Workspace,
312337
document: Document,
338+
settings: PluginSettings,
313339
) -> CodeAction:
314340
title = "Ruff: Fix All"
315341
kind = CodeActionKind.SourceFixAll
316342

317-
new_text = run_ruff_fix(workspace, document)
343+
new_text = run_ruff_fix(document=document, settings=settings)
318344
range = Range(
319345
start=Position(line=0, character=0),
320346
end=Position(line=len(document.lines), character=0),
@@ -345,9 +371,11 @@ def create_text_edits(fix: RuffFix) -> List[TextEdit]:
345371
return edits
346372

347373

348-
def run_ruff_check(workspace: Workspace, document: Document) -> List[RuffCheck]:
374+
def run_ruff_check(document: Document, settings: PluginSettings) -> List[RuffCheck]:
349375
result = run_ruff(
350-
workspace, document_path=document.path, document_source=document.source
376+
document_path=document.path,
377+
document_source=document.source,
378+
settings=settings,
351379
)
352380
try:
353381
result = json.loads(result)
@@ -356,38 +384,39 @@ def run_ruff_check(workspace: Workspace, document: Document) -> List[RuffCheck]:
356384
return converter.structure(result, List[RuffCheck])
357385

358386

359-
def run_ruff_fix(workspace: Workspace, document: Document) -> str:
387+
def run_ruff_fix(document: Document, settings: PluginSettings) -> str:
360388
result = run_ruff(
361-
workspace,
362389
document_path=document.path,
363390
document_source=document.source,
364391
fix=True,
392+
settings=settings,
365393
)
366394
return result
367395

368396

369397
def run_ruff_format(
370-
workspace: Workspace, document_path: str, document_source: str
398+
settings: PluginSettings,
399+
document_path: str,
400+
document_source: str,
371401
) -> str:
372-
settings = load_settings(workspace, document_path)
373402
fixable_codes = ["I"]
374403
if settings.format:
375404
fixable_codes.extend(settings.format)
376405
extra_arguments = [
377406
f"--fixable={','.join(fixable_codes)}",
378407
]
379408
result = run_ruff(
380-
workspace,
381-
document_path,
382-
document_source,
409+
settings=settings,
410+
document_path=document_path,
411+
document_source=document_source,
383412
fix=True,
384413
extra_arguments=extra_arguments,
385414
)
386415
return result
387416

388417

389418
def run_ruff(
390-
workspace: Workspace,
419+
settings: PluginSettings,
391420
document_path: str,
392421
document_source: str,
393422
fix: bool = False,
@@ -398,8 +427,8 @@ def run_ruff(
398427
399428
Parameters
400429
----------
401-
workspace : pyls.workspace.Workspace
402-
Workspace to run ruff in.
430+
settings : PluginSettings
431+
Settings to use.
403432
document_path : str
404433
Path to file to run ruff on.
405434
document_source : str
@@ -414,7 +443,6 @@ def run_ruff(
414443
-------
415444
String containing the result in json format.
416445
"""
417-
settings = load_settings(workspace, document_path)
418446
executable = settings.executable
419447
arguments = build_arguments(document_path, settings, fix, extra_arguments)
420448

@@ -558,6 +586,7 @@ def load_settings(workspace: Workspace, document_path: str) -> PluginSettings:
558586
extend_ignore=plugin_settings.extend_ignore,
559587
extend_select=plugin_settings.extend_select,
560588
format=plugin_settings.format,
589+
severities=plugin_settings.severities,
561590
)
562591

563592
return plugin_settings

pylsp_ruff/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class PluginSettings:
2424

2525
format: Optional[List[str]] = None
2626

27+
severities: Optional[Dict[str, str]] = None
28+
2729

2830
def to_camel_case(snake_str: str) -> str:
2931
components = snake_str.split("_")

tests/test_code_actions.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,16 @@ def f():
116116
"""
117117
)
118118
_, doc = temp_document(codeaction_str, workspace)
119-
fixed_str = ruff_lint.run_ruff_fix(workspace, doc)
119+
settings = ruff_lint.load_settings(workspace, doc.path)
120+
fixed_str = ruff_lint.run_ruff_fix(doc, settings)
120121
assert fixed_str == expected_str
121122

122123

123124
def test_format_document_default_settings(workspace):
124125
_, doc = temp_document(import_str, workspace)
126+
settings = ruff_lint.load_settings(workspace, doc.path)
125127
formatted_str = ruff_lint.run_ruff_format(
126-
workspace, document_path=doc.path, document_source=doc.source
128+
settings, document_path=doc.path, document_source=doc.source
127129
)
128130
assert formatted_str == import_str
129131

@@ -146,7 +148,8 @@ def test_format_document_settings(workspace):
146148
}
147149
)
148150
_, doc = temp_document(import_str, workspace)
151+
settings = ruff_lint.load_settings(workspace, doc.path)
149152
formatted_str = ruff_lint.run_ruff_format(
150-
workspace, document_path=doc.path, document_source=doc.source
153+
settings, document_path=doc.path, document_source=doc.source
151154
)
152155
assert formatted_str == expected_str

tests/test_ruff_lint.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ def f():
190190
"plugins": {
191191
"ruff": {
192192
"extendIgnore": ["D104"],
193+
"severities": {"E402": "E", "D103": "I"},
193194
}
194195
}
195196
}
@@ -206,6 +207,13 @@ def f():
206207
assert "D104" not in _list
207208
assert "F841" not in _list
208209

210+
# Check custom severities
211+
for diag in diags:
212+
if diag["code"] == "E402":
213+
assert diag["severity"] == 1
214+
if diag["code"] == "D103":
215+
assert diag["severity"] == 3
216+
209217
# Excludes
210218
doc_uri = uris.from_fs_path(os.path.join(workspace.root_path, "blah/__init__.py"))
211219
workspace.put_document(doc_uri, doc_str)

0 commit comments

Comments
 (0)