Skip to content

Commit d57d26a

Browse files
feat(biome_js_analyze): extracts class member references in a service
1 parent 075ce95 commit d57d26a

File tree

3 files changed

+46
-58
lines changed

3 files changed

+46
-58
lines changed

crates/biome_js_analyze/src/lint/style/use_readonly_class_properties.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,11 @@ impl Rule for UseReadonlyClassProperties {
135135
return Box::default();
136136
}
137137

138-
let ClassMemberReferences { writes, .. } = ctx.class_member_references();
139-
140138
let root = ctx.query();
141139
let members = root.members();
140+
141+
let ClassMemberReferences { writes, .. } = ctx.model.class_member_references(&members);
142+
142143
let private_only = !ctx.options().check_all_properties;
143144
let constructor_params: Vec<_> =
144145
collect_non_readonly_constructor_parameters(&members, private_only);

crates/biome_js_analyze/src/services/semantic_class.rs

Lines changed: 22 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,22 @@ use std::collections::HashSet;
1818

1919
#[derive(Clone)]
2020
pub struct SemanticClassServices {
21-
class_member_references: ClassMemberReferences,
21+
pub model: SemanticClassModel,
2222
}
2323

2424
impl SemanticClassServices {
25-
pub fn class_member_references(&self) -> &ClassMemberReferences {
26-
&self.class_member_references
25+
pub fn model(&self) -> &SemanticClassModel {
26+
&self.model
2727
}
2828
}
2929

3030
#[derive(Debug, Clone)]
31-
pub struct SemanticClassModel {
32-
pub references: ClassMemberReferences,
31+
pub struct SemanticClassModel {}
32+
33+
impl SemanticClassModel {
34+
pub fn class_member_references(&self, members: &JsClassMemberList) -> ClassMemberReferences {
35+
class_member_references(members)
36+
}
3337
}
3438

3539
impl FromServices for SemanticClassServices {
@@ -42,7 +46,7 @@ impl FromServices for SemanticClassServices {
4246
ServicesDiagnostic::new(rule_key.rule_name(), &["SemanticClassModel"])
4347
})?;
4448
Ok(Self {
45-
class_member_references: service.references.clone(),
49+
model: service.clone(),
4650
})
4751
}
4852
}
@@ -53,36 +57,25 @@ impl Phase for SemanticClassServices {
5357
}
5458
}
5559

56-
pub struct ClassMemberReferencesVisitor {
57-
references: ClassMemberReferences,
58-
}
59-
60-
impl ClassMemberReferencesVisitor {
61-
pub(crate) fn new(_root: &AnyJsRoot) -> Self {
62-
Self {
63-
references: ClassMemberReferences::default(),
64-
}
65-
}
66-
}
60+
pub struct ClassMemberReferencesVisitor {}
6761

6862
impl Visitor for ClassMemberReferencesVisitor {
6963
type Language = JsLanguage;
7064

71-
fn visit(&mut self, event: &WalkEvent<JsSyntaxNode>, _ctx: VisitorContext<JsLanguage>) {
65+
fn visit(
66+
&mut self,
67+
event: &WalkEvent<JsSyntaxNode>,
68+
mut ctx: VisitorContext<'_, '_, JsLanguage>,
69+
) {
7270
if let WalkEvent::Enter(node) = event
73-
&& let Some(js_class_declaration) = JsClassDeclaration::cast_ref(node)
71+
&& JsClassDeclaration::can_cast(node.kind())
7472
{
75-
let class_member_list = js_class_declaration.members();
76-
let refs = class_member_references(&class_member_list);
77-
self.references.reads.extend(refs.reads);
78-
self.references.writes.extend(refs.writes);
73+
ctx.match_query(node.clone());
7974
}
8075
}
8176

8277
fn finish(self: Box<Self>, ctx: VisitorFinishContext<JsLanguage>) {
83-
ctx.services.insert_service(SemanticClassModel {
84-
references: self.references,
85-
});
78+
ctx.services.insert_service(SemanticClassModel {});
8679
}
8780
}
8881

@@ -91,28 +84,10 @@ pub struct SemanticClass<N>(pub N);
9184

9285
impl QueryMatch for SemanticClass<JsClassDeclaration> {
9386
fn text_range(&self) -> TextRange {
94-
// return the text range of the class node
9587
self.0.syntax().text_trimmed_range()
9688
}
9789
}
9890

99-
struct SemanticClassVisitor;
100-
101-
impl Visitor for SemanticClassVisitor {
102-
type Language = JsLanguage;
103-
104-
fn visit(&mut self, event: &WalkEvent<JsSyntaxNode>, mut ctx: VisitorContext<JsLanguage>) {
105-
match event {
106-
WalkEvent::Enter(node) => {
107-
if JsClassDeclaration::can_cast(node.kind()) {
108-
ctx.match_query(node.clone());
109-
}
110-
}
111-
WalkEvent::Leave(_) => {}
112-
};
113-
}
114-
}
115-
11691
impl<N> Queryable for SemanticClass<N>
11792
where
11893
N: AstNode<Language = JsLanguage> + 'static,
@@ -123,9 +98,9 @@ where
12398
type Language = JsLanguage;
12499
type Services = SemanticClassServices;
125100

126-
fn build_visitor(analyzer: &mut impl AddVisitor<JsLanguage>, root: &AnyJsRoot) {
127-
analyzer.add_visitor(Phases::Syntax, || ClassMemberReferencesVisitor::new(root));
128-
analyzer.add_visitor(Phases::Semantic, || SemanticClassVisitor);
101+
fn build_visitor(analyzer: &mut impl AddVisitor<JsLanguage>, _root: &AnyJsRoot) {
102+
analyzer.add_visitor(Phases::Syntax, || ClassMemberReferencesVisitor {});
103+
analyzer.add_visitor(Phases::Semantic, || ClassMemberReferencesVisitor {});
129104
}
130105

131106
fn key() -> QueryKey<Self::Language> {

crates/biome_js_analyze/tests/quick_test.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,30 @@ fn project_layout_with_top_level_dependencies(dependencies: Dependencies) -> Arc
2525
}
2626

2727
// use this test check if your snippet produces the diagnostics you wish, without using a snapshot
28-
#[ignore]
28+
// #[ignore]
2929
#[test]
3030
fn quick_test() {
3131
const FILENAME: &str = "dummyFile.ts";
32-
const SOURCE: &str = r#"import * as postcssModules from "postcss-modules"
33-
34-
type PostcssOptions = Parameters<postcssModules>[0]
35-
36-
export function f(options: PostcssOptions) {
37-
console.log(options)
32+
const SOURCE: &str = r#"class TSDoubleUnusedPrivateConstructor {
33+
constructor(
34+
private usedOne: number,
35+
private usedTwo: string
36+
) {
37+
38+
// This constructor has two unused private properties
39+
}
40+
41+
method() {
42+
this.usedOne = usedOne + 'foo';
43+
console.warn(usedTwo);
44+
}
3845
}
39-
"#;
46+
47+
class TSPartiallyUsedPrivateConstructor {
48+
constructor(private param: number) {
49+
foo(param)
50+
}
51+
}"#;
4052

4153
let parsed = parse(SOURCE, JsFileSource::tsx(), JsParserOptions::default());
4254

@@ -52,7 +64,7 @@ export function f(options: PostcssOptions) {
5264
.with_configuration(
5365
AnalyzerConfiguration::default().with_jsx_runtime(JsxRuntime::ReactClassic),
5466
);
55-
let rule_filter = RuleFilter::Rule("correctness", "noUnusedImports");
67+
let rule_filter = RuleFilter::Rule("style", "useReadonlyClassProperties");
5668

5769
let dependencies = Dependencies(Box::new([("buffer".into(), "latest".into())]));
5870

0 commit comments

Comments
 (0)