Skip to content

Commit a67410a

Browse files
authored
PR: Add Ruff support for linting (Completions/Linting) (#24908)
1 parent 39b7071 commit a67410a

File tree

7 files changed

+122
-23
lines changed

7 files changed

+122
-23
lines changed

binder/environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ dependencies:
4242
- pyqt >=5.15,<5.16
4343
- pyqtwebengine >=5.15,<5.16
4444
- python-lsp-black >=2.0.0,<3.0.0
45+
- python-lsp-ruff >=2.2.2,<3.0.0
4546
- python-lsp-server >=1.13.0,<1.14.0
4647
- pyuca >=1.2
4748
- pyxdg >=0.26

requirements/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ dependencies:
4040
- pyqt >=5.15,<5.16
4141
- pyqtwebengine >=5.15,<5.16
4242
- python-lsp-black >=2.0.0,<3.0.0
43+
- python-lsp-ruff >=2.2.2,<3.0.0
4344
- python-lsp-server >=1.13.0,<1.14.0
4445
- pyuca >=1.2
4546
- pyzmq >=24.0.0

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ def run(self):
300300
'pylint-venv>=3.0.2',
301301
'pyls-spyder>=0.4.0',
302302
'python-lsp-black>=2.0.0,<3.0.0',
303+
'python-lsp-ruff>=2.2.2,<3.0.0',
303304
'python-lsp-server[all]>=1.13.0,<1.14.0',
304305
'pyuca>=1.2',
305306
'pyxdg>=0.26;platform_system=="Linux"',

spyder/config/lsp.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,12 @@
138138
'enabled': False,
139139
},
140140
'ruff': {
141-
# Disable it until we have a graphical option for users to
142-
# enable it.
143141
'enabled': False,
142+
'formatedEnabled': False,
143+
'exclude': [],
144+
'extendSelect': [],
145+
'extendIgnore': [],
146+
'lineLength': 79,
144147
},
145148
'no_linting': {
146149
'enabled' : False

spyder/dependencies.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
PYLINT_VENV_REQVER = '>=3.0.2'
6161
PYLSP_REQVER = '>=1.13.0,<1.14.0'
6262
PYLSP_BLACK_REQVER = '>=2.0.0,<3.0.0'
63+
PYLSP_RUFF_REQVER = '>=2.2.2,<3.0.0'
6364
PYLS_SPYDER_REQVER = '>=0.4.0'
6465
PYUCA_REQVER = '>=1.2'
6566
PYXDG_REQVER = '>=0.26'
@@ -220,6 +221,10 @@
220221
'features': _("Autoformat Python files in the Editor with the Black "
221222
"package"),
222223
'required_version': PYLSP_BLACK_REQVER},
224+
{'modname': 'pylsp_ruff',
225+
'package_name': 'python-lsp-ruff',
226+
'features': _("Provide linting with the Ruff package"),
227+
'required_version': PYLSP_RUFF_REQVER},
223228
{'modname': 'pyls_spyder',
224229
'package_name': 'pyls-spyder',
225230
'features': _('Spyder plugin for the Python LSP Server'),

spyder/plugins/completion/providers/languageserver/conftabs/linting.py

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,22 @@ def __init__(self, parent):
6161
'flake8',
6262
button_group=linting_bg
6363
)
64-
64+
ruff_linting_radio = self.create_radiobutton(
65+
_("Ruff (Advanced)"),
66+
'ruff',
67+
button_group=linting_bg
68+
)
6569
disable_linting_radio = self.create_radiobutton(
6670
_("Disable linting"),
6771
'no_linting',
6872
button_group=linting_bg
6973
)
70-
74+
7175
linting_select_layout = QVBoxLayout()
7276
linting_select_layout.addSpacing(3 * AppStyle.MarginSize)
7377
linting_select_layout.addWidget(basic_linting_radio)
7478
linting_select_layout.addWidget(flake_linting_radio)
79+
linting_select_layout.addWidget(ruff_linting_radio)
7580
linting_select_layout.addWidget(disable_linting_radio)
7681
linting_select_group.setLayout(linting_select_layout)
7782

@@ -89,6 +94,43 @@ def __init__(self, parent):
8994
configuration_options_group = QGroupBox(_("Provider options"))
9095
configuration_options_layout = QVBoxLayout()
9196

97+
# ruff options
98+
self.ruff_exclude = self.create_lineedit(
99+
_("Exclude these files or directories:"),
100+
'ruff/exclude',
101+
alignment=Qt.Horizontal,
102+
word_wrap=False,
103+
placeholder=_("Exclude test files: (?!test_).*\\.py"),
104+
)
105+
106+
ruff_select = self.create_lineedit(
107+
_("Show these errors or warnings:"),
108+
'ruff/extendSelect',
109+
alignment=Qt.Horizontal,
110+
word_wrap=False,
111+
placeholder=_("Example codes: E113, W391"),
112+
)
113+
114+
ruff_ignore = self.create_lineedit(
115+
_("Ignore these errors or warnings:"),
116+
'ruff/extendIgnore',
117+
alignment=Qt.Horizontal,
118+
word_wrap=False,
119+
placeholder=_("Default is: E"),
120+
)
121+
122+
ruff_layout = QGridLayout()
123+
ruff_layout.addWidget(self.ruff_exclude.label, 1, 0)
124+
ruff_layout.addWidget(self.ruff_exclude.textbox, 1, 1)
125+
ruff_layout.addWidget(ruff_select.label, 2, 0)
126+
ruff_layout.addWidget(ruff_select.textbox, 2, 1)
127+
ruff_layout.addWidget(ruff_ignore.label, 3, 0)
128+
ruff_layout.addWidget(ruff_ignore.textbox, 3, 1)
129+
130+
ruff_grid_widget = QWidget()
131+
ruff_grid_widget.setLayout(ruff_layout)
132+
133+
# Flake8 options
92134
self.flake8_filenames_match = self.create_lineedit(
93135
_("Only check these filenames:"),
94136
'flake8/filename',
@@ -130,38 +172,53 @@ def __init__(self, parent):
130172
flake8_layout.addWidget(flake8_select.textbox, 3, 1)
131173
flake8_layout.addWidget(flake8_ignore.label, 4, 0)
132174
flake8_layout.addWidget(flake8_ignore.textbox, 4, 1)
175+
flake8_grid_widget = QWidget()
176+
flake8_grid_widget.setLayout(flake8_layout)
133177

178+
# pyflakes options
134179
pyflakes_conf_options = QLabel(
135180
_("There are no configuration options for Pyflakes")
136181
)
137-
not_select_conf_options = QLabel(_("Linting is disabled"))
138182

139-
grid_widget = QWidget()
140-
grid_widget.setLayout(flake8_layout)
183+
# Disabled linting options
184+
not_select_conf_options = QLabel(_("Linting is disabled"))
141185

142-
configuration_options_layout.addWidget(grid_widget)
186+
configuration_options_layout.addWidget(ruff_grid_widget)
187+
configuration_options_layout.addWidget(flake8_grid_widget)
143188
configuration_options_layout.addWidget(pyflakes_conf_options)
144189
configuration_options_layout.addWidget(not_select_conf_options)
145190

191+
ruff_linting_radio.radiobutton.toggled.connect(
192+
lambda checked: (
193+
ruff_grid_widget.setVisible(checked),
194+
flake8_grid_widget.setVisible(False),
195+
pyflakes_conf_options.setVisible(False),
196+
not_select_conf_options.setVisible(False)
197+
) if checked else None
198+
)
199+
146200
flake_linting_radio.radiobutton.toggled.connect(
147201
lambda checked: (
148-
grid_widget.setVisible(checked),
149-
pyflakes_conf_options.setVisible(not checked),
202+
ruff_grid_widget.setVisible(False),
203+
flake8_grid_widget.setVisible(checked),
204+
pyflakes_conf_options.setVisible(False),
150205
not_select_conf_options.setVisible(False)
151206
) if checked else None
152207
)
153208

154209
basic_linting_radio.radiobutton.toggled.connect(
155210
lambda checked: (
156-
grid_widget.setVisible(False),
211+
ruff_grid_widget.setVisible(False),
212+
flake8_grid_widget.setVisible(False),
157213
pyflakes_conf_options.setVisible(checked),
158214
not_select_conf_options.setVisible(False)
159215
) if checked else None
160216
)
161217

162218
disable_linting_radio.radiobutton.toggled.connect(
163219
lambda checked: (
164-
grid_widget.setVisible(False),
220+
ruff_grid_widget.setVisible(False),
221+
flake8_grid_widget.setVisible(False),
165222
pyflakes_conf_options.setVisible(False),
166223
not_select_conf_options.setVisible(checked)
167224
) if checked else None
@@ -207,9 +264,16 @@ def is_valid(self):
207264
return False
208265

209266
try:
267+
# flake8 check
210268
flake8_excludes = self.flake8_exclude.textbox.text().split(",")
211269
for match in flake8_excludes:
212270
re.compile(match.strip())
271+
272+
# ruff check
273+
ruff_excludes = self.ruff_exclude.textbox.text().split(",")
274+
for match in ruff_excludes:
275+
re.compile(match.strip())
276+
213277
except re.error:
214278
self.report_invalid_regex(files=False)
215279
return False

spyder/plugins/completion/providers/languageserver/provider.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class LanguageServerProvider(SpyderCompletionProvider):
6161
('pyflakes', True),
6262
('mccabe', False),
6363
('flake8', False),
64+
('ruff', False),
6465
('no_linting', False),
6566
('formatting', 'autopep8'),
6667
('format_on_save', False),
@@ -69,6 +70,9 @@ class LanguageServerProvider(SpyderCompletionProvider):
6970
('flake8/extendSelect', ''),
7071
('flake8/extendIgnore', 'E,W,C90'),
7172
('flake8/max_line_length', 79),
73+
('ruff/exclude', ''),
74+
('ruff/extendSelect', ''),
75+
('ruff/extendIgnore', 'E'),
7276
('pydocstyle', False),
7377
('pydocstyle/convention', 'numpy'),
7478
('pydocstyle/select', ''),
@@ -723,8 +727,6 @@ def generate_python_config(self):
723727
host = self.get_conf('advanced/host', '127.0.0.1')
724728
port = self.get_conf('advanced/port', 2087)
725729

726-
727-
728730
# Flake8
729731
cs_max_line_length = self.get_conf('flake8/max_line_length', 79)
730732
f8_exclude = self.get_conf('flake8/exclude', '').split(',')
@@ -745,15 +747,6 @@ def generate_python_config(self):
745747
f8_indent.count(" ") + f8_indent.count("\t") * f8_tab_size
746748
)
747749

748-
pycodestyle = {
749-
'maxLineLength': cs_max_line_length
750-
}
751-
752-
# Linting - Pyflakes
753-
pyflakes = {
754-
'enabled': self.get_conf('pyflakes')
755-
}
756-
757750
flake8 = {
758751
"enabled": self.get_conf("flake8"),
759752
"filename": [
@@ -766,6 +759,36 @@ def generate_python_config(self):
766759
"maxLineLength": cs_max_line_length,
767760
}
768761

762+
# pycodestyle
763+
pycodestyle = {
764+
'maxLineLength': cs_max_line_length
765+
}
766+
767+
# Linting - Pyflakes
768+
pyflakes = {
769+
'enabled': self.get_conf('pyflakes')
770+
}
771+
772+
# Linting - ruff
773+
ruff_exclude = self.get_conf('ruff/exclude', '').split(',')
774+
ruff_select = self.get_conf('ruff/extendSelect', '').split(',')
775+
ruff_ignore = self.get_conf('ruff/extendIgnore', '').split(',')
776+
777+
ruff = {
778+
"enabled": self.get_conf("ruff"),
779+
"exclude": [
780+
exclude.strip() for exclude in ruff_exclude if exclude
781+
],
782+
"extendSelect": [
783+
select.strip() for select in ruff_select if select
784+
],
785+
"extendIgnore": [
786+
ignore.strip() for ignore in ruff_ignore if ignore
787+
],
788+
"lineLength": cs_max_line_length,
789+
}
790+
791+
# Linting disabled
769792
no_linting = {"enabled": self.get_conf("no_linting")}
770793

771794
# Pydocstyle
@@ -875,6 +898,7 @@ def generate_python_config(self):
875898
plugins['pyflakes'].update(pyflakes)
876899
plugins['pycodestyle'].update(pycodestyle)
877900
plugins['flake8'].update(flake8)
901+
plugins['ruff'].update(ruff)
878902
plugins['no_linting'].update(no_linting)
879903
plugins['pydocstyle'].update(pydocstyle)
880904
plugins['pyls_spyder'].update(pyls_spyder_options)

0 commit comments

Comments
 (0)