sonicop 26.8.106 → 26.8.107
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +1 -1
- data/Cargo.toml +1 -1
- data/lib/sonicop/version.rb +1 -1
- data/src/directives.rs +2 -2
- data/src/rules/layout/empty_line_after_magic_comment.rs +11 -1
- data/src/rules/layout/empty_line_after_multiline_condition.rs +2 -13
- data/src/rules/layout/empty_lines_after_module_inclusion.rs +2 -12
- data/src/rules/layout/line_length.rs +5 -10
- data/src/rules/layout/multiline_brace.rs +3 -18
- data/src/rules/layout/space_around_operators.rs +9 -18
- data/src/rules/layout/space_inside_array_percent_literal.rs +4 -2
- data/src/rules/layout/space_inside_block_braces.rs +14 -27
- data/src/rules/layout/trailing_empty_lines.rs +7 -1
- data/src/rules/lint/ambiguous_assignment.rs +1 -1
- data/src/rules/lint/comments_help.rs +39 -0
- data/src/rules/lint/constant_reassignment.rs +1 -1
- data/src/rules/lint/cop_directives.rs +1 -26
- data/src/rules/lint/empty_conditional_body.rs +23 -0
- data/src/rules/lint/empty_in_pattern.rs +2 -25
- data/src/rules/lint/empty_when.rs +2 -27
- data/src/rules/lint/missing_super.rs +1 -15
- data/src/rules/lint/mod.rs +2 -0
- data/src/rules/lint/ranges.rs +8 -6
- data/src/rules/lint/unused_arg_corrector.rs +55 -0
- data/src/rules/lint/unused_block_argument.rs +3 -45
- data/src/rules/lint/unused_method_argument.rs +4 -61
- data/src/rules/lint/useless_assignment.rs +2 -1
- data/src/rules/lint/variable_force.rs +1 -45
- data/src/rules/lint/void.rs +9 -7
- data/src/rules/metrics/complexity.rs +2 -1
- data/src/rules/metrics/locals.rs +1 -46
- data/src/rules/mod.rs +3 -1
- data/src/rules/naming/constant_name.rs +2 -1
- data/src/rules/naming/rescued_exceptions_variable_name.rs +2 -1
- data/src/rules/naming/support.rs +1 -35
- data/src/rules/style/collection_compact.rs +7 -1
- data/src/rules/style/hash_syntax.rs +2 -12
- data/src/rules/style/identical_conditional_branches.rs +2 -8
- data/src/rules/style/if_inside_else.rs +4 -10
- data/src/rules/style/if_unless_modifier.rs +10 -30
- data/src/rules/style/keyword_parameters_order.rs +9 -22
- data/src/rules/style/lambda.rs +2 -4
- data/src/rules/style/line_length_help.rs +4 -8
- data/src/rules/style/mod.rs +1 -0
- data/src/rules/style/multiline_method_signature.rs +1 -13
- data/src/rules/style/negated_if_else_condition.rs +2 -13
- data/src/rules/style/nested_parenthesized_calls.rs +19 -4
- data/src/rules/style/nested_ternary_operator.rs +13 -11
- data/src/rules/style/nil_lambda.rs +1 -12
- data/src/rules/style/parens.rs +38 -17
- data/src/rules/style/partition_instead_of_double_select.rs +2 -12
- data/src/rules/style/percent_array.rs +2 -6
- data/src/rules/style/ranges.rs +4 -22
- data/src/rules/style/redundant_begin.rs +9 -16
- data/src/rules/style/redundant_parentheses.rs +29 -25
- data/src/rules/style/safe_navigation.rs +9 -9
- data/src/rules/style/statement_modifier.rs +31 -0
- data/src/rules/style/struct_inheritance.rs +3 -12
- data/src/rules/style/while_until_modifier.rs +2 -22
- data/src/rules/support.rs +226 -14
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5972ef39a175515ffae2b30a2061487cc72c3d3945b58fb0a4277a55ed37517c
|
|
4
|
+
data.tar.gz: 70853c91d098efd59af3442c50ccee948279c76b77a8b2daa0ba66a34a5a3ca1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0cffd8b515ab79c07087248461158a204d92f47c1b88889c4f1ef14581493cff38c83891ed7cd8eac205db5b177292c9ec7ed2a63b2bf876a9cfd024174c5c26
|
|
7
|
+
data.tar.gz: f7d324f0b70eb06b5b12a1f905cb23a4fc8b681fa15b2943e2f6946593e95075b70f68f9f150ce994a90a7c35b94ccecac962c555de232a52a92c4d86f0ef99a
|
data/Cargo.lock
CHANGED
data/Cargo.toml
CHANGED
data/lib/sonicop/version.rb
CHANGED
data/src/directives.rs
CHANGED
|
@@ -334,7 +334,7 @@ fn cop_list(arguments: &str) -> Vec<String> {
|
|
|
334
334
|
|
|
335
335
|
/// The length of the cop name `text` starts with, following `COP_NAME_PATTERN`: one or more
|
|
336
336
|
/// `[A-Za-z]\w+` segments separated by slashes.
|
|
337
|
-
fn cop_name_length(text: &str) -> Option<usize> {
|
|
337
|
+
pub(crate) fn cop_name_length(text: &str) -> Option<usize> {
|
|
338
338
|
let bytes = text.as_bytes();
|
|
339
339
|
let mut index = 0;
|
|
340
340
|
loop {
|
|
@@ -619,7 +619,7 @@ fn commented_out(prefix: &str) -> bool {
|
|
|
619
619
|
prefix.starts_with('#')
|
|
620
620
|
&& prefix[1..]
|
|
621
621
|
.bytes()
|
|
622
|
-
.all(
|
|
622
|
+
.all(crate::rules::support::is_ruby_space)
|
|
623
623
|
}
|
|
624
624
|
|
|
625
625
|
/// A `# rubocop:` comment, parsed the way `RuboCop::DirectiveComment` parses one.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
use crate::diagnostic::{Edit, Offense};
|
|
2
2
|
use crate::magic_comment::MagicComment;
|
|
3
3
|
use crate::rules::RuleContext;
|
|
4
|
+
use crate::rules::support;
|
|
4
5
|
|
|
5
6
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
6
7
|
// RuboCop takes every comment that precedes the first line of code and keeps the *last* magic
|
|
@@ -11,7 +12,16 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
11
12
|
return;
|
|
12
13
|
};
|
|
13
14
|
let next = line_number + 1;
|
|
14
|
-
|
|
15
|
+
// `next_line.strip.empty?`. **`str::trim` would also reach over a no-break space**, which
|
|
16
|
+
// Ruby's `strip` leaves in place: a line holding one is not an empty line, and upstream asks for
|
|
17
|
+
// a real one to be added under the magic comment.
|
|
18
|
+
if next > context.source.line_count()
|
|
19
|
+
|| context
|
|
20
|
+
.source
|
|
21
|
+
.line(next)
|
|
22
|
+
.trim_matches(support::is_ruby_strippable)
|
|
23
|
+
.is_empty()
|
|
24
|
+
{
|
|
15
25
|
return;
|
|
16
26
|
}
|
|
17
27
|
let insertion = context.source.line_start(next);
|
|
@@ -6,6 +6,7 @@ use crate::diagnostic::{Edit, Offense};
|
|
|
6
6
|
use crate::rules::RuleContext;
|
|
7
7
|
use crate::rules::node_ext::NodeExt;
|
|
8
8
|
use crate::rules::send_node::named_children;
|
|
9
|
+
use crate::rules::support;
|
|
9
10
|
|
|
10
11
|
const MSG: &str = "Use empty line after multiline condition.";
|
|
11
12
|
|
|
@@ -139,7 +140,7 @@ fn rescue_branch(node: Node<'_>, context: &RuleContext<'_>) -> Option<Offense> {
|
|
|
139
140
|
|
|
140
141
|
/// The offense, whose correction writes a blank line under the last line of `anchor`.
|
|
141
142
|
fn offense(reported: Node<'_>, anchor: Node<'_>, context: &RuleContext<'_>) -> Offense {
|
|
142
|
-
let lines =
|
|
143
|
+
let lines = support::whole_lines_without_terminator(anchor.byte_range(), context);
|
|
143
144
|
context
|
|
144
145
|
.offense(MSG, reported.byte_range())
|
|
145
146
|
.corrections_anchored_at(lines.clone())
|
|
@@ -184,18 +185,6 @@ fn is_blank(line: usize, context: &RuleContext<'_>) -> bool {
|
|
|
184
185
|
line > context.source.line_count() || context.source.line(line).trim().is_empty()
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
/// `range_by_whole_lines(range)`: the lines the node sits on, without the break that closes them.
|
|
188
|
-
fn whole_lines(range: Range<usize>, context: &RuleContext<'_>) -> Range<usize> {
|
|
189
|
-
let text = context.source.text();
|
|
190
|
-
let start = text[..range.start]
|
|
191
|
-
.rfind('\n')
|
|
192
|
-
.map_or(0, |offset| offset + 1);
|
|
193
|
-
let end = text[range.end..]
|
|
194
|
-
.find('\n')
|
|
195
|
-
.map_or(text.len(), |offset| range.end + offset);
|
|
196
|
-
start..end
|
|
197
|
-
}
|
|
198
|
-
|
|
199
188
|
fn line(offset: usize, context: &RuleContext<'_>) -> usize {
|
|
200
189
|
context.source.line_column(offset).0
|
|
201
190
|
}
|
|
@@ -7,6 +7,7 @@ use crate::directives::directive_header;
|
|
|
7
7
|
use crate::rules::RuleContext;
|
|
8
8
|
use crate::rules::node_ext::NodeExt;
|
|
9
9
|
use crate::rules::send_node::{arguments, is_plain_send, named_children, send_range};
|
|
10
|
+
use crate::rules::support;
|
|
10
11
|
|
|
11
12
|
const MSG: &str = "Add an empty line after module inclusion.";
|
|
12
13
|
|
|
@@ -73,7 +74,7 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
73
74
|
}
|
|
74
75
|
// `autocorrect`: the blank line goes after the inclusion, or after the `rubocop:enable`
|
|
75
76
|
// comment written directly below it.
|
|
76
|
-
let mut anchor =
|
|
77
|
+
let mut anchor = support::whole_lines_without_terminator(send_range(node, context), context);
|
|
77
78
|
let below = context.source.line_column(anchor.end).0 + 1;
|
|
78
79
|
if let Some(comment) = enable_directive_at(below, context) {
|
|
79
80
|
anchor = comment;
|
|
@@ -184,14 +185,3 @@ fn enable_directive_at(line: usize, context: &RuleContext<'_>) -> Option<Range<u
|
|
|
184
185
|
(header.mode == "enable").then(|| comment.clone())
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
/// `range_by_whole_lines(range)`: the lines the node sits on, without the break that closes them.
|
|
188
|
-
fn whole_lines(range: Range<usize>, context: &RuleContext<'_>) -> Range<usize> {
|
|
189
|
-
let text = context.source.text();
|
|
190
|
-
let start = text[..range.start]
|
|
191
|
-
.rfind('\n')
|
|
192
|
-
.map_or(0, |offset| offset + 1);
|
|
193
|
-
let end = text[range.end..]
|
|
194
|
-
.find('\n')
|
|
195
|
-
.map_or(text.len(), |offset| range.end + offset);
|
|
196
|
-
start..end
|
|
197
|
-
}
|
|
@@ -9,6 +9,7 @@ use crate::rules::RuleContext;
|
|
|
9
9
|
use crate::rules::support::Interpolations;
|
|
10
10
|
use crate::source::is_protected;
|
|
11
11
|
use crate::rules::node_ext::NodeExt;
|
|
12
|
+
use crate::rules::support::is_ruby_space_char;
|
|
12
13
|
|
|
13
14
|
/// `Layout/IndentationStyle`'s `IndentationWidth` is unset by default, so RuboCop falls back to
|
|
14
15
|
/// `Layout/IndentationWidth`'s `Width`, which is 2. A cop only ever sees its own configuration
|
|
@@ -117,12 +118,6 @@ fn allow_cop_directives(context: &RuleContext<'_>) -> bool {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
/// Ruby's `\s`, which is ASCII-only. Using `char::is_whitespace` here would let a non-breaking
|
|
121
|
-
/// space end a word that RuboCop keeps going through.
|
|
122
|
-
fn is_ruby_space(character: char) -> bool {
|
|
123
|
-
matches!(character, ' ' | '\t' | '\r' | '\n' | '\u{b}' | '\u{c}')
|
|
124
|
-
}
|
|
125
|
-
|
|
126
121
|
fn indentation_difference(line: &str) -> usize {
|
|
127
122
|
if !line.starts_with('\t') {
|
|
128
123
|
return 0;
|
|
@@ -209,14 +204,14 @@ fn is_endless_method(node: Node<'_>) -> bool {
|
|
|
209
204
|
fn marker_only(prefix: &str) -> bool {
|
|
210
205
|
prefix
|
|
211
206
|
.strip_prefix('#')
|
|
212
|
-
.is_some_and(|rest| rest.chars().all(
|
|
207
|
+
.is_some_and(|rest| rest.chars().all(is_ruby_space_char))
|
|
213
208
|
}
|
|
214
209
|
|
|
215
210
|
fn length_without_directive(line: &str) -> usize {
|
|
216
211
|
DIRECTIVE
|
|
217
212
|
.find(line)
|
|
218
213
|
.map_or(line, |found| &line[..found.start()])
|
|
219
|
-
.trim_end_matches(
|
|
214
|
+
.trim_end_matches(is_ruby_space_char)
|
|
220
215
|
.chars()
|
|
221
216
|
.count()
|
|
222
217
|
}
|
|
@@ -260,8 +255,8 @@ fn extended_end(line: &str, mut end: usize) -> usize {
|
|
|
260
255
|
}
|
|
261
256
|
}
|
|
262
257
|
let rest = &line[end..];
|
|
263
|
-
if rest.starts_with(|character| !
|
|
264
|
-
end += rest.find(
|
|
258
|
+
if rest.starts_with(|character| !is_ruby_space_char(character)) {
|
|
259
|
+
end += rest.find(is_ruby_space_char).unwrap_or(rest.len());
|
|
265
260
|
}
|
|
266
261
|
end
|
|
267
262
|
}
|
|
@@ -11,6 +11,7 @@ use tree_sitter::Node;
|
|
|
11
11
|
use crate::diagnostic::{Edit, Offense};
|
|
12
12
|
use crate::rules::RuleContext;
|
|
13
13
|
use crate::rules::node_ext::NodeExt;
|
|
14
|
+
use crate::rules::support;
|
|
14
15
|
|
|
15
16
|
/// The four sentences one of these cops reports with.
|
|
16
17
|
pub(super) struct Messages {
|
|
@@ -260,28 +261,12 @@ fn line_end(context: &RuleContext<'_>, offset: usize) -> usize {
|
|
|
260
261
|
/// `range_with_surrounding_space(range, side: :left)`: the blanks before `offset`, then the line
|
|
261
262
|
/// breaks before those -- and no further, so a blank line above is left alone.
|
|
262
263
|
fn space_before(text: &str, offset: usize) -> usize {
|
|
263
|
-
|
|
264
|
-
let mut position = offset;
|
|
265
|
-
while position > 0 && matches!(bytes[position - 1], b' ' | b'\t') {
|
|
266
|
-
position -= 1;
|
|
267
|
-
}
|
|
268
|
-
while position > 0 && bytes[position - 1] == b'\n' {
|
|
269
|
-
position -= 1;
|
|
270
|
-
}
|
|
271
|
-
position
|
|
264
|
+
support::final_pos(text, offset, false, false, true, false)
|
|
272
265
|
}
|
|
273
266
|
|
|
274
267
|
/// `range_with_surrounding_space(range, side: :right)`.
|
|
275
268
|
fn space_after(text: &str, offset: usize) -> usize {
|
|
276
|
-
|
|
277
|
-
let mut position = offset;
|
|
278
|
-
while position < bytes.len() && matches!(bytes[position], b' ' | b'\t') {
|
|
279
|
-
position += 1;
|
|
280
|
-
}
|
|
281
|
-
while position < bytes.len() && bytes[position] == b'\n' {
|
|
282
|
-
position += 1;
|
|
283
|
-
}
|
|
284
|
-
position
|
|
269
|
+
support::final_pos(text, offset, true, false, true, false)
|
|
285
270
|
}
|
|
286
271
|
|
|
287
272
|
/// `Node#chained?`: the node is the receiver of the call written around it.
|
|
@@ -17,6 +17,8 @@ use crate::diagnostic::{Edit, Offense};
|
|
|
17
17
|
use crate::ruby_version::RubyVersion;
|
|
18
18
|
use crate::rules::RuleContext;
|
|
19
19
|
use crate::rules::node_ext::NodeExt;
|
|
20
|
+
use crate::rules::support;
|
|
21
|
+
use crate::rules::support::is_ruby_space;
|
|
20
22
|
|
|
21
23
|
/// The node kinds carrying an operator RuboCop checks. `binary` stands in for `on_send`,
|
|
22
24
|
/// `on_binary`, `on_and` and `on_or`, which all reduce to the same shape in tree-sitter.
|
|
@@ -455,22 +457,15 @@ fn check_operator<'src>(
|
|
|
455
457
|
/// either side, plus the line breaks that follow it. `None` stands for the range that begins
|
|
456
458
|
/// with a line break, which `check_operator` refuses to judge.
|
|
457
459
|
fn surrounding_space(source: &str, operator: &Range<usize>) -> Option<Range<usize>> {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if start > 0 &&
|
|
460
|
+
// The left side is `range_with_surrounding_space(side: :left, newlines: false)` and the right is
|
|
461
|
+
// the same with `newlines: true`, **but this cop gives up entirely when a line break sits on the
|
|
462
|
+
// left**: an operator that opens a line is aligned rather than spaced, so the range is no longer
|
|
463
|
+
// the thing to measure. That decision belongs to the cop and stays here.
|
|
464
|
+
let start = support::final_pos(source, operator.start, false, false, false, false);
|
|
465
|
+
if start > 0 && source.as_bytes()[start - 1] == b'\n' {
|
|
464
466
|
return None;
|
|
465
467
|
}
|
|
466
|
-
|
|
467
|
-
while end < bytes.len() && matches!(bytes[end], b' ' | b'\t') {
|
|
468
|
-
end += 1;
|
|
469
|
-
}
|
|
470
|
-
while end < bytes.len() && bytes[end] == b'\n' {
|
|
471
|
-
end += 1;
|
|
472
|
-
}
|
|
473
|
-
Some(start..end)
|
|
468
|
+
Some(start..support::final_pos(source, operator.end, true, false, true, false))
|
|
474
469
|
}
|
|
475
470
|
|
|
476
471
|
fn should_not_have_surrounding_space(
|
|
@@ -533,10 +528,6 @@ fn wrapped_in_space(padded: &str) -> bool {
|
|
|
533
528
|
false
|
|
534
529
|
}
|
|
535
530
|
|
|
536
|
-
fn is_ruby_space(byte: u8) -> bool {
|
|
537
|
-
matches!(byte, b' ' | b'\t' | b'\r' | b'\n' | 0x0b | 0x0c)
|
|
538
|
-
}
|
|
539
|
-
|
|
540
531
|
fn excess_leading_space(
|
|
541
532
|
settings: &Settings,
|
|
542
533
|
alignment: &Alignment<'_>,
|
|
@@ -54,7 +54,7 @@ fn unnecessary_spaces(contents: &str) -> Vec<std::ops::Range<usize>> {
|
|
|
54
54
|
if index - start < 2
|
|
55
55
|
|| bytes
|
|
56
56
|
.get(index)
|
|
57
|
-
.is_none_or(|byte| byte
|
|
57
|
+
.is_none_or(|byte| crate::rules::support::separates_words(*byte))
|
|
58
58
|
{
|
|
59
59
|
continue;
|
|
60
60
|
}
|
|
@@ -72,5 +72,7 @@ fn word_ends_before(bytes: &[u8], start: usize) -> bool {
|
|
|
72
72
|
while position >= 2 && bytes[position - 1] == b' ' && bytes[position - 2] == b'\\' {
|
|
73
73
|
position -= 2;
|
|
74
74
|
}
|
|
75
|
-
position > 0
|
|
75
|
+
position > 0
|
|
76
|
+
&& !crate::rules::support::separates_words(bytes[position - 1])
|
|
77
|
+
&& bytes[position - 1] != b'\\'
|
|
76
78
|
}
|
|
@@ -6,6 +6,8 @@ use super::support::{character_column, parser_node_start};
|
|
|
6
6
|
use crate::diagnostic::{Edit, Offense};
|
|
7
7
|
use crate::rules::RuleContext;
|
|
8
8
|
use crate::rules::node_ext::NodeExt;
|
|
9
|
+
use crate::rules::support;
|
|
10
|
+
use crate::rules::support::is_ruby_space;
|
|
9
11
|
|
|
10
12
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
11
13
|
enum Style {
|
|
@@ -91,7 +93,7 @@ impl Cop<'_, '_> {
|
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
93
95
|
let inner = &self.text()[left.end_byte()..right.start_byte()];
|
|
94
|
-
if inner.bytes().any(|byte| !
|
|
96
|
+
if inner.bytes().any(|byte| !is_ruby_space(byte)) {
|
|
95
97
|
self.braces_with_contents_inside(node, braces, inner, offenses);
|
|
96
98
|
} else if self.empty_style == Style::NoSpace {
|
|
97
99
|
self.offense(
|
|
@@ -127,7 +129,7 @@ impl Cop<'_, '_> {
|
|
|
127
129
|
pipe: Option<Node<'_>>,
|
|
128
130
|
offenses: &mut Vec<Offense>,
|
|
129
131
|
) {
|
|
130
|
-
if inner.bytes().next().is_some_and(|byte| !
|
|
132
|
+
if inner.bytes().next().is_some_and(|byte| !is_ruby_space(byte)) {
|
|
131
133
|
self.no_space_inside_left_brace(left, pipe, offenses);
|
|
132
134
|
} else {
|
|
133
135
|
self.space_inside_left_brace(left, pipe, offenses);
|
|
@@ -192,7 +194,7 @@ impl Cop<'_, '_> {
|
|
|
192
194
|
&& inner
|
|
193
195
|
.bytes()
|
|
194
196
|
.next_back()
|
|
195
|
-
.is_some_and(|byte| !
|
|
197
|
+
.is_some_and(|byte| !is_ruby_space(byte))
|
|
196
198
|
{
|
|
197
199
|
self.no_space(
|
|
198
200
|
right.start_byte(),
|
|
@@ -263,7 +265,7 @@ impl Cop<'_, '_> {
|
|
|
263
265
|
return;
|
|
264
266
|
}
|
|
265
267
|
let source = &self.text()[begin_pos..end_pos];
|
|
266
|
-
let edit = if source.bytes().any(
|
|
268
|
+
let edit = if source.bytes().any(is_ruby_space) {
|
|
267
269
|
Edit {
|
|
268
270
|
start: begin_pos,
|
|
269
271
|
end: end_pos,
|
|
@@ -330,12 +332,6 @@ fn last_line_spaces(inner: &str) -> i64 {
|
|
|
330
332
|
})
|
|
331
333
|
}
|
|
332
334
|
|
|
333
|
-
/// `\s` as Ruby's regexp engine defines it, which is ASCII only -- a non-breaking space is a
|
|
334
|
-
/// character the cop counts as content.
|
|
335
|
-
fn is_space(byte: u8) -> bool {
|
|
336
|
-
matches!(byte, b' ' | b'\t' | b'\r' | b'\n' | 0x0b | 0x0c)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
335
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
340
336
|
enum Direction {
|
|
341
337
|
Left,
|
|
@@ -345,23 +341,14 @@ enum Direction {
|
|
|
345
341
|
/// `RangeHelp#range_with_surrounding_space` with the defaults this cop passes: the run of spaces
|
|
346
342
|
/// and tabs, and then the run of newlines beyond it.
|
|
347
343
|
fn expand_space(text: &str, offset: usize, direction: Direction) -> usize {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
Direction::
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
Direction::Right => position + 1,
|
|
357
|
-
};
|
|
358
|
-
while peek(position).is_some_and(|byte| byte == b' ' || byte == b'\t') {
|
|
359
|
-
position = step(position);
|
|
360
|
-
}
|
|
361
|
-
while peek(position).is_some_and(|byte| byte == b'\n') {
|
|
362
|
-
position = step(position);
|
|
363
|
-
}
|
|
364
|
-
position
|
|
344
|
+
support::final_pos(
|
|
345
|
+
text,
|
|
346
|
+
offset,
|
|
347
|
+
direction == Direction::Right,
|
|
348
|
+
false,
|
|
349
|
+
true,
|
|
350
|
+
false,
|
|
351
|
+
)
|
|
365
352
|
}
|
|
366
353
|
|
|
367
354
|
/// Moves `offset` by `delta` characters. Upstream addresses source by character, so a span it
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
use crate::diagnostic::{Edit, Offense};
|
|
2
2
|
use crate::rules::RuleContext;
|
|
3
|
+
use crate::rules::support;
|
|
3
4
|
|
|
4
5
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
5
6
|
let text = context.source.text();
|
|
@@ -14,7 +15,12 @@ pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
|
14
15
|
// All trailing whitespace, as RuboCop's `/\s*\Z/` takes it -- not just newlines, so a file
|
|
15
16
|
// ending in spaces is measured from where the whitespace starts rather than from the last
|
|
16
17
|
// newline. Only `\n` is counted, leaving carriage returns to `Layout/EndOfLine`.
|
|
17
|
-
|
|
18
|
+
// **`char::is_whitespace` would reach over a no-break space**, which `/\s/` does not match:
|
|
19
|
+
// a last line holding one is not trailing whitespace, and counting it as such reports a blank
|
|
20
|
+
// line upstream does not see -- and this cop's correction would then delete that line.
|
|
21
|
+
let whitespace_start = text
|
|
22
|
+
.trim_end_matches(support::is_ruby_space_char)
|
|
23
|
+
.len();
|
|
18
24
|
let whitespace = &text[whitespace_start..];
|
|
19
25
|
let blank_lines = whitespace.matches('\n').count() as isize - 1;
|
|
20
26
|
if blank_lines == wanted_blank_lines {
|
|
@@ -3,8 +3,8 @@ use tree_sitter::Node;
|
|
|
3
3
|
use crate::diagnostic::Offense;
|
|
4
4
|
use crate::rules::RuleContext;
|
|
5
5
|
use crate::rules::node_ext::NodeExt;
|
|
6
|
+
use crate::rules::support::spurious_assignment_list;
|
|
6
7
|
|
|
7
|
-
use super::variable_force::spurious_assignment_list;
|
|
8
8
|
|
|
9
9
|
/// `MISTAKES`: the operator each two-character run was probably meant to be.
|
|
10
10
|
const MISTAKES: [(u8, &str); 4] = [(b'-', "-="), (b'+', "+="), (b'*', "*="), (b'!', "!=")];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//! `CommentsHelp`: the lines a branch owns, which is where a cop looks for the comment that
|
|
2
|
+
//! excuses an empty one.
|
|
3
|
+
//!
|
|
4
|
+
//! Upstream is one mixin included by both `Lint/EmptyWhen` and `Lint/EmptyInPattern`, so the two
|
|
5
|
+
//! read the same lines for `when` and for `in`.
|
|
6
|
+
|
|
7
|
+
use std::ops::Range;
|
|
8
|
+
|
|
9
|
+
use tree_sitter::Node;
|
|
10
|
+
|
|
11
|
+
use crate::rules::RuleContext;
|
|
12
|
+
use crate::rules::node_ext::NodeExt;
|
|
13
|
+
|
|
14
|
+
/// `find_end_line`: a `when` runs up to the line the next branch starts on, or to the `end` of the
|
|
15
|
+
/// `case` when it is the last. The range excludes that line, which is why the keyword itself never
|
|
16
|
+
/// counts as a comment of the branch before it.
|
|
17
|
+
pub(super) fn comment_search_lines(
|
|
18
|
+
context: &RuleContext<'_>,
|
|
19
|
+
case: Node<'_>,
|
|
20
|
+
children: &[Node<'_>],
|
|
21
|
+
index: usize,
|
|
22
|
+
) -> Range<usize> {
|
|
23
|
+
let branch = children[index];
|
|
24
|
+
let (start, _) = context.source.line_column(branch.start_byte());
|
|
25
|
+
let end = children[index + 1..]
|
|
26
|
+
.iter()
|
|
27
|
+
.find(|sibling| sibling.kind_str() != "comment")
|
|
28
|
+
.map_or_else(
|
|
29
|
+
|| {
|
|
30
|
+
// `parent.loc.end.line`, the `end` keyword closing the `case`.
|
|
31
|
+
let (line, _) = context
|
|
32
|
+
.source
|
|
33
|
+
.line_column(case.end_byte().saturating_sub(1));
|
|
34
|
+
line
|
|
35
|
+
},
|
|
36
|
+
|next| context.source.line_column(next.start_byte()).0,
|
|
37
|
+
);
|
|
38
|
+
start..end
|
|
39
|
+
}
|
|
@@ -6,8 +6,8 @@ use crate::diagnostic::Offense;
|
|
|
6
6
|
use crate::rules::RuleContext;
|
|
7
7
|
use crate::rules::node_ext::NodeExt;
|
|
8
8
|
use crate::rules::send_node::{arguments, named_children, string_text, symbol_name};
|
|
9
|
+
use crate::rules::support::spurious_assignment_list;
|
|
9
10
|
|
|
10
|
-
use super::variable_force::spurious_assignment_list;
|
|
11
11
|
|
|
12
12
|
/// The parts of a constant path, as `each_path` walks them.
|
|
13
13
|
struct ConstantPath {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
use std::ops::Range;
|
|
14
14
|
|
|
15
|
+
use crate::directives::cop_name_length;
|
|
15
16
|
use crate::rules::RuleContext;
|
|
16
17
|
|
|
17
18
|
/// The departments a cop name can be shortened to. `cop_registry.department?` answers from the
|
|
@@ -140,29 +141,3 @@ fn next_is_name_character(text: &str, index: usize) -> bool {
|
|
|
140
141
|
.get(index)
|
|
141
142
|
.is_some_and(|byte| byte.is_ascii_alphanumeric() || *byte == b'_')
|
|
142
143
|
}
|
|
143
|
-
|
|
144
|
-
/// `COP_NAME_PATTERN`: one or more `[A-Za-z]\w+` segments joined by slashes.
|
|
145
|
-
fn cop_name_length(text: &str) -> Option<usize> {
|
|
146
|
-
let bytes = text.as_bytes();
|
|
147
|
-
let mut index = 0;
|
|
148
|
-
loop {
|
|
149
|
-
let start = index;
|
|
150
|
-
if !bytes.get(index).is_some_and(u8::is_ascii_alphabetic) {
|
|
151
|
-
return None;
|
|
152
|
-
}
|
|
153
|
-
index += 1;
|
|
154
|
-
while bytes
|
|
155
|
-
.get(index)
|
|
156
|
-
.is_some_and(|byte| byte.is_ascii_alphanumeric() || *byte == b'_')
|
|
157
|
-
{
|
|
158
|
-
index += 1;
|
|
159
|
-
}
|
|
160
|
-
if index - start < 2 {
|
|
161
|
-
return None;
|
|
162
|
-
}
|
|
163
|
-
if bytes.get(index) != Some(&b'/') {
|
|
164
|
-
return Some(index);
|
|
165
|
-
}
|
|
166
|
-
index += 1;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
@@ -132,6 +132,16 @@ fn contains_comments(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
|
132
132
|
let end = match node.field("alternative") {
|
|
133
133
|
// `find_end_line`: the `else` or `elsif` that follows ends the span.
|
|
134
134
|
Some(clause) => context.source.line_column(clause.start_byte()).0,
|
|
135
|
+
// `find_end_line`'s `elsif?` branch: `node.each_ancestor(:if).find(&:if?).loc.end.line`.
|
|
136
|
+
// An `elsif` with nothing after it owns the lines up to the `end` that closes the whole
|
|
137
|
+
// conditional, and that `end` belongs to the `if` above it. The grammar stops the `elsif`
|
|
138
|
+
// node at its own last statement, so reading its end instead leaves an empty span and the
|
|
139
|
+
// comment that excuses the branch is never seen.
|
|
140
|
+
None if node.kind_str() == "elsif" => enclosing_end(node, context)
|
|
141
|
+
.map_or_else(
|
|
142
|
+
|| context.source.line_column(node.end_byte()).0,
|
|
143
|
+
|keyword| context.source.line_column(keyword.start_byte()).0,
|
|
144
|
+
),
|
|
135
145
|
None => context.source.line_column(node.end_byte()).0,
|
|
136
146
|
};
|
|
137
147
|
context.comment_ranges().iter().any(|comment| {
|
|
@@ -140,6 +150,19 @@ fn contains_comments(node: Node<'_>, context: &RuleContext<'_>) -> bool {
|
|
|
140
150
|
})
|
|
141
151
|
}
|
|
142
152
|
|
|
153
|
+
/// The `end` closing the conditional an `elsif` sits in, which is the `if` above it. A chain of
|
|
154
|
+
/// `elsif` nests, so the walk passes any number of them before it reaches the `if`.
|
|
155
|
+
fn enclosing_end<'tree>(node: Node<'tree>, context: &RuleContext<'_>) -> Option<Node<'tree>> {
|
|
156
|
+
let mut current = node.parent();
|
|
157
|
+
while let Some(ancestor) = current {
|
|
158
|
+
if ancestor.kind_str() == "if" {
|
|
159
|
+
return end_keyword(ancestor, context);
|
|
160
|
+
}
|
|
161
|
+
current = ancestor.parent();
|
|
162
|
+
}
|
|
163
|
+
None
|
|
164
|
+
}
|
|
165
|
+
|
|
143
166
|
/// `node.loc.begin`: the `then` or `;` the branch was introduced with. The grammar puts a `then`
|
|
144
167
|
/// inside the branch it opens, but a `;` stays beside the condition -- and a branch holding nothing
|
|
145
168
|
/// is no node at all, which leaves the `;` as the only trace of it.
|
|
@@ -6,6 +6,8 @@ use crate::diagnostic::Offense;
|
|
|
6
6
|
use crate::directives::DirectiveState;
|
|
7
7
|
use crate::rules::RuleContext;
|
|
8
8
|
use crate::rules::node_ext::NodeExt;
|
|
9
|
+
|
|
10
|
+
use super::comments_help::comment_search_lines;
|
|
9
11
|
use crate::ruby_version::RubyVersion;
|
|
10
12
|
|
|
11
13
|
use super::statements::statements;
|
|
@@ -64,31 +66,6 @@ fn allowed_by_comments(
|
|
|
64
66
|
has_comment && !comments_contain_disables(context, &lines)
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
/// `find_end_line`: a branch runs up to the line the next one starts on, or to the `end` of the
|
|
68
|
-
/// `case` when it is the last. The range excludes that line.
|
|
69
|
-
fn comment_search_lines(
|
|
70
|
-
context: &RuleContext<'_>,
|
|
71
|
-
case: Node<'_>,
|
|
72
|
-
children: &[Node<'_>],
|
|
73
|
-
index: usize,
|
|
74
|
-
) -> Range<usize> {
|
|
75
|
-
let branch = children[index];
|
|
76
|
-
let (start, _) = context.source.line_column(branch.start_byte());
|
|
77
|
-
let end = children[index + 1..]
|
|
78
|
-
.iter()
|
|
79
|
-
.find(|sibling| sibling.kind_str() != "comment")
|
|
80
|
-
.map_or_else(
|
|
81
|
-
|| {
|
|
82
|
-
let (line, _) = context
|
|
83
|
-
.source
|
|
84
|
-
.line_column(case.end_byte().saturating_sub(1));
|
|
85
|
-
line
|
|
86
|
-
},
|
|
87
|
-
|next| context.source.line_column(next.start_byte()).0,
|
|
88
|
-
);
|
|
89
|
-
start..end
|
|
90
|
-
}
|
|
91
|
-
|
|
92
69
|
fn comments_contain_disables(context: &RuleContext<'_>, lines: &Range<usize>) -> bool {
|
|
93
70
|
let directives = DirectiveState::parse(context.source, context.comment_ranges());
|
|
94
71
|
directives
|
|
@@ -7,6 +7,8 @@ use crate::directives::DirectiveState;
|
|
|
7
7
|
use crate::rules::RuleContext;
|
|
8
8
|
use crate::rules::node_ext::NodeExt;
|
|
9
9
|
|
|
10
|
+
use super::comments_help::comment_search_lines;
|
|
11
|
+
|
|
10
12
|
const MSG: &str = "Avoid `when` branches without a body.";
|
|
11
13
|
|
|
12
14
|
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
@@ -61,33 +63,6 @@ fn allowed_by_comments(
|
|
|
61
63
|
has_comment && !comments_contain_disables(context, &lines)
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
/// `find_end_line`: a `when` runs up to the line the next branch starts on, or to the `end` of the
|
|
65
|
-
/// `case` when it is the last. The range excludes that line, which is why the keyword itself never
|
|
66
|
-
/// counts as a comment of the branch before it.
|
|
67
|
-
fn comment_search_lines(
|
|
68
|
-
context: &RuleContext<'_>,
|
|
69
|
-
case: Node<'_>,
|
|
70
|
-
children: &[Node<'_>],
|
|
71
|
-
index: usize,
|
|
72
|
-
) -> Range<usize> {
|
|
73
|
-
let branch = children[index];
|
|
74
|
-
let (start, _) = context.source.line_column(branch.start_byte());
|
|
75
|
-
let end = children[index + 1..]
|
|
76
|
-
.iter()
|
|
77
|
-
.find(|sibling| sibling.kind_str() != "comment")
|
|
78
|
-
.map_or_else(
|
|
79
|
-
|| {
|
|
80
|
-
// `parent.loc.end.line`, the `end` keyword closing the `case`.
|
|
81
|
-
let (line, _) = context
|
|
82
|
-
.source
|
|
83
|
-
.line_column(case.end_byte().saturating_sub(1));
|
|
84
|
-
line
|
|
85
|
-
},
|
|
86
|
-
|next| context.source.line_column(next.start_byte()).0,
|
|
87
|
-
);
|
|
88
|
-
start..end
|
|
89
|
-
}
|
|
90
|
-
|
|
91
66
|
/// `comments_contain_disables?`: whether a `rubocop:disable` of this cop overlaps the branch. The
|
|
92
67
|
/// directives are read only here, where a branch already turned out to be empty and commented.
|
|
93
68
|
fn comments_contain_disables(context: &RuleContext<'_>, lines: &Range<usize>) -> bool {
|