Skip to content

Commit 335aa17

Browse files
committed
refactor: use run_on_jest_node for performance
1 parent 61f1928 commit 335aa17

File tree

2 files changed

+152
-123
lines changed

2 files changed

+152
-123
lines changed

crates/oxc_linter/src/rules/jest/padding_around_test_blocks.rs

Lines changed: 105 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
use std::ops::Deref;
22

3-
use itertools::Itertools;
4-
use oxc_ast::AstKind;
3+
use oxc_ast::{AstKind, ast::Statement};
54
use oxc_diagnostics::OxcDiagnostic;
65
use oxc_macros::declare_oxc_lint;
6+
use oxc_semantic::AstNode;
77
use oxc_span::{CompactStr, GetSpan, Span};
88

99
use crate::{
10-
AstNode,
1110
context::LintContext,
1211
rule::Rule,
1312
utils::{
14-
JestGeneralFnKind, ParsedGeneralJestFnCall, collect_possible_jest_call_node,
15-
parse_general_jest_fn_call,
13+
JestGeneralFnKind, ParsedGeneralJestFnCall, PossibleJestNode, parse_general_jest_fn_call,
1614
},
1715
};
1816

@@ -67,94 +65,88 @@ declare_oxc_lint!(
6765
fix
6866
);
6967

70-
enum NodeType {
71-
Test(CompactStr),
72-
Statement,
73-
}
74-
7568
impl Rule for PaddingAroundTestBlocks {
76-
fn run_once(&self, ctx: &LintContext<'_>) {
77-
let mut nodes = collect_possible_jest_call_node(ctx)
78-
.iter()
79-
.filter_map(|possible_jest_node| {
80-
let node = possible_jest_node.node;
81-
let AstKind::CallExpression(call_expr) = node.kind() else {
82-
return None;
69+
fn run_on_jest_node<'a, 'c>(
70+
&self,
71+
jest_node: &PossibleJestNode<'a, 'c>,
72+
ctx: &'c LintContext<'a>,
73+
) {
74+
let node = jest_node.node;
75+
let AstKind::CallExpression(call_expr) = node.kind() else {
76+
return;
77+
};
78+
let Some(jest_fn_call) = parse_general_jest_fn_call(call_expr, jest_node, ctx) else {
79+
return;
80+
};
81+
let ParsedGeneralJestFnCall { kind, name, .. } = &jest_fn_call;
82+
let Some(kind) = kind.to_general() else {
83+
return;
84+
};
85+
if kind != JestGeneralFnKind::Test {
86+
return;
87+
}
88+
let scope_node = ctx.nodes().get_node(ctx.scoping().get_node_id(node.scope_id()));
89+
let prev_statement_span = match scope_node.kind() {
90+
AstKind::Program(program) => {
91+
get_statement_span_before_node(*node, program.body.as_slice())
92+
}
93+
AstKind::ArrowFunctionExpression(arrow_func_expr) => {
94+
get_statement_span_before_node(*node, arrow_func_expr.body.statements.as_slice())
95+
}
96+
AstKind::Function(function) => {
97+
let Some(body) = &function.body else {
98+
return;
8399
};
84-
let jest_fn_call = parse_general_jest_fn_call(call_expr, possible_jest_node, ctx)?;
85-
let ParsedGeneralJestFnCall { kind, name, .. } = &jest_fn_call;
86-
let kind = kind.to_general()?;
87-
if kind != JestGeneralFnKind::Test {
88-
return None;
89-
}
90-
Some((NodeType::Test(name.deref().into()), node))
91-
})
92-
.collect_vec();
93-
for node in ctx.nodes() {
94-
if node.kind().is_statement() {
95-
nodes.push((NodeType::Statement, node));
100+
get_statement_span_before_node(*node, body.statements.as_slice())
96101
}
97-
}
98-
nodes.sort_by_key(|(_, node)| node.span().end);
99-
let mut prev_node: Option<&AstNode<'_>> = None;
100-
for (node_type, node) in nodes {
101-
match node_type {
102-
NodeType::Test(name) => {
103-
if let Some(prev_node) = prev_node {
104-
if prev_node.span().end > node.span().start {
105-
continue;
106-
}
107-
let mut comments_range =
108-
ctx.comments_range(prev_node.span().end..node.span().start);
109-
let mut span_between_start = prev_node.span().end;
110-
let mut span_between_end = node.span().start;
111-
if let Some(last_comment_span) =
112-
comments_range.next_back().map(|comment| comment.span)
113-
{
114-
let space_after_last_comment = ctx
115-
.source_range(Span::new(last_comment_span.end, node.span().start));
116-
let space_before_last_comment = ctx.source_range(Span::new(
117-
prev_node.span().end,
118-
last_comment_span.start,
119-
));
120-
if space_after_last_comment.matches('\n').count() > 1
121-
|| space_before_last_comment.matches('\n').count() == 0
122-
{
123-
span_between_start = last_comment_span.end;
124-
} else {
125-
span_between_end = last_comment_span.start;
126-
}
127-
}
128-
let span_between = Span::new(span_between_start, span_between_end);
129-
let content = ctx.source_range(span_between);
130-
if content.matches('\n').count() < 2 {
131-
ctx.diagnostic_with_fix(
132-
padding_around_test_blocks_diagnostic(
133-
Span::new(span_between_end, span_between_end),
134-
&name,
135-
),
136-
|fixer| {
137-
let spaces_after_last_line = content
138-
.rfind('\n')
139-
.map_or("", |index| content.split_at(index + 1).1);
140-
fixer.replace(
141-
span_between,
142-
format!("\n\n{spaces_after_last_line}"),
143-
)
144-
},
145-
);
146-
}
147-
}
148-
prev_node = Some(node);
149-
}
150-
NodeType::Statement => {
151-
prev_node = Some(node);
152-
}
102+
_ => None,
103+
};
104+
let Some(prev_statement_span) = prev_statement_span else {
105+
return;
106+
};
107+
let mut comments_range = ctx.comments_range(prev_statement_span.end..node.span().start);
108+
let mut span_between_start = prev_statement_span.end;
109+
let mut span_between_end = node.span().start;
110+
if let Some(last_comment_span) = comments_range.next_back().map(|comment| comment.span) {
111+
let space_after_last_comment =
112+
ctx.source_range(Span::new(last_comment_span.end, node.span().start));
113+
let space_before_last_comment =
114+
ctx.source_range(Span::new(prev_statement_span.end, last_comment_span.start));
115+
if space_after_last_comment.matches('\n').count() > 1
116+
|| space_before_last_comment.matches('\n').count() == 0
117+
{
118+
span_between_start = last_comment_span.end;
119+
} else {
120+
span_between_end = last_comment_span.start;
153121
}
154122
}
123+
let span_between = Span::new(span_between_start, span_between_end);
124+
let content = ctx.source_range(span_between);
125+
if content.matches('\n').count() < 2 {
126+
ctx.diagnostic_with_fix(
127+
padding_around_test_blocks_diagnostic(
128+
Span::new(span_between_end, span_between_end),
129+
&name.deref().into(),
130+
),
131+
|fixer| {
132+
let whitespace_after_last_line =
133+
content.rfind('\n').map_or("", |index| content.split_at(index + 1).1);
134+
fixer.replace(span_between, format!("\n\n{whitespace_after_last_line}"))
135+
},
136+
);
137+
}
155138
}
156139
}
157140

141+
fn get_statement_span_before_node(node: AstNode, statements: &[Statement]) -> Option<Span> {
142+
statements
143+
.iter()
144+
.filter_map(|statement| {
145+
if statement.span().end <= node.span().start { Some(statement.span()) } else { None }
146+
})
147+
.next_back()
148+
}
149+
158150
#[test]
159151
fn test() {
160152
use crate::tester::Tester;
@@ -198,6 +190,14 @@ test('bar bar', () => {});
198190
.skip('skippy skip', () => {});
199191
xit('bar foo', () => {});
200192
",
193+
r"
194+
describe('other bar', function() {
195+
test('is another bar w/ test', () => {
196+
});
197+
it('is another bar w/ it', () => {
198+
});
199+
});
200+
",
201201
];
202202

203203
let fix = vec![
@@ -281,8 +281,28 @@ test
281281
xit('bar foo', () => {});
282282
",
283283
),
284+
(
285+
r"
286+
describe('other bar', function() {
287+
test('is another bar w/ test', () => {
288+
});
289+
it('is another bar w/ it', () => {
290+
});
291+
});
292+
",
293+
r"
294+
describe('other bar', function() {
295+
test('is another bar w/ test', () => {
296+
});
297+
298+
it('is another bar w/ it', () => {
299+
});
300+
});
301+
",
302+
),
284303
];
285304
Tester::new(PaddingAroundTestBlocks::NAME, PaddingAroundTestBlocks::PLUGIN, pass, fail)
305+
.with_jest_plugin(true)
286306
.expect_fix(fix)
287307
.test_and_snapshot();
288308
}

crates/oxc_linter/src/snapshots/jest_padding_around_test_blocks.snap

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,6 @@ source: crates/oxc_linter/src/tester.rs
3333
╰────
3434
help: Make sure there is an empty new line before the test block
3535

36-
eslint-plugin-jest(padding-around-test-blocks): Missing padding before it block
37-
╭─[padding_around_test_blocks.tsx:4:1]
38-
3const bar = 'baz';
39-
4it('foo', () => {
40-
· ▲
41-
5// stuff
42-
╰────
43-
help: Make sure there is an empty new line before the it block
44-
4536
eslint-plugin-jest(padding-around-test-blocks): Missing padding before fit block
4637
╭─[padding_around_test_blocks.tsx:7:1]
4738
6 │ });
@@ -51,6 +42,15 @@ source: crates/oxc_linter/src/tester.rs
5142
╰────
5243
help: Make sure there is an empty new line before the fit block
5344

45+
eslint-plugin-jest(padding-around-test-blocks): Missing padding before xit block
46+
╭─[padding_around_test_blocks.tsx:26:2]
47+
25 │ .skip('skippy skip', () => {});
48+
26xit('bar foo', () => {});
49+
· ▲
50+
27
51+
╰────
52+
help: Make sure there is an empty new line before the xit block
53+
5454
eslint-plugin-jest(padding-around-test-blocks): Missing padding before test block
5555
╭─[padding_around_test_blocks.tsx:10:1]
5656
9 │ });
@@ -78,15 +78,6 @@ source: crates/oxc_linter/src/tester.rs
7878
╰────
7979
help: Make sure there is an empty new line before the test block
8080

81-
eslint-plugin-jest(padding-around-test-blocks): Missing padding before it block
82-
╭─[padding_around_test_blocks.tsx:18:6]
83-
17 │ });
84-
18// With a comment
85-
· ▲
86-
19it('is another bar w/ it', () => {
87-
╰────
88-
help: Make sure there is an empty new line before the it block
89-
9081
eslint-plugin-jest(padding-around-test-blocks): Missing padding before test block
9182
╭─[padding_around_test_blocks.tsx:21:6]
9283
20 │ });
@@ -96,14 +87,14 @@ source: crates/oxc_linter/src/tester.rs
9687
╰────
9788
help: Make sure there is an empty new line before the test block
9889

99-
eslint-plugin-jest(padding-around-test-blocks): Missing padding before it block
100-
╭─[padding_around_test_blocks.tsx:22:6]
101-
21test.skip('skipping', () => {}); // Another comment
102-
22it.skip('skipping too', () => {});
103-
· ▲
90+
eslint-plugin-jest(padding-around-test-blocks): Missing padding before test block
91+
╭─[padding_around_test_blocks.tsx:24:2]
10492
23 │ });xtest('weird', () => {});
93+
24test
94+
· ▲
95+
25 │ .skip('skippy skip', () => {});
10596
╰────
106-
help: Make sure there is an empty new line before the it block
97+
help: Make sure there is an empty new line before the test block
10798

10899
eslint-plugin-jest(padding-around-test-blocks): Missing padding before xtest block
109100
╭─[padding_around_test_blocks.tsx:23:5]
@@ -114,20 +105,38 @@ source: crates/oxc_linter/src/tester.rs
114105
╰────
115106
help: Make sure there is an empty new line before the xtest block
116107

117-
eslint-plugin-jest(padding-around-test-blocks): Missing padding before test block
118-
╭─[padding_around_test_blocks.tsx:24:2]
119-
23 │ });xtest('weird', () => {});
120-
24test
121-
· ▲
122-
25 │ .skip('skippy skip', () => {});
108+
eslint-plugin-jest(padding-around-test-blocks): Missing padding before it block
109+
╭─[padding_around_test_blocks.tsx:4:1]
110+
3const bar = 'baz';
111+
4it('foo', () => {
112+
· ▲
113+
5// stuff
114+
╰────
115+
help: Make sure there is an empty new line before the it block
116+
117+
eslint-plugin-jest(padding-around-test-blocks): Missing padding before it block
118+
╭─[padding_around_test_blocks.tsx:18:6]
119+
17 │ });
120+
18// With a comment
121+
· ▲
122+
19it('is another bar w/ it', () => {
123123
╰────
124-
help: Make sure there is an empty new line before the test block
124+
help: Make sure there is an empty new line before the it block
125125

126-
eslint-plugin-jest(padding-around-test-blocks): Missing padding before xit block
127-
╭─[padding_around_test_blocks.tsx:26:2]
128-
25 │ .skip('skippy skip', () => {});
129-
26xit('bar foo', () => {});
130-
· ▲
131-
27
126+
eslint-plugin-jest(padding-around-test-blocks): Missing padding before it block
127+
╭─[padding_around_test_blocks.tsx:22:6]
128+
21 test.skip('skipping', () => {}); // Another comment
129+
22 it.skip('skipping too', () => {});
130+
·
131+
23});xtest('weird', () => {});
132132
╰────
133-
help: Make sure there is an empty new line before the xit block
133+
help: Make sure there is an empty new line before the it block
134+
135+
eslint-plugin-jest(padding-around-test-blocks): Missing padding before it block
136+
╭─[padding_around_test_blocks.tsx:5:5]
137+
4 │ });
138+
5it('is another bar w/ it', () => {
139+
· ▲
140+
6 │ });
141+
╰────
142+
help: Make sure there is an empty new line before the it block

0 commit comments

Comments
 (0)