diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index 0c5b30d83f16..0ec24438943e 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -3,10 +3,10 @@ import { isConditionalType, isObjectType, isUnionType, - isUnionOrIntersectionType, unionTypeParts, isPropertyReadonlyInType, isSymbolFlagSet, + isIntersectionType, } from 'tsutils'; import * as ts from 'typescript'; import { getTypeOfPropertyOfType } from './propertyTypes'; @@ -224,6 +224,32 @@ function isTypeReadonlyRecurser( return readonlyness; } + if (isIntersectionType(type)) { + // Special case for handling arrays/tuples (as readonly arrays/tuples always have mutable methods). + if ( + type.types.some(t => checker.isArrayType(t) || checker.isTupleType(t)) + ) { + const allReadonlyParts = type.types.every( + t => + seenTypes.has(t) || + isTypeReadonlyRecurser(checker, t, options, seenTypes) === + Readonlyness.Readonly, + ); + return allReadonlyParts ? Readonlyness.Readonly : Readonlyness.Mutable; + } + + // Normal case. + const isReadonlyObject = isTypeReadonlyObject( + checker, + type, + options, + seenTypes, + ); + if (isReadonlyObject !== Readonlyness.UnknownType) { + return isReadonlyObject; + } + } + if (isConditionalType(type)) { const result = [type.root.node.trueType, type.root.node.falseType] .map(checker.getTypeFromTypeNode) @@ -240,7 +266,7 @@ function isTypeReadonlyRecurser( // all non-object, non-intersection types are readonly. // this should only be primitive types - if (!isObjectType(type) && !isUnionOrIntersectionType(type)) { + if (!isObjectType(type)) { return Readonlyness.Readonly; } diff --git a/packages/type-utils/tests/isTypeReadonly.test.ts b/packages/type-utils/tests/isTypeReadonly.test.ts index 0924bd6122f1..f6f2cebd12bc 100644 --- a/packages/type-utils/tests/isTypeReadonly.test.ts +++ b/packages/type-utils/tests/isTypeReadonly.test.ts @@ -165,6 +165,50 @@ describe('isTypeReadonly', () => { }); }); + describe('Intersection', () => { + describe('is readonly', () => { + const runTests = runTestIsReadonly; + + it.each([ + [ + 'type Test = Readonly<{ foo: string; bar: number; }> & Readonly<{ bar: number; }>;', + ], + ])('handles an intersection of 2 fully readonly types', runTests); + + it.each([ + [ + 'type Test = Readonly<{ foo: string; bar: number; }> & { foo: string; };', + ], + ])( + 'handles an intersection of a fully readonly type with a mutable subtype', + runTests, + ); + + // Array - special case. + // Note: Methods are mutable but arrays are treated special; hence no failure. + it.each([ + ['type Test = ReadonlyArray & Readonly<{ foo: string; }>;'], + [ + 'type Test = readonly [string, number] & Readonly<{ foo: string; }>;', + ], + ])('handles an intersections involving a readonly array', runTests); + }); + + describe('is not readonly', () => { + const runTests = runTestIsNotReadonly; + + it.each([ + ['type Test = { foo: string; bar: number; } & { bar: number; };'], + [ + 'type Test = { foo: string; bar: number; } & Readonly<{ bar: number; }>;', + ], + [ + 'type Test = Readonly<{ bar: number; }> & { foo: string; bar: number; };', + ], + ])('handles an intersection of non fully readonly types', runTests); + }); + }); + describe('Conditional Types', () => { describe('is readonly', () => { const runTests = runTestIsReadonly; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy