fix(isolated-declarations): support readonly parameter types (#27009)

Fixes #26944.

Optional parameter properties such as `constructor(readonly value?:
readonly string[])` currently produce TS9025 instead of adding `|
undefined`. Treat the `readonly` type operator as safe to widen,
matching the existing handling of array and tuple types. This also
handles readonly defaults followed by required parameters.

The change is limited to `readonly`; `keyof`, `unique`, unresolved
references, and the existing union-classification policy are unchanged.
Unions containing `any` or `unknown` can still emit a redundant `|
undefined`; this patch does not attempt full textual parity with
TypeScript. Add a diagnostic regression test and declaration snapshots
covering arrays, tuples, generic elements, parentheses,
unions/intersections, explicit undefined, and optional/default/rest
controls.

Validation:

- The new regression fails with 10 TS9025 diagnostics before the fix and
passes after it; all 21 isolated-declarations tests pass.
- The 14 declarations in the new fixture match TypeScript 7.0.2 with
`exactOptionalPropertyTypes` both enabled and disabled, after
normalizing formatting and redundant parentheses.
- Strict crate Clippy and formatting checks pass.
- The pinned transpiler suite remains at 20/21 on both baseline and
patch, with the identical pre-existing `declarationBasicSyntax.ts`
mismatch. Run with `--filter ''` to avoid updating the unrelated stale
snapshot revision.
- Full post-commit `just ready` passes: 4,979 tests passed, 161 ignored,
0 failed; generated files leave the working tree clean.

AI assistance: OpenAI Codex was used to investigate, implement, test,
and perform a separate automated review of this change.

---------

Co-authored-by: Pybsama <294532706+Pybsama@users.noreply.github.com>
Co-authored-by: Dunqing <dengqing0821@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-09-29 10:53:32 +08:00
1 parent 36e14df3ec
commit 4896287ad8
4 files changed
+105 -10

No files matched your search

@@ -335,6 +335,9 @@ fn can_add_undefined(ts_type: &TSType<'_>) -> bool {
| TSType::TSTypeLiteral(_)
| TSType::TSTemplateLiteralType(_)
| TSType::TSThisType(_) => true,
TSType::TSTypeOperatorType(operator) => {
operator.operator == TSTypeOperatorOperator::Readonly
}
TSType::TSParenthesizedType(parenthesized) => {
can_add_undefined(&parenthesized.type_annotation)
}
@@ -0,0 +1,52 @@
export class ReadonlyArrayProperty {
constructor(readonly value?: readonly string[]) {}
}
export class ReadonlyTupleProperty {
constructor(public value?: readonly [string, number?]) {}
}
export class GenericReadonlyProperty<T> {
constructor(readonly value?: readonly (T | undefined)[]) {}
}
export class ParenthesizedReadonlyProperty {
constructor(readonly value?: (readonly string[])) {}
}
export class ReadonlyUnionProperty {
constructor(readonly value?: string | readonly string[]) {}
}
export class ReadonlyIntersectionProperty {
constructor(readonly value?: (readonly string[]) & { length: number }) {}
}
export class ExplicitUndefinedReadonlyProperty {
constructor(readonly value?: readonly string[] | undefined) {}
}
export class DefaultReadonlyProperty {
constructor(readonly value: readonly string[] = [], required: string) {}
}
export function defaultReadonlyArray(
value: readonly string[] = [],
required: string,
): void {}
export function defaultReadonlyTuple(
value: readonly [string] = [""],
required: string,
): void {}
export function defaultGenericReadonlyArray<T>(
value: readonly T[] = [],
required: string,
): void {}
export function trailingReadonlyDefault(value: readonly string[] = []): void {}
export function optionalReadonlyArray(value?: readonly string[]): void {}
export function readonlyRest(...values: readonly string[]): void {}
@@ -10,7 +10,7 @@ interface Source {
}
export declare function mapped(value: { [K in keyof Source]: Source[K] }, required: string): void;
export declare function keyOf(value: keyof Source, required: string): void;
export declare function readonlyTuple(value: readonly [string], required: string): void;
export declare function readonlyTuple(value: readonly [string] | undefined, required: string): void;
export declare function intersection(value: {
optional?: string;
} & number[] | undefined, required: string): void;
@@ -46,15 +46,6 @@ export {};
12 | required: string,
`----
x TS9025: Declaration emit for this parameter requires implicitly adding
| undefined to it's type. This is not supported with --isolatedDeclarations.
,-[16:3]
15 | export function readonlyTuple(
16 | value: readonly [string] = [""],
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
17 | required: string,
`----
x TS9025: Declaration emit for this parameter requires implicitly adding
| undefined to it's type. This is not supported with --isolatedDeclarations.
,-[26:3]
@@ -0,0 +1,49 @@
---
source: crates/oxc_isolated_declarations/tests/mod.rs
input_file: crates/oxc_isolated_declarations/tests/fixtures/readonly-parameters.ts
---
```
==================== .D.TS ====================
export declare class ReadonlyArrayProperty {
readonly value?: readonly string[] | undefined;
constructor(value?: readonly string[] | undefined);
}
export declare class ReadonlyTupleProperty {
value?: readonly [string, number?] | undefined;
constructor(value?: readonly [string, number?] | undefined);
}
export declare class GenericReadonlyProperty<T> {
readonly value?: readonly (T | undefined)[] | undefined;
constructor(value?: readonly (T | undefined)[] | undefined);
}
export declare class ParenthesizedReadonlyProperty {
readonly value?: (readonly string[]) | undefined;
constructor(value?: (readonly string[]) | undefined);
}
export declare class ReadonlyUnionProperty {
readonly value?: string | readonly string[] | undefined;
constructor(value?: string | readonly string[] | undefined);
}
export declare class ReadonlyIntersectionProperty {
readonly value?: (readonly string[]) & {
length: number;
} | undefined;
constructor(value?: (readonly string[]) & {
length: number;
} | undefined);
}
export declare class ExplicitUndefinedReadonlyProperty {
readonly value?: readonly string[] | undefined;
constructor(value?: readonly string[] | undefined);
}
export declare class DefaultReadonlyProperty {
readonly value: readonly string[];
constructor(value: readonly string[] | undefined, required: string);
}
export declare function defaultReadonlyArray(value: readonly string[] | undefined, required: string): void;
export declare function defaultReadonlyTuple(value: readonly [string] | undefined, required: string): void;
export declare function defaultGenericReadonlyArray<T>(value: readonly T[] | undefined, required: string): void;
export declare function trailingReadonlyDefault(value?: readonly string[]): void;
export declare function optionalReadonlyArray(value?: readonly string[]): void;
export declare function readonlyRest(...values: readonly string[]): void;