sonicop 26.8.101
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 +7 -0
- data/CONFORMANCE.md +109 -0
- data/Cargo.lock +975 -0
- data/Cargo.toml +40 -0
- data/LICENSE +21 -0
- data/NOTICE +7 -0
- data/README.ja.md +161 -0
- data/README.md +173 -0
- data/config/default.yml +6305 -0
- data/exe/sonicop +7 -0
- data/ext/sonicop/extconf.rb +38 -0
- data/lib/sonicop/runner.rb +67 -0
- data/lib/sonicop/version.rb +6 -0
- data/lib/sonicop.rb +8 -0
- data/licenses/RUBOCOP.txt +20 -0
- data/licenses/TREE_SITTER_RUBY.txt +21 -0
- data/src/cli.rs +940 -0
- data/src/config/inheritance.rs +332 -0
- data/src/config/loader.rs +117 -0
- data/src/config/mod.rs +461 -0
- data/src/config/paths.rs +436 -0
- data/src/config/plugin.rs +247 -0
- data/src/config/store.rs +141 -0
- data/src/cop_name.rs +71 -0
- data/src/diagnostic.rs +205 -0
- data/src/directives.rs +305 -0
- data/src/engine.rs +734 -0
- data/src/formatter.rs +684 -0
- data/src/lib.rs +42 -0
- data/src/main.rs +3 -0
- data/src/ruby_version.rs +393 -0
- data/src/rules/layout/empty_line_after_magic_comment.rs +49 -0
- data/src/rules/layout/end_of_line.rs +35 -0
- data/src/rules/layout/line_length.rs +167 -0
- data/src/rules/layout/mod.rs +13 -0
- data/src/rules/layout/space_after_comma.rs +32 -0
- data/src/rules/layout/space_around_operators.rs +104 -0
- data/src/rules/layout/space_inside_parens.rs +100 -0
- data/src/rules/layout/support.rs +23 -0
- data/src/rules/layout/trailing_empty_lines.rs +38 -0
- data/src/rules/layout/trailing_whitespace.rs +26 -0
- data/src/rules/lint/duplicate_methods.rs +72 -0
- data/src/rules/lint/mod.rs +7 -0
- data/src/rules/lint/syntax.rs +200 -0
- data/src/rules/lint/unused_block_argument.rs +74 -0
- data/src/rules/lint/useless_assignment.rs +104 -0
- data/src/rules/metrics/block_length.rs +39 -0
- data/src/rules/metrics/class_length.rs +34 -0
- data/src/rules/metrics/method_length.rs +10 -0
- data/src/rules/metrics/mod.rs +10 -0
- data/src/rules/metrics/module_length.rs +17 -0
- data/src/rules/metrics/parameter_lists.rs +21 -0
- data/src/rules/metrics/support.rs +105 -0
- data/src/rules/mod.rs +380 -0
- data/src/rules/naming/ascii_identifiers.rs +17 -0
- data/src/rules/naming/constant_name.rs +49 -0
- data/src/rules/naming/method_name.rs +51 -0
- data/src/rules/naming/mod.rs +9 -0
- data/src/rules/naming/support.rs +22 -0
- data/src/rules/naming/variable_name.rs +50 -0
- data/src/rules/security/eval.rs +170 -0
- data/src/rules/security/mod.rs +6 -0
- data/src/rules/style/frozen_string_literal_comment.rs +56 -0
- data/src/rules/style/hash_syntax.rs +63 -0
- data/src/rules/style/mod.rs +9 -0
- data/src/rules/style/numeric_literals.rs +59 -0
- data/src/rules/style/redundant_return.rs +143 -0
- data/src/rules/style/semicolon.rs +48 -0
- data/src/rules/style/string_literals.rs +74 -0
- data/src/rules/support.rs +31 -0
- data/src/source.rs +118 -0
- metadata +117 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
use std::collections::HashSet;
|
|
2
|
+
|
|
3
|
+
use tree_sitter::Node;
|
|
4
|
+
|
|
5
|
+
use crate::diagnostic::{Edit, Offense};
|
|
6
|
+
use crate::rules::{RuleContext, first_identifier, walk_named};
|
|
7
|
+
|
|
8
|
+
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
9
|
+
let ignore_empty: bool = context.setting("IgnoreEmptyBlocks").unwrap_or(true);
|
|
10
|
+
for node in context.nodes_of_any(&["block", "do_block"]) {
|
|
11
|
+
let (Some(parameters), Some(body)) = (
|
|
12
|
+
node.child_by_field_name("parameters"),
|
|
13
|
+
node.child_by_field_name("body"),
|
|
14
|
+
) else {
|
|
15
|
+
continue;
|
|
16
|
+
};
|
|
17
|
+
if ignore_empty && context.source.node_text(body).trim().is_empty() {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let mut parameter_nodes = Vec::new();
|
|
22
|
+
let mut seen = HashSet::new();
|
|
23
|
+
let mut cursor = parameters.walk();
|
|
24
|
+
for parameter in parameters.named_children(&mut cursor) {
|
|
25
|
+
if parameter.kind() == "identifier" {
|
|
26
|
+
if seen.insert(parameter.start_byte()) {
|
|
27
|
+
parameter_nodes.push(parameter);
|
|
28
|
+
}
|
|
29
|
+
} else if let Some(identifier) = first_identifier(parameter)
|
|
30
|
+
&& seen.insert(identifier.start_byte())
|
|
31
|
+
{
|
|
32
|
+
parameter_nodes.push(identifier);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for parameter in parameter_nodes {
|
|
37
|
+
let name = context.source.node_text(parameter);
|
|
38
|
+
if name.starts_with('_') || identifier_used(body, name, parameter, context) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
offenses.push(
|
|
42
|
+
context
|
|
43
|
+
.offense(
|
|
44
|
+
format!("Unused block argument - `{name}`. If it's necessary, use `_` or `_name` as an argument name."),
|
|
45
|
+
parameter.byte_range(),
|
|
46
|
+
)
|
|
47
|
+
.corrected_by(Edit {
|
|
48
|
+
start: parameter.start_byte(),
|
|
49
|
+
end: parameter.start_byte(),
|
|
50
|
+
replacement: "_".to_owned(),
|
|
51
|
+
safe: true,
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn identifier_used(
|
|
59
|
+
body: Node<'_>,
|
|
60
|
+
name: &str,
|
|
61
|
+
definition: Node<'_>,
|
|
62
|
+
context: &RuleContext<'_>,
|
|
63
|
+
) -> bool {
|
|
64
|
+
let mut used = false;
|
|
65
|
+
walk_named(body, &mut |candidate| {
|
|
66
|
+
if candidate.kind() == "identifier"
|
|
67
|
+
&& candidate.byte_range() != definition.byte_range()
|
|
68
|
+
&& context.source.node_text(candidate) == name
|
|
69
|
+
{
|
|
70
|
+
used = true;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
used
|
|
74
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
use tree_sitter::Node;
|
|
2
|
+
|
|
3
|
+
use crate::diagnostic::{Edit, Offense};
|
|
4
|
+
use crate::rules::{RuleContext, walk_named};
|
|
5
|
+
|
|
6
|
+
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
7
|
+
let root = context.root_node();
|
|
8
|
+
for assignment in context.nodes_of("assignment") {
|
|
9
|
+
let Some(left) = assignment.child_by_field_name("left") else {
|
|
10
|
+
continue;
|
|
11
|
+
};
|
|
12
|
+
if left.kind() != "identifier" {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
let name = context.source.node_text(left);
|
|
16
|
+
if name.starts_with('_') {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if assignment.parent().is_some_and(|parent| {
|
|
20
|
+
parent.kind() == "assignment"
|
|
21
|
+
&& parent
|
|
22
|
+
.child_by_field_name("right")
|
|
23
|
+
.is_some_and(|right| right.byte_range() == assignment.byte_range())
|
|
24
|
+
}) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
let scope = enclosing_scope(assignment).unwrap_or(root);
|
|
28
|
+
if has_other_read(scope, assignment, name, context) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
offenses.push(
|
|
32
|
+
context
|
|
33
|
+
.offense(
|
|
34
|
+
format!("Useless assignment to variable - `{name}`."),
|
|
35
|
+
left.byte_range(),
|
|
36
|
+
)
|
|
37
|
+
.corrected_by(Edit {
|
|
38
|
+
start: assignment.start_byte(),
|
|
39
|
+
end: assignment
|
|
40
|
+
.child_by_field_name("right")
|
|
41
|
+
.map_or(left.end_byte(), |right| right.start_byte()),
|
|
42
|
+
replacement: String::new(),
|
|
43
|
+
safe: true,
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fn enclosing_scope(mut node: Node<'_>) -> Option<Node<'_>> {
|
|
50
|
+
while let Some(parent) = node.parent() {
|
|
51
|
+
if matches!(parent.kind(), "method" | "singleton_method") {
|
|
52
|
+
return Some(parent);
|
|
53
|
+
}
|
|
54
|
+
node = parent;
|
|
55
|
+
}
|
|
56
|
+
None
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fn has_other_read(
|
|
60
|
+
scope: Node<'_>,
|
|
61
|
+
assignment: Node<'_>,
|
|
62
|
+
name: &str,
|
|
63
|
+
context: &RuleContext<'_>,
|
|
64
|
+
) -> bool {
|
|
65
|
+
let mut found = false;
|
|
66
|
+
walk_named(scope, &mut |candidate| {
|
|
67
|
+
if found {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if candidate.kind() == "pair"
|
|
71
|
+
&& context.source.node_text(candidate).trim() == format!("{name}:")
|
|
72
|
+
{
|
|
73
|
+
found = true;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if candidate.kind() == "call"
|
|
77
|
+
&& candidate
|
|
78
|
+
.child_by_field_name("method")
|
|
79
|
+
.is_some_and(|method| context.source.node_text(method) == "binding")
|
|
80
|
+
{
|
|
81
|
+
found = true;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if candidate.kind() == "identifier" && context.source.node_text(candidate) == "binding" {
|
|
85
|
+
found = true;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if candidate.kind() != "identifier" || context.source.node_text(candidate) != name {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
let is_this_assignment = candidate.byte_range()
|
|
92
|
+
== assignment
|
|
93
|
+
.child_by_field_name("left")
|
|
94
|
+
.map_or(assignment.byte_range(), |left| left.byte_range());
|
|
95
|
+
let is_write = candidate.parent().is_some_and(|parent| {
|
|
96
|
+
parent.kind() == "assignment"
|
|
97
|
+
&& parent
|
|
98
|
+
.child_by_field_name("left")
|
|
99
|
+
.is_some_and(|left| left.byte_range() == candidate.byte_range())
|
|
100
|
+
});
|
|
101
|
+
found = !is_this_assignment && !is_write;
|
|
102
|
+
});
|
|
103
|
+
found
|
|
104
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
use tree_sitter::Node;
|
|
2
|
+
|
|
3
|
+
use super::support::{LengthTarget, report_length};
|
|
4
|
+
use crate::diagnostic::Offense;
|
|
5
|
+
use crate::rules::RuleContext;
|
|
6
|
+
|
|
7
|
+
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
8
|
+
let max: usize = context.setting("Max").unwrap_or(25);
|
|
9
|
+
let allowed: Vec<String> = context.setting("AllowedMethods").unwrap_or_default();
|
|
10
|
+
for node in context.nodes_of_any(&["block", "do_block"]) {
|
|
11
|
+
if block_method_allowed(node, context, &allowed) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
report_length(context, offenses, node, max, "Block", LengthTarget::Block);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// `Class.new`/`Struct.new` bodies are class definitions in disguise, which RuboCop measures with
|
|
19
|
+
/// `Metrics/ClassLength` instead.
|
|
20
|
+
fn block_method_allowed(node: Node<'_>, context: &RuleContext<'_>, allowed: &[String]) -> bool {
|
|
21
|
+
let Some(call) = node.parent().filter(|parent| parent.kind() == "call") else {
|
|
22
|
+
return false;
|
|
23
|
+
};
|
|
24
|
+
let Some(method) = call.child_by_field_name("method") else {
|
|
25
|
+
return false;
|
|
26
|
+
};
|
|
27
|
+
if context.source.node_text(method) == "new"
|
|
28
|
+
&& call
|
|
29
|
+
.child_by_field_name("receiver")
|
|
30
|
+
.is_some_and(|receiver| {
|
|
31
|
+
matches!(context.source.node_text(receiver), "Class" | "Struct")
|
|
32
|
+
})
|
|
33
|
+
{
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
allowed
|
|
37
|
+
.iter()
|
|
38
|
+
.any(|name| name == context.source.node_text(method))
|
|
39
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
use tree_sitter::Node;
|
|
2
|
+
|
|
3
|
+
use super::support::{LengthTarget, report_length};
|
|
4
|
+
use crate::diagnostic::Offense;
|
|
5
|
+
use crate::rules::RuleContext;
|
|
6
|
+
|
|
7
|
+
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
8
|
+
let max: usize = context.setting("Max").unwrap_or(100);
|
|
9
|
+
for node in context.nodes_of_any(&["class", "singleton_class"]) {
|
|
10
|
+
// A `class << self` inside a class body is part of that class's length rather than a
|
|
11
|
+
// class of its own.
|
|
12
|
+
if node.kind() == "singleton_class" && has_class_ancestor(node) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
report_length(
|
|
16
|
+
context,
|
|
17
|
+
offenses,
|
|
18
|
+
node,
|
|
19
|
+
max,
|
|
20
|
+
"Class",
|
|
21
|
+
LengthTarget::Classlike,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fn has_class_ancestor(mut node: Node<'_>) -> bool {
|
|
27
|
+
while let Some(parent) = node.parent() {
|
|
28
|
+
if parent.kind() == "class" {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
node = parent;
|
|
32
|
+
}
|
|
33
|
+
false
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
use super::support::{LengthTarget, report_length};
|
|
2
|
+
use crate::diagnostic::Offense;
|
|
3
|
+
use crate::rules::RuleContext;
|
|
4
|
+
|
|
5
|
+
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
6
|
+
let max: usize = context.setting("Max").unwrap_or(10);
|
|
7
|
+
for node in context.nodes_of_any(&["method", "singleton_method"]) {
|
|
8
|
+
report_length(context, offenses, node, max, "Method", LengthTarget::Method);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
department_rules! {
|
|
2
|
+
"Metrics";
|
|
3
|
+
block_length => ("BlockLength", Convention),
|
|
4
|
+
class_length => ("ClassLength", Convention),
|
|
5
|
+
method_length => ("MethodLength", Convention),
|
|
6
|
+
module_length => ("ModuleLength", Convention),
|
|
7
|
+
parameter_lists => ("ParameterLists", Convention),
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
mod support;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
use super::support::{LengthTarget, report_length};
|
|
2
|
+
use crate::diagnostic::Offense;
|
|
3
|
+
use crate::rules::RuleContext;
|
|
4
|
+
|
|
5
|
+
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
6
|
+
let max: usize = context.setting("Max").unwrap_or(100);
|
|
7
|
+
for node in context.nodes_of("module") {
|
|
8
|
+
report_length(
|
|
9
|
+
context,
|
|
10
|
+
offenses,
|
|
11
|
+
node,
|
|
12
|
+
max,
|
|
13
|
+
"Module",
|
|
14
|
+
LengthTarget::Classlike,
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
use crate::diagnostic::Offense;
|
|
2
|
+
use crate::rules::RuleContext;
|
|
3
|
+
|
|
4
|
+
pub(super) fn check(context: &RuleContext<'_>, offenses: &mut Vec<Offense>) {
|
|
5
|
+
let max: usize = context.setting("Max").unwrap_or(5);
|
|
6
|
+
let count_keywords: bool = context.setting("CountKeywordArgs").unwrap_or(true);
|
|
7
|
+
for node in context.nodes_of("method_parameters") {
|
|
8
|
+
let mut cursor = node.walk();
|
|
9
|
+
let count = node
|
|
10
|
+
.named_children(&mut cursor)
|
|
11
|
+
.filter(|parameter| count_keywords || parameter.kind() != "keyword_parameter")
|
|
12
|
+
.count();
|
|
13
|
+
if count <= max {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
offenses.push(context.offense(
|
|
17
|
+
format!("Avoid parameter lists longer than {max} parameters. [{count}/{max}]"),
|
|
18
|
+
node.byte_range(),
|
|
19
|
+
));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
//! Line counting shared by the length cops.
|
|
2
|
+
|
|
3
|
+
use std::collections::HashSet;
|
|
4
|
+
|
|
5
|
+
use tree_sitter::Node;
|
|
6
|
+
|
|
7
|
+
use crate::diagnostic::Offense;
|
|
8
|
+
use crate::rules::{RuleContext, walk_named};
|
|
9
|
+
|
|
10
|
+
/// What kind of construct a length cop measures. The three differ in how the body is counted and
|
|
11
|
+
/// where the offense is reported, so naming the kind keeps those differences in one place instead
|
|
12
|
+
/// of spreading cop-name comparisons through the counting code.
|
|
13
|
+
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
14
|
+
pub(super) enum LengthTarget {
|
|
15
|
+
/// A method, counted over its body.
|
|
16
|
+
Method,
|
|
17
|
+
/// A class or module, counted over its interior with nested classes and modules removed.
|
|
18
|
+
Classlike,
|
|
19
|
+
/// A block, reported against the call that owns it.
|
|
20
|
+
Block,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Reports `node` when it holds more than `max` lines of code, in the shape RuboCop's length cops
|
|
24
|
+
/// use: `Method has too many lines. [12/10]`.
|
|
25
|
+
pub(super) fn report_length(
|
|
26
|
+
context: &RuleContext<'_>,
|
|
27
|
+
offenses: &mut Vec<Offense>,
|
|
28
|
+
node: Node<'_>,
|
|
29
|
+
max: usize,
|
|
30
|
+
label: &str,
|
|
31
|
+
target: LengthTarget,
|
|
32
|
+
) {
|
|
33
|
+
let count_comments: bool = context.setting("CountComments").unwrap_or(false);
|
|
34
|
+
let Some(body) = node.child_by_field_name("body") else {
|
|
35
|
+
return;
|
|
36
|
+
};
|
|
37
|
+
let mut length = if target == LengthTarget::Classlike {
|
|
38
|
+
classlike_code_line_count(node, context, count_comments)
|
|
39
|
+
} else {
|
|
40
|
+
code_line_count(body, context, count_comments)
|
|
41
|
+
};
|
|
42
|
+
// A block whose whole body is one heredoc spends a line on the heredoc opener, which RuboCop
|
|
43
|
+
// attributes to the enclosing statement rather than to the block.
|
|
44
|
+
if target == LengthTarget::Block
|
|
45
|
+
&& body.named_child_count() == 1
|
|
46
|
+
&& context.heredoc_count(body.byte_range()) > 0
|
|
47
|
+
{
|
|
48
|
+
length = length.saturating_sub(1);
|
|
49
|
+
}
|
|
50
|
+
if length <= max {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
let location = if target == LengthTarget::Block {
|
|
54
|
+
node.parent()
|
|
55
|
+
.filter(|parent| parent.kind() == "call")
|
|
56
|
+
.unwrap_or(node)
|
|
57
|
+
} else {
|
|
58
|
+
node
|
|
59
|
+
};
|
|
60
|
+
offenses.push(context.offense(
|
|
61
|
+
format!("{label} has too many lines. [{length}/{max}]"),
|
|
62
|
+
location.byte_range(),
|
|
63
|
+
));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fn classlike_code_line_count(
|
|
67
|
+
node: Node<'_>,
|
|
68
|
+
context: &RuleContext<'_>,
|
|
69
|
+
count_comments: bool,
|
|
70
|
+
) -> usize {
|
|
71
|
+
let mut excluded_lines = HashSet::new();
|
|
72
|
+
walk_named(node, &mut |descendant| {
|
|
73
|
+
if descendant == node || !matches!(descendant.kind(), "class" | "module") {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
let first = descendant.start_position().row + 1;
|
|
77
|
+
let last = descendant.end_position().row + 1;
|
|
78
|
+
excluded_lines.extend(first..=last);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// RuboCop's ProcessedSource is indexed from zero after constructing the
|
|
82
|
+
// one-based interior line range. Preserve that observable offset exactly.
|
|
83
|
+
let start = node.start_position().row + 2;
|
|
84
|
+
let end = node.end_position().row;
|
|
85
|
+
(start..=end)
|
|
86
|
+
.filter(|line| {
|
|
87
|
+
if excluded_lines.contains(line) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
let text = context.source.line(*line + 1).trim();
|
|
91
|
+
!text.is_empty() && (count_comments || !text.starts_with('#'))
|
|
92
|
+
})
|
|
93
|
+
.count()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn code_line_count(node: Node<'_>, context: &RuleContext<'_>, count_comments: bool) -> usize {
|
|
97
|
+
let start = node.start_position().row + 1;
|
|
98
|
+
let end = node.end_position().row + 1;
|
|
99
|
+
(start..=end)
|
|
100
|
+
.filter(|line| {
|
|
101
|
+
let text = context.source.line(*line).trim();
|
|
102
|
+
!text.is_empty() && (count_comments || !text.starts_with('#'))
|
|
103
|
+
})
|
|
104
|
+
.count()
|
|
105
|
+
}
|