Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(prettier): Verify printing module imports #8633

Merged
merged 15 commits into from
Jan 23, 2025
266 changes: 84 additions & 182 deletions crates/oxc_prettier/src/format/js.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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) })
}
}

Expand All @@ -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)
}
}

Expand All @@ -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))),
)
}
}

Expand All @@ -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)])
}
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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,
Expand All @@ -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),
}
})
}
}

Expand Down Expand Up @@ -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)])
})
}
}

Expand All @@ -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)])
}
}

Expand Down Expand Up @@ -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!("("));
Expand Down Expand Up @@ -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)])
}
}

Expand All @@ -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)])
})
}
}
1 change: 1 addition & 0 deletions crates/oxc_prettier/src/format/print/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>),
Expand Down
Loading
Loading