@@ -11,8 +11,8 @@ use ruff_text_size::{Ranged, TextRange};
11
11
12
12
use crate :: checkers:: ast:: Checker ;
13
13
use crate :: rules:: ruff:: rules:: sequence_sorting:: {
14
- sort_single_line_elements_sequence, MultilineStringSequenceValue , SequenceKind ,
15
- SortClassification , SortingStyle ,
14
+ sort_single_line_elements_sequence, CommentComplexity , MultilineStringSequenceValue ,
15
+ SequenceKind , SortClassification , SortingStyle ,
16
16
} ;
17
17
use crate :: Locator ;
18
18
@@ -37,24 +37,51 @@ use crate::Locator;
37
37
/// ```
38
38
///
39
39
/// ## Fix safety
40
- /// This rule's fix is marked as unsafe whenever Ruff can detect that code
41
- /// elsewhere in the same file reads the `__slots__` variable in some way.
42
- /// This is because the order of the items in `__slots__` may have semantic
43
- /// significance if the `__slots__` of a class is being iterated over, or
44
- /// being assigned to another value.
40
+ /// This rule's fix is marked as unsafe in three situations.
41
+ ///
42
+ /// Firstly, the fix is unsafe if there are any comments that take up
43
+ /// a whole line by themselves inside the `__slots__` definition, for example:
44
+ /// ```py
45
+ /// class Foo:
46
+ /// __slots__ = [
47
+ /// # eggy things
48
+ /// "duck_eggs",
49
+ /// "chicken_eggs",
50
+ /// # hammy things
51
+ /// "country_ham",
52
+ /// "parma_ham",
53
+ /// ]
54
+ /// ```
55
+ ///
56
+ /// This is a common pattern used to delimit categories within a class's slots,
57
+ /// but it would be out of the scope of this rule to attempt to maintain these
58
+ /// categories when applying a natural sort to the items of `__slots__`.
59
+ ///
60
+ /// Secondly, the fix is also marked as unsafe if there are more than two
61
+ /// `__slots__` items on a single line and that line also has a trailing
62
+ /// comment, since here it is impossible to accurately gauge which item the
63
+ /// comment should be moved with when sorting `__slots__`:
64
+ /// ```py
65
+ /// class Bar:
66
+ /// __slots__ = [
67
+ /// "a", "c", "e", # a comment
68
+ /// "b", "d", "f", # a second comment
69
+ /// ]
70
+ /// ```
71
+ ///
72
+ /// Lastly, this rule's fix is marked as unsafe whenever Ruff can detect that
73
+ /// code elsewhere in the same file reads the `__slots__` variable in some way
74
+ /// and the `__slots__` variable is not assigned to a set. This is because the
75
+ /// order of the items in `__slots__` may have semantic significance if the
76
+ /// `__slots__` of a class is being iterated over, or being assigned to another
77
+ /// value.
45
78
///
46
79
/// In the vast majority of other cases, this rule's fix is unlikely to
47
80
/// cause breakage; as such, Ruff will otherwise mark this rule's fix as
48
81
/// safe. However, note that (although it's rare) the value of `__slots__`
49
82
/// could still be read by code outside of the module in which the
50
83
/// `__slots__` definition occurs, in which case this rule's fix could
51
84
/// theoretically cause breakage.
52
- ///
53
- /// Additionally, note that for multiline `__slots__` definitions that
54
- /// include comments on their own line, it can be hard to tell where the
55
- /// comments should be moved to when sorting the contents of `__slots__`.
56
- /// While this rule's fix will never delete a comment, it might *sometimes*
57
- /// move a comment to an unexpected location.
58
85
#[ violation]
59
86
pub struct UnsortedDunderSlots {
60
87
class_name : ast:: name:: Name ,
@@ -122,15 +149,17 @@ pub(crate) fn sort_dunder_slots(checker: &Checker, binding: &Binding) -> Option<
122
149
) ;
123
150
124
151
if let SortClassification :: UnsortedAndMaybeFixable { items } = sort_classification {
125
- if let Some ( sorted_source_code) = display. generate_sorted_source_code ( & items, checker) {
152
+ if let Some ( ( sorted_source_code, comment_complexity) ) =
153
+ display. generate_sorted_source_code ( & items, checker)
154
+ {
126
155
let edit = Edit :: range_replacement ( sorted_source_code, display. range ( ) ) ;
127
-
128
- let applicability = if display. kind . is_set_literal ( ) || binding. is_unused ( ) {
129
- Applicability :: Safe
130
- } else {
156
+ let applicability = if comment_complexity. is_complex ( )
157
+ || ( binding. is_used ( ) && !display. kind . is_set_literal ( ) )
158
+ {
131
159
Applicability :: Unsafe
160
+ } else {
161
+ Applicability :: Safe
132
162
} ;
133
-
134
163
diagnostic. set_fix ( Fix :: applicable_edit ( edit, applicability) ) ;
135
164
}
136
165
}
@@ -219,7 +248,11 @@ impl<'a> StringLiteralDisplay<'a> {
219
248
Some ( result)
220
249
}
221
250
222
- fn generate_sorted_source_code ( & self , elements : & [ & str ] , checker : & Checker ) -> Option < String > {
251
+ fn generate_sorted_source_code (
252
+ & self ,
253
+ elements : & [ & str ] ,
254
+ checker : & Checker ,
255
+ ) -> Option < ( String , CommentComplexity ) > {
223
256
let locator = checker. locator ( ) ;
224
257
225
258
let multiline_classification = if locator. contains_line_break ( self . range ( ) ) {
@@ -238,26 +271,31 @@ impl<'a> StringLiteralDisplay<'a> {
238
271
elements,
239
272
) ?;
240
273
assert_eq ! ( analyzed_sequence. len( ) , self . elts. len( ) ) ;
241
- Some ( analyzed_sequence. into_sorted_source_code (
274
+ let comment_complexity = analyzed_sequence. comment_complexity ( ) ;
275
+ let sorted_code = analyzed_sequence. into_sorted_source_code (
242
276
SORTING_STYLE ,
243
277
locator,
244
278
checker. stylist ( ) ,
245
- ) )
279
+ ) ;
280
+ Some ( ( sorted_code, comment_complexity) )
246
281
}
247
282
// Sorting multiline dicts is unsupported
248
283
( DisplayKind :: Dict { .. } , MultilineClassification :: Multiline ) => None ,
249
284
( DisplayKind :: Sequence ( sequence_kind) , MultilineClassification :: SingleLine ) => {
250
- Some ( sort_single_line_elements_sequence (
285
+ let sorted_code = sort_single_line_elements_sequence (
251
286
* sequence_kind,
252
287
& self . elts ,
253
288
elements,
254
289
locator,
255
290
SORTING_STYLE ,
256
- ) )
291
+ ) ;
292
+ Some ( ( sorted_code, CommentComplexity :: Simple ) )
293
+ }
294
+ ( DisplayKind :: Dict { items } , MultilineClassification :: SingleLine ) => {
295
+ let sorted_code =
296
+ sort_single_line_elements_dict ( & self . elts , elements, items, locator) ;
297
+ Some ( ( sorted_code, CommentComplexity :: Simple ) )
257
298
}
258
- ( DisplayKind :: Dict { items } , MultilineClassification :: SingleLine ) => Some (
259
- sort_single_line_elements_dict ( & self . elts , elements, items, locator) ,
260
- ) ,
261
299
}
262
300
}
263
301
}
0 commit comments