fix(ast): jsx attribute value and text child should be jsx string (#1089)

This commit is contained in:
Boshen 2023-10-29 15:32:24 +08:00 committed by GitHub
parent 1051f15005
commit 6295f9ce18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 36 additions and 47 deletions

View file

@ -190,7 +190,7 @@ pub enum JSXAttributeName<'a> {
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(untagged))] #[cfg_attr(feature = "serde", derive(Serialize), serde(untagged))]
pub enum JSXAttributeValue<'a> { pub enum JSXAttributeValue<'a> {
StringLiteral(StringLiteral), String(JSXString),
ExpressionContainer(JSXExpressionContainer<'a>), ExpressionContainer(JSXExpressionContainer<'a>),
Element(Box<'a, JSXElement<'a>>), Element(Box<'a, JSXElement<'a>>),
Fragment(Box<'a, JSXFragment<'a>>), Fragment(Box<'a, JSXFragment<'a>>),
@ -210,7 +210,7 @@ pub struct JSXIdentifier {
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(untagged))] #[cfg_attr(feature = "serde", derive(Serialize), serde(untagged))]
pub enum JSXChild<'a> { pub enum JSXChild<'a> {
Text(JSXText), Text(JSXString),
Element(Box<'a, JSXElement<'a>>), Element(Box<'a, JSXElement<'a>>),
Fragment(Box<'a, JSXFragment<'a>>), Fragment(Box<'a, JSXFragment<'a>>),
ExpressionContainer(JSXExpressionContainer<'a>), ExpressionContainer(JSXExpressionContainer<'a>),
@ -225,10 +225,12 @@ pub struct JSXSpreadChild<'a> {
pub expression: Expression<'a>, pub expression: Expression<'a>,
} }
/// JSX Text /// JSX String
///
/// <https://facebook.github.io/jsx/#sec-jsx-string>
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))] #[cfg_attr(feature = "serde", derive(Serialize), serde(tag = "type"))]
pub struct JSXText { pub struct JSXString {
#[cfg_attr(feature = "serde", serde(flatten))] #[cfg_attr(feature = "serde", serde(flatten))]
pub span: Span, pub span: Span,
pub value: Atom, pub value: Atom,

View file

@ -1125,8 +1125,8 @@ impl<'a> AstBuilder<'a> {
JSXIdentifier { span, name } JSXIdentifier { span, name }
} }
pub fn jsx_text(&self, span: Span, value: Atom) -> JSXText { pub fn jsx_string(&self, span: Span, value: Atom) -> JSXString {
JSXText { span, value } JSXString { span, value }
} }
/* ---------- TypeScript ---------- */ /* ---------- TypeScript ---------- */

View file

@ -115,7 +115,7 @@ pub enum AstKind<'a> {
JSXElementName(&'a JSXElementName<'a>), JSXElementName(&'a JSXElementName<'a>),
JSXExpressionContainer(&'a JSXExpressionContainer<'a>), JSXExpressionContainer(&'a JSXExpressionContainer<'a>),
JSXAttributeItem(&'a JSXAttributeItem<'a>), JSXAttributeItem(&'a JSXAttributeItem<'a>),
JSXText(&'a JSXText), JSXString(&'a JSXString),
// TypeScript // TypeScript
TSModuleBlock(&'a TSModuleBlock<'a>), TSModuleBlock(&'a TSModuleBlock<'a>),
@ -242,7 +242,7 @@ impl<'a> AstKind<'a> {
| Self::JSXElementName(_) | Self::JSXElementName(_)
| Self::JSXFragment(_) | Self::JSXFragment(_)
| Self::JSXAttributeItem(_) | Self::JSXAttributeItem(_)
| Self::JSXText(_) | Self::JSXString(_)
| Self::JSXExpressionContainer(_) | Self::JSXExpressionContainer(_)
) )
} }
@ -365,7 +365,7 @@ impl<'a> GetSpan for AstKind<'a> {
Self::JSXElement(x) => x.span, Self::JSXElement(x) => x.span,
Self::JSXFragment(x) => x.span, Self::JSXFragment(x) => x.span,
Self::JSXAttributeItem(x) => x.span(), Self::JSXAttributeItem(x) => x.span(),
Self::JSXText(x) => x.span, Self::JSXString(x) => x.span,
Self::JSXExpressionContainer(x) => x.span, Self::JSXExpressionContainer(x) => x.span,
Self::TSModuleBlock(x) => x.span, Self::TSModuleBlock(x) => x.span,
@ -532,7 +532,7 @@ impl<'a> AstKind<'a> {
Self::JSXElement(_) => "JSXElement".into(), Self::JSXElement(_) => "JSXElement".into(),
Self::JSXFragment(_) => "JSXFragment".into(), Self::JSXFragment(_) => "JSXFragment".into(),
Self::JSXAttributeItem(_) => "JSXAttributeItem".into(), Self::JSXAttributeItem(_) => "JSXAttributeItem".into(),
Self::JSXText(_) => "JSXText".into(), Self::JSXString(_) => "JSXString".into(),
Self::JSXExpressionContainer(_) => "JSXExpressionContainer".into(), Self::JSXExpressionContainer(_) => "JSXExpressionContainer".into(),
Self::TSModuleBlock(_) => "TSModuleBlock".into(), Self::TSModuleBlock(_) => "TSModuleBlock".into(),

View file

@ -1064,7 +1064,7 @@ pub trait Visit<'a>: Sized {
} }
JSXAttributeValue::Element(elem) => self.visit_jsx_element(elem), JSXAttributeValue::Element(elem) => self.visit_jsx_element(elem),
JSXAttributeValue::Fragment(elem) => self.visit_jsx_fragment(elem), JSXAttributeValue::Fragment(elem) => self.visit_jsx_fragment(elem),
JSXAttributeValue::StringLiteral(lit) => self.visit_string_literal(lit), JSXAttributeValue::String(s) => self.visit_jsx_string(s),
} }
} }
@ -1097,7 +1097,7 @@ pub trait Visit<'a>: Sized {
JSXChild::Fragment(elem) => self.visit_jsx_fragment(elem), JSXChild::Fragment(elem) => self.visit_jsx_fragment(elem),
JSXChild::ExpressionContainer(expr) => self.visit_jsx_expression_container(expr), JSXChild::ExpressionContainer(expr) => self.visit_jsx_expression_container(expr),
JSXChild::Spread(expr) => self.visit_jsx_spread_child(expr), JSXChild::Spread(expr) => self.visit_jsx_spread_child(expr),
JSXChild::Text(expr) => self.visit_jsx_text(expr), JSXChild::Text(expr) => self.visit_jsx_string(expr),
} }
} }
@ -1105,8 +1105,8 @@ pub trait Visit<'a>: Sized {
self.visit_expression(&child.expression); self.visit_expression(&child.expression);
} }
fn visit_jsx_text(&mut self, child: &JSXText) { fn visit_jsx_string(&mut self, child: &JSXString) {
let kind = AstKind::JSXText(self.alloc(child)); let kind = AstKind::JSXString(self.alloc(child));
self.enter_node(kind); self.enter_node(kind);
self.leave_node(kind); self.leave_node(kind);
} }

View file

@ -756,7 +756,7 @@ pub trait VisitMut<'a>: Sized {
} }
JSXAttributeValue::Element(elem) => self.visit_jsx_element(elem), JSXAttributeValue::Element(elem) => self.visit_jsx_element(elem),
JSXAttributeValue::Fragment(elem) => self.visit_jsx_fragment(elem), JSXAttributeValue::Fragment(elem) => self.visit_jsx_fragment(elem),
JSXAttributeValue::StringLiteral(_) => {} JSXAttributeValue::String(_) => {}
} }
} }

View file

@ -1838,7 +1838,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for JSXAttributeValue<'a> {
match self { match self {
Self::Fragment(fragment) => fragment.gen(p, ctx), Self::Fragment(fragment) => fragment.gen(p, ctx),
Self::Element(el) => el.gen(p, ctx), Self::Element(el) => el.gen(p, ctx),
Self::StringLiteral(lit) => lit.gen(p, ctx), Self::String(lit) => lit.gen(p, ctx),
Self::ExpressionContainer(expr_container) => expr_container.gen(p, ctx), Self::ExpressionContainer(expr_container) => expr_container.gen(p, ctx),
} }
} }
@ -1909,7 +1909,7 @@ impl<const MINIFY: bool> Gen<MINIFY> for JSXClosingFragment {
} }
} }
impl<const MINIFY: bool> Gen<MINIFY> for JSXText { impl<const MINIFY: bool> Gen<MINIFY> for JSXString {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, _ctx: Context) { fn gen(&self, p: &mut Codegen<{ MINIFY }>, _ctx: Context) {
p.print_str(self.value.as_bytes()); p.print_str(self.value.as_bytes());
} }

View file

@ -1556,7 +1556,7 @@ impl<'a> Gen for JSXAttributeValue<'a> {
match self { match self {
Self::Fragment(fragment) => fragment.gen(p), Self::Fragment(fragment) => fragment.gen(p),
Self::Element(el) => el.gen(p), Self::Element(el) => el.gen(p),
Self::StringLiteral(lit) => lit.gen(p), Self::String(lit) => lit.gen(p),
Self::ExpressionContainer(expr_container) => expr_container.gen(p), Self::ExpressionContainer(expr_container) => expr_container.gen(p),
} }
} }
@ -1625,7 +1625,7 @@ impl Gen for JSXClosingFragment {
} }
} }
impl Gen for JSXText { impl Gen for JSXString {
fn gen(&self, p: &mut Formatter) { fn gen(&self, p: &mut Formatter) {
p.print_str(self.value.as_bytes()); p.print_str(self.value.as_bytes());
} }

View file

@ -4,7 +4,6 @@ use oxc_diagnostics::{
thiserror::Error, thiserror::Error,
}; };
use oxc_macros::declare_oxc_lint; use oxc_macros::declare_oxc_lint;
use oxc_semantic::AstNodeId;
use oxc_span::Span; use oxc_span::Span;
use crate::{context::LintContext, rule::Rule, AstNode}; use crate::{context::LintContext, rule::Rule, AstNode};
@ -40,14 +39,12 @@ impl Rule for NoUselessEscape {
{ {
check( check(
ctx, ctx,
node.id(),
literal.span.start, literal.span.start,
&check_regexp(literal.span.source_text(ctx.source_text())), &check_regexp(literal.span.source_text(ctx.source_text())),
); );
} }
AstKind::StringLiteral(literal) => check( AstKind::StringLiteral(literal) => check(
ctx, ctx,
node.id(),
literal.span.start, literal.span.start,
&check_string(literal.span.source_text(ctx.source_text())), &check_string(literal.span.source_text(ctx.source_text())),
), ),
@ -55,7 +52,6 @@ impl Rule for NoUselessEscape {
for template_element in &literal.quasis { for template_element in &literal.quasis {
check( check(
ctx, ctx,
node.id(),
template_element.span.start - 1, template_element.span.start - 1,
&check_template(template_element.span.source_text(ctx.source_text())), &check_template(template_element.span.source_text(ctx.source_text())),
); );
@ -66,15 +62,8 @@ impl Rule for NoUselessEscape {
} }
} }
fn is_within_jsx_attribute_item(id: AstNodeId, ctx: &LintContext) -> bool {
if matches!(ctx.nodes().parent_kind(id), Some(AstKind::JSXAttributeItem(_))) {
return true;
}
false
}
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
fn check(ctx: &LintContext<'_>, node_id: AstNodeId, start: u32, offsets: &[usize]) { fn check(ctx: &LintContext<'_>, start: u32, offsets: &[usize]) {
let source_text = ctx.source_text(); let source_text = ctx.source_text();
for offset in offsets { for offset in offsets {
let offset = start as usize + offset; let offset = start as usize + offset;
@ -82,9 +71,7 @@ fn check(ctx: &LintContext<'_>, node_id: AstNodeId, start: u32, offsets: &[usize
let offset = offset as u32; let offset = offset as u32;
let len = c.len_utf8() as u32; let len = c.len_utf8() as u32;
if !is_within_jsx_attribute_item(node_id, ctx) { ctx.diagnostic(NoUselessEscapeDiagnostic(c, Span::new(offset - 1, offset + len)));
ctx.diagnostic(NoUselessEscapeDiagnostic(c, Span::new(offset - 1, offset + len)));
}
} }
} }

View file

@ -55,7 +55,7 @@ declare_oxc_lint!(
impl Rule for JsxNoCommentTextNodes { impl Rule for JsxNoCommentTextNodes {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let AstKind::JSXText(jsx_text) = node.kind() else { return }; let AstKind::JSXString(jsx_text) = node.kind() else { return };
if control_patterns(&jsx_text.value) { if control_patterns(&jsx_text.value) {
ctx.diagnostic(JsxNoCommentTextNodesDiagnostic(jsx_text.span)); ctx.diagnostic(JsxNoCommentTextNodesDiagnostic(jsx_text.span));

View file

@ -94,7 +94,7 @@ fn is_literal_ref_attribute(attr: &JSXAttribute, no_template_literals: bool) ->
JSXAttributeValue::ExpressionContainer(expr_container) => { JSXAttributeValue::ExpressionContainer(expr_container) => {
contains_string_literal(expr_container, no_template_literals) contains_string_literal(expr_container, no_template_literals)
} }
JSXAttributeValue::StringLiteral(_) => true, JSXAttributeValue::String(_) => true,
_ => false, _ => false,
}; };
} }

View file

@ -48,7 +48,7 @@ declare_oxc_lint!(
impl Rule for NoUnescapedEntities { impl Rule for NoUnescapedEntities {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::JSXText(jsx_text) = node.kind() { if let AstKind::JSXString(jsx_text) = node.kind() {
let source = jsx_text.span.source_text(ctx.source_text()); let source = jsx_text.span.source_text(ctx.source_text());
for (i, char) in source.chars().enumerate() { for (i, char) in source.chars().enumerate() {

View file

@ -54,7 +54,7 @@ impl Rule for TextEncodingIdentifierCase {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
let (str, span) = match node.kind() { let (str, span) = match node.kind() {
AstKind::StringLiteral(string_lit) => (&string_lit.value, string_lit.span), AstKind::StringLiteral(string_lit) => (&string_lit.value, string_lit.span),
AstKind::JSXText(jsx_text) => (&jsx_text.value, jsx_text.span), AstKind::JSXString(jsx_string) => (&jsx_string.value, jsx_string.span),
_ => { _ => {
return; return;
} }

View file

@ -219,7 +219,7 @@ impl<'a> Parser<'a> {
.map(JSXChild::ExpressionContainer) .map(JSXChild::ExpressionContainer)
.map(Some), .map(Some),
// text // text
Kind::JSXText => Ok(Some(JSXChild::Text(self.parse_jsx_text()))), Kind::JSXText => Ok(Some(JSXChild::Text(self.parse_jsx_string()))),
_ => Err(self.unexpected()), _ => Err(self.unexpected()),
} }
} }
@ -333,7 +333,7 @@ impl<'a> Parser<'a> {
fn parse_jsx_attribute_value(&mut self) -> Result<JSXAttributeValue<'a>> { fn parse_jsx_attribute_value(&mut self) -> Result<JSXAttributeValue<'a>> {
match self.cur_kind() { match self.cur_kind() {
Kind::Str => self.parse_literal_string().map(JSXAttributeValue::StringLiteral), Kind::Str => Ok(JSXAttributeValue::String(self.parse_jsx_string())),
Kind::LCurly => { Kind::LCurly => {
let expr = self.parse_jsx_expression_container(false)?; let expr = self.parse_jsx_expression_container(false)?;
Ok(JSXAttributeValue::ExpressionContainer(expr)) Ok(JSXAttributeValue::ExpressionContainer(expr))
@ -365,10 +365,10 @@ impl<'a> Parser<'a> {
Ok(self.ast.jsx_identifier(self.end_span(span), name)) Ok(self.ast.jsx_identifier(self.end_span(span), name))
} }
fn parse_jsx_text(&mut self) -> JSXText { fn parse_jsx_string(&mut self) -> JSXString {
let span = self.start_span(); let span = self.start_span();
let value = Atom::from(self.cur_string().unwrap()); let value = Atom::from(self.cur_string().unwrap());
self.bump_any(); self.bump_any();
self.ast.jsx_text(self.end_span(span), value) self.ast.jsx_string(self.end_span(span), value)
} }
} }

View file

@ -1871,7 +1871,7 @@ mod jsxattribute {
oxc_ast::ast::JSXExpression::EmptyExpression(_) => None, oxc_ast::ast::JSXExpression::EmptyExpression(_) => None,
}, },
JSXAttributeValue::Fragment(_) JSXAttributeValue::Fragment(_)
| JSXAttributeValue::StringLiteral(_) | JSXAttributeValue::String(_)
| JSXAttributeValue::Element(_) => None, | JSXAttributeValue::Element(_) => None,
}) })
.into_iter(), .into_iter(),

View file

@ -13,7 +13,7 @@ use crate::{
pub fn jsx_attribute_to_constant_string<'a>(attr: &'a JSXAttribute<'a>) -> Option<String> { pub fn jsx_attribute_to_constant_string<'a>(attr: &'a JSXAttribute<'a>) -> Option<String> {
attr.value.as_ref().and_then(|attr_value| match attr_value { attr.value.as_ref().and_then(|attr_value| match attr_value {
JSXAttributeValue::StringLiteral(slit) => slit.value.to_string().into(), JSXAttributeValue::String(slit) => slit.value.to_string().into(),
JSXAttributeValue::ExpressionContainer(expr) => match &expr.expression { JSXAttributeValue::ExpressionContainer(expr) => match &expr.expression {
oxc_ast::ast::JSXExpression::Expression(expr) => expr_to_maybe_const_string(expr), oxc_ast::ast::JSXExpression::Expression(expr) => expr_to_maybe_const_string(expr),
oxc_ast::ast::JSXExpression::EmptyExpression(_) => None, oxc_ast::ast::JSXExpression::EmptyExpression(_) => None,

View file

@ -38,7 +38,7 @@ pub enum Vertex<'a> {
JSXOpeningElement(Rc<JSXOpeningElementVertex<'a>>), JSXOpeningElement(Rc<JSXOpeningElementVertex<'a>>),
JSXSpreadAttribute(&'a JSXSpreadAttribute<'a>), JSXSpreadAttribute(&'a JSXSpreadAttribute<'a>),
JSXSpreadChild(&'a JSXSpreadChild<'a>), JSXSpreadChild(&'a JSXSpreadChild<'a>),
JSXText(&'a JSXText), JSXText(&'a JSXString),
ObjectLiteral(Rc<ObjectLiteralVertex<'a>>), ObjectLiteral(Rc<ObjectLiteralVertex<'a>>),
NumberLiteral(Rc<NumberLiteralVertex<'a>>), NumberLiteral(Rc<NumberLiteralVertex<'a>>),
Name(Rc<NameVertex<'a>>), Name(Rc<NameVertex<'a>>),

View file

@ -407,7 +407,7 @@ impl<'a> ReactJsx<'a> {
value: Option<&JSXAttributeValue<'a>>, value: Option<&JSXAttributeValue<'a>>,
) -> Expression<'a> { ) -> Expression<'a> {
match value { match value {
Some(JSXAttributeValue::StringLiteral(s)) => { Some(JSXAttributeValue::String(s)) => {
let jsx_text = Self::decode_jsx_text(&s.value); let jsx_text = Self::decode_jsx_text(&s.value);
let literal = StringLiteral::new(s.span, jsx_text.into()); let literal = StringLiteral::new(s.span, jsx_text.into());
self.ast.literal_string_expression(literal) self.ast.literal_string_expression(literal)
@ -460,7 +460,7 @@ impl<'a> ReactJsx<'a> {
} }
} }
fn transform_jsx_text(&self, text: &JSXText) -> Option<Expression<'a>> { fn transform_jsx_text(&self, text: &JSXString) -> Option<Expression<'a>> {
let text = text.value.trim(); let text = text.value.trim();
(!text.trim().is_empty()).then(|| { (!text.trim().is_empty()).then(|| {
let text = text let text = text