diff --git a/crates/oxc_isolated_declarations/src/types.rs b/crates/oxc_isolated_declarations/src/types.rs index 69c4b94cea14e..b5b6640fcfdf7 100644 --- a/crates/oxc_isolated_declarations/src/types.rs +++ b/crates/oxc_isolated_declarations/src/types.rs @@ -3,11 +3,11 @@ use oxc_ast::{ NONE, ast::{ ArrayExpression, ArrayExpressionElement, ArrowFunctionExpression, Expression, Function, - ObjectExpression, ObjectPropertyKind, TSLiteral, TSMethodSignatureKind, TSTupleElement, - TSType, TSTypeOperatorOperator, + ObjectExpression, ObjectPropertyKind, PropertyKey, PropertyKind, TSLiteral, + TSMethodSignatureKind, TSTupleElement, TSType, TSTypeOperatorOperator, }, }; -use oxc_span::{GetSpan, SPAN, Span}; +use oxc_span::{ContentEq, GetSpan, SPAN, Span}; use crate::{ IsolatedDeclarations, @@ -81,6 +81,13 @@ impl<'a> IsolatedDeclarations<'a> { expr: &ObjectExpression<'a>, is_const: bool, ) -> TSType<'a> { + // The span of accessors that cannot infer the type. + let mut accessor_spans = Vec::new(); + // If either a setter or getter is inferred, the PropertyKey will be added. + // Use `Vec` rather than `HashSet` because the `PropertyKey` doesn't support + // `Hash` trait, fortunately, the number of accessors is small. + let mut accessor_inferred: Vec<&PropertyKey<'a>> = Vec::new(); + let members = self.ast.vec_from_iter(expr.properties.iter().filter_map(|property| match property { ObjectPropertyKind::ObjectProperty(object) => { @@ -93,13 +100,15 @@ impl<'a> IsolatedDeclarations<'a> { return None; } + let key = &object.key; + if let Expression::FunctionExpression(function) = &object.value { if !is_const && object.method { let return_type = self.infer_function_return_type(function); let params = self.transform_formal_parameters(&function.params); return Some(self.ast.ts_signature_method_signature( object.span, - object.key.clone_in(self.ast.allocator), + key.clone_in(self.ast.allocator), object.computed, false, TSMethodSignatureKind::Method, @@ -111,26 +120,73 @@ impl<'a> IsolatedDeclarations<'a> { } } - let type_annotation = if is_const { - self.transform_expression_to_ts_type(&object.value) - } else { - self.infer_type_from_expression(&object.value) - }; + let type_annotation = match object.kind { + PropertyKind::Get => { + if accessor_inferred.iter().any(|k| k.content_eq(key)) { + return None; + } - if type_annotation.is_none() { - self.error(inferred_type_of_expression(object.value.span())); - return None; - } + let Expression::FunctionExpression(function) = &object.value else { + unreachable!( + "`object.kind` being `Get` guarantees that it is a function" + ); + }; + + let annotation = self.infer_function_return_type(function); + if annotation.is_none() { + accessor_spans.push((key, key.span())); + return None; + } + + accessor_inferred.push(key); + annotation + } + PropertyKind::Set => { + if accessor_inferred.iter().any(|k| k.content_eq(key)) { + return None; + } + + let Expression::FunctionExpression(function) = &object.value else { + unreachable!( + "`object.kind` being `Set` guarantees that it is a function" + ); + }; + let annotation = function.params.items.first().and_then(|param| { + param.pattern.type_annotation.clone_in(self.ast.allocator) + }); + if annotation.is_none() { + accessor_spans.push((key, function.params.span)); + return None; + } + + accessor_inferred.push(key); + annotation + } + PropertyKind::Init => { + 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())); + return None; + } + + type_annotation.map(|type_annotation| { + self.ast.alloc_ts_type_annotation(SPAN, type_annotation) + }) + } + }; let property_signature = self.ast.ts_signature_property_signature( object.span, false, false, is_const, - object.key.clone_in(self.ast.allocator), - type_annotation.map(|type_annotation| { - self.ast.ts_type_annotation(SPAN, type_annotation) - }), + key.clone_in(self.ast.allocator), + type_annotation, ); Some(property_signature) } @@ -139,6 +195,14 @@ impl<'a> IsolatedDeclarations<'a> { None } })); + + // Report an error if the type of neither the setter nor the getter is inferred. + for (key, span) in accessor_spans { + if !accessor_inferred.iter().any(|k| k.content_eq(key)) { + self.error(inferred_type_of_expression(span)); + } + } + self.ast.ts_type_type_literal(SPAN, members) } diff --git a/crates/oxc_isolated_declarations/tests/fixtures/object.ts b/crates/oxc_isolated_declarations/tests/fixtures/object.ts new file mode 100644 index 0000000000000..5be9b8879b34c --- /dev/null +++ b/crates/oxc_isolated_declarations/tests/fixtures/object.ts @@ -0,0 +1,38 @@ +const A = { + get state(): number { + return 0; + }, +}; + +const B = { + set state(v: string) { + // do something + }, +}; + +const C = { + get state(): number { + return 0; + }, + set state(v: number) { + // do something + }, +}; + +const D = { + get state(): number { + return 0; + }, + set state(v: string) { + // do something + }, +}; + +const E = { + get state() { + return A; + }, + set state(v) { + // do something + }, +}; diff --git a/crates/oxc_isolated_declarations/tests/snapshots/object.snap b/crates/oxc_isolated_declarations/tests/snapshots/object.snap new file mode 100644 index 0000000000000..d437a1d02b96e --- /dev/null +++ b/crates/oxc_isolated_declarations/tests/snapshots/object.snap @@ -0,0 +1,42 @@ +--- +source: crates/oxc_isolated_declarations/tests/mod.rs +input_file: crates/oxc_isolated_declarations/tests/fixtures/object.ts +--- +``` +==================== .D.TS ==================== + +declare const A: { + state: number +}; +declare const B: { + state: string +}; +declare const C: { + state: number +}; +declare const D: { + state: number +}; +declare const E: {}; + + +==================== Errors ==================== + + x TS9013: Expression type can't be inferred with --isolatedDeclarations. + ,-[32:6] + 31 | const E = { + 32 | get state() { + : ^^^^^ + 33 | return A; + `---- + + x TS9013: Expression type can't be inferred with --isolatedDeclarations. + ,-[35:11] + 34 | }, + 35 | set state(v) { + : ^^^ + 36 | // do something + `---- + + +```