mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-15 06:39:17 +00:00
fix(isolated-declarations): preserve undefined for defaulted any (#25292)
## Summary - emit `any | undefined` for default-initialized `any` parameters followed by required parameters - add a regression fixture for #25290 and update the existing parameter snapshot - preserve the existing handling of `unknown` Closes #25290
This commit is contained in:
1 parent
9ed10b728d
commit
53f9295ac5
8 files changed
+195
-38
No files matched your search
@@ -316,6 +316,31 @@ impl<'a> IsolatedDeclarations<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Infer reusable parameter-property types once so transforming the constructor and the
|
||||
/// generated property cannot report the same inference diagnostic twice.
|
||||
fn infer_constructor_parameter_property_types(
|
||||
&self,
|
||||
params: &ArenaBox<'a, FormalParameters<'a>>,
|
||||
) -> Option<ArenaBox<'a, FormalParameters<'a>>> {
|
||||
let mut inferred_params = None;
|
||||
|
||||
for (index, param) in params.items.iter().enumerate() {
|
||||
if param.has_modifier()
|
||||
&& !param.accessibility.is_some_and(TSAccessibility::is_private)
|
||||
&& param.initializer.is_some()
|
||||
&& param.type_annotation.is_none()
|
||||
&& let Some(ts_type) = self.infer_type_from_formal_parameter(param)
|
||||
{
|
||||
let inferred_params =
|
||||
inferred_params.get_or_insert_with(|| params.clone_in(self.allocator()));
|
||||
inferred_params.items[index].type_annotation =
|
||||
Some(TSTypeAnnotation::boxed(SPAN, ts_type, self));
|
||||
}
|
||||
}
|
||||
|
||||
inferred_params
|
||||
}
|
||||
|
||||
/// Transform constructor parameters to class properties.
|
||||
///
|
||||
/// For example:
|
||||
@@ -327,17 +352,16 @@ impl<'a> IsolatedDeclarations<'a> {
|
||||
/// `class C { public x: string; constructor(x: string) {} }`
|
||||
fn transform_constructor_parameter_properties(
|
||||
&self,
|
||||
function: &Function<'a>,
|
||||
params: &FormalParameters<'a>,
|
||||
typed_params: &FormalParameters<'a>,
|
||||
) -> ArenaVec<'a, ClassElement<'a>> {
|
||||
ArenaVec::from_iter_in(
|
||||
function
|
||||
.params
|
||||
params
|
||||
.items
|
||||
.iter()
|
||||
.filter(|param| {
|
||||
// To follow up `transform_formal_parameters`'s behavior
|
||||
typed_params.items.len() == function.params.items.len() || param.has_modifier()
|
||||
typed_params.items.len() == params.items.len() || param.has_modifier()
|
||||
})
|
||||
.enumerate()
|
||||
.filter_map(|(index, param)| {
|
||||
@@ -347,6 +371,8 @@ impl<'a> IsolatedDeclarations<'a> {
|
||||
let type_annotation =
|
||||
if param.accessibility.is_some_and(TSAccessibility::is_private) {
|
||||
None
|
||||
} else if param.initializer.is_some() {
|
||||
param.type_annotation.clone_in(self.allocator())
|
||||
} else {
|
||||
// transformed params will definitely have type annotation
|
||||
typed_params.items[index].type_annotation.clone_in(self.allocator())
|
||||
@@ -552,11 +578,18 @@ impl<'a> IsolatedDeclarations<'a> {
|
||||
let is_private =
|
||||
method.accessibility.is_some_and(TSAccessibility::is_private);
|
||||
|
||||
let inferred_params =
|
||||
self.infer_constructor_parameter_property_types(&function.params);
|
||||
let function_params =
|
||||
inferred_params.as_ref().unwrap_or(&function.params);
|
||||
let params =
|
||||
self.transform_formal_parameters(&function.params, is_private);
|
||||
self.transform_formal_parameters(function_params, is_private);
|
||||
elements.splice(
|
||||
0..0,
|
||||
self.transform_constructor_parameter_properties(function, ¶ms),
|
||||
self.transform_constructor_parameter_properties(
|
||||
function_params,
|
||||
¶ms,
|
||||
),
|
||||
);
|
||||
|
||||
if is_function_overloads && function.body.is_some() {
|
||||
|
||||
@@ -86,22 +86,21 @@ impl<'a> IsolatedDeclarations<'a> {
|
||||
.map(|ts_type| {
|
||||
// jf next param is not optional and current param is assignment pattern
|
||||
// we need to add undefined to it's type
|
||||
if is_remaining_params_have_required || (param.optional && param.has_modifier())
|
||||
if (is_remaining_params_have_required
|
||||
|| (param.optional && param.has_modifier()))
|
||||
&& !contains_undefined(&ts_type)
|
||||
{
|
||||
if matches!(ts_type, TSType::TSTypeReference(_)) {
|
||||
self.error(implicitly_adding_undefined_to_type(param.span));
|
||||
} else if !ts_type.is_maybe_undefined() {
|
||||
// union with `undefined`
|
||||
return TSTypeAnnotation::boxed(
|
||||
SPAN,
|
||||
TSType::new_ts_union_type(
|
||||
SPAN,
|
||||
[ts_type, TSType::new_ts_undefined_keyword(SPAN, self)],
|
||||
self,
|
||||
),
|
||||
self,
|
||||
);
|
||||
if can_add_undefined(&ts_type) {
|
||||
let undefined = TSType::new_ts_undefined_keyword(SPAN, self);
|
||||
let ts_type = if let TSType::TSUnionType(mut union) = ts_type {
|
||||
union.types.push(undefined);
|
||||
TSType::TSUnionType(union)
|
||||
} else {
|
||||
TSType::new_ts_union_type(SPAN, [ts_type, undefined], self)
|
||||
};
|
||||
return TSTypeAnnotation::boxed(SPAN, ts_type, self);
|
||||
}
|
||||
self.error(implicitly_adding_undefined_to_type(param.span));
|
||||
}
|
||||
|
||||
TSTypeAnnotation::boxed(SPAN, ts_type, self)
|
||||
@@ -188,3 +187,40 @@ impl<'a> IsolatedDeclarations<'a> {
|
||||
pub fn get_function_span(func: &Function<'_>) -> Span {
|
||||
func.id.as_ref().map_or_else(|| Span::empty(func.params.span.start), |id| id.span)
|
||||
}
|
||||
|
||||
fn contains_undefined(ts_type: &TSType<'_>) -> bool {
|
||||
match ts_type {
|
||||
TSType::TSUndefinedKeyword(_) => true,
|
||||
TSType::TSUnionType(union) => union.types.iter().any(contains_undefined),
|
||||
TSType::TSParenthesizedType(parenthesized) => {
|
||||
contains_undefined(&parenthesized.type_annotation)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether TypeScript can syntactically add `undefined` without using type information.
|
||||
fn can_add_undefined(ts_type: &TSType<'_>) -> bool {
|
||||
if ts_type.is_keyword() {
|
||||
return true;
|
||||
}
|
||||
|
||||
match ts_type {
|
||||
TSType::TSLiteralType(_)
|
||||
| TSType::TSFunctionType(_)
|
||||
| TSType::TSConstructorType(_)
|
||||
| TSType::TSArrayType(_)
|
||||
| TSType::TSTupleType(_)
|
||||
| TSType::TSTypeLiteral(_)
|
||||
| TSType::TSTemplateLiteralType(_)
|
||||
| TSType::TSThisType(_) => true,
|
||||
TSType::TSParenthesizedType(parenthesized) => {
|
||||
can_add_undefined(&parenthesized.type_annotation)
|
||||
}
|
||||
TSType::TSUnionType(union) => union.types.iter().all(can_add_undefined),
|
||||
TSType::TSIntersectionType(intersection) => {
|
||||
intersection.types.iter().all(can_add_undefined)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,14 @@ export class PrivateFieldsWithConstructorAssignments {
|
||||
constructor(public first: number) {}
|
||||
}
|
||||
|
||||
export class DefaultedAnyParameterProperty {
|
||||
constructor(public value: any = 1, required: string) {}
|
||||
}
|
||||
|
||||
export class DefaultedArrayParameterProperty {
|
||||
constructor(public values = [], required: string) {}
|
||||
}
|
||||
|
||||
|
||||
export class PrivateMethodClass {
|
||||
private good(a): void {}
|
||||
|
||||
@@ -29,7 +29,16 @@ export const fooBad2 = ({a, b} = { a: 1, b: 2 }): number => {
|
||||
|
||||
|
||||
export function withAny(a: any = 1, b: string): void { }
|
||||
export function withAnyUnion(a: any | string = 1, b: string): void { }
|
||||
export function withAnyArrayUnion(a: any | number[] = [], b: string): void { }
|
||||
export function withAnyUndefinedUnion(a: any | undefined = 1, b: string): void { }
|
||||
type Foo = {};
|
||||
export function withAnyTypeReferenceUnion(a: any | Foo = {}, b: string): void { }
|
||||
export function withAnyTypeReferenceUndefinedUnion(a: any | Foo | undefined = {}, b: string): void { }
|
||||
export function withAnyTypeOperatorUnion(a: any | keyof {} = 1, b: string): void { }
|
||||
export function withAnyIntersectionTypeReferenceUnion(a: any | (Foo & {}) = {}, b: string): void { }
|
||||
export function withUnknown(a: unknown = 1, b: string): void { }
|
||||
export function withUnknownUnion(a: unknown | string = 1, b: string): void { }
|
||||
|
||||
export function withTypeAssertion(a = /regular-repression-cannot-infer/ as any): void { }
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export function ssrGetDynamicModelProps(
|
||||
existingProps: any = {},
|
||||
model: unknown,
|
||||
): { checked: true } | { value: any } | null {
|
||||
return {};
|
||||
}
|
||||
@@ -63,6 +63,14 @@ export declare class PrivateFieldsWithConstructorAssignments {
|
||||
private second;
|
||||
constructor(first: number);
|
||||
}
|
||||
export declare class DefaultedAnyParameterProperty {
|
||||
value: any;
|
||||
constructor(value: any | undefined, required: string);
|
||||
}
|
||||
export declare class DefaultedArrayParameterProperty {
|
||||
values: unknown;
|
||||
constructor(values: unknown | undefined, required: string);
|
||||
}
|
||||
export declare class PrivateMethodClass {
|
||||
private good;
|
||||
private get goodGetter();
|
||||
@@ -145,40 +153,48 @@ export declare class ProtectedConstructorWithPrivateParamPropertyInferredDefault
|
||||
|
||||
==================== Errors ====================
|
||||
|
||||
x TS9017: Only const arrays can be inferred with --isolatedDeclarations.
|
||||
,-[75:31]
|
||||
74 | export class DefaultedArrayParameterProperty {
|
||||
75 | constructor(public values = [], required: string) {}
|
||||
: ^^
|
||||
76 | }
|
||||
`----
|
||||
|
||||
x TS9038: Computed property names on class or object literals cannot be
|
||||
| inferred with --isolatedDeclarations.
|
||||
,-[81:14]
|
||||
80 | public get badGetter() {
|
||||
81 | return {[('x')]: 1};
|
||||
,-[89:14]
|
||||
88 | public get badGetter() {
|
||||
89 | return {[('x')]: 1};
|
||||
: ^^^^^
|
||||
82 | }
|
||||
90 | }
|
||||
`----
|
||||
|
||||
x TS9011: Parameter must have an explicit type annotation with
|
||||
| --isolatedDeclarations.
|
||||
,-[79:14]
|
||||
78 | export class PublicMethodClass {
|
||||
79 | public bad(a): void {}
|
||||
,-[87:14]
|
||||
86 | export class PublicMethodClass {
|
||||
87 | public bad(a): void {}
|
||||
: ^
|
||||
80 | public get badGetter() {
|
||||
88 | public get badGetter() {
|
||||
`----
|
||||
|
||||
x TS9011: Parameter must have an explicit type annotation with
|
||||
| --isolatedDeclarations.
|
||||
,-[192:3]
|
||||
191 | private constructor(
|
||||
192 | public readonly a = new InferredDefault(),
|
||||
,-[200:3]
|
||||
199 | private constructor(
|
||||
200 | public readonly a = new InferredDefault(),
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
193 | ) {}
|
||||
201 | ) {}
|
||||
`----
|
||||
|
||||
x TS9011: Parameter must have an explicit type annotation with
|
||||
| --isolatedDeclarations.
|
||||
,-[201:3]
|
||||
200 | protected constructor(
|
||||
201 | private readonly a = new InferredDefault(),
|
||||
,-[209:3]
|
||||
208 | protected constructor(
|
||||
209 | private readonly a = new InferredDefault(),
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
202 | ) {}
|
||||
210 | ) {}
|
||||
`----
|
||||
|
||||
|
||||
|
||||
@@ -15,14 +15,24 @@ export declare function fnDeclBad2<T>(p: T, r2: T): void;
|
||||
export declare function fnDeclBad3<T>(p: T, r2: T, rParam?: T): void;
|
||||
export declare function fooBad(): number;
|
||||
export declare const fooBad2: () => number;
|
||||
export declare function withAny(a: any, b: string): void;
|
||||
export declare function withUnknown(a: unknown, b: string): void;
|
||||
export declare function withAny(a: any | undefined, b: string): void;
|
||||
export declare function withAnyUnion(a: any | string | undefined, b: string): void;
|
||||
export declare function withAnyArrayUnion(a: any | number[] | undefined, b: string): void;
|
||||
export declare function withAnyUndefinedUnion(a: any | undefined, b: string): void;
|
||||
type Foo = {};
|
||||
export declare function withAnyTypeReferenceUnion(a: any | Foo, b: string): void;
|
||||
export declare function withAnyTypeReferenceUndefinedUnion(a: any | Foo | undefined, b: string): void;
|
||||
export declare function withAnyTypeOperatorUnion(a: any | keyof {}, b: string): void;
|
||||
export declare function withAnyIntersectionTypeReferenceUnion(a: any | (Foo & {}), b: string): void;
|
||||
export declare function withUnknown(a: unknown | undefined, b: string): void;
|
||||
export declare function withUnknownUnion(a: unknown | string | undefined, b: string): void;
|
||||
export declare function withTypeAssertion(a?: any): void;
|
||||
export declare function restWithDefault(...[value, options]: [string, object]): void;
|
||||
export declare function restWithNestedDefault(...[a, { b, c }]: [number, {
|
||||
b: number;
|
||||
c: number;
|
||||
}]): void;
|
||||
export {};
|
||||
|
||||
|
||||
==================== Errors ====================
|
||||
@@ -81,5 +91,32 @@ export declare function restWithNestedDefault(...[a, { b, c }]: [number, {
|
||||
27 | return 2;
|
||||
`----
|
||||
|
||||
x TS9025: Declaration emit for this parameter requires implicitly adding
|
||||
| undefined to it's type. This is not supported with --isolatedDeclarations.
|
||||
,-[36:43]
|
||||
35 | type Foo = {};
|
||||
36 | export function withAnyTypeReferenceUnion(a: any | Foo = {}, b: string): void { }
|
||||
: ^^^^^^^^^^^^^^^^^
|
||||
37 | export function withAnyTypeReferenceUndefinedUnion(a: any | Foo | undefined = {}, b: string): void { }
|
||||
`----
|
||||
|
||||
x TS9025: Declaration emit for this parameter requires implicitly adding
|
||||
| undefined to it's type. This is not supported with --isolatedDeclarations.
|
||||
,-[38:42]
|
||||
37 | export function withAnyTypeReferenceUndefinedUnion(a: any | Foo | undefined = {}, b: string): void { }
|
||||
38 | export function withAnyTypeOperatorUnion(a: any | keyof {} = 1, b: string): void { }
|
||||
: ^^^^^^^^^^^^^^^^^^^^^
|
||||
39 | export function withAnyIntersectionTypeReferenceUnion(a: any | (Foo & {}) = {}, b: string): void { }
|
||||
`----
|
||||
|
||||
x TS9025: Declaration emit for this parameter requires implicitly adding
|
||||
| undefined to it's type. This is not supported with --isolatedDeclarations.
|
||||
,-[39:55]
|
||||
38 | export function withAnyTypeOperatorUnion(a: any | keyof {} = 1, b: string): void { }
|
||||
39 | export function withAnyIntersectionTypeReferenceUnion(a: any | (Foo & {}) = {}, b: string): void { }
|
||||
: ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
40 | export function withUnknown(a: unknown = 1, b: string): void { }
|
||||
`----
|
||||
|
||||
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
source: crates/oxc_isolated_declarations/tests/mod.rs
|
||||
input_file: crates/oxc_isolated_declarations/tests/fixtures/issue-25290.ts
|
||||
---
|
||||
```
|
||||
==================== .D.TS ====================
|
||||
|
||||
export declare function ssrGetDynamicModelProps(existingProps: any | undefined, model: unknown): {
|
||||
checked: true;
|
||||
} | {
|
||||
value: any;
|
||||
} | null;
|
||||
Reference in new issue
Block a user