Skip to content

Commit c7dfc05

Browse files
committed
refactor(formatter): move some CallExpression-related checking functions to utils (#12584)
1 parent 72da5ae commit c7dfc05

File tree

4 files changed

+198
-194
lines changed

4 files changed

+198
-194
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use oxc_ast::ast::*;
2+
3+
use crate::generated::ast_nodes::{AstNode, AstNodes};
4+
5+
/// This is a specialized function that checks if the current [call expression]
6+
/// resembles a call expression usually used by a testing frameworks.
7+
///
8+
/// If the [call expression] matches the criteria, a different formatting is applied.
9+
///
10+
/// To evaluate the eligibility of a [call expression] to be a test framework like,
11+
/// we need to check its [callee] and its [arguments].
12+
///
13+
/// 1. The [callee] must contain a name or a chain of names that belongs to the
14+
/// test frameworks, for example: `test()`, `test.only()`, etc.
15+
/// 2. The [arguments] should be at the least 2
16+
/// 3. The first argument has to be a string literal
17+
/// 4. The third argument, if present, has to be a number literal
18+
/// 5. The second argument has to be an [arrow function expression] or [function expression]
19+
/// 6. Both function must have zero or one parameters
20+
///
21+
/// [call expression]: CallExpression
22+
/// [callee]: Expression
23+
/// [arguments]: CallExpression::arguments
24+
/// [arrow function expression]: ArrowFunctionExpression
25+
/// [function expression]: Function
26+
pub fn is_test_call_expression(call: &AstNode<CallExpression<'_>>) -> bool {
27+
let callee = &call.callee;
28+
let arguments = &call.arguments;
29+
30+
let mut args = arguments.iter();
31+
32+
match (args.next(), args.next(), args.next()) {
33+
(Some(argument), None, None) if arguments.len() == 1 => {
34+
if is_angular_test_wrapper(call) && {
35+
if let AstNodes::CallExpression(call) = call.parent.parent() {
36+
is_test_call_expression(call)
37+
} else {
38+
false
39+
}
40+
} {
41+
return matches!(
42+
argument,
43+
Argument::ArrowFunctionExpression(_) | Argument::FunctionExpression(_)
44+
);
45+
}
46+
47+
if is_unit_test_set_up_callee(callee) {
48+
return argument.as_expression().is_some_and(is_angular_test_wrapper_expression);
49+
}
50+
51+
false
52+
}
53+
54+
// it("description", ..)
55+
// it(Test.name, ..)
56+
(_, Some(second), third) if arguments.len() <= 3 && contains_a_test_pattern(callee) => {
57+
// it('name', callback, duration)
58+
if !matches!(third, None | Some(Argument::NumericLiteral(_))) {
59+
return false;
60+
}
61+
62+
if second.as_expression().is_some_and(is_angular_test_wrapper_expression) {
63+
return true;
64+
}
65+
66+
let (parameter_count, has_block_body) = match second {
67+
Argument::FunctionExpression(function) => {
68+
(function.params.parameters_count(), true)
69+
}
70+
Argument::ArrowFunctionExpression(arrow) => {
71+
(arrow.params.parameters_count(), !arrow.expression)
72+
}
73+
_ => return false,
74+
};
75+
76+
parameter_count == 2 || (parameter_count <= 1 && has_block_body)
77+
}
78+
_ => false,
79+
}
80+
}
81+
82+
/// Note: `inject` is used in AngularJS 1.x, `async` and `fakeAsync` in
83+
/// Angular 2+, although `async` is deprecated and replaced by `waitForAsync`
84+
/// since Angular 12.
85+
///
86+
/// example: <https://docs.angularjs.org/guide/unit-testing#using-beforeall->
87+
///
88+
/// @param {CallExpression} node
89+
/// @returns {boolean}
90+
///
91+
fn is_angular_test_wrapper_expression(expression: &Expression) -> bool {
92+
matches!(expression, Expression::CallExpression(call) if is_angular_test_wrapper(call))
93+
}
94+
95+
fn is_angular_test_wrapper(call: &CallExpression) -> bool {
96+
matches!(&call.callee,
97+
Expression::Identifier(ident) if
98+
matches!(ident.name.as_str(), "async" | "inject" | "fakeAsync" | "waitForAsync")
99+
)
100+
}
101+
102+
/// Tests if the callee is a `beforeEach`, `beforeAll`, `afterEach` or `afterAll` identifier
103+
/// that is commonly used in test frameworks.
104+
fn is_unit_test_set_up_callee(callee: &Expression) -> bool {
105+
matches!(callee, Expression::Identifier(ident) if {
106+
matches!(ident.name.as_str(), "beforeEach" | "beforeAll" | "afterEach" | "afterAll")
107+
})
108+
}
109+
110+
/// Iterator that returns the callee names in "top down order".
111+
///
112+
/// # Examples
113+
///
114+
/// ```javascript
115+
/// it.only() -> [`only`, `it`]
116+
/// ```
117+
///
118+
/// Same as <https://github.com/biomejs/biome/blob/4a5ef84930344ae54f3877da36888a954711f4a6/crates/biome_js_syntax/src/expr_ext.rs#L1402-L1438>.
119+
pub fn callee_name_iterator<'b>(expr: &'b Expression<'_>) -> impl Iterator<Item = &'b str> {
120+
let mut current = Some(expr);
121+
std::iter::from_fn(move || match current {
122+
Some(Expression::Identifier(ident)) => {
123+
current = None;
124+
Some(ident.name.as_str())
125+
}
126+
Some(Expression::StaticMemberExpression(static_member)) => {
127+
current = Some(&static_member.object);
128+
Some(static_member.property.name.as_str())
129+
}
130+
_ => None,
131+
})
132+
}
133+
134+
/// This function checks if a call expressions has one of the following members:
135+
/// - `it`
136+
/// - `it.only`
137+
/// - `it.skip`
138+
/// - `describe`
139+
/// - `describe.only`
140+
/// - `describe.skip`
141+
/// - `test`
142+
/// - `test.only`
143+
/// - `test.skip`
144+
/// - `test.step`
145+
/// - `test.describe`
146+
/// - `test.describe.only`
147+
/// - `test.describe.parallel`
148+
/// - `test.describe.parallel.only`
149+
/// - `test.describe.serial`
150+
/// - `test.describe.serial.only`
151+
/// - `skip`
152+
/// - `xit`
153+
/// - `xdescribe`
154+
/// - `xtest`
155+
/// - `fit`
156+
/// - `fdescribe`
157+
/// - `ftest`
158+
/// - `Deno.test`
159+
///
160+
/// Based on this [article]
161+
///
162+
/// [article]: https://craftinginterpreters.com/scanning-on-demand.html#tries-and-state-machines
163+
pub fn contains_a_test_pattern(expr: &Expression<'_>) -> bool {
164+
let mut names = callee_name_iterator(expr);
165+
166+
match names.next() {
167+
Some("it" | "describe" | "Deno") => match names.next() {
168+
None => true,
169+
Some("only" | "skip" | "test") => names.next().is_none(),
170+
_ => false,
171+
},
172+
Some("test") => match names.next() {
173+
None => true,
174+
Some("only" | "skip" | "step") => names.next().is_none(),
175+
Some("describe") => match names.next() {
176+
None => true,
177+
Some("only") => names.next().is_none(),
178+
Some("parallel" | "serial") => match names.next() {
179+
None => true,
180+
Some("only") => names.next().is_none(),
181+
_ => false,
182+
},
183+
_ => false,
184+
},
185+
_ => false,
186+
},
187+
Some("skip" | "xit" | "xdescribe" | "xtest" | "fit" | "fdescribe" | "ftest") => true,
188+
_ => false,
189+
}
190+
}

crates/oxc_formatter/src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod assignment_like;
2+
pub mod call_expression;
23
pub mod member_chain;
34

45
use oxc_allocator::Address;

crates/oxc_formatter/src/write/call_arguments.rs

Lines changed: 2 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use crate::{
2020
},
2121
generated::ast_nodes::{AstNode, AstNodes},
2222
utils::{
23-
is_long_curried_call, member_chain::simple_argument::SimpleArgument,
24-
write_arguments_multi_line,
23+
call_expression::is_test_call_expression, is_long_curried_call,
24+
member_chain::simple_argument::SimpleArgument, write_arguments_multi_line,
2525
},
2626
write,
2727
write::{
@@ -981,190 +981,3 @@ fn is_react_hook_with_deps_array(
981981
_ => false,
982982
}
983983
}
984-
985-
/// This is a specialized function that checks if the current [call expression]
986-
/// resembles a call expression usually used by a testing frameworks.
987-
///
988-
/// If the [call expression] matches the criteria, a different formatting is applied.
989-
///
990-
/// To evaluate the eligibility of a [call expression] to be a test framework like,
991-
/// we need to check its [callee] and its [arguments].
992-
///
993-
/// 1. The [callee] must contain a name or a chain of names that belongs to the
994-
/// test frameworks, for example: `test()`, `test.only()`, etc.
995-
/// 2. The [arguments] should be at the least 2
996-
/// 3. The first argument has to be a string literal
997-
/// 4. The third argument, if present, has to be a number literal
998-
/// 5. The second argument has to be an [arrow function expression] or [function expression]
999-
/// 6. Both function must have zero or one parameters
1000-
///
1001-
/// [call expression]: CallExpression
1002-
/// [callee]: Expression
1003-
/// [arguments]: CallExpression::arguments
1004-
/// [arrow function expression]: ArrowFunctionExpression
1005-
/// [function expression]: Function
1006-
pub fn is_test_call_expression(call: &AstNode<CallExpression<'_>>) -> bool {
1007-
let callee = &call.callee;
1008-
let arguments = &call.arguments;
1009-
1010-
let mut args = arguments.iter();
1011-
1012-
match (args.next(), args.next(), args.next()) {
1013-
(Some(argument), None, None) if arguments.len() == 1 => {
1014-
if is_angular_test_wrapper(call) && {
1015-
if let AstNodes::CallExpression(call) = call.parent.parent() {
1016-
is_test_call_expression(call)
1017-
} else {
1018-
false
1019-
}
1020-
} {
1021-
return matches!(
1022-
argument,
1023-
Argument::ArrowFunctionExpression(_) | Argument::FunctionExpression(_)
1024-
);
1025-
}
1026-
1027-
if is_unit_test_set_up_callee(callee) {
1028-
return argument.as_expression().is_some_and(is_angular_test_wrapper_expression);
1029-
}
1030-
1031-
false
1032-
}
1033-
1034-
// it("description", ..)
1035-
// it(Test.name, ..)
1036-
(_, Some(second), third) if arguments.len() <= 3 && contains_a_test_pattern(callee) => {
1037-
// it('name', callback, duration)
1038-
if !matches!(third, None | Some(Argument::NumericLiteral(_))) {
1039-
return false;
1040-
}
1041-
1042-
if second.as_expression().is_some_and(is_angular_test_wrapper_expression) {
1043-
return true;
1044-
}
1045-
1046-
let (parameter_count, has_block_body) = match second {
1047-
Argument::FunctionExpression(function) => {
1048-
(function.params.parameters_count(), true)
1049-
}
1050-
Argument::ArrowFunctionExpression(arrow) => {
1051-
(arrow.params.parameters_count(), !arrow.expression)
1052-
}
1053-
_ => return false,
1054-
};
1055-
1056-
parameter_count == 2 || (parameter_count <= 1 && has_block_body)
1057-
}
1058-
_ => false,
1059-
}
1060-
}
1061-
1062-
/// Note: `inject` is used in AngularJS 1.x, `async` and `fakeAsync` in
1063-
/// Angular 2+, although `async` is deprecated and replaced by `waitForAsync`
1064-
/// since Angular 12.
1065-
///
1066-
/// example: <https://docs.angularjs.org/guide/unit-testing#using-beforeall->
1067-
///
1068-
/// @param {CallExpression} node
1069-
/// @returns {boolean}
1070-
///
1071-
fn is_angular_test_wrapper_expression(expression: &Expression) -> bool {
1072-
matches!(expression, Expression::CallExpression(call) if is_angular_test_wrapper(call))
1073-
}
1074-
1075-
fn is_angular_test_wrapper(call: &CallExpression) -> bool {
1076-
matches!(&call.callee,
1077-
Expression::Identifier(ident) if
1078-
matches!(ident.name.as_str(), "async" | "inject" | "fakeAsync" | "waitForAsync")
1079-
)
1080-
}
1081-
1082-
/// Tests if the callee is a `beforeEach`, `beforeAll`, `afterEach` or `afterAll` identifier
1083-
/// that is commonly used in test frameworks.
1084-
fn is_unit_test_set_up_callee(callee: &Expression) -> bool {
1085-
matches!(callee, Expression::Identifier(ident) if {
1086-
matches!(ident.name.as_str(), "beforeEach" | "beforeAll" | "afterEach" | "afterAll")
1087-
})
1088-
}
1089-
1090-
/// Iterator that returns the callee names in "top down order".
1091-
///
1092-
/// # Examples
1093-
///
1094-
/// ```javascript
1095-
/// it.only() -> [`only`, `it`]
1096-
/// ```
1097-
///
1098-
/// Same as <https://github.com/biomejs/biome/blob/4a5ef84930344ae54f3877da36888a954711f4a6/crates/biome_js_syntax/src/expr_ext.rs#L1402-L1438>.
1099-
pub fn callee_name_iterator<'b>(expr: &'b Expression<'_>) -> impl Iterator<Item = &'b str> {
1100-
let mut current = Some(expr);
1101-
std::iter::from_fn(move || match current {
1102-
Some(Expression::Identifier(ident)) => {
1103-
current = None;
1104-
Some(ident.name.as_str())
1105-
}
1106-
Some(Expression::StaticMemberExpression(static_member)) => {
1107-
current = Some(&static_member.object);
1108-
Some(static_member.property.name.as_str())
1109-
}
1110-
_ => None,
1111-
})
1112-
}
1113-
1114-
/// This function checks if a call expressions has one of the following members:
1115-
/// - `it`
1116-
/// - `it.only`
1117-
/// - `it.skip`
1118-
/// - `describe`
1119-
/// - `describe.only`
1120-
/// - `describe.skip`
1121-
/// - `test`
1122-
/// - `test.only`
1123-
/// - `test.skip`
1124-
/// - `test.step`
1125-
/// - `test.describe`
1126-
/// - `test.describe.only`
1127-
/// - `test.describe.parallel`
1128-
/// - `test.describe.parallel.only`
1129-
/// - `test.describe.serial`
1130-
/// - `test.describe.serial.only`
1131-
/// - `skip`
1132-
/// - `xit`
1133-
/// - `xdescribe`
1134-
/// - `xtest`
1135-
/// - `fit`
1136-
/// - `fdescribe`
1137-
/// - `ftest`
1138-
/// - `Deno.test`
1139-
///
1140-
/// Based on this [article]
1141-
///
1142-
/// [article]: https://craftinginterpreters.com/scanning-on-demand.html#tries-and-state-machines
1143-
pub fn contains_a_test_pattern(expr: &Expression<'_>) -> bool {
1144-
let mut names = callee_name_iterator(expr);
1145-
1146-
match names.next() {
1147-
Some("it" | "describe" | "Deno") => match names.next() {
1148-
None => true,
1149-
Some("only" | "skip" | "test") => names.next().is_none(),
1150-
_ => false,
1151-
},
1152-
Some("test") => match names.next() {
1153-
None => true,
1154-
Some("only" | "skip" | "step") => names.next().is_none(),
1155-
Some("describe") => match names.next() {
1156-
None => true,
1157-
Some("only") => names.next().is_none(),
1158-
Some("parallel" | "serial") => match names.next() {
1159-
None => true,
1160-
Some("only") => names.next().is_none(),
1161-
_ => false,
1162-
},
1163-
_ => false,
1164-
},
1165-
_ => false,
1166-
},
1167-
Some("skip" | "xit" | "xdescribe" | "xtest" | "fit" | "fdescribe" | "ftest") => true,
1168-
_ => false,
1169-
}
1170-
}

0 commit comments

Comments
 (0)