refactor(ast): check whether there is a single quasi in TemplateLiteral::quasi (#12265)

As the changes show, `TemplateLiteral::quasi` is always paired with a check to use, that check is whether there is only one single `quasi` in this `TemplateLiteral`. This PR is to put this check into the `TemplateLiteral::quasi` API, so that we can simplify the use of `TemplateLiteral::quasi`
This commit is contained in:
Dunqing committed 2025-07-14 19:24:48 +00:00
1 parent 2a1e805d55
commit 32c32aff4f
12 files changed
+28 -48

No files matched your search

+2 -2
View File
@@ -544,12 +544,12 @@ impl<'a> TemplateLiteral<'a> {
/// - `` `foo` `` => `true`
/// - `` `foo${bar}qux` `` => `false`
pub fn is_no_substitution_template(&self) -> bool {
self.expressions.is_empty() && self.quasis.len() == 1
self.quasis.len() == 1
}
/// Get single quasi from `template`
pub fn quasi(&self) -> Option<Atom<'a>> {
self.quasis.first().and_then(|quasi| quasi.value.cooked)
if self.is_no_substitution_template() { self.quasis[0].value.cooked } else { None }
}
}
@@ -417,13 +417,9 @@ impl<'a> MayHaveSideEffects<'a> for ComputedMemberExpression<'a> {
Expression::StringLiteral(s) => {
property_access_may_have_side_effects(&self.object, &s.value, ctx)
}
Expression::TemplateLiteral(t) if t.is_no_substitution_template() => {
property_access_may_have_side_effects(
&self.object,
&t.quasi().expect("template literal must have at least one quasi"),
ctx,
)
}
Expression::TemplateLiteral(t) => t.quasi().is_some_and(|quasi| {
property_access_may_have_side_effects(&self.object, &quasi, ctx)
}),
Expression::NumericLiteral(n) => !n.value.to_integer_index().is_some_and(|n| {
!integer_index_property_access_may_have_side_effects(&self.object, n, ctx)
}),
+1 -4
View File
@@ -93,10 +93,7 @@ pub fn maybe_object_with_to_primitive_related_properties_overridden(
matches!(str.value.as_str(), "toString" | "valueOf")
}
PropertyKey::TemplateLiteral(temp) => {
!temp.is_no_substitution_template()
|| temp
.quasi()
.is_some_and(|val| matches!(val.as_str(), "toString" | "valueOf"))
temp.quasi().is_some_and(|val| matches!(val.as_str(), "toString" | "valueOf"))
}
_ => true,
},
+1 -3
View File
@@ -308,9 +308,7 @@ pub fn extract_regex_flags<'a>(
}
let flag_arg = match &args[1] {
Argument::StringLiteral(flag_arg) => flag_arg.value,
Argument::TemplateLiteral(template) if template.is_no_substitution_template() => {
template.quasi().expect("no-substitution templates always have a quasi")
}
Argument::TemplateLiteral(template) => template.quasi()?,
_ => return None,
};
let mut flags = RegExpFlags::empty();
@@ -124,8 +124,8 @@ impl Rule for ValidTypeof {
}
if let Expression::TemplateLiteral(template) = sibling {
if template.expressions.is_empty() {
if template.quasi().is_some_and(|value| !VALID_TYPES.contains(&value.as_str())) {
if let Some(quasi) = template.quasi() {
if !VALID_TYPES.contains(&quasi.as_str()) {
ctx.diagnostic(invalid_value(None, sibling.span()));
}
return;
@@ -180,9 +180,6 @@ impl ValidTitle {
);
}
Argument::TemplateLiteral(template_literal) => {
if !template_literal.is_no_substitution_template() {
return;
}
if let Some(quasi) = template_literal.quasi() {
validate_title(
quasi.as_str(),
@@ -180,15 +180,9 @@ impl ButtonHasType {
Expression::StringLiteral(str) => {
self.is_valid_button_type_prop_string_literal(str.value.as_str())
}
Expression::TemplateLiteral(template_literal) => {
if !template_literal.is_no_substitution_template() {
return false;
}
if let Some(quasi) = template_literal.quasi() {
return self.is_valid_button_type_prop_string_literal(quasi.as_str());
}
false
}
Expression::TemplateLiteral(template_literal) => template_literal
.quasi()
.is_some_and(|quasi| self.is_valid_button_type_prop_string_literal(quasi.as_str())),
Expression::ConditionalExpression(conditional_expr) => {
self.is_valid_button_type_prop_expression(&conditional_expr.consequent)
&& self.is_valid_button_type_prop_expression(&conditional_expr.alternate)
@@ -462,8 +462,9 @@ impl JsxCurlyBracePresence {
}
}
Expression::TemplateLiteral(template) => {
if allowed.is_never() && template.is_no_substitution_template() {
let string = template.quasi().unwrap();
if allowed.is_never()
&& let Some(string) = template.quasi()
{
if contains_quote_characters(string.as_str())
|| is_allowed_string_like(
ctx,
+2 -2
View File
@@ -34,8 +34,8 @@ pub fn as_endpoint_registration<'a, 'n>(
Expression::StringLiteral(path) => {
Some((Some(path.value), &call.arguments.as_slice()[1..]))
}
Expression::TemplateLiteral(template) if template.is_no_substitution_template() => {
Some((template.quasi(), &call.arguments.as_slice()[1..]))
Expression::TemplateLiteral(template) => {
template.quasi().map(|quasi| (Some(quasi), &call.arguments.as_slice()[1..]))
}
_ => Some((None, call.arguments.as_slice())),
}
+5 -7
View File
@@ -3,7 +3,7 @@ use std::borrow::Cow;
use oxc_ast::{
AstKind,
ast::{
CallExpression, Expression, ImportDeclaration, ImportDeclarationSpecifier, TemplateLiteral,
CallExpression, Expression, ImportDeclaration, ImportDeclarationSpecifier,
match_member_expression,
},
};
@@ -271,8 +271,10 @@ pub fn get_node_name_vec<'a>(expr: &'a Expression<'a>) -> Vec<Cow<'a, str>> {
Expression::StringLiteral(string_literal) => {
chain.push(Cow::Borrowed(&string_literal.value));
}
Expression::TemplateLiteral(template_literal) if is_pure_string(template_literal) => {
chain.push(Cow::Borrowed(template_literal.quasi().unwrap().as_str()));
Expression::TemplateLiteral(template_literal) => {
if let Some(quasi) = template_literal.quasi() {
chain.push(Cow::Borrowed(quasi.as_str()));
}
}
Expression::TaggedTemplateExpression(tagged_expr) => {
chain.extend(get_node_name_vec(&tagged_expr.tag));
@@ -294,10 +296,6 @@ pub fn get_node_name_vec<'a>(expr: &'a Expression<'a>) -> Vec<Cow<'a, str>> {
chain
}
fn is_pure_string(template_literal: &TemplateLiteral) -> bool {
template_literal.expressions.is_empty() && template_literal.quasis.len() == 1
}
pub fn is_equality_matcher(matcher: &KnownMemberExpressionProperty) -> bool {
matcher.is_name_equal("toBe")
|| matcher.is_name_equal("toEqual")
@@ -13,7 +13,7 @@ use oxc_span::Span;
use crate::{
context::LintContext,
utils::jest::{JestFnKind, JestGeneralFnKind, PossibleJestNode, is_pure_string},
utils::jest::{JestFnKind, JestGeneralFnKind, PossibleJestNode},
utils::valid_vitest_fn::is_valid_vitest_call,
};
@@ -554,7 +554,9 @@ fn recurse_extend_node_chain<'a>(
span: string_literal.span,
});
}
Expression::TemplateLiteral(template_literal) if is_pure_string(template_literal) => {
Expression::TemplateLiteral(template_literal)
if template_literal.is_no_substitution_template() =>
{
chain.push(KnownMemberExpressionProperty {
element: MemberExpressionElement::Expression(expr),
parent: *parent,
+2 -5
View File
@@ -181,8 +181,7 @@ pub fn is_same_expression(left: &Expression, right: &Expression, ctx: &LintConte
}
(Expression::StringLiteral(string_lit), Expression::TemplateLiteral(template_lit))
| (Expression::TemplateLiteral(template_lit), Expression::StringLiteral(string_lit)) => {
return template_lit.is_no_substitution_template()
&& string_lit.value == template_lit.quasi().unwrap();
return template_lit.quasi().is_some_and(|val| val.as_str() == string_lit.value);
}
(Expression::TemplateLiteral(left_str), Expression::TemplateLiteral(right_str)) => {
return left_str.quasis.content_eq(&right_str.quasis)
@@ -304,9 +303,7 @@ pub fn is_same_member_expression(
// ex) x[/regex/] === x[`/regex/`]
(Expression::TemplateLiteral(template_lit), Expression::RegExpLiteral(regex_lit))
| (Expression::RegExpLiteral(regex_lit), Expression::TemplateLiteral(template_lit)) => {
if !(template_lit.is_no_substitution_template()
&& template_lit.quasi().unwrap() == regex_lit.raw.as_ref().unwrap())
{
if !template_lit.quasi().is_some_and(|val| val == regex_lit.raw.as_ref().unwrap()) {
return false;
}
}