Skip to content

Commit

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

- ExportAllDeclaration
- ExportNamedDeclaration
- ExportDefaultDeclaration
- (ExportNamespaceSpecifier = ExportAllDeclaration+exported)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
leaysgur and autofix-ci[bot] authored Jan 23, 2025
1 parent 13e4a45 commit a529412
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 99 deletions.
71 changes: 31 additions & 40 deletions crates/oxc_prettier/src/format/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,13 +506,14 @@ impl<'a> Format<'a> for DebuggerStatement {

impl<'a> Format<'a> for ModuleDeclaration<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
wrap!(p, self, ModuleDeclaration, {
if let ModuleDeclaration::ImportDeclaration(decl) = self {
decl.format(p)
} else {
module::print_export_declaration(p, self)
}
})
match self {
ModuleDeclaration::ImportDeclaration(import) => import.format(p),
ModuleDeclaration::ExportDefaultDeclaration(export) => export.format(p),
ModuleDeclaration::ExportNamedDeclaration(export) => export.format(p),
ModuleDeclaration::ExportAllDeclaration(export) => export.format(p),
ModuleDeclaration::TSExportAssignment(export) => export.format(p),
ModuleDeclaration::TSNamespaceExportDeclaration(export) => export.format(p),
}
}
}

Expand Down Expand Up @@ -693,24 +694,6 @@ impl<'a> Format<'a> for ImportAttribute<'a> {
}
}

impl<'a> Format<'a> for ExportNamedDeclaration<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
let mut parts = Vec::new_in(p.allocator);
if let Some(decl) = &self.declaration {
parts.push(text!(" "));
parts.push(decl.format(p));
} else {
parts.push(module::print_module_specifiers(
p,
&self.specifiers,
/* include_default */ false,
/* include_namespace */ false,
));
}
array!(p, parts)
}
}

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
Expand All @@ -734,26 +717,34 @@ impl<'a> Format<'a> for ModuleExportName<'a> {

impl<'a> Format<'a> for ExportAllDeclaration<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
let mut parts = Vec::new_in(p.allocator);
parts.push(text!(" *"));
if let Some(exported) = &self.exported {
parts.push(text!(" as "));
parts.push(exported.format(p));
}
array!(p, parts)
wrap!(p, self, ExportAllDeclaration, {
module::print_export_declaration(
p,
&module::ExportDeclarationLike::ExportAllDeclaration(self),
)
})
}
}

impl<'a> Format<'a> for ExportDefaultDeclaration<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
match &self.declaration {
match_expression!(ExportDefaultDeclarationKind) => {
self.declaration.to_expression().format(p)
}
ExportDefaultDeclarationKind::FunctionDeclaration(decl) => decl.format(p),
ExportDefaultDeclarationKind::ClassDeclaration(decl) => decl.format(p),
ExportDefaultDeclarationKind::TSInterfaceDeclaration(decl) => decl.format(p),
}
wrap!(p, self, ExportDefaultDeclaration, {
module::print_export_declaration(
p,
&module::ExportDeclarationLike::ExportDefaultDeclaration(self),
)
})
}
}

impl<'a> Format<'a> for ExportNamedDeclaration<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
wrap!(p, self, ExportNamedDeclaration, {
module::print_export_declaration(
p,
&module::ExportDeclarationLike::ExportNamedDeclaration(self),
)
})
}
}

Expand Down
125 changes: 89 additions & 36 deletions crates/oxc_prettier/src/format/print/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,75 +79,128 @@ fn print_import_attributes<'a>(p: &mut Prettier<'a>, with_clause: &WithClause<'a
array!(p, parts)
}

pub fn print_export_declaration<'a>(p: &mut Prettier<'a>, decl: &ModuleDeclaration<'a>) -> Doc<'a> {
debug_assert!(decl.is_export());
#[allow(clippy::enum_variant_names)]
pub enum ExportDeclarationLike<'a, 'b> {
ExportAllDeclaration(&'b ExportAllDeclaration<'a>),
ExportNamedDeclaration(&'b ExportNamedDeclaration<'a>),
ExportDefaultDeclaration(&'b ExportDefaultDeclaration<'a>),
}

pub fn print_export_declaration<'a>(
p: &mut Prettier<'a>,
decl: &ExportDeclarationLike<'a, '_>,
) -> Doc<'a> {
let mut parts = Vec::new_in(p.allocator);

// TODO: Print decorators before export for ExportDefaultDeclaration and ExportNamedDeclaration
// ```
// @deco export class X {}
// ```
// Print decorators here, then skip them in the Class.decorators

parts.push(text!("export"));

if decl.is_default_export() {
if matches!(decl, ExportDeclarationLike::ExportDefaultDeclaration(_)) {
parts.push(text!(" default "));
}

parts.push(match decl {
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!(),
});
// TODO: Dangling comments

match decl {
ExportDeclarationLike::ExportAllDeclaration(decl) => {
if decl.export_kind.is_type() {
parts.push(text!(" type"));
}
parts.push(text!(" *"));
if let Some(exported) = &decl.exported {
parts.push(text!(" as "));
parts.push(exported.format(p));
}
}
ExportDeclarationLike::ExportNamedDeclaration(decl) => {
if decl.export_kind.is_type() {
parts.push(text!(" type"));
}
if let Some(decl) = &decl.declaration {
parts.push(text!(" "));
parts.push(decl.format(p));
} else {
parts.push(print_module_specifiers(p, &decl.specifiers, false, false));
}
}
ExportDeclarationLike::ExportDefaultDeclaration(decl) => {
parts.push(match &decl.declaration {
match_expression!(ExportDefaultDeclarationKind) => {
decl.declaration.to_expression().format(p)
}
ExportDefaultDeclarationKind::FunctionDeclaration(decl) => decl.format(p),
ExportDefaultDeclarationKind::ClassDeclaration(decl) => decl.format(p),
ExportDefaultDeclarationKind::TSInterfaceDeclaration(decl) => decl.format(p),
});
}
}

if let Some(source) = decl.source() {
if let Some(source_doc) = match decl {
ExportDeclarationLike::ExportAllDeclaration(decl) => Some(decl.source.format(p)),
ExportDeclarationLike::ExportNamedDeclaration(decl) => {
decl.source.as_ref().map(|s| s.format(p))
}
ExportDeclarationLike::ExportDefaultDeclaration(_) => None,
} {
parts.push(text!(" from "));
parts.push(source.format(p));
parts.push(source_doc);
}

if let Some(with_clause) = decl.with_clause() {
if let Some(with_clause) = match decl {
ExportDeclarationLike::ExportAllDeclaration(decl) => decl.with_clause.as_ref(),
ExportDeclarationLike::ExportNamedDeclaration(decl) => decl.with_clause.as_ref(),
ExportDeclarationLike::ExportDefaultDeclaration(_) => None,
} {
parts.push(print_import_attributes(p, with_clause));
}

if let Some(doc) = print_semicolon_after_export_declaration(p, decl) {
parts.push(doc);
if print_semicolon_after_export_declaration(p, decl) {
parts.push(text!(";"));
}

array!(p, parts)
}

fn print_semicolon_after_export_declaration<'a>(
p: &Prettier<'a>,
decl: &ModuleDeclaration<'a>,
) -> Option<Doc<'a>> {
decl: &ExportDeclarationLike<'a, '_>,
) -> bool {
if !p.options.semi {
return None;
return false;
}

match decl {
ModuleDeclaration::ExportDefaultDeclaration(decl) => match decl.declaration {
match_expression!(ExportDefaultDeclarationKind) => Some(text!(";")),
_ => None,
},
ModuleDeclaration::ExportNamedDeclaration(decl) => {
ExportDeclarationLike::ExportAllDeclaration(_) => true,
ExportDeclarationLike::ExportNamedDeclaration(decl) => {
let Some(declaration) = &decl.declaration else {
return Some(text!(";"));
return true;
};

match declaration {
Declaration::TSInterfaceDeclaration(_)
| Declaration::VariableDeclaration(_)
| Declaration::ClassDeclaration(_)
| Declaration::TSModuleDeclaration(_) => None,
_ => Some(text!(";")),
}
// Prettier's `shouldOmitSemicolon()` function
!matches!(
declaration,
Declaration::ClassDeclaration(_)
| Declaration::VariableDeclaration(_)
| Declaration::FunctionDeclaration(_)
| Declaration::TSInterfaceDeclaration(_)
| Declaration::TSEnumDeclaration(_)
| Declaration::TSModuleDeclaration(_)
| Declaration::TSImportEqualsDeclaration(_)
)
}
ModuleDeclaration::ExportAllDeclaration(_) | ModuleDeclaration::TSExportAssignment(_) => {
Some(text!(";"))
ExportDeclarationLike::ExportDefaultDeclaration(decl) => {
matches!(decl.declaration, match_expression!(ExportDefaultDeclarationKind))
}
_ => None,
_ => false,
}
}

pub fn print_module_specifiers<'a, T: Format<'a>>(
fn print_module_specifiers<'a, T: Format<'a>>(
p: &mut Prettier<'a>,
specifiers: &Vec<'a, T>,
include_default: bool,
Expand Down
26 changes: 22 additions & 4 deletions crates/oxc_prettier/src/format/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -888,15 +888,33 @@ impl<'a> Format<'a> for TSTupleElement<'a> {

impl<'a> Format<'a> for TSExportAssignment<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
let expression_doc = self.expression.format(p);
array!(p, [text!(" = "), expression_doc])
wrap!(p, self, TSExportAssignment, {
let mut parts = Vec::new_in(p.allocator);

parts.push(text!("export = "));
parts.push(self.expression.format(p));
if let Some(semi) = p.semi() {
parts.push(semi);
}

array!(p, parts)
})
}
}

impl<'a> Format<'a> for TSNamespaceExportDeclaration<'a> {
fn format(&self, p: &mut Prettier<'a>) -> Doc<'a> {
let id_doc = self.id.format(p);
array!(p, [text!(" as namespace "), id_doc, text!(";")])
// wrap!(p, self, TSNamespaceExportDeclaration, {
let mut parts = Vec::new_in(p.allocator);

parts.push(text!("export as namespace "));
parts.push(self.id.format(p));
if let Some(semi) = p.semi() {
parts.push(semi);
}

array!(p, parts)
// })
}
}

Expand Down
13 changes: 5 additions & 8 deletions crates/oxc_prettier/src/needs_parens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
use oxc_ast::{
ast::{
match_member_expression, AssignmentTarget, ChainElement, ExportDefaultDeclarationKind,
Expression, ForStatementInit, ForStatementLeft, MemberExpression, ModuleDeclaration,
ObjectExpression, SimpleAssignmentTarget,
Expression, ForStatementInit, ForStatementLeft, MemberExpression, ObjectExpression,
SimpleAssignmentTarget,
},
AstKind,
};
Expand Down Expand Up @@ -146,7 +146,7 @@ impl<'a> Prettier<'a> {
| AstKind::SpreadElement(_)
| AstKind::BinaryExpression(_)
| AstKind::LogicalExpression(_)
| AstKind::ModuleDeclaration(ModuleDeclaration::ExportDefaultDeclaration(_))
| AstKind::ExportDefaultDeclaration(_)
| AstKind::AwaitExpression(_)
| AstKind::JSXSpreadAttribute(_)
| AstKind::TSAsExpression(_)
Expand Down Expand Up @@ -217,7 +217,7 @@ impl<'a> Prettier<'a> {
}
}
}
AstKind::ModuleDeclaration(ModuleDeclaration::ExportDefaultDeclaration(decl)) => {
AstKind::ExportDefaultDeclaration(decl) => {
return matches!(
decl.declaration,
ExportDefaultDeclarationKind::SequenceExpression(_)
Expand Down Expand Up @@ -493,10 +493,7 @@ impl<'a> Prettier<'a> {

fn should_wrap_function_for_export_default(&mut self) -> bool {
let kind = self.current_kind();
let b = matches!(
self.parent_kind(),
AstKind::ModuleDeclaration(ModuleDeclaration::ExportDefaultDeclaration(_))
);
let b = matches!(self.parent_kind(), AstKind::ExportDefaultDeclaration(_));
if matches!(kind, AstKind::Function(f) if f.is_expression())
|| matches!(kind, AstKind::Class(c) if c.is_expression())
{
Expand Down
3 changes: 1 addition & 2 deletions tasks/prettier_conformance/snapshots/prettier.js.snap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
js compatibility: 250/641 (39.00%)
js compatibility: 251/641 (39.16%)

# Failed

Expand Down Expand Up @@ -83,7 +83,6 @@ js compatibility: 250/641 (39.00%)
* js/call/first-argument-expansion/expression-2nd-arg.js | 💥
* js/call/first-argument-expansion/issue-13237.js | 💥
* js/call/first-argument-expansion/issue-2456.js | 💥
* js/call/first-argument-expansion/issue-4401.js | 💥
* js/call/first-argument-expansion/issue-5172.js | 💥
* js/call/first-argument-expansion/jsx.js | 💥
* js/call/first-argument-expansion/test.js | 💥
Expand Down
10 changes: 1 addition & 9 deletions tasks/prettier_conformance/snapshots/prettier.ts.snap.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ts compatibility: 195/568 (34.33%)
ts compatibility: 199/568 (35.04%)

# Failed

Expand Down Expand Up @@ -133,7 +133,6 @@ ts compatibility: 195/568 (34.33%)
* typescript/assignment/issue-10846.ts | 💥
* typescript/assignment/issue-10848.tsx | 💥
* typescript/assignment/issue-10850.ts | 💥
* typescript/assignment/issue-2482.ts | 💥
* typescript/assignment/parenthesized.ts | 💥

### typescript/call-signature
Expand Down Expand Up @@ -267,9 +266,6 @@ ts compatibility: 195/568 (34.33%)
* typescript/conformance/types/any/anyAsFunctionCall.ts | 💥
* typescript/conformance/types/any/anyAsGenericFunctionCall.ts | 💥

### typescript/conformance/types/firstTypeNode
* typescript/conformance/types/firstTypeNode/firstTypeNode.ts | 💥

### typescript/conformance/types/functions
* typescript/conformance/types/functions/functionImplementationErrors.ts | 💥
* typescript/conformance/types/functions/functionImplementations.ts | 💥
Expand Down Expand Up @@ -389,7 +385,6 @@ ts compatibility: 195/568 (34.33%)
### typescript/export
* typescript/export/comment.ts | 💥
* typescript/export/export-type-star-from-2.ts | 💥
* typescript/export/export-type-star-from.ts | 💥

### typescript/export-default
* typescript/export-default/function_as.ts | 💥
Expand All @@ -411,9 +406,6 @@ ts compatibility: 195/568 (34.33%)
* typescript/import-export/empty-import.ts | 💥
* typescript/import-export/type-modifier.ts | 💥

### typescript/import-require
* typescript/import-require/type-imports.ts | 💥

### typescript/import-type
* typescript/import-type/import-type.ts | 💥💥

Expand Down

0 comments on commit a529412

Please sign in to comment.