diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index b8757b0a222c..0c5b30d83f16 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -1,5 +1,6 @@ import { ESLintUtils } from '@typescript-eslint/utils'; import { + isConditionalType, isObjectType, isUnionType, isUnionOrIntersectionType, @@ -223,6 +224,20 @@ function isTypeReadonlyRecurser( return readonlyness; } + if (isConditionalType(type)) { + const result = [type.root.node.trueType, type.root.node.falseType] + .map(checker.getTypeFromTypeNode) + .every( + t => + seenTypes.has(t) || + isTypeReadonlyRecurser(checker, t, options, seenTypes) === + Readonlyness.Readonly, + ); + + const readonlyness = result ? Readonlyness.Readonly : Readonlyness.Mutable; + return readonlyness; + } + // all non-object, non-intersection types are readonly. // this should only be primitive types if (!isObjectType(type) && !isUnionOrIntersectionType(type)) { diff --git a/packages/type-utils/tests/isTypeReadonly.test.ts b/packages/type-utils/tests/isTypeReadonly.test.ts index 6c39955b7ee9..0924bd6122f1 100644 --- a/packages/type-utils/tests/isTypeReadonly.test.ts +++ b/packages/type-utils/tests/isTypeReadonly.test.ts @@ -164,6 +164,38 @@ describe('isTypeReadonly', () => { ])('handles a union of non fully readonly types', runTests); }); }); + + describe('Conditional Types', () => { + describe('is readonly', () => { + const runTests = runTestIsReadonly; + + it.each([ + [ + 'type Test = T extends readonly number[] ? readonly string[] : readonly number[];', + ], + ])('handles conditional type that are fully readonly', runTests); + + it.each([ + [ + 'type Test = T extends number[] ? readonly string[] : readonly number[];', + ], + ])('should ignore mutable conditions', runTests); + }); + + describe('is not readonly', () => { + const runTests = runTestIsNotReadonly; + + it.each([ + ['type Test = T extends number[] ? string[] : number[];'], + [ + 'type Test = T extends number[] ? string[] : readonly number[];', + ], + [ + 'type Test = T extends number[] ? readonly string[] : number[];', + ], + ])('handles non fully readonly conditional types', runTests); + }); + }); }); describe('treatMethodsAsReadonly', () => {