1
+ import enum
1
2
import json
2
3
import logging
3
4
import re
45
46
r"(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?"
46
47
)
47
48
49
+
48
50
UNNECESSITY_CODES = {
49
51
"F401" , # `module` imported but unused
50
52
"F504" , # % format unused named arguments
60
62
"H" : DiagnosticSeverity .Hint ,
61
63
}
62
64
65
+ ISORT_FIXES = "I"
66
+
67
+
68
+ class Subcommand (str , enum .Enum ):
69
+ CHECK = "check"
70
+ FORMAT = "format"
71
+
72
+ def __str__ (self ) -> str :
73
+ return self .value
74
+
75
+ def build_args (
76
+ self ,
77
+ document_path : str ,
78
+ settings : PluginSettings ,
79
+ fix : bool = False ,
80
+ extra_arguments : Optional [List [str ]] = None ,
81
+ ) -> List [str ]:
82
+ if self == Subcommand .CHECK :
83
+ return build_check_arguments (document_path , settings , fix , extra_arguments )
84
+ elif self == Subcommand .FORMAT :
85
+ return build_format_arguments (document_path , settings , extra_arguments )
86
+ else :
87
+ logging .warn (f"subcommand without argument builder '{ self } '" )
88
+ return []
89
+
63
90
64
91
@hookimpl
65
92
def pylsp_settings ():
@@ -103,8 +130,16 @@ def pylsp_format_document(workspace: Workspace, document: Document) -> Generator
103
130
settings = settings , document_path = document .path , document_source = source
104
131
)
105
132
133
+ settings .select = [ISORT_FIXES ] # clobber to just run import sorting
134
+ new_text = run_ruff (
135
+ settings = settings ,
136
+ document_path = document .path ,
137
+ document_source = new_text ,
138
+ fix = True ,
139
+ )
140
+
106
141
# Avoid applying empty text edit
107
- if new_text == source :
142
+ if not new_text or new_text == source :
108
143
return
109
144
110
145
range = Range (
@@ -395,6 +430,7 @@ def run_ruff_check(document: Document, settings: PluginSettings) -> List[RuffChe
395
430
document_path = document .path ,
396
431
document_source = document .source ,
397
432
settings = settings ,
433
+ subcommand = Subcommand .CHECK ,
398
434
)
399
435
try :
400
436
result = json .loads (result )
@@ -418,26 +454,19 @@ def run_ruff_format(
418
454
document_path : str ,
419
455
document_source : str ,
420
456
) -> str :
421
- fixable_codes = ["I" ]
422
- if settings .format :
423
- fixable_codes .extend (settings .format )
424
- extra_arguments = [
425
- f"--fixable={ ',' .join (fixable_codes )} " ,
426
- ]
427
- result = run_ruff (
457
+ return run_ruff (
428
458
settings = settings ,
429
459
document_path = document_path ,
430
460
document_source = document_source ,
431
- fix = True ,
432
- extra_arguments = extra_arguments ,
461
+ subcommand = Subcommand .FORMAT ,
433
462
)
434
- return result
435
463
436
464
437
465
def run_ruff (
438
466
settings : PluginSettings ,
439
467
document_path : str ,
440
468
document_source : str ,
469
+ subcommand : Subcommand = Subcommand .CHECK ,
441
470
fix : bool = False ,
442
471
extra_arguments : Optional [List [str ]] = None ,
443
472
) -> str :
@@ -453,6 +482,8 @@ def run_ruff(
453
482
document_source : str
454
483
Document source or to apply ruff on.
455
484
Needed when the source differs from the file source, e.g. during formatting.
485
+ subcommand: Subcommand
486
+ The ruff subcommand to run. Default = Subcommand.CHECK.
456
487
fix : bool
457
488
Whether to run fix or no-fix.
458
489
extra_arguments : List[str]
@@ -463,7 +494,8 @@ def run_ruff(
463
494
String containing the result in json format.
464
495
"""
465
496
executable = settings .executable
466
- arguments = build_arguments (document_path , settings , fix , extra_arguments )
497
+
498
+ arguments = subcommand .build_args (document_path , settings , fix , extra_arguments )
467
499
468
500
if executable is not None :
469
501
log .debug (f"Calling { executable } with args: { arguments } on '{ document_path } '" )
@@ -474,7 +506,7 @@ def run_ruff(
474
506
except Exception :
475
507
log .error (f"Can't execute ruff with given executable '{ executable } '." )
476
508
else :
477
- cmd = [sys .executable , "-m" , "ruff" ]
509
+ cmd = [sys .executable , "-m" , "ruff" , str ( subcommand ) ]
478
510
cmd .extend (arguments )
479
511
p = Popen (cmd , stdin = PIPE , stdout = PIPE , stderr = PIPE )
480
512
(stdout , stderr ) = p .communicate (document_source .encode ())
@@ -485,14 +517,14 @@ def run_ruff(
485
517
return stdout .decode ()
486
518
487
519
488
- def build_arguments (
520
+ def build_check_arguments (
489
521
document_path : str ,
490
522
settings : PluginSettings ,
491
523
fix : bool = False ,
492
524
extra_arguments : Optional [List [str ]] = None ,
493
525
) -> List [str ]:
494
526
"""
495
- Build arguments for ruff.
527
+ Build arguments for ruff check .
496
528
497
529
Parameters
498
530
----------
@@ -565,6 +597,51 @@ def build_arguments(
565
597
return args
566
598
567
599
600
+ def build_format_arguments (
601
+ document_path : str ,
602
+ settings : PluginSettings ,
603
+ extra_arguments : Optional [List [str ]] = None ,
604
+ ) -> List [str ]:
605
+ """
606
+ Build arguments for ruff format.
607
+
608
+ Parameters
609
+ ----------
610
+ document : pylsp.workspace.Document
611
+ Document to apply ruff on.
612
+ settings : PluginSettings
613
+ Settings to use for arguments to pass to ruff.
614
+ extra_arguments : List[str]
615
+ Extra arguments to pass to ruff.
616
+
617
+ Returns
618
+ -------
619
+ List containing the arguments.
620
+ """
621
+ args = []
622
+ # Suppress update announcements
623
+ args .append ("--quiet" )
624
+
625
+ # Always force excludes
626
+ args .append ("--force-exclude" )
627
+ # Pass filename to ruff for per-file-ignores, catch unsaved
628
+ if document_path != "" :
629
+ args .append (f"--stdin-filename={ document_path } " )
630
+
631
+ if settings .config :
632
+ args .append (f"--config={ settings .config } " )
633
+
634
+ if settings .exclude :
635
+ args .append (f"--exclude={ ',' .join (settings .exclude )} " )
636
+
637
+ if extra_arguments :
638
+ args .extend (extra_arguments )
639
+
640
+ args .extend (["--" , "-" ])
641
+
642
+ return args
643
+
644
+
568
645
def load_settings (workspace : Workspace , document_path : str ) -> PluginSettings :
569
646
"""
570
647
Load settings from pyproject.toml file in the project path.
0 commit comments