Skip to content

Commit 01074d8

Browse files
committed
fix(linter): narrow annotation spans to highlight minimal code regions
closes #745 Signed-off-by: azjezz <[email protected]>
1 parent 488c039 commit 01074d8

File tree

5 files changed

+34
-15
lines changed

5 files changed

+34
-15
lines changed

crates/linter/src/rule/correctness/strict_types.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,21 @@ impl LintRule for StrictTypesRule {
142142
}
143143

144144
if !found {
145+
let annotation_span = program
146+
.statements
147+
.iter()
148+
.find_map(|s| match s {
149+
Statement::OpeningTag(tag) => Some(tag.span()),
150+
_ => None,
151+
})
152+
.unwrap_or_else(|| program.span());
153+
145154
let issue = Issue::new(
146155
self.cfg.level(),
147156
"Missing `declare(strict_types=1);` statement at the beginning of the file.",
148157
)
149158
.with_code(self.meta.code)
150-
.with_annotation(Annotation::primary(program.span()))
159+
.with_annotation(Annotation::primary(annotation_span))
151160
.with_note("The `strict_types` directive enforces strict type checking, which can prevent subtle bugs.")
152161
.with_help("Add `declare(strict_types=1);` at the top of your file.");
153162

crates/linter/src/rule/maintainability/too_many_enum_cases.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use serde::Serialize;
55
use mago_reporting::Annotation;
66
use mago_reporting::Issue;
77
use mago_reporting::Level;
8-
use mago_span::HasSpan;
98
use mago_syntax::ast::ClassLikeMember;
109
use mago_syntax::ast::Node;
1110
use mago_syntax::ast::NodeKind;
@@ -15,6 +14,7 @@ use crate::context::LintContext;
1514
use crate::requirements::RuleRequirements;
1615
use crate::rule::Config;
1716
use crate::rule::LintRule;
17+
use crate::rule::utils::misc::get_class_like_header_span;
1818
use crate::rule_meta::RuleMeta;
1919
use crate::settings::RuleSettings;
2020

@@ -126,7 +126,7 @@ impl LintRule for TooManyEnumCasesRule {
126126
Issue::new(self.cfg.level, "Enum has too many cases.")
127127
.with_code(self.meta.code)
128128
.with_annotation(
129-
Annotation::primary(r#enum.span()).with_message(format!(
129+
Annotation::primary(get_class_like_header_span(node)).with_message(format!(
130130
"Enum has {cases} cases, which exceeds the threshold of {}.",
131131
self.cfg.threshold
132132
))

crates/linter/src/rule/maintainability/too_many_methods.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ use serde::Serialize;
55
use mago_reporting::Annotation;
66
use mago_reporting::Issue;
77
use mago_reporting::Level;
8-
use mago_span::HasSpan;
98
use mago_syntax::ast::*;
109

1110
use crate::category::Category;
1211
use crate::context::LintContext;
1312
use crate::requirements::RuleRequirements;
1413
use crate::rule::Config;
1514
use crate::rule::LintRule;
15+
use crate::rule::utils::misc::get_class_like_header_span;
1616
use crate::rule::utils::misc::is_method_setter_or_getter;
1717
use crate::rule_meta::RuleMeta;
1818
use crate::settings::RuleSettings;
@@ -150,17 +150,13 @@ impl LintRule for TooManyMethodsRule {
150150
ctx.collector.report(
151151
Issue::new(self.cfg.level, format!("{kind} has too many methods."))
152152
.with_code(self.meta.code)
153-
.with_annotation(Annotation::primary(node.span()).with_message(format!(
153+
.with_annotation(Annotation::primary(get_class_like_header_span(node)).with_message(format!(
154154
"{kind} has {count} methods, which exceeds the threshold of {}.",
155155
self.cfg.threshold
156156
)))
157157
.with_note("Having a large number of methods can make structures harder to understand and maintain.")
158158
.with_help("Try reducing the number of methods, or consider splitting the structure into smaller, more focused structures.")
159159
);
160-
161-
// If this structure has too many methods, we don't need to check the nested structures.
162-
} else {
163-
// Continue checking nested structures, if any.
164160
}
165161
}
166162
}

crates/linter/src/rule/maintainability/too_many_properties.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use serde::Serialize;
55
use mago_reporting::Annotation;
66
use mago_reporting::Issue;
77
use mago_reporting::Level;
8-
use mago_span::HasSpan;
98
use mago_syntax::ast::ClassLikeMember;
109
use mago_syntax::ast::Node;
1110
use mago_syntax::ast::NodeKind;
@@ -16,6 +15,7 @@ use crate::context::LintContext;
1615
use crate::requirements::RuleRequirements;
1716
use crate::rule::Config;
1817
use crate::rule::LintRule;
18+
use crate::rule::utils::misc::get_class_like_header_span;
1919
use crate::rule_meta::RuleMeta;
2020
use crate::settings::RuleSettings;
2121

@@ -128,17 +128,13 @@ impl LintRule for TooManyPropertiesRule {
128128
ctx.collector.report(
129129
Issue::new(self.cfg.level, format!("{kind} has too many properties."))
130130
.with_code(self.meta.code)
131-
.with_annotation(Annotation::primary(node.span()).with_message(format!(
131+
.with_annotation(Annotation::primary(get_class_like_header_span(node)).with_message(format!(
132132
"{kind} has {properties} properties, which exceeds the threshold of {}.",
133133
self.cfg.threshold
134134
)))
135135
.with_note("Having a large number of properties can make classes harder to understand and maintain.")
136136
.with_help("Try reducing the number of properties, or consider grouping related properties into a single object.")
137137
);
138-
139-
// If this structure has too many props, we don't need to check the nested structures.
140-
} else {
141-
// Continue checking nested structures, if any.
142138
}
143139
}
144140
}

crates/linter/src/rule/utils/misc.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
1+
use mago_span::HasSpan;
2+
use mago_span::Span;
13
use mago_syntax::ast::*;
24

5+
/// Returns the minimal span for a class-like node (just keyword + name).
6+
///
7+
/// For named classes/traits/enums/interfaces, this returns the span from the keyword to the name.
8+
/// For anonymous classes, this returns just the `class` keyword span.
9+
#[inline]
10+
pub fn get_class_like_header_span(node: Node<'_, '_>) -> Span {
11+
match node {
12+
Node::Class(class) => class.class.span().join(class.name.span()),
13+
Node::Trait(r#trait) => r#trait.r#trait.span().join(r#trait.name.span()),
14+
Node::Enum(r#enum) => r#enum.r#enum.span().join(r#enum.name.span()),
15+
Node::Interface(interface) => interface.interface.span().join(interface.name.span()),
16+
Node::AnonymousClass(class) => class.class.span(),
17+
_ => node.span(),
18+
}
19+
}
20+
321
#[inline]
422
pub fn get_single_return_statement<'ast, 'arena>(block: &'ast Block<'arena>) -> Option<&'ast Return<'arena>> {
523
let statements = block.statements.as_slice();

0 commit comments

Comments
 (0)