diff --git a/crates/oxc_isolated_declarations/src/declaration.rs b/crates/oxc_isolated_declarations/src/declaration.rs index 8d675b278..d88f22604 100644 --- a/crates/oxc_isolated_declarations/src/declaration.rs +++ b/crates/oxc_isolated_declarations/src/declaration.rs @@ -73,7 +73,12 @@ impl<'a> IsolatedDeclarations<'a> { if let Some(init_expr) = &decl.init { // if kind is const and it doesn't need to infer type from expression if decl.kind.is_const() && !Self::is_need_to_infer_type_from_expression(init_expr) { - init = Some(self.ast.copy(init_expr)); + if let Expression::TemplateLiteral(lit) = init_expr { + init = + self.transform_template_to_string(lit).map(Expression::StringLiteral); + } else { + init = Some(self.ast.copy(init_expr)); + } } else { // otherwise, we need to infer type from expression binding_type = self.infer_type_from_expression(init_expr); diff --git a/crates/oxc_isolated_declarations/src/inferrer.rs b/crates/oxc_isolated_declarations/src/inferrer.rs index eb0ea0b3e..12d562afc 100644 --- a/crates/oxc_isolated_declarations/src/inferrer.rs +++ b/crates/oxc_isolated_declarations/src/inferrer.rs @@ -21,8 +21,13 @@ impl<'a> IsolatedDeclarations<'a> { Expression::NumericLiteral(_) | Expression::BigintLiteral(_) => { Some(self.ast.ts_number_keyword(SPAN)) } - Expression::StringLiteral(_) | Expression::TemplateLiteral(_) => { - Some(self.ast.ts_string_keyword(SPAN)) + Expression::StringLiteral(_) => Some(self.ast.ts_string_keyword(SPAN)), + Expression::TemplateLiteral(lit) => { + if lit.expressions.is_empty() { + Some(self.ast.ts_string_keyword(SPAN)) + } else { + None + } } Expression::Identifier(ident) => match ident.name.as_str() { "undefined" => Some(self.ast.ts_undefined_keyword(SPAN)), @@ -133,12 +138,12 @@ impl<'a> IsolatedDeclarations<'a> { } pub fn is_need_to_infer_type_from_expression(expr: &Expression) -> bool { - !matches!( - expr, + match expr { Expression::NumericLiteral(_) - | Expression::BigintLiteral(_) - | Expression::StringLiteral(_) - | Expression::TemplateLiteral(_) - ) + | Expression::BigintLiteral(_) + | Expression::StringLiteral(_) => false, + Expression::TemplateLiteral(lit) => !lit.expressions.is_empty(), + _ => true, + } } } diff --git a/crates/oxc_isolated_declarations/src/lib.rs b/crates/oxc_isolated_declarations/src/lib.rs index 490261106..3b2c33e07 100644 --- a/crates/oxc_isolated_declarations/src/lib.rs +++ b/crates/oxc_isolated_declarations/src/lib.rs @@ -11,6 +11,7 @@ mod diagnostics; mod r#enum; mod function; mod inferrer; +mod literal; mod module; mod return_type; mod scope; diff --git a/crates/oxc_isolated_declarations/src/literal.rs b/crates/oxc_isolated_declarations/src/literal.rs new file mode 100644 index 000000000..319ba4828 --- /dev/null +++ b/crates/oxc_isolated_declarations/src/literal.rs @@ -0,0 +1,22 @@ +use oxc_allocator::Box; +use oxc_ast::ast::{StringLiteral, TemplateLiteral}; + +use crate::IsolatedDeclarations; + +impl<'a> IsolatedDeclarations<'a> { + pub fn transform_template_to_string( + &self, + lit: &TemplateLiteral<'a>, + ) -> Option>> { + if lit.expressions.is_empty() { + lit.quasis.first().map(|item| { + self.ast.alloc(self.ast.string_literal( + lit.span, + if let Some(cooked) = &item.value.cooked { cooked } else { &item.value.raw }, + )) + }) + } else { + None + } + } +} diff --git a/crates/oxc_isolated_declarations/src/types.rs b/crates/oxc_isolated_declarations/src/types.rs index 8ed6e06d6..75bac7d52 100644 --- a/crates/oxc_isolated_declarations/src/types.rs +++ b/crates/oxc_isolated_declarations/src/types.rs @@ -189,25 +189,9 @@ impl<'a> IsolatedDeclarations<'a> { "undefined" => Some(self.ast.ts_undefined_keyword(ident.span)), _ => None, }, - Expression::TemplateLiteral(lit) => { - 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::TemplateLiteral(lit) => self + .transform_template_to_string(lit) + .map(|string| self.ast.ts_literal_type(lit.span, TSLiteral::StringLiteral(string))), Expression::UnaryExpression(expr) => Some( self.ast.ts_literal_type(SPAN, TSLiteral::UnaryExpression(self.ast.copy(expr))), ), diff --git a/crates/oxc_isolated_declarations/tests/fixtures/infer-template-literal.ts b/crates/oxc_isolated_declarations/tests/fixtures/infer-template-literal.ts new file mode 100644 index 000000000..875168425 --- /dev/null +++ b/crates/oxc_isolated_declarations/tests/fixtures/infer-template-literal.ts @@ -0,0 +1,10 @@ +export const CSS_VARS_HELPER = `useCssVars` + +export function g(func = `useCssVar`) : void {} + +export const F = { + a: `a`, + b: [`b`] +} as const + +export const BAD = `useCssV${v}ars` \ No newline at end of file diff --git a/crates/oxc_isolated_declarations/tests/snapshots/infer-template-literal.snap b/crates/oxc_isolated_declarations/tests/snapshots/infer-template-literal.snap new file mode 100644 index 000000000..b869fbd28 --- /dev/null +++ b/crates/oxc_isolated_declarations/tests/snapshots/infer-template-literal.snap @@ -0,0 +1,24 @@ +--- +source: crates/oxc_isolated_declarations/tests/mod.rs +input_file: crates/oxc_isolated_declarations/tests/fixtures/infer-template-literal.ts +--- +==================== .D.TS ==================== + +export declare const CSS_VARS_HELPER = 'useCssVars'; +export declare function g(func?: string): void; +export declare const F: { + readonly a: 'a'; + readonly b: readonly ['b']; +}; +export declare const BAD: unknown; + + +==================== Errors ==================== + + x TS9010: Variable must have an explicit type annotation with + | --isolatedDeclarations. + ,-[10:14] + 9 | + 10 | export const BAD = `useCssV${v}ars` + : ^^^ + `----