From b0d7355c981afd4b67dac17dd35a8581322dec23 Mon Sep 17 00:00:00 2001 From: Dunqing <29533304+Dunqing@users.noreply.github.com> Date: Thu, 20 Jun 2024 15:02:41 +0000 Subject: [PATCH] feat(isolated-declarations): transform const expression correctly (#3793) --- .../oxc_isolated_declarations/src/inferrer.rs | 2 +- crates/oxc_isolated_declarations/src/types.rs | 78 +++++++++++++------ .../tests/fixtures/as-const.ts | 16 ++++ .../tests/snapshots/as-const.snap | 22 ++++++ 4 files changed, 92 insertions(+), 26 deletions(-) create mode 100644 crates/oxc_isolated_declarations/tests/fixtures/as-const.ts create mode 100644 crates/oxc_isolated_declarations/tests/snapshots/as-const.snap diff --git a/crates/oxc_isolated_declarations/src/inferrer.rs b/crates/oxc_isolated_declarations/src/inferrer.rs index 874c8bfb9c878..eb0ea0b3effed 100644 --- a/crates/oxc_isolated_declarations/src/inferrer.rs +++ b/crates/oxc_isolated_declarations/src/inferrer.rs @@ -43,7 +43,7 @@ impl<'a> IsolatedDeclarations<'a> { } Expression::TSAsExpression(expr) => { if expr.type_annotation.is_const_type_reference() { - Some(self.transform_expression_to_ts_type(&expr.expression)) + self.transform_expression_to_ts_type(&expr.expression) } else { Some(self.ast.copy(&expr.type_annotation)) } diff --git a/crates/oxc_isolated_declarations/src/types.rs b/crates/oxc_isolated_declarations/src/types.rs index c2b03417394b0..8ed6e06d6e6bd 100644 --- a/crates/oxc_isolated_declarations/src/types.rs +++ b/crates/oxc_isolated_declarations/src/types.rs @@ -108,7 +108,11 @@ impl<'a> IsolatedDeclarations<'a> { } } - let type_annotation = self.infer_type_from_expression(&object.value); + let type_annotation = if is_const { + self.transform_expression_to_ts_type(&object.value) + } else { + self.infer_type_from_expression(&object.value) + }; if type_annotation.is_none() { self.error(inferred_type_of_expression(object.value.span())); @@ -142,17 +146,19 @@ impl<'a> IsolatedDeclarations<'a> { is_const: bool, ) -> TSType<'a> { let element_types = - self.ast.new_vec_from_iter(expr.elements.iter().filter_map(|element| match element { - ArrayExpressionElement::SpreadElement(spread) => { - self.error(arrays_with_spread_elements(spread.span)); - None - } - ArrayExpressionElement::Elision(elision) => { - Some(TSTupleElement::from(self.ast.ts_undefined_keyword(elision.span))) + self.ast.new_vec_from_iter(expr.elements.iter().filter_map(|element| { + match element { + ArrayExpressionElement::SpreadElement(spread) => { + self.error(arrays_with_spread_elements(spread.span)); + None + } + ArrayExpressionElement::Elision(elision) => { + Some(TSTupleElement::from(self.ast.ts_undefined_keyword(elision.span))) + } + _ => self + .transform_expression_to_ts_type(element.to_expression()) + .map(TSTupleElement::from), } - _ => Some(TSTupleElement::from( - self.transform_expression_to_ts_type(element.to_expression()), - )), })); let ts_type = self.ast.ts_tuple_type(SPAN, element_types); @@ -164,36 +170,58 @@ impl<'a> IsolatedDeclarations<'a> { } // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions - pub fn transform_expression_to_ts_type(&self, expr: &Expression<'a>) -> TSType<'a> { + pub fn transform_expression_to_ts_type(&self, expr: &Expression<'a>) -> Option> { match expr { Expression::BooleanLiteral(lit) => { - self.ast.ts_literal_type(SPAN, TSLiteral::BooleanLiteral(self.ast.copy(lit))) + Some(self.ast.ts_literal_type(SPAN, TSLiteral::BooleanLiteral(self.ast.copy(lit)))) } Expression::NumericLiteral(lit) => { - self.ast.ts_literal_type(SPAN, TSLiteral::NumericLiteral(self.ast.copy(lit))) + Some(self.ast.ts_literal_type(SPAN, TSLiteral::NumericLiteral(self.ast.copy(lit)))) } Expression::BigintLiteral(lit) => { - self.ast.ts_literal_type(SPAN, TSLiteral::BigintLiteral(self.ast.copy(lit))) + Some(self.ast.ts_literal_type(SPAN, TSLiteral::BigintLiteral(self.ast.copy(lit)))) } Expression::StringLiteral(lit) => { - self.ast.ts_literal_type(SPAN, TSLiteral::StringLiteral(self.ast.copy(lit))) + Some(self.ast.ts_literal_type(SPAN, TSLiteral::StringLiteral(self.ast.copy(lit)))) } + Expression::NullLiteral(lit) => Some(self.ast.ts_null_keyword(lit.span)), + Expression::Identifier(ident) => match ident.name.as_str() { + "undefined" => Some(self.ast.ts_undefined_keyword(ident.span)), + _ => None, + }, Expression::TemplateLiteral(lit) => { - self.ast.ts_literal_type(SPAN, TSLiteral::TemplateLiteral(self.ast.copy(lit))) - } - Expression::UnaryExpression(expr) => { - self.ast.ts_literal_type(SPAN, TSLiteral::UnaryExpression(self.ast.copy(expr))) + if lit.expressions.is_empty() { + lit.quasis.first().map(|item| { + self.ast.ts_literal_type( + SPAN, + TSLiteral::StringLiteral(self.ast.alloc(self.ast.string_literal( + lit.span, + if let Some(cooked) = &item.value.cooked { + cooked + } else { + &item.value.raw + }, + ))), + ) + }) + } else { + None + } } + Expression::UnaryExpression(expr) => Some( + self.ast.ts_literal_type(SPAN, TSLiteral::UnaryExpression(self.ast.copy(expr))), + ), Expression::ArrayExpression(expr) => { - self.transform_array_expression_to_ts_type(expr, true) + Some(self.transform_array_expression_to_ts_type(expr, true)) } Expression::ObjectExpression(expr) => { - // { readonly a: number } - self.transform_object_expression_to_ts_type(expr, true) + Some(self.transform_object_expression_to_ts_type(expr, true)) } - _ => { - unreachable!() + Expression::FunctionExpression(func) => self.transform_function_to_ts_type(func), + Expression::ArrowFunctionExpression(func) => { + self.transform_arrow_function_to_ts_type(func) } + _ => None, } } } diff --git a/crates/oxc_isolated_declarations/tests/fixtures/as-const.ts b/crates/oxc_isolated_declarations/tests/fixtures/as-const.ts new file mode 100644 index 0000000000000..b9638532cfa2d --- /dev/null +++ b/crates/oxc_isolated_declarations/tests/fixtures/as-const.ts @@ -0,0 +1,16 @@ +const F = { + string: `string`, + templateLiteral: `templateLiteral`, + number: 1.23, + bigint: -1_2_3n, + boolean: true, + null: null, + undefined: undefined, + function(a: string): void {}, + arrow: (a: string): void => {}, + object: { + a: `a`, + b: `b` + }, + array: [`a`, , { b: `\n` }], +} as const \ No newline at end of file diff --git a/crates/oxc_isolated_declarations/tests/snapshots/as-const.snap b/crates/oxc_isolated_declarations/tests/snapshots/as-const.snap new file mode 100644 index 0000000000000..b35fe392a066a --- /dev/null +++ b/crates/oxc_isolated_declarations/tests/snapshots/as-const.snap @@ -0,0 +1,22 @@ +--- +source: crates/oxc_isolated_declarations/tests/mod.rs +input_file: crates/oxc_isolated_declarations/tests/fixtures/as-const.ts +--- +==================== .D.TS ==================== + +declare const F: { + readonly string: 'string'; + readonly templateLiteral: 'templateLiteral'; + readonly number: 1.23; + readonly bigint: -1_2_3n; + readonly boolean: true; + readonly null: null; + readonly undefined: undefined; + readonly function: (a: string) => void; + readonly arrow: (a: string) => void; + readonly object: { + readonly a: 'a'; + readonly b: 'b'; + }; + readonly array: readonly ['a', undefined, { readonly b: '\n'}]; +};