diff --git a/crates/oxc_prettier/src/format/js.rs b/crates/oxc_prettier/src/format/js.rs index 5d328a94ee4da..8dcaa05ac84c3 100644 --- a/crates/oxc_prettier/src/format/js.rs +++ b/crates/oxc_prettier/src/format/js.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use cow_utils::CowUtils; -use oxc_allocator::Vec; +use oxc_allocator::{Box, Vec}; use oxc_ast::{ast::*, AstKind}; use oxc_span::GetSpan; use oxc_syntax::identifier::{is_identifier_name, is_line_terminator}; @@ -627,39 +627,7 @@ impl<'a> Format<'a> for FormalParameter<'a> { impl<'a> Format<'a> for ImportDeclaration<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let mut parts = Vec::new_in(p.allocator); - parts.push(text!("import")); - if self.import_kind.is_type() { - parts.push(text!(" type")); - } - - if let Some(specifiers) = &self.specifiers { - let is_default = specifiers.first().is_some_and(|x| { - matches!(x, ImportDeclarationSpecifier::ImportDefaultSpecifier(_)) - }); - - let validate_namespace = |x: &ImportDeclarationSpecifier| { - matches!(x, ImportDeclarationSpecifier::ImportNamespaceSpecifier(_)) - }; - - let is_namespace = specifiers.first().is_some_and(validate_namespace) - || specifiers.get(1).is_some_and(validate_namespace); - - parts.push(module::print_module_specifiers(p, specifiers, is_default, is_namespace)); - parts.push(text!(" from")); - } - parts.push(text!(" ")); - parts.push(self.source.format(p)); - - if let Some(with_clause) = &self.with_clause { - parts.push(text!(" ")); - parts.push(with_clause.format(p)); - } - - if let Some(semi) = p.semi() { - parts.push(semi); - } - array!(p, parts) + wrap!(p, self, ImportDeclaration, { module::print_import_declaration(p, self) }) } } @@ -675,16 +643,22 @@ impl<'a> Format<'a> for ImportDeclarationSpecifier<'a> { impl<'a> Format<'a> for ImportSpecifier<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let typed = if self.import_kind.is_type() { text!("type ") } else { text!("") }; + let mut parts = Vec::new_in(p.allocator); + if self.import_kind.is_type() { + parts.push(text!("type ")); + } + + // If both imported and local are the same name if self.imported.span() == self.local.span { - let local_doc = self.local.format(p); - array!(p, [typed, local_doc]) - } else { - let imported_doc = self.imported.format(p); - let local_doc = self.local.format(p); - array!(p, [typed, imported_doc, text!(" as "), local_doc]) + parts.push(self.local.format(p)); + return array!(p, parts); } + + parts.push(self.imported.format(p)); + parts.push(text!(" as ")); + parts.push(self.local.format(p)); + array!(p, parts) } } @@ -696,26 +670,26 @@ impl<'a> Format<'a> for ImportDefaultSpecifier<'a> { impl<'a> Format<'a> for ImportNamespaceSpecifier<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let local_doc = self.local.format(p); - array!(p, [text!("* as "), local_doc]) - } -} - -impl<'a> Format<'a> for WithClause<'a> { - fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let attribute_keyword_doc = self.attributes_keyword.format(p); - let with_clause_doc = object::print_object(p, object::ObjectLike::WithClause(self)); - array!(p, [attribute_keyword_doc, text!(" "), with_clause_doc]) + array!(p, [text!("* as "), self.local.format(p)]) } } impl<'a> Format<'a> for ImportAttribute<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let key_doc = match &self.key { - ImportAttributeKey::Identifier(ident) => ident.format(p), - ImportAttributeKey::StringLiteral(literal) => literal.format(p), - }; - group!(p, [group!(p, [key_doc]), text!(": "), self.value.format(p)]) + let left_doc = property::print_property_key( + p, + &property::PropertyKeyLike::ImportAttributeKey(&self.key), + false, // Can not be computed + ); + + assignment::print_assignment( + p, + assignment::AssignmentLike::ImportAttribute(self), + left_doc, + text!(":"), + // PERF: Can be better without clone...? + Some(&Expression::StringLiteral(Box::new_in(self.value.clone(), p.allocator))), + ) } } @@ -739,13 +713,12 @@ impl<'a> Format<'a> for ExportNamedDeclaration<'a> { impl<'a> Format<'a> for ExportSpecifier<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { + // If both exported and local are the same name if self.exported.span() == self.local.span() { - self.local.format(p) - } else { - let local_doc = self.local.format(p); - let exported_doc = self.exported.format(p); - array!(p, [local_doc, text!(" as "), exported_doc]) + return self.local.format(p); } + + array!(p, [self.local.format(p), text!(" as "), self.exported.format(p)]) } } @@ -1022,32 +995,23 @@ impl<'a> Format<'a> for ObjectProperty<'a> { wrap!(p, self, ObjectProperty, { if self.method || self.kind == PropertyKind::Get || self.kind == PropertyKind::Set { let mut parts = Vec::new_in(p.allocator); - let mut method = self.method; match self.kind { PropertyKind::Get => { parts.push(text!("get ")); - method = true; } PropertyKind::Set => { parts.push(text!("set ")); - method = true; } PropertyKind::Init => (), } - if method { - if let Expression::FunctionExpression(func_expr) = &self.value { - parts.push(wrap!(p, func_expr, Function, { - function::print_function( - p, - func_expr, - Some(self.key.span().source_text(p.source_text)), - ) - })); - } - } else { - parts.push(self.key.format(p)); - parts.push(text!(": ")); - parts.push(self.value.format(p)); + if let Expression::FunctionExpression(func_expr) = &self.value { + parts.push(wrap!(p, func_expr, Function, { + function::print_function( + p, + func_expr, + Some(self.key.span().source_text(p.source_text)), + ) + })); } return group!(p, parts); } @@ -1056,12 +1020,11 @@ impl<'a> Format<'a> for ObjectProperty<'a> { return self.value.format(p); } - let left_doc = if self.computed { - let key_doc = self.key.format(p); - array!(p, [text!("["), key_doc, text!("]")]) - } else { - self.key.format(p) - }; + let left_doc = property::print_property_key( + p, + &property::PropertyKeyLike::PropertyKey(&self.key), + self.computed, + ); assignment::print_assignment( p, @@ -1076,91 +1039,11 @@ impl<'a> Format<'a> for ObjectProperty<'a> { impl<'a> Format<'a> for PropertyKey<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let is_parent_computed = match p.current_kind() { - AstKind::MethodDefinition(node) => node.computed, - AstKind::PropertyDefinition(node) => node.computed, - _ => false, - }; - if is_parent_computed { - let mut parts = Vec::new_in(p.allocator); - parts.push(text!("[")); - let doc = match self { - PropertyKey::StaticIdentifier(ident) => ident.format(p), - PropertyKey::PrivateIdentifier(ident) => ident.format(p), - match_expression!(PropertyKey) => self.to_expression().format(p), - }; - parts.push(doc); - parts.push(text!("]")); - return array!(p, parts); + match self { + PropertyKey::StaticIdentifier(ident) => ident.format(p), + PropertyKey::PrivateIdentifier(ident) => ident.format(p), + match_expression!(PropertyKey) => self.to_expression().format(p), } - - wrap!(p, self, PropertyKey, { - // Perf: Cache the result of `need_quote` to avoid checking it in each PropertyKey - let need_quote = p.options.quote_props.consistent() - && match p.parent_parent_kind() { - Some(AstKind::ObjectExpression(a)) => a.properties.iter().any(|x| match x { - ObjectPropertyKind::ObjectProperty(p) => { - property::is_property_key_has_quote(&p.key) - } - ObjectPropertyKind::SpreadProperty(_) => false, - }), - Some(AstKind::ClassBody(a)) => a.body.iter().any(|x| match x { - ClassElement::PropertyDefinition(p) => { - property::is_property_key_has_quote(&p.key) - } - _ => false, - }), - _ => false, - }; - - match self { - PropertyKey::StaticIdentifier(ident) => { - if need_quote { - literal::print_string_from_not_quoted_raw_text( - p, - &ident.name, - p.options.single_quote, - ) - } else { - ident.format(p) - } - } - PropertyKey::PrivateIdentifier(ident) => ident.format(p), - PropertyKey::StringLiteral(literal) => { - // This does not pass quotes/objects.js - // because prettier uses the function `isEs5IdentifierName` based on unicode version 3, - // but `is_identifier_name` uses the latest unicode version. - if is_identifier_name(literal.value.as_str()) - && (p.options.quote_props.as_needed() - || (p.options.quote_props.consistent()/* && !needsQuoteProps.get(parent) */)) - { - dynamic_text!(p, literal.value.as_str()) - } else { - literal::print_string_from_not_quoted_raw_text( - p, - literal.value.as_str(), - p.options.single_quote, - ) - } - } - PropertyKey::NumericLiteral(literal) => { - if need_quote { - literal::print_string_from_not_quoted_raw_text( - p, - &literal.raw_str(), - p.options.single_quote, - ) - } else { - literal.format(p) - } - } - PropertyKey::Identifier(ident) => { - let ident_doc = ident.format(p); - array!(p, [text!("["), ident_doc, text!("]")]) - } - match_expression!(PropertyKey) => self.to_expression().format(p), - } - }) } } @@ -1339,9 +1222,9 @@ impl<'a> Format<'a> for ObjectAssignmentTarget<'a> { impl<'a> Format<'a> for AssignmentTargetWithDefault<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let binding_doc = self.binding.format(p); - let init_doc = self.init.format(p); - array!(p, [binding_doc, text!(" = "), init_doc]) + wrap!(p, self, AssignmentTargetWithDefault, { + array!(p, [self.binding.format(p), text!(" = "), self.init.format(p)]) + }) } } @@ -1358,19 +1241,30 @@ impl<'a> Format<'a> for AssignmentTargetPropertyIdentifier<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { let mut parts = Vec::new_in(p.allocator); parts.push(self.binding.format(p)); + if let Some(init) = &self.init { parts.push(text!(" = ")); parts.push(init.format(p)); } + array!(p, parts) } } impl<'a> Format<'a> for AssignmentTargetPropertyProperty<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let name_doc = self.name.format(p); - let binding_doc = self.binding.format(p); - array!(p, [name_doc, text!(": "), binding_doc]) + let left_doc = self.name.format(p); + + // TODO: How to convert `AssignmentTargetMaybeDefault` to `Expression`? + // Or `print_assignment` is not needed? + // assignment::print_assignment( + // p, + // assignment::AssignmentLike::AssignmentTargetPropertyProperty(self), + // left_doc, + // text!(":"), + // // self.binding + // ) + group!(p, [left_doc, text!(": "), self.binding.format(p)]) } } @@ -1400,6 +1294,7 @@ impl<'a> Format<'a> for ParenthesizedExpression<'a> { impl<'a> Format<'a> for ImportExpression<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { wrap!(p, self, ImportExpression, { + // TODO: Use `print_call_expression`? let mut parts = Vec::new_in(p.allocator); parts.push(text!("import")); parts.push(text!("(")); @@ -1588,12 +1483,21 @@ impl<'a> Format<'a> for ObjectPattern<'a> { impl<'a> Format<'a> for BindingProperty<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { if self.shorthand { - self.value.format(p) - } else { - let key_doc = self.key.format(p); - let value_doc = self.value.format(p); - group!(p, [key_doc, text!(": "), value_doc]) + return self.value.format(p); } + + let left_doc = self.key.format(p); + + // TODO: How to convert `BindingPattern` to `Expression`...? + // Or `print_assignment` is not needed? + // assignment::print_assignment( + // p, + // assignment::AssignmentLike::BindingProperty(self), + // left_doc, + // text!(":"), + // Some(&self.value), + // ) + group!(p, [left_doc, text!(": "), self.value.format(p)]) } } @@ -1613,9 +1517,7 @@ impl<'a> Format<'a> for ArrayPattern<'a> { impl<'a> Format<'a> for AssignmentPattern<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { wrap!(p, self, AssignmentPattern, { - let left_doc = self.left.format(p); - let right_doc = self.right.format(p); - array!(p, [left_doc, text!(" = "), right_doc]) + array!(p, [self.left.format(p), text!(" = "), self.right.format(p)]) }) } } diff --git a/crates/oxc_prettier/src/format/print/array.rs b/crates/oxc_prettier/src/format/print/array.rs index a5c1a54c9720c..eeb54d1e95979 100644 --- a/crates/oxc_prettier/src/format/print/array.rs +++ b/crates/oxc_prettier/src/format/print/array.rs @@ -11,6 +11,7 @@ use crate::{ line, softline, text, Format, Prettier, }; +// TODO: Rename `ArrayLike` #[allow(clippy::enum_variant_names)] pub enum Array<'a, 'b> { ArrayExpression(&'b ArrayExpression<'a>), diff --git a/crates/oxc_prettier/src/format/print/assignment.rs b/crates/oxc_prettier/src/format/print/assignment.rs index 4cb1691d416f0..9f290f06aaf71 100644 --- a/crates/oxc_prettier/src/format/print/assignment.rs +++ b/crates/oxc_prettier/src/format/print/assignment.rs @@ -16,19 +16,7 @@ pub enum AssignmentLike<'a, 'b> { PropertyDefinition(&'b PropertyDefinition<'a>), AccessorProperty(&'b AccessorProperty<'a>), ObjectProperty(&'b ObjectProperty<'a>), -} - -impl<'a, 'b> From> for AssignmentLike<'a, 'b> { - fn from(class_memberish: class::ClassPropertyLike<'a, 'b>) -> Self { - match class_memberish { - class::ClassPropertyLike::PropertyDefinition(property_def) => { - Self::PropertyDefinition(property_def) - } - class::ClassPropertyLike::AccessorProperty(accessor_prop) => { - Self::AccessorProperty(accessor_prop) - } - } - } + ImportAttribute(&'b ImportAttribute<'a>), } pub fn print_assignment<'a>( @@ -135,6 +123,9 @@ fn choose_layout<'a>( return Layout::BreakAfterOperator; } + if let AssignmentLike::ImportAttribute(import_attr) = assignment_like_node { + return Layout::NeverBreakAfterOperator; + } if let Expression::CallExpression(call_expr) = right_expr { if let Expression::Identifier(ident) = &call_expr.callee { if ident.name == "require" { @@ -238,10 +229,7 @@ pub fn is_arrow_function_variable_declarator(expr: &AssignmentLike) -> bool { } false } - AssignmentLike::AssignmentExpression(_) - | AssignmentLike::PropertyDefinition(_) - | AssignmentLike::ObjectProperty(_) - | AssignmentLike::AccessorProperty(_) => false, + _ => false, } } diff --git a/crates/oxc_prettier/src/format/print/class.rs b/crates/oxc_prettier/src/format/print/class.rs index 5b6134721b87a..7352c5dea8c5a 100644 --- a/crates/oxc_prettier/src/format/print/class.rs +++ b/crates/oxc_prettier/src/format/print/class.rs @@ -6,7 +6,7 @@ use oxc_span::GetSpan; use crate::{ array, - format::print::{assignment, function}, + format::print::{assignment, function, property}, group, hardline, if_break, indent, ir::{Doc, JoinSeparator}, join, line, softline, text, Format, Prettier, @@ -130,14 +130,19 @@ pub enum ClassPropertyLike<'a, 'b> { impl<'a> ClassPropertyLike<'a, '_> { fn format_key(&self, p: &mut Prettier<'a>) -> Doc<'a> { - match self { + let (computed, property_key) = match self { ClassPropertyLike::PropertyDefinition(property_definition) => { - property_definition.key.format(p) + (property_definition.computed, &property_definition.key) } ClassPropertyLike::AccessorProperty(accessor_property) => { - accessor_property.key.format(p) + (accessor_property.computed, &accessor_property.key) } - } + }; + property::print_property_key( + p, + &property::PropertyKeyLike::PropertyKey(property_key), + computed, + ) } fn decorators(&self) -> Option<&oxc_allocator::Vec>> { diff --git a/crates/oxc_prettier/src/format/print/function.rs b/crates/oxc_prettier/src/format/print/function.rs index e930b1a5a9e75..67fe81cde9c12 100644 --- a/crates/oxc_prettier/src/format/print/function.rs +++ b/crates/oxc_prettier/src/format/print/function.rs @@ -2,8 +2,11 @@ use oxc_allocator::Vec; use oxc_ast::ast::*; use crate::{ - array, dynamic_text, format::print::function_parameters::should_group_function_parameters, - group, if_break, indent, ir::Doc, softline, text, Format, Prettier, + array, dynamic_text, + format::print::{function_parameters, property}, + group, if_break, indent, + ir::Doc, + softline, text, Format, Prettier, }; pub fn print_function<'a>( @@ -44,7 +47,7 @@ pub fn print_function<'a>( parts.push(group!( p, [{ - if should_group_function_parameters(func) { + if function_parameters::should_group_function_parameters(func) { group!(p, [params_doc]) } else { params_doc @@ -91,7 +94,11 @@ pub fn print_method<'a>(p: &mut Prettier<'a>, method: &MethodDefinition<'a>) -> parts.push(text!("*")); } - parts.push(method.key.format(p)); + parts.push(property::print_property_key( + p, + &property::PropertyKeyLike::PropertyKey(&method.key), + method.computed, + )); if method.optional { parts.push(text!("?")); @@ -105,7 +112,7 @@ pub fn print_method<'a>(p: &mut Prettier<'a>, method: &MethodDefinition<'a>) -> pub fn print_method_value<'a>(p: &mut Prettier<'a>, function: &Function<'a>) -> Doc<'a> { let mut parts = Vec::new_in(p.allocator); let parameters_doc = function.params.format(p); - let should_group_parameters = should_group_function_parameters(function); + let should_group_parameters = function_parameters::should_group_function_parameters(function); let parameters_doc = if should_group_parameters { group!(p, [parameters_doc]) } else { parameters_doc }; diff --git a/crates/oxc_prettier/src/format/print/module.rs b/crates/oxc_prettier/src/format/print/module.rs index 5cd7d61bd4398..59c93d854b9ef 100644 --- a/crates/oxc_prettier/src/format/print/module.rs +++ b/crates/oxc_prettier/src/format/print/module.rs @@ -1,14 +1,84 @@ -use std::collections::VecDeque; - use oxc_allocator::Vec; use oxc_ast::ast::*; use crate::{ - array, group, if_break, indent, + array, dynamic_text, group, if_break, indent, ir::{Doc, JoinSeparator}, join, line, softline, text, Format, Prettier, }; +pub fn print_import_declaration<'a>(p: &mut Prettier<'a>, decl: &ImportDeclaration<'a>) -> Doc<'a> { + let mut parts = Vec::new_in(p.allocator); + + parts.push(text!("import")); + + if let Some(phase) = decl.phase { + parts.push(text!(" ")); + parts.push(dynamic_text!(p, phase.as_str())); + } + + if decl.import_kind.is_type() { + parts.push(text!(" type")); + } + + if let Some(specifiers) = &decl.specifiers { + let validate_namespace = |ids: &ImportDeclarationSpecifier| { + matches!(ids, ImportDeclarationSpecifier::ImportNamespaceSpecifier(_)) + }; + + let is_default = specifiers.first().is_some_and(|ids| { + matches!(ids, ImportDeclarationSpecifier::ImportDefaultSpecifier(_)) + }); + let is_namespace = specifiers.first().is_some_and(validate_namespace) + || specifiers.get(1).is_some_and(validate_namespace); + + parts.push(print_module_specifiers(p, specifiers, is_default, is_namespace)); + parts.push(text!(" from")); + } + + parts.push(text!(" ")); + parts.push(decl.source.format(p)); + + if let Some(with_clause) = &decl.with_clause { + parts.push(print_import_attributes(p, with_clause)); + } + + if let Some(semi) = p.semi() { + parts.push(semi); + } + + array!(p, parts) +} + +fn print_import_attributes<'a>(p: &mut Prettier<'a>, with_clause: &WithClause<'a>) -> Doc<'a> { + let mut parts = Vec::new_in(p.allocator); + + parts.push(text!(" ")); + parts.push(with_clause.attributes_keyword.format(p)); + parts.push(text!(" {")); + + if !with_clause.with_entries.is_empty() { + if p.options.bracket_spacing { + parts.push(text!(" ")); + } + + let attributes_doc = with_clause + .with_entries + .iter() + .map(|import_attr| import_attr.format(p)) + .collect::>(); + parts.push(join!(p, JoinSeparator::CommaSpace, attributes_doc)); + + if p.options.bracket_spacing { + parts.push(text!(" ")); + } + } + + parts.push(text!("}")); + + array!(p, parts) +} + pub fn print_export_declaration<'a>(p: &mut Prettier<'a>, decl: &ModuleDeclaration<'a>) -> Doc<'a> { debug_assert!(decl.is_export()); @@ -20,12 +90,12 @@ pub fn print_export_declaration<'a>(p: &mut Prettier<'a>, decl: &ModuleDeclarati } parts.push(match decl { - ModuleDeclaration::ImportDeclaration(decl) => unreachable!(), ModuleDeclaration::ExportAllDeclaration(decl) => decl.format(p), ModuleDeclaration::ExportDefaultDeclaration(decl) => decl.format(p), ModuleDeclaration::ExportNamedDeclaration(decl) => decl.format(p), ModuleDeclaration::TSExportAssignment(decl) => decl.format(p), ModuleDeclaration::TSNamespaceExportDeclaration(decl) => decl.format(p), + ModuleDeclaration::ImportDeclaration(_) => unreachable!(), }); if let Some(source) = decl.source() { @@ -34,8 +104,7 @@ pub fn print_export_declaration<'a>(p: &mut Prettier<'a>, decl: &ModuleDeclarati } if let Some(with_clause) = decl.with_clause() { - parts.push(text!(" ")); - parts.push(with_clause.format(p)); + parts.push(print_import_attributes(p, with_clause)); } if let Some(doc) = print_semicolon_after_export_declaration(p, decl) { @@ -84,60 +153,60 @@ pub fn print_module_specifiers<'a, T: Format<'a>>( include_default: bool, include_namespace: bool, ) -> Doc<'a> { - let mut parts = Vec::new_in(p.allocator); if specifiers.is_empty() { - parts.push(text!(" {}")); - } else { - parts.push(text!(" ")); + return text!(" {}"); + } - let mut specifiers_iter: VecDeque<_> = specifiers.iter().collect(); - if include_default { - parts.push(specifiers_iter.pop_front().unwrap().format(p)); - if !specifiers_iter.is_empty() { - parts.push(text!(", ")); - } - } + let mut parts = Vec::new_in(p.allocator); + parts.push(text!(" ")); - if include_namespace { - parts.push(specifiers_iter.pop_front().unwrap().format(p)); - if !specifiers_iter.is_empty() { - parts.push(text!(", ")); - } + let mut specifiers_iter: std::collections::VecDeque<_> = specifiers.iter().collect(); + if include_default { + parts.push(specifiers_iter.pop_front().unwrap().format(p)); + if !specifiers_iter.is_empty() { + parts.push(text!(", ")); } + } + if include_namespace { + parts.push(specifiers_iter.pop_front().unwrap().format(p)); if !specifiers_iter.is_empty() { - let can_break = specifiers.len() > 1; - - if can_break { - let docs = - specifiers_iter.iter().map(|s| s.format(p)).collect::>(); - parts.push(group!( - p, - [ - text!("{"), - indent!( - p, - [ - if p.options.bracket_spacing { line!() } else { softline!() }, - join!(p, JoinSeparator::CommaLine, docs) - ] - ), - if_break!(p, text!(if p.should_print_es5_comma() { "," } else { "" })), - if p.options.bracket_spacing { line!() } else { softline!() }, - text!("}"), - ] - )); - } else { - parts.push(text!("{")); - if p.options.bracket_spacing { - parts.push(text!(" ")); - } - parts.extend(specifiers_iter.iter().map(|s| s.format(p))); - if p.options.bracket_spacing { - parts.push(text!(" ")); - } - parts.push(text!("}")); + parts.push(text!(", ")); + } + } + + if !specifiers_iter.is_empty() { + let can_break = specifiers.len() > 1; + let specifier_docs = + specifiers_iter.iter().map(|s| s.format(p)).collect::>(); + + if can_break { + parts.push(group!( + p, + [ + text!("{"), + indent!( + p, + [ + if p.options.bracket_spacing { line!() } else { softline!() }, + join!(p, JoinSeparator::CommaLine, specifier_docs) + ] + ), + if_break!(p, text!(if p.should_print_es5_comma() { "," } else { "" })), + if p.options.bracket_spacing { line!() } else { softline!() }, + text!("}"), + ] + )); + } else { + parts.push(text!("{")); + if p.options.bracket_spacing { + parts.push(text!(" ")); + } + parts.extend(specifier_docs); + if p.options.bracket_spacing { + parts.push(text!(" ")); } + parts.push(text!("}")); } } diff --git a/crates/oxc_prettier/src/format/print/object.rs b/crates/oxc_prettier/src/format/print/object.rs index 568f87305cfe3..d0cacfa319580 100644 --- a/crates/oxc_prettier/src/format/print/object.rs +++ b/crates/oxc_prettier/src/format/print/object.rs @@ -15,7 +15,6 @@ pub enum ObjectLike<'a, 'b> { Expression(&'b ObjectExpression<'a>), AssignmentTarget(&'b ObjectAssignmentTarget<'a>), Pattern(&'b ObjectPattern<'a>), - WithClause(&'b WithClause<'a>), TSTypeLiteral(&'b TSTypeLiteral<'a>), } @@ -25,7 +24,6 @@ impl<'a, 'b> ObjectLike<'a, 'b> { Self::Expression(expr) => expr.properties.len(), Self::AssignmentTarget(target) => target.properties.len(), Self::Pattern(object) => object.properties.len(), - Self::WithClause(attributes) => attributes.with_entries.len(), Self::TSTypeLiteral(literal) => literal.members.len(), } } @@ -35,7 +33,7 @@ impl<'a, 'b> ObjectLike<'a, 'b> { Self::Expression(expr) => false, Self::AssignmentTarget(target) => target.rest.is_some(), Self::Pattern(object) => object.rest.is_some(), - Self::WithClause(_) | Self::TSTypeLiteral(_) => false, + Self::TSTypeLiteral(_) => false, } } @@ -44,7 +42,6 @@ impl<'a, 'b> ObjectLike<'a, 'b> { Self::Expression(object) => object.properties.is_empty(), Self::AssignmentTarget(object) => object.is_empty(), Self::Pattern(object) => object.is_empty(), - Self::WithClause(attributes) => attributes.with_entries.is_empty(), Self::TSTypeLiteral(literal) => literal.members.is_empty(), } } @@ -58,7 +55,6 @@ impl<'a, 'b> ObjectLike<'a, 'b> { Self::Expression(object) => object.span, Self::AssignmentTarget(object) => object.span, Self::Pattern(object) => object.span, - Self::WithClause(attributes) => attributes.span, Self::TSTypeLiteral(literal) => literal.span, } } @@ -72,9 +68,6 @@ impl<'a, 'b> ObjectLike<'a, 'b> { Box::new(object.properties.iter().map(|prop| prop.format(p))) } Self::Pattern(object) => Box::new(object.properties.iter().map(|prop| prop.format(p))), - Self::WithClause(attributes) => { - Box::new(attributes.with_entries.iter().map(|entry| entry.format(p))) - } Self::TSTypeLiteral(literal) => { Box::new(literal.members.iter().map(|member| member.format(p))) } @@ -127,9 +120,7 @@ pub fn print_object<'a>(p: &mut Prettier<'a>, object: ObjectLike<'a, '_>) -> Doc } match object { - ObjectLike::Expression(_) - | ObjectLike::WithClause(_) - | ObjectLike::TSTypeLiteral(_) => {} + ObjectLike::Expression(_) | ObjectLike::TSTypeLiteral(_) => {} ObjectLike::AssignmentTarget(target) => { if let Some(rest) = &target.rest { indent_parts.push(rest.format(p)); diff --git a/crates/oxc_prettier/src/format/print/property.rs b/crates/oxc_prettier/src/format/print/property.rs index 59a60323d4baf..57b2f7a52c959 100644 --- a/crates/oxc_prettier/src/format/print/property.rs +++ b/crates/oxc_prettier/src/format/print/property.rs @@ -1,16 +1,133 @@ -use oxc_ast::ast::PropertyKey; +use oxc_allocator::Vec; +use oxc_ast::{ast::*, AstKind}; use oxc_syntax::identifier::is_identifier_name; -pub fn is_property_key_has_quote(key: &PropertyKey<'_>) -> bool { +use crate::{array, dynamic_text, format::print::literal, ir::Doc, text, Format, Prettier}; + +pub enum PropertyKeyLike<'a, 'b> { + ImportAttributeKey(&'b ImportAttributeKey<'a>), + PropertyKey(&'b PropertyKey<'a>), +} + +pub fn print_property_key<'a>( + p: &mut Prettier<'a>, + property_key: &PropertyKeyLike<'a, '_>, + is_computed: bool, +) -> Doc<'a> { + if let PropertyKeyLike::PropertyKey(property_key) = property_key { + if is_computed { + return array!(p, [text!("["), property_key.format(p), text!("]")]); + } + } + + // PERF: Cache this result by key-holder to avoid re-calculation by each property + let needs_quote = p.options.quote_props.consistent() + && match p.parent_kind() { + AstKind::ObjectExpression(oe) => oe.properties.iter().any(|opk| match opk { + ObjectPropertyKind::ObjectProperty(p) => { + !p.computed && is_property_key_has_quote(&p.key) + } + ObjectPropertyKind::SpreadProperty(_) => false, + }), + AstKind::ClassBody(cb) => cb.body.iter().any(|ce| match ce { + ClassElement::PropertyDefinition(d) => { + !d.computed && is_property_key_has_quote(&d.key) + } + _ => false, + }), + _ => false, + }; + + match property_key { + PropertyKeyLike::ImportAttributeKey(import_attribute_key) => match import_attribute_key { + ImportAttributeKey::Identifier(ident) => { + if needs_quote { + literal::print_string_from_not_quoted_raw_text( + p, + &ident.name, + p.options.single_quote, + ) + } else { + ident.format(p) + } + } + ImportAttributeKey::StringLiteral(literal) => { + // This does not pass quotes/objects.js + // because prettier uses the function `isEs5IdentifierName` based on unicode version 3, + // but `is_identifier_name` uses the latest unicode version. + if is_identifier_name(literal.value.as_str()) + && (p.options.quote_props.as_needed() + || (p.options.quote_props.consistent()/* && !needsQuoteProps.get(parent) */)) + { + dynamic_text!(p, literal.value.as_str()) + } else { + literal::print_string_from_not_quoted_raw_text( + p, + literal.value.as_str(), + p.options.single_quote, + ) + } + } + }, + PropertyKeyLike::PropertyKey(property_key) => { + match property_key { + PropertyKey::StaticIdentifier(ident) => { + if needs_quote { + literal::print_string_from_not_quoted_raw_text( + p, + &ident.name, + p.options.single_quote, + ) + } else { + ident.format(p) + } + } + PropertyKey::StringLiteral(literal) => { + // This does not pass quotes/objects.js + // because prettier uses the function `isEs5IdentifierName` based on unicode version 3, + // but `is_identifier_name` uses the latest unicode version. + if is_identifier_name(literal.value.as_str()) + && (p.options.quote_props.as_needed() + || (p.options.quote_props.consistent()/* && !needsQuoteProps.get(parent) */)) + { + dynamic_text!(p, literal.value.as_str()) + } else { + literal::print_string_from_not_quoted_raw_text( + p, + literal.value.as_str(), + p.options.single_quote, + ) + } + } + PropertyKey::NumericLiteral(literal) => { + if needs_quote { + literal::print_string_from_not_quoted_raw_text( + p, + &literal.raw_str(), + p.options.single_quote, + ) + } else { + literal.format(p) + } + } + PropertyKey::PrivateIdentifier(ident) => ident.format(p), + PropertyKey::Identifier(ident) => ident.format(p), + match_expression!(PropertyKey) => property_key.to_expression().format(p), + } + } + } +} + +fn is_property_key_has_quote(key: &PropertyKey<'_>) -> bool { matches!(key, PropertyKey::StringLiteral(literal) if is_string_prop_safe_to_unquote(literal.value.as_str())) } -pub fn is_string_prop_safe_to_unquote(value: &str) -> bool { +fn is_string_prop_safe_to_unquote(value: &str) -> bool { !is_identifier_name(value) && !is_simple_number(value) } // Matches β€œsimple” numbers like `123` and `2.5` but not `1_000`, `1e+100` or `0b10`. -pub fn is_simple_number(str: &str) -> bool { +fn is_simple_number(str: &str) -> bool { let mut bytes = str.as_bytes().iter(); let mut has_dot = false; bytes.next().is_some_and(u8::is_ascii_digit) diff --git a/crates/oxc_prettier/src/format/typescript.rs b/crates/oxc_prettier/src/format/typescript.rs index 434a30d614e97..0f20077ff36cd 100644 --- a/crates/oxc_prettier/src/format/typescript.rs +++ b/crates/oxc_prettier/src/format/typescript.rs @@ -5,7 +5,7 @@ use oxc_span::GetSpan; use crate::{ array, dynamic_text, format::{ - print::{array, object, template_literal}, + print::{array, object, property, template_literal}, Format, }, group, hardline, indent, @@ -415,7 +415,9 @@ impl<'a> Format<'a> for TSTupleType<'a> { impl<'a> Format<'a> for TSTypeLiteral<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - object::print_object(p, object::ObjectLike::TSTypeLiteral(self)) + wrap!(p, self, TSTypeLiteral, { + object::print_object(p, object::ObjectLike::TSTypeLiteral(self)) + }) } } @@ -538,62 +540,64 @@ impl<'a> Format<'a> for JSDocUnknownType { impl<'a> Format<'a> for TSInterfaceDeclaration<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let mut parts = Vec::new_in(p.allocator); + wrap!(p, self, TSInterfaceDeclaration, { + let mut parts = Vec::new_in(p.allocator); - if self.declare { - parts.push(text!("declare ")); - } + if self.declare { + parts.push(text!("declare ")); + } - parts.push(text!("interface ")); - parts.push(self.id.format(p)); + parts.push(text!("interface ")); + parts.push(self.id.format(p)); - if let Some(type_parameters) = &self.type_parameters { - parts.push(type_parameters.format(p)); - } + if let Some(type_parameters) = &self.type_parameters { + parts.push(type_parameters.format(p)); + } - parts.push(text!(" ")); + parts.push(text!(" ")); - if let Some(extends) = &self.extends { - if extends.len() > 0 { - let mut extends_parts = Vec::new_in(p.allocator); - let mut display_comma = false; + if let Some(extends) = &self.extends { + if extends.len() > 0 { + let mut extends_parts = Vec::new_in(p.allocator); + let mut display_comma = false; - extends_parts.push(text!("extends ")); + extends_parts.push(text!("extends ")); - for extend in extends { - if display_comma { - extends_parts.push(text!(", ")); - } else { - display_comma = true; - } + for extend in extends { + if display_comma { + extends_parts.push(text!(", ")); + } else { + display_comma = true; + } - extends_parts.push(extend.expression.format(p)); - if let Some(type_parameters) = &extend.type_parameters { - extends_parts.push(type_parameters.format(p)); + extends_parts.push(extend.expression.format(p)); + if let Some(type_parameters) = &extend.type_parameters { + extends_parts.push(type_parameters.format(p)); + } } - } - parts.extend(extends_parts); - parts.push(text!(" ")); + parts.extend(extends_parts); + parts.push(text!(" ")); + } } - } - parts.push(text!("{")); - if self.body.body.len() > 0 { - let mut indent_parts = Vec::new_in(p.allocator); - for sig in &self.body.body { - indent_parts.push(hardline!(p)); - indent_parts.push(sig.format(p)); + parts.push(text!("{")); + if self.body.body.len() > 0 { + let mut indent_parts = Vec::new_in(p.allocator); + for sig in &self.body.body { + indent_parts.push(hardline!(p)); + indent_parts.push(sig.format(p)); - if let Some(semi) = p.semi() { - indent_parts.push(semi); + if let Some(semi) = p.semi() { + indent_parts.push(semi); + } } + parts.push(indent!(p, indent_parts)); + parts.push(hardline!(p)); } - parts.push(indent!(p, indent_parts)); - parts.push(hardline!(p)); - } - parts.push(text!("}")); - array!(p, parts) + parts.push(text!("}")); + array!(p, parts) + }) } } @@ -969,20 +973,27 @@ impl<'a> Format<'a> for TSIndexSignatureName<'a> { impl<'a> Format<'a> for TSPropertySignature<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { - let mut parts = Vec::new_in(p.allocator); - if self.readonly { - parts.push(text!("readonly ")); - } - parts.push(self.key.format(p)); - if let Some(ty) = &self.type_annotation { - if self.optional { - parts.push(text!("?")); + wrap!(p, self, TSPropertySignature, { + let mut parts = Vec::new_in(p.allocator); + if self.readonly { + parts.push(text!("readonly ")); } - parts.push(text!(":")); - parts.push(text!(" ")); - parts.push(ty.type_annotation.format(p)); - } - array!(p, parts) + let key_doc = property::print_property_key( + p, + &property::PropertyKeyLike::PropertyKey(&self.key), + self.computed, + ); + parts.push(key_doc); + if let Some(ty) = &self.type_annotation { + if self.optional { + parts.push(text!("?")); + } + parts.push(text!(":")); + parts.push(text!(" ")); + parts.push(ty.type_annotation.format(p)); + } + array!(p, parts) + }) } } @@ -1030,15 +1041,12 @@ impl<'a> Format<'a> for TSMethodSignature<'a> { fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> { let mut parts = Vec::new_in(p.allocator); - if self.computed { - parts.push(text!("[")); - } - - parts.push(self.key.format(p)); - - if self.computed { - parts.push(text!("]")); - } + let key_doc = if self.computed { + array!(p, [text!("["), self.key.format(p), text!("]")]) + } else { + self.key.format(p) + }; + parts.push(key_doc); if self.optional { parts.push(text!("?")); diff --git a/crates/oxc_prettier/src/ir/doc.rs b/crates/oxc_prettier/src/ir/doc.rs index d72b4e404902f..b124486c766e3 100644 --- a/crates/oxc_prettier/src/ir/doc.rs +++ b/crates/oxc_prettier/src/ir/doc.rs @@ -87,6 +87,7 @@ impl<'a> Fill<'a> { pub enum JoinSeparator { Softline, Hardline, - CommaLine, // [",", line] + CommaLine, // [",", line] + CommaSpace, // ", " Literalline, } diff --git a/crates/oxc_prettier/src/macros.rs b/crates/oxc_prettier/src/macros.rs index 6c15fbf069ea8..b67b194847007 100644 --- a/crates/oxc_prettier/src/macros.rs +++ b/crates/oxc_prettier/src/macros.rs @@ -210,6 +210,7 @@ macro_rules! join { $crate::ir::JoinSeparator::CommaLine => { parts.extend([$crate::text!(","), $crate::line!()]); } + $crate::ir::JoinSeparator::CommaSpace => parts.push($crate::text!(", ")), $crate::ir::JoinSeparator::Literalline => parts.push($crate::literalline!($p)), } } diff --git a/tasks/prettier_conformance/snapshots/prettier.js.snap.md b/tasks/prettier_conformance/snapshots/prettier.js.snap.md index 9a675294d6b02..e5cbfe721b21b 100644 --- a/tasks/prettier_conformance/snapshots/prettier.js.snap.md +++ b/tasks/prettier_conformance/snapshots/prettier.js.snap.md @@ -1,4 +1,4 @@ -js compatibility: 249/641 (38.85%) +js compatibility: 250/641 (39.00%) # Failed @@ -291,9 +291,6 @@ js compatibility: 249/641 (38.85%) * js/import-attributes/keyword-detect.js | πŸ’₯ * js/import-attributes/long-sources.js | πŸ’₯ -### js/import-attributes/quote-props -* js/import-attributes/quote-props/quoted-keys.js | πŸ’₯πŸ’₯✨ - ### js/label * js/label/comment.js | πŸ’₯ diff --git a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md index f7a16f4cc88ae..4af88814f7f5e 100644 --- a/tasks/prettier_conformance/snapshots/prettier.ts.snap.md +++ b/tasks/prettier_conformance/snapshots/prettier.ts.snap.md @@ -1,4 +1,4 @@ -ts compatibility: 194/568 (34.15%) +ts compatibility: 195/568 (34.33%) # Failed @@ -324,9 +324,6 @@ ts compatibility: 194/568 (34.15%) ### typescript/const * typescript/const/initializer-ambient-context.ts | πŸ’₯ -### typescript/custom/computedProperties -* typescript/custom/computedProperties/symbol.ts | πŸ’₯ - ### typescript/custom/modifiers * typescript/custom/modifiers/minustoken.ts | πŸ’₯ * typescript/custom/modifiers/question.ts | πŸ’₯