fix(codegen): improve typescript codegen (#3708)

Remaining issues are tracked in https://github.com/oxc-project/oxc/issues/3692
This commit is contained in:
Boshen 2024-06-17 09:34:54 +00:00
parent 910193e0aa
commit da1e2d0e9b
9 changed files with 4847 additions and 4859 deletions

3
.gitignore vendored
View file

@ -27,6 +27,3 @@ tasks/coverage/babel/
tasks/coverage/test262/
tasks/coverage/typescript/
tasks/prettier_conformance/prettier/
# Ignore the failures directory, which is used to store the results of the codegen coverage tests
tasks/coverage/failures/

View file

@ -3,8 +3,8 @@ use oxc_syntax::precedence::{GetPrecedence, Precedence};
use crate::ast::{
match_member_expression, ArrowFunctionExpression, AssignmentExpression, AwaitExpression,
BinaryExpression, CallExpression, ConditionalExpression, Expression, ImportExpression,
LogicalExpression, MemberExpression, NewExpression, SequenceExpression, UnaryExpression,
UpdateExpression, YieldExpression,
LogicalExpression, MemberExpression, NewExpression, SequenceExpression, TSTypeAssertion,
UnaryExpression, UpdateExpression, YieldExpression,
};
impl<'a> GetPrecedence for Expression<'a> {
@ -119,3 +119,9 @@ impl<'a> GetPrecedence for MemberExpression<'a> {
Precedence::Member
}
}
impl<'a> GetPrecedence for TSTypeAssertion<'a> {
fn precedence(&self) -> Precedence {
Precedence::lowest()
}
}

View file

@ -109,7 +109,10 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for Statement<'a> {
Self::WhileStatement(stmt) => stmt.gen(p, ctx),
Self::WithStatement(stmt) => stmt.gen(p, ctx),
match_module_declaration!(Self) => self.to_module_declaration().gen(p, ctx),
match_declaration!(Self) => self.to_declaration().gen(p, ctx),
match_declaration!(Self) => {
p.print_indent();
self.to_declaration().gen(p, ctx);
}
}
}
}
@ -150,7 +153,7 @@ fn print_if<const MINIFY: bool>(
match &if_stmt.consequent {
Statement::BlockStatement(block) => {
p.print_block1(block, ctx);
p.print_block_statement(block, ctx);
}
stmt if wrap_to_avoid_ambiguous_else(stmt) => {
p.print_block_start(stmt.span().start);
@ -173,7 +176,7 @@ fn print_if<const MINIFY: bool>(
p.print_str(b"else ");
match alternate {
Statement::BlockStatement(block) => {
p.print_block1(block, ctx);
p.print_block_statement(block, ctx);
p.print_soft_newline();
}
Statement::IfStatement(if_stmt) => {
@ -215,7 +218,7 @@ fn wrap_to_avoid_ambiguous_else(stmt: &Statement) -> bool {
impl<'a, const MINIFY: bool> Gen<MINIFY> for BlockStatement<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.print_indent();
p.print_block1(self, ctx);
p.print_block_statement(self, ctx);
p.print_soft_newline();
}
}
@ -249,13 +252,12 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ForStatement<'a> {
p.print_semicolon();
if let Some(update) = self.update.as_ref() {
p.print_soft_space();
p.print_expression(update);
}
p.print(b')');
p.print_soft_space();
self.body.gen(p, ctx);
p.print_body(&self.body, ctx);
}
}
@ -274,7 +276,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ForInStatement<'a> {
p.print_expression(&self.right);
p.print(b')');
p.print_soft_space();
self.body.gen(p, ctx);
p.print_body(&self.body, ctx);
}
}
@ -292,11 +294,10 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ForOfStatement<'a> {
p.print_soft_space();
p.print_space_before_identifier();
p.print_str(b"of ");
p.print_soft_space();
self.right.gen_expr(p, Precedence::Assign, Context::default());
p.print(b')');
p.print_soft_space();
self.body.gen(p, ctx);
p.print_body(&self.body, ctx);
}
}
@ -321,10 +322,12 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for WhileStatement<'a> {
p.add_source_mapping(self.span.start);
p.print_indent();
p.print_str(b"while");
p.print_soft_space();
p.print(b'(');
p.print_expression(&self.test);
p.print(b')');
self.body.gen(p, ctx);
p.print_soft_space();
p.print_body(&self.body, ctx);
}
}
@ -334,7 +337,8 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for DoWhileStatement<'a> {
p.print_indent();
p.print_str(b"do ");
if let Statement::BlockStatement(block) = &self.body {
p.print_block1(block, ctx);
p.print_block_statement(block, ctx);
p.print_soft_space();
} else {
p.print_soft_newline();
p.indent();
@ -344,6 +348,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for DoWhileStatement<'a> {
p.print_indent();
}
p.print_str(b"while");
p.print_soft_space();
p.print(b'(');
p.print_expression(&self.test);
p.print(b')');
@ -356,6 +361,7 @@ impl<const MINIFY: bool> Gen<MINIFY> for EmptyStatement {
p.add_source_mapping(self.span.start);
p.print_indent();
p.print_semicolon();
p.print_soft_newline();
}
}
@ -390,15 +396,17 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for SwitchStatement<'a> {
p.add_source_mapping(self.span.start);
p.print_indent();
p.print_str(b"switch");
p.print_soft_space();
p.print(b'(');
p.print_expression(&self.discriminant);
p.print(b')');
p.print_block_start(self.span.start);
for case in &self.cases {
p.add_source_mapping(case.span.start);
case.gen(p, ctx);
}
p.print_block_end(self.span.end);
p.print_soft_space();
p.print_curly_braces(self.span, self.cases.is_empty(), |p| {
for case in &self.cases {
p.add_source_mapping(case.span.start);
case.gen(p, ctx);
}
});
p.print_soft_newline();
p.needs_semicolon = false;
}
@ -410,13 +418,22 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for SwitchCase<'a> {
p.print_indent();
match &self.test {
Some(test) => {
p.print_str(b"case");
p.print_hard_space();
p.print_str(b"case ");
p.print_expression(test);
}
None => p.print_str(b"default"),
}
p.print_colon();
if self.consequent.len() == 1 {
if let Statement::BlockStatement(block) = &self.consequent[0] {
p.print_soft_space();
p.print_block_statement(block, ctx);
p.print_soft_newline();
return;
}
}
p.print_soft_newline();
p.indent();
for item in &self.consequent {
@ -446,7 +463,8 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for LabeledStatement<'a> {
p.print_indent();
self.label.gen(p, ctx);
p.print_colon();
self.body.gen(p, ctx);
p.print_soft_space();
p.print_body(&self.body, ctx);
}
}
@ -454,21 +472,29 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TryStatement<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.add_source_mapping(self.span.start);
p.print_indent();
p.print_space_before_identifier();
p.print_str(b"try");
p.print_block1(&self.block, ctx);
p.print_soft_space();
p.print_block_statement(&self.block, ctx);
if let Some(handler) = &self.handler {
p.print_soft_space();
p.print_str(b"catch");
if let Some(param) = &handler.param {
p.print_soft_space();
p.print_str(b"(");
param.pattern.gen(p, ctx);
p.print_str(b")");
}
p.print_block1(&handler.body, ctx);
p.print_soft_space();
p.print_block_statement(&handler.body, ctx);
}
if let Some(finalizer) = &self.finalizer {
p.print_soft_space();
p.print_str(b"finally");
p.print_block1(finalizer, ctx);
p.print_soft_space();
p.print_block_statement(finalizer, ctx);
}
p.print_soft_newline();
}
}
@ -490,7 +516,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for WithStatement<'a> {
p.print(b'(');
p.print_expression(&self.object);
p.print(b')');
self.body.gen(p, ctx);
p.print_body(&self.body, ctx);
}
}
@ -510,16 +536,8 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ModuleDeclaration<'a> {
Self::ExportAllDeclaration(decl) => decl.gen(p, ctx),
Self::ExportDefaultDeclaration(decl) => decl.gen(p, ctx),
Self::ExportNamedDeclaration(decl) => decl.gen(p, ctx),
Self::TSExportAssignment(decl) => {
p.print_str(b"export = ");
decl.expression.gen_expr(p, Precedence::lowest(), ctx);
p.print_semicolon_after_statement();
}
Self::TSNamespaceExportDeclaration(decl) => {
p.print_str(b"export as namespace ");
decl.id.gen(p, ctx);
p.print_semicolon_after_statement();
}
Self::TSExportAssignment(decl) => decl.gen(p, ctx),
Self::TSNamespaceExportDeclaration(decl) => decl.gen(p, ctx),
}
}
}
@ -528,18 +546,15 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for Declaration<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
match self {
Self::VariableDeclaration(decl) => {
p.print_indent();
decl.gen(p, ctx);
p.print_semicolon_after_statement();
}
Self::FunctionDeclaration(decl) => {
p.print_indent();
p.print_space_before_identifier();
decl.gen(p, ctx);
p.print_soft_newline();
}
Self::ClassDeclaration(decl) => {
p.print_indent();
p.print_space_before_identifier();
decl.gen(p, ctx);
p.print_soft_newline();
@ -549,9 +564,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for Declaration<'a> {
declaration.gen(p, ctx);
p.print_soft_newline();
}
Self::TSModuleDeclaration(decl) => {
decl.gen(p, ctx);
}
Self::TSModuleDeclaration(decl) => decl.gen(p, ctx),
Self::TSTypeAliasDeclaration(decl) => {
if decl.modifiers.contains(ModifierKind::Export) {
p.print_str(b"export ");
@ -573,9 +586,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for Declaration<'a> {
}
Declaration::TSInterfaceDeclaration(decl) => decl.gen(p, ctx),
Declaration::TSEnumDeclaration(decl) => decl.gen(p, ctx),
Declaration::TSImportEqualsDeclaration(decl) => {
decl.gen(p, ctx);
}
Declaration::TSImportEqualsDeclaration(decl) => decl.gen(p, ctx),
}
}
}
@ -668,11 +679,11 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for Function<'a> {
p.print_str(b": ");
return_type.gen(p, ctx);
}
p.print_soft_space();
if let Some(body) = &self.body {
p.print_soft_space();
body.gen(p, ctx);
} else {
p.print_semicolon_after_statement();
p.print_semicolon();
}
});
}
@ -680,14 +691,14 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for Function<'a> {
impl<'a, const MINIFY: bool> Gen<MINIFY> for FunctionBody<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.print_block_start(self.span.start);
p.print_directives_and_statements_with_semicolon_order(
Some(&self.directives),
&self.statements,
ctx,
true,
);
p.print_block_end(self.span.end);
p.print_curly_braces(self.span, self.is_empty(), |p| {
p.print_directives_and_statements_with_semicolon_order(
Some(&self.directives),
&self.statements,
ctx,
true,
);
});
p.needs_semicolon = false;
}
}
@ -721,6 +732,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for FormalParameters<'a> {
impl<'a, const MINIFY: bool> Gen<MINIFY> for ImportDeclaration<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.add_source_mapping(self.span.start);
p.print_indent();
p.print_str(b"import ");
if self.import_kind.is_type() {
p.print_str(b"type ");
@ -843,6 +855,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ImportAttribute<'a> {
impl<'a, const MINIFY: bool> Gen<MINIFY> for ExportNamedDeclaration<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.add_source_mapping(self.span.start);
p.print_indent();
if p.options.preserve_annotate_comments {
match &self.declaration {
Some(Declaration::FunctionDeclaration(_)) => {
@ -869,7 +882,9 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ExportNamedDeclaration<'a> {
p.print_str(b"type ");
}
match &self.declaration {
Some(decl) => decl.gen(p, ctx),
Some(decl) => {
decl.gen(p, ctx);
}
None => {
p.print(b'{');
if !self.specifiers.is_empty() {
@ -884,12 +899,30 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ExportNamedDeclaration<'a> {
p.print_soft_space();
source.gen(p, ctx);
}
p.needs_semicolon = true;
p.print_semicolon_after_statement();
}
}
}
}
impl<'a, const MINIFY: bool> Gen<MINIFY> for TSExportAssignment<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.print_indent();
p.print_str(b"export = ");
self.expression.gen_expr(p, Precedence::lowest(), ctx);
p.print_semicolon_after_statement();
}
}
impl<'a, const MINIFY: bool> Gen<MINIFY> for TSNamespaceExportDeclaration<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.print_indent();
p.print_str(b"export as namespace ");
self.id.gen(p, ctx);
p.print_semicolon_after_statement();
}
}
impl<'a, const MINIFY: bool> Gen<MINIFY> for ExportSpecifier<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
if self.export_kind.is_type() {
@ -917,6 +950,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ModuleExportName<'a> {
impl<'a, const MINIFY: bool> Gen<MINIFY> for ExportAllDeclaration<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.add_source_mapping(self.span.start);
p.print_indent();
p.print_str(b"export ");
if self.export_kind.is_type() {
p.print_str(b"type ");
@ -942,6 +976,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ExportAllDeclaration<'a> {
impl<'a, const MINIFY: bool> Gen<MINIFY> for ExportDefaultDeclaration<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.add_source_mapping(self.span.start);
p.print_indent();
p.print_str(b"export default ");
self.declaration.gen(p, ctx);
}
@ -1443,28 +1478,18 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ArrayExpression<'a> {
impl<'a, const MINIFY: bool> GenExpr<MINIFY> for ObjectExpression<'a> {
fn gen_expr(&self, p: &mut Codegen<{ MINIFY }>, _precedence: Precedence, ctx: Context) {
let n = p.code_len();
let is_multi_line = !self.properties.is_empty();
p.wrap(p.start_of_stmt == n || p.start_of_arrow_expr == n, |p| {
p.add_source_mapping(self.span.start);
p.print(b'{');
if is_multi_line {
p.indent();
}
for (i, item) in self.properties.iter().enumerate() {
if i != 0 {
p.print_comma();
p.print_curly_braces(self.span, self.properties.is_empty(), |p| {
for (index, item) in self.properties.iter().enumerate() {
if index != 0 {
p.print_comma();
p.print_soft_space();
}
p.print_indent();
item.gen(p, ctx);
p.print_soft_newline();
}
p.print_soft_newline();
p.print_indent();
item.gen(p, ctx);
}
if is_multi_line {
p.print_soft_newline();
p.dedent();
p.print_indent();
}
p.print(b'}');
p.add_source_mapping(self.span.end);
});
});
}
}
@ -1516,6 +1541,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ObjectProperty<'a> {
func.params.gen(p, ctx);
p.print(b')');
if let Some(body) = &func.body {
p.print_soft_space();
body.gen(p, ctx);
}
return;
@ -1532,6 +1558,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ObjectProperty<'a> {
}
if !self.shorthand {
p.print_colon();
p.print_soft_space();
}
self.value.gen_expr(p, Precedence::Assign, Context::default());
}
@ -1597,6 +1624,7 @@ impl<'a, const MINIFY: bool> GenExpr<MINIFY> for YieldExpression<'a> {
p.print_str(b"yield");
if self.delegate {
p.print(b'*');
p.print_soft_space();
}
if let Some(argument) = self.argument.as_ref() {
if !self.delegate {
@ -1620,6 +1648,7 @@ impl<'a, const MINIFY: bool> GenExpr<MINIFY> for UpdateExpression<'a> {
p.prev_op_end = p.code().len();
self.argument.gen_expr(p, Precedence::Prefix, ctx);
} else {
p.print_space_before_operator(self.operator.into());
self.argument.gen_expr(p, Precedence::Postfix, ctx);
p.print_str(operator);
p.prev_op = Some(self.operator.into());
@ -2045,35 +2074,47 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for Class<'a> {
super_class.gen_expr(p, Precedence::Call, Context::default());
}
p.print_soft_space();
p.print_block_start(self.body.span.start);
for item in &self.body.body {
p.print_indent();
p.print_semicolon_if_needed();
item.gen(p, ctx);
if matches!(
item,
ClassElement::PropertyDefinition(_)
| ClassElement::AccessorProperty(_)
| ClassElement::TSIndexSignature(_)
) {
p.print_semicolon_after_statement();
}
p.print_soft_newline();
}
p.print_block_end(self.body.span.end);
self.body.gen(p, ctx);
p.needs_semicolon = false;
});
}
}
impl<'a, const MINIFY: bool> Gen<MINIFY> for ClassBody<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.print_curly_braces(self.span, self.body.is_empty(), |p| {
for item in &self.body {
p.print_semicolon_if_needed();
p.print_indent();
item.gen(p, ctx);
}
});
}
}
impl<'a, const MINIFY: bool> Gen<MINIFY> for ClassElement<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
match self {
Self::StaticBlock(elem) => elem.gen(p, ctx),
Self::MethodDefinition(elem) => elem.gen(p, ctx),
Self::PropertyDefinition(elem) => elem.gen(p, ctx),
Self::AccessorProperty(elem) => elem.gen(p, ctx),
Self::TSIndexSignature(elem) => elem.gen(p, ctx),
Self::StaticBlock(elem) => {
elem.gen(p, ctx);
p.print_soft_newline();
}
Self::MethodDefinition(elem) => {
elem.gen(p, ctx);
p.print_soft_newline();
}
Self::PropertyDefinition(elem) => {
elem.gen(p, ctx);
p.print_semicolon_after_statement();
}
Self::AccessorProperty(elem) => {
elem.gen(p, ctx);
p.print_semicolon_after_statement();
}
Self::TSIndexSignature(elem) => {
elem.gen(p, ctx);
p.print_semicolon_after_statement();
}
}
}
}
@ -2284,12 +2325,12 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for StaticBlock<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.add_source_mapping(self.span.start);
p.print_str(b"static");
p.print_block_start(self.span.start);
for stmt in &self.body {
p.print_semicolon_if_needed();
stmt.gen(p, ctx);
}
p.print_block_end(self.span.end);
p.print_curly_braces(self.span, self.body.is_empty(), |p| {
for stmt in &self.body {
p.print_semicolon_if_needed();
stmt.gen(p, ctx);
}
});
p.needs_semicolon = false;
}
}
@ -2343,9 +2384,10 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for MethodDefinition<'a> {
return_type.gen(p, ctx);
}
if let Some(body) = &self.value.body {
p.print_soft_space();
body.gen(p, ctx);
} else {
p.print_semicolon_after_statement();
p.print_semicolon();
}
}
}
@ -2380,7 +2422,9 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for PropertyDefinition<'a> {
type_annotation.gen(p, ctx);
}
if let Some(value) = &self.value {
p.print_soft_space();
p.print_equal();
p.print_soft_space();
value.gen_expr(p, Precedence::Assign, Context::default());
}
}
@ -2407,6 +2451,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for AccessorProperty<'a> {
p.print_equal();
value.gen_expr(p, Precedence::Assign, Context::default());
}
p.print_semicolon();
}
}
@ -2487,6 +2532,9 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ArrayPattern<'a> {
for (index, item) in self.elements.iter().enumerate() {
if index != 0 {
p.print_comma();
if item.is_some() {
p.print_soft_space();
}
}
if let Some(item) = item {
item.gen(p, ctx);
@ -2496,6 +2544,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ArrayPattern<'a> {
}
}
if let Some(rest) = &self.rest {
p.print_soft_space();
rest.gen(p, ctx);
}
p.print(b']');
@ -2506,7 +2555,9 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for ArrayPattern<'a> {
impl<'a, const MINIFY: bool> Gen<MINIFY> for AssignmentPattern<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
self.left.gen(p, ctx);
p.print_soft_space();
p.print_equal();
p.print_soft_space();
self.right.gen_expr(p, Precedence::Assign, Context::default());
}
}
@ -2825,12 +2876,14 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSTemplateLiteralType<'a> {
impl<'a, const MINIFY: bool> Gen<MINIFY> for TSTypeLiteral<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.print_block_start(self.span.start);
for item in &self.members {
item.gen(p, ctx);
p.print_semicolon();
}
p.print_block_end(self.span.end);
p.print_curly_braces(self.span, self.members.is_empty(), |p| {
for item in &self.members {
p.print_indent();
item.gen(p, ctx);
p.print_semicolon();
p.print_soft_newline();
}
});
}
}
@ -3123,6 +3176,7 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSNamedTupleMember<'a> {
impl<'a, const MINIFY: bool> Gen<MINIFY> for TSModuleDeclaration<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.print_indent();
if self.modifiers.contains(ModifierKind::Export) {
p.print_str(b"export ");
}
@ -3131,31 +3185,50 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSModuleDeclaration<'a> {
}
p.print_str(b"module");
p.print_space_before_identifier();
let name = self.id.name();
p.wrap_quote(name, |p, _| p.print_str(name.as_bytes()));
p.print_hard_space();
match &self.body {
Some(TSModuleDeclarationBody::TSModuleDeclaration(body)) => {
p.print_block_start(body.span.start);
body.gen(p, ctx);
p.print_block_end(body.span.end);
}
Some(TSModuleDeclarationBody::TSModuleBlock(body)) => {
p.print_block_start(body.span.start);
for item in &body.body {
p.print_semicolon_if_needed();
item.gen(p, ctx);
self.id.gen(p, ctx);
if let Some(body) = &self.body {
let mut body = body;
loop {
match body {
TSModuleDeclarationBody::TSModuleDeclaration(b) => {
p.print(b'.');
b.id.gen(p, ctx);
if let Some(b) = &b.body {
body = b;
} else {
break;
}
}
TSModuleDeclarationBody::TSModuleBlock(body) => {
p.print_soft_space();
body.gen(p, ctx);
break;
}
}
p.print_semicolon_if_needed();
p.print_block_end(body.span.end);
}
None => {}
}
if MINIFY {
p.print_semicolon();
p.needs_semicolon = false;
}
}
impl<'a, const MINIFY: bool> Gen<MINIFY> for TSModuleDeclarationName<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
match self {
Self::Identifier(ident) => ident.gen(p, ctx),
Self::StringLiteral(s) => s.gen(p, ctx),
}
p.print_hard_space();
p.print_soft_newline();
}
}
impl<'a, const MINIFY: bool> Gen<MINIFY> for TSModuleBlock<'a> {
fn gen(&self, p: &mut Codegen<{ MINIFY }>, ctx: Context) {
p.print_curly_braces(self.span, self.body.is_empty(), |p| {
for item in &self.body {
item.gen(p, ctx);
p.print_semicolon_if_needed();
}
});
}
}
@ -3187,14 +3260,14 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSInterfaceDeclaration<'a> {
}
}
p.print_soft_space();
p.print_block_start(self.body.span.start);
for item in &self.body.body {
p.print_indent();
p.print_semicolon_if_needed();
item.gen(p, ctx);
p.print_semicolon_after_statement();
}
p.print_block_end(self.body.span.end);
p.print_curly_braces(self.body.span, self.body.body.is_empty(), |p| {
for item in &self.body.body {
p.print_indent();
p.print_semicolon_if_needed();
item.gen(p, ctx);
p.print_semicolon_after_statement();
}
});
if MINIFY {
p.print_hard_space();
}
@ -3228,10 +3301,19 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSEnumDeclaration<'a> {
p.print_str(b"enum ");
self.id.gen(p, ctx);
p.print_space_before_identifier();
p.print_block_start(self.span.start);
p.print_list(&self.members, ctx);
p.print_block_end(self.span.end);
p.print_hard_space();
p.print_curly_braces(self.span, self.members.is_empty(), |p| {
for member in &self.members {
p.print_indent();
member.gen(p, ctx);
p.print_comma();
p.print_soft_newline();
}
});
if MINIFY {
p.print_hard_space();
}
p.print_soft_newline();
p.needs_semicolon = false;
}
}
@ -3247,6 +3329,12 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSEnumMember<'a> {
p.print_str(b"]");
}
}
if let Some(init) = &self.initializer {
p.print_soft_space();
p.print_equal();
p.print_soft_space();
init.gen_expr(p, Precedence::lowest(), ctx);
}
}
}
@ -3294,10 +3382,12 @@ impl<'a, const MINIFY: bool> Gen<MINIFY> for TSModuleReference<'a> {
impl<'a, const MINIFY: bool> GenExpr<MINIFY> for TSTypeAssertion<'a> {
fn gen_expr(&self, p: &mut Codegen<{ MINIFY }>, precedence: Precedence, ctx: Context) {
p.print_str(b"<");
self.type_annotation.gen(p, ctx);
p.print_str(b">");
self.expression.gen_expr(p, precedence, ctx);
p.wrap(precedence > self.precedence(), |p| {
p.print_str(b"<");
self.type_annotation.gen(p, ctx);
p.print_str(b">");
self.expression.gen_expr(p, Precedence::Grouping, ctx);
});
}
}

View file

@ -311,18 +311,32 @@ impl<'a, const MINIFY: bool> Codegen<'a, MINIFY> {
}
fn print_sequence<T: Gen<MINIFY>>(&mut self, items: &[T], separator: Separator, ctx: Context) {
let len = items.len();
for (index, item) in items.iter().enumerate() {
for item in items {
item.gen(self, ctx);
match separator {
Separator::Semicolon => self.print_semicolon(),
Separator::Comma => self.print(b','),
Separator::None => {}
}
if index != len - 1 {}
}
}
fn print_curly_braces<F: FnOnce(&mut Self)>(&mut self, span: Span, single_line: bool, op: F) {
self.add_source_mapping(span.start);
self.print(b'{');
if !single_line {
self.print_soft_newline();
self.indent();
}
op(self);
if !single_line {
self.dedent();
self.print_indent();
}
self.add_source_mapping(span.end);
self.print(b'}');
}
fn print_block_start(&mut self, position: u32) {
self.add_source_mapping(position);
self.print(b'{');
@ -337,11 +351,21 @@ impl<'a, const MINIFY: bool> Codegen<'a, MINIFY> {
self.print(b'}');
}
fn print_block1(&mut self, stmt: &BlockStatement<'_>, ctx: Context) {
self.print_block_start(stmt.span.start);
self.print_directives_and_statements_with_semicolon_order(None, &stmt.body, ctx, true);
self.print_block_end(stmt.span.end);
self.needs_semicolon = false;
fn print_body(&mut self, stmt: &Statement<'_>, ctx: Context) {
match stmt {
Statement::BlockStatement(stmt) => {
self.print_block_statement(stmt, ctx);
self.print_soft_newline();
}
stmt => stmt.gen(self, ctx),
}
}
fn print_block_statement(&mut self, stmt: &BlockStatement<'_>, ctx: Context) {
self.print_curly_braces(stmt.span, stmt.body.is_empty(), |p| {
p.print_directives_and_statements_with_semicolon_order(None, &stmt.body, ctx, true);
p.needs_semicolon = false;
});
}
fn print_block<T: Gen<MINIFY>>(
@ -379,6 +403,7 @@ impl<'a, const MINIFY: bool> Codegen<'a, MINIFY> {
for (index, item) in items.iter().enumerate() {
if index != 0 {
self.print_comma();
self.print_soft_space();
}
item.gen_expr(self, precedence, ctx);
}
@ -461,6 +486,7 @@ impl<'a, const MINIFY: bool> Codegen<'a, MINIFY> {
if let Some(Statement::ExpressionStatement(s)) = statements.first() {
if matches!(s.expression.get_inner_expression(), Expression::StringLiteral(_)) {
self.print_semicolon();
self.print_soft_newline();
}
}
} else {

View file

@ -132,13 +132,13 @@ fn new_expr() {
#[test]
fn for_stmt() {
test("for (let x = 0; x < 10; x++) {}", "for (let x = 0; x < 10; x++) {\n}\n", None);
test("for (;;) {}", "for (;;) {\n}\n", None);
test("for (let x = 1;;) {}", "for (let x = 1;;) {\n}\n", None);
test("for (;true;) {}", "for (; true;) {\n}\n", None);
test("for (;;i++) {}", "for (;; i++) {\n}\n", None);
test("for (let x = 0; x < 10; x++) {}", "for (let x = 0; x < 10; x++) {}\n", None);
test("for (;;) {}", "for (;;) {}\n", None);
test("for (let x = 1;;) {}", "for (let x = 1;;) {}\n", None);
test("for (;true;) {}", "for (; true;) {}\n", None);
test("for (;;i++) {}", "for (;; i++) {}\n", None);
test("for (using x = 1;;) {}", "for (using x = 1;;) {\n}\n", None);
test("for (using x = 1;;) {}", "for (using x = 1;;) {}\n", None);
}
#[test]
@ -178,7 +178,11 @@ fn typescript() {
// type-only imports/exports
test_ts("import type { Foo } from 'foo';", "import type {Foo} from 'foo';\n", false);
test_ts("import { Foo, type Bar } from 'foo';", "import {Foo,type Bar} from 'foo';\n", false);
test_ts("export { Foo, type Bar } from 'foo';", "export { Foo, type Bar } from 'foo';", false);
test_ts(
"export { Foo, type Bar } from 'foo';",
"export { Foo, type Bar } from 'foo';\n",
false,
);
}
fn test_comment_helper(source_text: &str, expected: &str) {
@ -201,17 +205,8 @@ fn annotate_comment() {
/* #__NO_SIDE_EFFECTS__ */ async function y() {},
/* #__NO_SIDE_EFFECTS__ */ async function*() {},
/* #__NO_SIDE_EFFECTS__ */ async function* y() {},
])
",
r"x([/* #__NO_SIDE_EFFECTS__ */ function() {
}, /* #__NO_SIDE_EFFECTS__ */ function y() {
}, /* #__NO_SIDE_EFFECTS__ */ function* () {
}, /* #__NO_SIDE_EFFECTS__ */ function* y() {
}, /* #__NO_SIDE_EFFECTS__ */ async function() {
}, /* #__NO_SIDE_EFFECTS__ */ async function y() {
}, /* #__NO_SIDE_EFFECTS__ */ async function* () {
}, /* #__NO_SIDE_EFFECTS__ */ async function* y() {
},]);
])",
r"x([/* #__NO_SIDE_EFFECTS__ */ function() {}, /* #__NO_SIDE_EFFECTS__ */ function y() {}, /* #__NO_SIDE_EFFECTS__ */ function* () {}, /* #__NO_SIDE_EFFECTS__ */ function* y() {}, /* #__NO_SIDE_EFFECTS__ */ async function() {}, /* #__NO_SIDE_EFFECTS__ */ async function y() {}, /* #__NO_SIDE_EFFECTS__ */ async function* () {}, /* #__NO_SIDE_EFFECTS__ */ async function* y() {},]);
",
);
@ -225,9 +220,7 @@ fn annotate_comment() {
/* #__NO_SIDE_EFFECTS__ */ async () => {},
/* #__NO_SIDE_EFFECTS__ */ async (y) => (y),
])",
r"x([/* #__NO_SIDE_EFFECTS__ */ (y) => y, /* #__NO_SIDE_EFFECTS__ */ () => {
}, /* #__NO_SIDE_EFFECTS__ */ (y) => y, /* #__NO_SIDE_EFFECTS__ */ async (y) => y, /* #__NO_SIDE_EFFECTS__ */ async () => {
}, /* #__NO_SIDE_EFFECTS__ */ async (y) => y,]);
r"x([/* #__NO_SIDE_EFFECTS__ */ (y) => y, /* #__NO_SIDE_EFFECTS__ */ () => {}, /* #__NO_SIDE_EFFECTS__ */ (y) => y, /* #__NO_SIDE_EFFECTS__ */ async (y) => y, /* #__NO_SIDE_EFFECTS__ */ async () => {}, /* #__NO_SIDE_EFFECTS__ */ async (y) => y,]);
",
);
test_comment_helper(
@ -240,9 +233,7 @@ fn annotate_comment() {
/* #__NO_SIDE_EFFECTS__ */ async () => {},
/* #__NO_SIDE_EFFECTS__ */ async (y) => (y),
])",
r"x([/* #__NO_SIDE_EFFECTS__ */ (y) => y, /* #__NO_SIDE_EFFECTS__ */ () => {
}, /* #__NO_SIDE_EFFECTS__ */ (y) => y, /* #__NO_SIDE_EFFECTS__ */ async (y) => y, /* #__NO_SIDE_EFFECTS__ */ async () => {
}, /* #__NO_SIDE_EFFECTS__ */ async (y) => y,]);
r"x([/* #__NO_SIDE_EFFECTS__ */ (y) => y, /* #__NO_SIDE_EFFECTS__ */ () => {}, /* #__NO_SIDE_EFFECTS__ */ (y) => y, /* #__NO_SIDE_EFFECTS__ */ async (y) => y, /* #__NO_SIDE_EFFECTS__ */ async () => {}, /* #__NO_SIDE_EFFECTS__ */ async (y) => y,]);
",
);
//
@ -258,17 +249,13 @@ fn annotate_comment() {
async function* d() {}
",
r"// #__NO_SIDE_EFFECTS__
function a() {
}
function a() {}
// #__NO_SIDE_EFFECTS__
function* b() {
}
function* b() {}
// #__NO_SIDE_EFFECTS__
async function c() {
}
async function c() {}
// #__NO_SIDE_EFFECTS__
async function* d() {
}
async function* d() {}
",
);
@ -284,17 +271,13 @@ async function* d() {
async function* d() {}
",
r"// #__NO_SIDE_EFFECTS__
function a() {
}
function a() {}
// #__NO_SIDE_EFFECTS__
function* b() {
}
function* b() {}
// #__NO_SIDE_EFFECTS__
async function c() {
}
async function c() {}
// #__NO_SIDE_EFFECTS__
async function* d() {
}
async function* d() {}
",
);
@ -303,15 +286,11 @@ async function* d() {
/* @__NO_SIDE_EFFECTS__ */ export function a() {}
/* @__NO_SIDE_EFFECTS__ */ export function* b() {}
/* @__NO_SIDE_EFFECTS__ */ export async function c() {}
/* @__NO_SIDE_EFFECTS__ */ export async function* d() {} ",
r"/* @__NO_SIDE_EFFECTS__ */ export function a() {
}
/* @__NO_SIDE_EFFECTS__ */ export function* b() {
}
/* @__NO_SIDE_EFFECTS__ */ export async function c() {
}
/* @__NO_SIDE_EFFECTS__ */ export async function* d() {
}
/* @__NO_SIDE_EFFECTS__ */ export async function* d() {}",
r"/* @__NO_SIDE_EFFECTS__ */ export function a() {}
/* @__NO_SIDE_EFFECTS__ */ export function* b() {}
/* @__NO_SIDE_EFFECTS__ */ export async function c() {}
/* @__NO_SIDE_EFFECTS__ */ export async function* d() {}
",
);
// Only "c0" and "c2" should have "no side effects" (Rollup only respects "const" and only for the first one)
@ -324,24 +303,12 @@ async function* d() {
/* #__NO_SIDE_EFFECTS__ */ export let l2 = () => {}, l3 = () => {}
/* #__NO_SIDE_EFFECTS__ */ export const c2 = () => {}, c3 = () => {}
",
r"export var v0 = function() {
}, v1 = function() {
};
export let l0 = function() {
}, l1 = function() {
};
export const c0 = /* #__NO_SIDE_EFFECTS__ */ function() {
}, c1 = function() {
};
export var v2 = () => {
}, v3 = () => {
};
export let l2 = () => {
}, l3 = () => {
};
export const c2 = /* #__NO_SIDE_EFFECTS__ */ () => {
}, c3 = () => {
};
r"export var v0 = function() {}, v1 = function() {};
export let l0 = function() {}, l1 = function() {};
export const c0 = /* #__NO_SIDE_EFFECTS__ */ function() {}, c1 = function() {};
export var v2 = () => {}, v3 = () => {};
export let l2 = () => {}, l3 = () => {};
export const c2 = /* #__NO_SIDE_EFFECTS__ */ () => {}, c3 = () => {};
",
);
// Only "c0" and "c2" should have "no side effects" (Rollup only respects "const" and only for the first one)
@ -354,24 +321,12 @@ export const c2 = /* #__NO_SIDE_EFFECTS__ */ () => {
/* #__NO_SIDE_EFFECTS__ */ let l2 = () => {}, l3 = () => {}
/* #__NO_SIDE_EFFECTS__ */ const c2 = () => {}, c3 = () => {}
",
r"var v0 = function() {
}, v1 = function() {
};
let l0 = function() {
}, l1 = function() {
};
const c0 = /* #__NO_SIDE_EFFECTS__ */ function() {
}, c1 = function() {
};
var v2 = () => {
}, v3 = () => {
};
let l2 = () => {
}, l3 = () => {
};
const c2 = /* #__NO_SIDE_EFFECTS__ */ () => {
}, c3 = () => {
};
r"var v0 = function() {}, v1 = function() {};
let l0 = function() {}, l1 = function() {};
const c0 = /* #__NO_SIDE_EFFECTS__ */ function() {}, c1 = function() {};
var v2 = () => {}, v3 = () => {};
let l2 = () => {}, l3 = () => {};
const c2 = /* #__NO_SIDE_EFFECTS__ */ () => {}, c3 = () => {};
",
);
}

View file

@ -252,10 +252,8 @@ impl<'a> ParserImpl<'a> {
Some(TSModuleDeclarationBody::TSModuleDeclaration(decl))
} else if self.at(Kind::LCurly) {
let block = self.parse_ts_module_block()?;
self.asi()?;
Some(TSModuleDeclarationBody::TSModuleBlock(block))
} else {
self.asi()?;
None
};

File diff suppressed because it is too large Load diff

View file

@ -2,9 +2,8 @@ commit: d8086f14
codegen_typescript Summary:
AST Parsed : 5283/5283 (100.00%)
Positive Passed: 5278/5283 (99.91%)
Default failed: "compiler/castExpressionParentheses.ts"
Default failed: "compiler/genericTypeAssertions3.ts"
Default failed: "compiler/jsxMultilineAttributeStringValues.tsx"
Default failed: "compiler/jsxMultilineAttributeValuesReact.tsx"
Default failed: "conformance/jsx/tsxReactEmitEntities.tsx"
Positive Passed: 5279/5283 (99.92%)
Normal failed: "compiler/genericTypeAssertions3.ts"
Normal failed: "compiler/jsxMultilineAttributeStringValues.tsx"
Normal failed: "compiler/jsxMultilineAttributeValuesReact.tsx"
Normal failed: "conformance/jsx/tsxReactEmitEntities.tsx"

View file

@ -13,18 +13,13 @@ use crate::{
typescript::TypeScriptCase,
};
fn get_result(
case_name: &str,
file_name: &Path,
source_text: &str,
source_type: SourceType,
) -> TestResult {
let normal_result = get_normal_result(case_name, file_name, source_text, source_type);
let minify_result = get_minify_result(case_name, file_name, source_text, source_type);
fn get_result(source_text: &str, source_type: SourceType) -> TestResult {
let normal_result = get_normal_result(source_text, source_type);
if !normal_result {
return TestResult::CodegenError("Default");
}
return TestResult::CodegenError("Normal");
};
let minify_result = get_minify_result(source_text, source_type);
if !minify_result {
return TestResult::CodegenError("Minify");
}
@ -32,82 +27,27 @@ fn get_result(
TestResult::Passed
}
#[allow(clippy::too_many_arguments)]
fn write_failure(
case_name: &str,
file_name: &Path,
result_type: &str,
original: &str,
parser_result1: &str,
source_text1: &str,
parser_result2: &str,
source_text2: &str,
) {
let base_path = Path::new(&format!("./tasks/coverage/failures/{case_name}"))
.join(file_name)
.join(result_type);
let _ = std::fs::create_dir_all(&base_path);
std::fs::write(base_path.join("original.ts"), original).expect("Error writing original.ts");
std::fs::write(base_path.join("parser_result1.txt"), parser_result1)
.expect("Error writing parser_result1.json");
std::fs::write(base_path.join("source_text1.ts"), source_text1)
.expect("Error writing source_text1.ts");
std::fs::write(base_path.join("parser_result2.txt"), parser_result2)
.expect("Error writing parser_result2.json");
std::fs::write(base_path.join("source_text2.ts"), source_text2)
.expect("Error writing source_text2.ts");
}
/// Idempotency test
fn get_normal_result(
case_name: &str,
file_name: &Path,
source_text: &str,
source_type: SourceType,
) -> bool {
fn get_normal_result(source_text: &str, source_type: SourceType) -> bool {
let options = CodegenOptions::default();
let allocator = Allocator::default();
let parse_result1 = Parser::new(&allocator, source_text, source_type).parse();
let source_text1 = Codegen::<false>::new("", source_text, parse_result1.trivias, options)
.build(&parse_result1.program)
.source_text;
let parse_result2 = Parser::new(&allocator, &source_text1, source_type).parse();
let source_text2 = Codegen::<false>::new("", &source_text1, parse_result2.trivias, options)
.build(&parse_result2.program)
.source_text;
let result = source_text1 == source_text2;
let source_text1 = {
let ret = Parser::new(&allocator, source_text, source_type).parse();
Codegen::<false>::new("", source_text, ret.trivias, options).build(&ret.program).source_text
};
if !result {
let parse_result1 = format!(
"Panicked: {:#?}\nErrors:\n{:#?}\nProgram:\n{:#?}",
parse_result1.panicked, parse_result1.errors, parse_result1.program
);
let parse_result2 = format!(
"Panicked: {:#?}\nErrors:\n{:#?}\nProgram:\n{:#?}",
parse_result2.panicked, parse_result2.errors, parse_result2.program
);
write_failure(
case_name,
file_name,
"normal",
source_text,
&parse_result1,
&source_text1,
&parse_result2,
&source_text2,
);
}
let source_text2 = {
let ret = Parser::new(&allocator, &source_text1, source_type).parse();
Codegen::<false>::new("", &source_text1, ret.trivias, options)
.build(&ret.program)
.source_text
};
result
source_text1 == source_text2
}
/// Minify idempotency test
fn get_minify_result(
case_name: &str,
file_name: &Path,
source_text: &str,
source_type: SourceType,
) -> bool {
fn get_minify_result(source_text: &str, source_type: SourceType) -> bool {
let options = CodegenOptions::default();
let allocator = Allocator::default();
let parse_result1 = Parser::new(&allocator, source_text, source_type).parse();
@ -119,30 +59,7 @@ fn get_minify_result(
let source_text2 = Codegen::<true>::new("", &source_text1, parse_result2.trivias, options)
.build(&parse_result2.program)
.source_text;
let result = source_text1 == source_text2;
if !result {
let parse_result1 = format!(
"Panicked: {:#?}\nErrors:\n{:#?}\nProgram:\n{:#?}",
parse_result1.panicked, parse_result1.errors, parse_result1.program
);
let parse_result2 = format!(
"Panicked: {:#?}\nErrors:\n{:#?}\nProgram:\n{:#?}",
parse_result2.panicked, parse_result2.errors, parse_result2.program
);
write_failure(
case_name,
file_name,
"minify",
source_text,
&parse_result1,
&source_text1,
&parse_result2,
&source_text2,
);
}
result
source_text1 == source_text2
}
pub struct CodegenTest262Case {
@ -174,7 +91,7 @@ impl Case for CodegenTest262Case {
let source_text = self.base.code();
let is_module = self.base.meta().flags.contains(&TestFlag::Module);
let source_type = SourceType::default().with_module(is_module);
let result = get_result("test262", self.base.path(), source_text, source_type);
let result = get_result(source_text, source_type);
self.base.set_result(result);
}
}
@ -207,7 +124,7 @@ impl Case for CodegenBabelCase {
fn run(&mut self) {
let source_text = self.base.code();
let source_type = self.base.source_type();
let result = get_result("babel", self.base.path(), source_text, source_type);
let result = get_result(source_text, source_type);
self.base.set_result(result);
}
}
@ -240,7 +157,7 @@ impl Case for CodegenTypeScriptCase {
fn run(&mut self) {
let source_text = self.base.code();
let source_type = self.base.source_type();
let result = get_result("typescript", self.base.path(), source_text, source_type);
let result = get_result(source_text, source_type);
self.base.set_result(result);
}
}
@ -273,7 +190,7 @@ impl Case for CodegenMiscCase {
fn run(&mut self) {
let source_text = self.base.code();
let source_type = self.base.source_type();
let result = get_result("misc", self.base.path(), source_text, source_type);
let result = get_result(source_text, source_type);
self.base.set_result(result);
}
}