diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index 3b1cc4dc6..74ec6c5ef 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -1248,6 +1248,13 @@ impl<'a> ForStatementInit<'a> { pub fn is_lexical_declaration(&self) -> bool { matches!(self, Self::VariableDeclaration(decl) if decl.kind.is_lexical()) } + + pub fn expression(&self) -> Option<&Expression<'a>> { + match self { + Self::Expression(e) => Some(e), + _ => None, + } + } } /// For-In Statement diff --git a/crates/oxc_prettier/src/format/mod.rs b/crates/oxc_prettier/src/format/mod.rs index 8cfc840f2..d467f7687 100644 --- a/crates/oxc_prettier/src/format/mod.rs +++ b/crates/oxc_prettier/src/format/mod.rs @@ -28,7 +28,7 @@ use std::borrow::Cow; use oxc_allocator::{Box, Vec}; use oxc_ast::{ast::*, AstKind}; use oxc_span::GetSpan; -use oxc_syntax::identifier::is_identify_name; +use oxc_syntax::identifier::is_identifier_name; use crate::{ array, @@ -95,8 +95,8 @@ impl<'a> Format<'a> for Directive { self.expression.value.as_str(), p.options.single_quote, ))); - if p.options.semi { - parts.push(ss!(";")); + if let Some(semi) = p.semi() { + parts.push(semi); } parts.extend(hardline!()); Doc::Array(parts) @@ -135,8 +135,8 @@ impl<'a> Format<'a> for ExpressionStatement<'a> { wrap!(p, self, ExpressionStatement, { let mut parts = p.vec(); parts.push(self.expression.format(p)); - if p.options.semi { - parts.push(ss!(";")); + if let Some(semi) = p.semi() { + parts.push(semi); } Doc::Array(parts) }) @@ -363,8 +363,8 @@ impl<'a> Format<'a> for DoWhileStatement<'a> { parts.push(ss!("while (")); parts.push(group!(p, indent!(p, softline!(), format!(p, self.test)), softline!())); parts.push(ss!(")")); - if p.options.semi { - parts.push(ss!(";")); + if let Some(semi) = p.semi() { + parts.push(semi); } Doc::Array(parts) @@ -651,7 +651,9 @@ impl<'a> Format<'a> for VariableDeclaration<'a> { })); if !parent_for_loop.is_some_and(|span| span != self.span) { - parts.push(ss!(";")); + if let Some(semi) = p.semi() { + parts.push(semi); + } } Doc::Group(Group::new(parts, false)) @@ -674,8 +676,8 @@ impl<'a> Format<'a> for TSTypeAliasDeclaration<'a> { parts.push(ss!(" = ")); parts.push(format!(p, self.type_annotation)); - if p.options.semi { - parts.push(ss!(";")); + if let Some(semi) = p.semi() { + parts.push(semi); } Doc::Array(parts) @@ -1047,8 +1049,8 @@ impl<'a> Format<'a> for ImportDeclaration<'a> { } parts.push(ss!(" from ")); parts.push(self.source.format(p)); - if p.options.semi { - parts.push(ss!(";")); + if let Some(semi) = p.semi() { + parts.push(semi); } Doc::Array(parts) } @@ -1223,7 +1225,7 @@ impl<'a> Format<'a> for Expression<'a> { impl<'a> Format<'a> for IdentifierReference { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - p.str(self.name.as_str()) + wrap!(p, self, IdentifierReference, { p.str(self.name.as_str()) }) } } @@ -1551,7 +1553,7 @@ impl<'a> Format<'a> for PropertyKey<'a> { let unquote = if need_quote { false } else { - is_identify_name(literal.value.as_str()) + is_identifier_name(literal.value.as_str()) }; if !unquote || p.options.quote_props.is_preserve() { diff --git a/crates/oxc_prettier/src/format/property.rs b/crates/oxc_prettier/src/format/property.rs index 1535c4bc2..9fa6b8b85 100644 --- a/crates/oxc_prettier/src/format/property.rs +++ b/crates/oxc_prettier/src/format/property.rs @@ -1,12 +1,12 @@ use oxc_ast::ast::{Expression, PropertyKey}; -use oxc_syntax::identifier::is_identify_name; +use oxc_syntax::identifier::is_identifier_name; pub(super) fn is_property_key_has_quote(key: &PropertyKey<'_>) -> bool { matches!(key, PropertyKey::Expression(Expression::StringLiteral(literal)) if is_string_prop_safe_to_unquote(literal.value.as_str())) } pub(super) fn is_string_prop_safe_to_unquote(value: &str) -> bool { - !is_identify_name(value) && !is_simple_number(value) + !is_identifier_name(value) && !is_simple_number(value) } // Matches “simple” numbers like `123` and `2.5` but not `1_000`, `1e+100` or `0b10`. diff --git a/crates/oxc_prettier/src/lib.rs b/crates/oxc_prettier/src/lib.rs index 7a0367bbd..0cdbbe66a 100644 --- a/crates/oxc_prettier/src/lib.rs +++ b/crates/oxc_prettier/src/lib.rs @@ -121,6 +121,10 @@ impl<'a> Prettier<'a> { unsafe { std::mem::transmute(t) } } + pub fn semi(&self) -> Option> { + self.options.semi.then(|| Doc::Str(";")) + } + pub fn should_print_es5_comma(&self) -> bool { self.should_print_comma_impl(false) } diff --git a/crates/oxc_prettier/src/needs_parens.rs b/crates/oxc_prettier/src/needs_parens.rs index 03c0f5102..ef1fa54f6 100644 --- a/crates/oxc_prettier/src/needs_parens.rs +++ b/crates/oxc_prettier/src/needs_parens.rs @@ -11,7 +11,8 @@ use oxc_ast::{ ast::{ AssignmentTarget, AssignmentTargetPattern, ChainElement, ExportDefaultDeclarationKind, - Expression, ModuleDeclaration, ObjectExpression, SimpleAssignmentTarget, + Expression, ForStatementLeft, MemberExpression, ModuleDeclaration, ObjectExpression, + SimpleAssignmentTarget, }, AstKind, }; @@ -30,34 +31,23 @@ impl<'a> Prettier<'a> { } fn need_parens(&mut self, kind: AstKind<'a>) -> bool { - if matches!(kind, AstKind::Program(_)) { + if matches!(kind, AstKind::Program(_)) || kind.is_statement() || kind.is_declaration() { return false; } - if kind.is_statement() || kind.is_declaration() { - return false; - } - - let parent_kind = self.parent_kind(); - - if let AstKind::ObjectExpression(e) = kind { - if self.check_object_expression(e) { - return true; - } - } - - if self.check_parent_kind(kind, parent_kind) { - return true; - } - - if self.check_kind(kind, parent_kind) { + if matches!(kind, AstKind::ObjectExpression(e) if self.check_object_expression(e)) + || self.check_let_object(kind) + || self.check_parent_kind(kind) + || self.check_kind(kind) + { return true; } false } - fn check_kind(&self, kind: AstKind<'a>, parent_kind: AstKind<'a>) -> bool { + fn check_kind(&self, kind: AstKind<'a>) -> bool { + let parent_kind = self.parent_kind(); match kind { AstKind::NumberLiteral(literal) => { matches!(parent_kind, AstKind::MemberExpression(e) if e.object().span() == literal.span) @@ -190,8 +180,8 @@ impl<'a> Prettier<'a> { } } - fn check_parent_kind(&mut self, kind: AstKind<'a>, parent_kind: AstKind<'a>) -> bool { - match parent_kind { + fn check_parent_kind(&mut self, kind: AstKind<'a>) -> bool { + match self.parent_kind() { AstKind::Class(class) => { if let Some(h) = &class.super_class { match kind { @@ -263,6 +253,54 @@ impl<'a> Prettier<'a> { false } + /// `(let)[a] = 1` + fn check_let_object(&self, kind: AstKind<'a>) -> bool { + let AstKind::IdentifierReference(ident) = kind else { return false }; + if ident.name != "let" { + return false; + } + let AstKind::MemberExpression(MemberExpression::ComputedMemberExpression(expr)) = + self.parent_kind() + else { + return false; + }; + if !matches!(&expr.object, Expression::Identifier(ident) if ident.name == "let") { + return false; + } + let Some(statement) = self.nodes.iter().rev().find(|node| { + matches!( + node, + AstKind::ExpressionStatement(_) + | AstKind::ForStatement(_) + | AstKind::ForInStatement(_) + ) + }) else { + return false; + }; + match statement { + AstKind::ExpressionStatement(stmt) => { + Self::starts_with_no_lookahead_token(&stmt.expression, ident.span) + } + AstKind::ForStatement(stmt) => stmt + .init + .as_ref() + .and_then(|init| init.expression()) + .map_or(false, |e| Self::starts_with_no_lookahead_token(e, ident.span)), + AstKind::ForInStatement(stmt) => { + if let ForStatementLeft::AssignmentTarget( + AssignmentTarget::SimpleAssignmentTarget( + SimpleAssignmentTarget::MemberAssignmentTarget(e), + ), + ) = &stmt.left + { + return Self::starts_with_no_lookahead_token(e.object(), ident.span); + } + false + } + _ => false, + } + } + fn check_object_function_class(&self, span: Span) -> bool { for ast_kind in self.nodes.iter().rev() { if let AstKind::ExpressionStatement(e) = ast_kind { diff --git a/crates/oxc_syntax/src/identifier.rs b/crates/oxc_syntax/src/identifier.rs index 55ab127e9..ba07377f9 100644 --- a/crates/oxc_syntax/src/identifier.rs +++ b/crates/oxc_syntax/src/identifier.rs @@ -109,7 +109,7 @@ pub fn is_identifier_part(c: char) -> bool { is_id_continue_unicode(c) || c == ZWNJ || c == ZWJ } -pub fn is_identify_name(name: &str) -> bool { +pub fn is_identifier_name(name: &str) -> bool { let mut chars = name.chars(); chars.next().is_some_and(is_identifier_start_all) && chars.all(is_identifier_part) } diff --git a/tasks/prettier_conformance/prettier.snap.md b/tasks/prettier_conformance/prettier.snap.md index e2c62800b..e191bd403 100644 --- a/tasks/prettier_conformance/prettier.snap.md +++ b/tasks/prettier_conformance/prettier.snap.md @@ -1,4 +1,4 @@ -Compatibility: 201/561 (35.83%) +Compatibility: 203/561 (36.19%) # Failed @@ -157,7 +157,6 @@ Compatibility: 201/561 (35.83%) * comments/jsdoc.js * comments/jsx.js * comments/last-arg.js -* comments/multi-comments-2.js * comments/multi-comments-on-same-line-2.js * comments/multi-comments-on-same-line.js * comments/multi-comments.js @@ -277,7 +276,6 @@ Compatibility: 201/561 (35.83%) * identifier/for-of/let.js ### identifier/parentheses -* identifier/parentheses/const.js * identifier/parentheses/let.js ### if