Skip to content

Commit

Permalink
refactor(prettier): Verify printing module imports (#8633)
Browse files Browse the repository at this point in the history
Part of #5068 

- [x] ImportDeclaration
- [x] ImportSpecifier
- [x] ImportDefaultSpecifier
- [x] ImportNamespaceSpecifier
- [x] WithClause
- [x] ImportAttribute
  • Loading branch information
leaysgur authored Jan 23, 2025
1 parent 3be0392 commit 1e9f385
Show file tree
Hide file tree
Showing 13 changed files with 435 additions and 351 deletions.
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

0 comments on commit 1e9f385

Please sign in to comment.