From 974858a82236c3e6c5bb16759c4e0d969d9172ff Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Sun, 9 Jan 2022 19:07:35 +1300 Subject: [PATCH 1/9] fix(type-utils): make isTypeReadonly's options param optional fix #4410 --- packages/type-utils/src/isTypeReadonly.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index c6bc0f5761a4..7f689b5b7df1 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -243,7 +243,7 @@ function isTypeReadonlyRecurser( function isTypeReadonly( checker: ts.TypeChecker, type: ts.Type, - options: ReadonlynessOptions, + options: ReadonlynessOptions = readonlynessOptionsDefaults, ): boolean { return ( isTypeReadonlyRecurser(checker, type, options, new Set()) === From bb209f41e5961931fd27e5bc26b39136efeba13d Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Sun, 9 Jan 2022 19:59:56 +1300 Subject: [PATCH 2/9] test(type-utils): add basic tests for isTypeReadonly --- .../type-utils/tests/isTypeReadonly.test.ts | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 packages/type-utils/tests/isTypeReadonly.test.ts diff --git a/packages/type-utils/tests/isTypeReadonly.test.ts b/packages/type-utils/tests/isTypeReadonly.test.ts new file mode 100644 index 000000000000..5ecfc078f988 --- /dev/null +++ b/packages/type-utils/tests/isTypeReadonly.test.ts @@ -0,0 +1,138 @@ +import * as ts from 'typescript'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import { parseForESLint } from '@typescript-eslint/parser'; +import { + isTypeReadonly, + type ReadonlynessOptions, +} from '../src/isTypeReadonly'; +import path from 'path'; + +describe('isTypeReadonly', () => { + const rootDir = path.join(__dirname, 'fixtures'); + + describe('TSTypeAliasDeclaration ', () => { + function getType(code: string): { + type: ts.Type; + checker: ts.TypeChecker; + } { + const { ast, services } = parseForESLint(code, { + project: './tsconfig.json', + filePath: path.join(rootDir, 'file.ts'), + tsconfigRootDir: rootDir, + }); + const checker = services.program.getTypeChecker(); + const esTreeNodeToTSNodeMap = services.esTreeNodeToTSNodeMap; + + const declaration = ast.body[0] as TSESTree.TSTypeAliasDeclaration; + return { + type: checker.getTypeAtLocation( + esTreeNodeToTSNodeMap.get(declaration.id), + ), + checker, + }; + } + + function runTestForAliasDeclaration( + code: string, + options: ReadonlynessOptions | undefined, + expected: boolean, + ): void { + const { type, checker } = getType(code); + + const result = isTypeReadonly(checker, type, options); + expect(result).toBe(expected); + } + + describe('default options', () => { + const options = undefined; + + function runTestIsReadonly(code: string): void { + runTestForAliasDeclaration(code, options, true); + } + + function runTestIsNotReadonly(code: string): void { + runTestForAliasDeclaration(code, options, false); + } + + describe('basics', () => { + describe('is readonly', () => { + const runTests = runTestIsReadonly; + + // Record. + it.each([ + ['type Test = { readonly bar: string; };'], + ['type Test = Readonly<{ bar: string; }>;'], + ])('handles fully readonly records', runTests); + + // Array. + it.each([ + ['type Test = Readonly;'], + ['type Test = Readonly>;'], + ])('handles fully readonly arrays', runTests); + + // Array - special case. + // Note: Methods are mutable but arrays are treated special; hence no failure. + it.each([ + ['type Test = readonly string[];'], + ['type Test = ReadonlyArray;'], + ])('treats readonly arrays as fully readonly', runTests); + + // Set and Map. + it.each([ + ['type Test = Readonly>;'], + ['type Test = Readonly>;'], + ])('handles fully readonly sets and maps', runTests); + }); + + describe('is not readonly', () => { + const runTests = runTestIsNotReadonly; + + // Record. + it.each([ + ['type Test = { foo: string; };'], + ['type Test = { foo: string; readonly bar: number; };'], + ])('handles non fully readonly records', runTests); + + // Array. + it.each([['type Test = string[]'], ['type Test = Array']])( + 'handles non fully readonly arrays', + runTests, + ); + + // Set and Map. + // Note: Methods are mutable for ReadonlySet and ReadonlyMet; hence failure. + it.each([ + ['type Test = Set;'], + ['type Test = Map;'], + ['type Test = ReadonlySet;'], + ['type Test = ReadonlyMap;'], + ])('handles non fully readonly sets and maps', runTests); + }); + }); + }); + + describe('treatMethodsAsReadonly', () => { + const options: ReadonlynessOptions = { + treatMethodsAsReadonly: true, + }; + + function runTestIsReadonly(code: string): void { + runTestForAliasDeclaration(code, options, true); + } + + // function runTestIsNotReadonly(code: string): void { + // runTestForAliasDeclaration(code, options, false); + // } + + describe('is readonly', () => { + const runTests = runTestIsReadonly; + + // Set and Map. + it.each([ + ['type Test = ReadonlySet;'], + ['type Test = ReadonlyMap;'], + ])('handles non fully readonly sets and maps', runTests); + }); + }); + }); +}); From 468ac17c48b739f2c758a53f878f085d5deaf67a Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Sun, 9 Jan 2022 21:38:45 +1300 Subject: [PATCH 3/9] test: add union tests for isTypeReadonly --- .../type-utils/tests/isTypeReadonly.test.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/type-utils/tests/isTypeReadonly.test.ts b/packages/type-utils/tests/isTypeReadonly.test.ts index 5ecfc078f988..f2d277ff88e1 100644 --- a/packages/type-utils/tests/isTypeReadonly.test.ts +++ b/packages/type-utils/tests/isTypeReadonly.test.ts @@ -109,6 +109,33 @@ describe('isTypeReadonly', () => { ])('handles non fully readonly sets and maps', runTests); }); }); + + describe('Union', () => { + describe('is readonly', () => { + const runTests = runTestIsReadonly; + + it.each([ + [ + 'type Test = Readonly<{ foo: string; bar: number; }> & Readonly<{ bar: number; }>;', + ], + ['type Test = readonly string[] | readonly number[];'], + ])('handles a union of 2 fully readonly types', 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<{ foo: string; bar: number; }> | { bar: number; };', + ], + ])('handles a union of non fully readonly types', runTests); + }); + }); }); describe('treatMethodsAsReadonly', () => { From dfd5a248305cd499cec9e8d8c3bb43c364285270 Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Sun, 9 Jan 2022 21:39:32 +1300 Subject: [PATCH 4/9] fix(type-utils): union types always being marked as readonly fix #4418 --- packages/type-utils/src/isTypeReadonly.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index 7f689b5b7df1..7d24aabaa0bc 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -192,7 +192,8 @@ function isTypeReadonlyRecurser( const result = unionTypeParts(type).every( t => seenTypes.has(t) || - isTypeReadonlyRecurser(checker, t, options, seenTypes), + isTypeReadonlyRecurser(checker, t, options, seenTypes) === + Readonlyness.Readonly, ); const readonlyness = result ? Readonlyness.Readonly : Readonlyness.Mutable; return readonlyness; From 1983d528cf1500537cb5268a7d56600c989b877a Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Sun, 9 Jan 2022 23:28:52 +1300 Subject: [PATCH 5/9] test(type-utils): add conditional type tests to isTypeReadonly --- .../type-utils/tests/isTypeReadonly.test.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/type-utils/tests/isTypeReadonly.test.ts b/packages/type-utils/tests/isTypeReadonly.test.ts index 5ecfc078f988..e0c195b7ec42 100644 --- a/packages/type-utils/tests/isTypeReadonly.test.ts +++ b/packages/type-utils/tests/isTypeReadonly.test.ts @@ -109,6 +109,34 @@ describe('isTypeReadonly', () => { ])('handles non fully readonly sets and maps', 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', () => { From 2b0e454bc2009c9195190650f16fff23d6f1b8b5 Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Sun, 9 Jan 2022 23:30:14 +1300 Subject: [PATCH 6/9] fix(type-utils): isTypeReadonly now handles conditional types fix #4420 --- packages/type-utils/src/isTypeReadonly.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index 7f689b5b7df1..ed59e88a5a0f 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -1,5 +1,6 @@ import { ESLintUtils } from '@typescript-eslint/experimental-utils'; import { + isConditionalType, isObjectType, isUnionType, isUnionOrIntersectionType, @@ -198,6 +199,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)) { From 011647f1cfac6e00bc97f9d958aed6122aad1743 Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Tue, 11 Jan 2022 00:36:48 +1300 Subject: [PATCH 7/9] test(type-utils): add intersections tests for isTypeReadonly --- .../type-utils/tests/isTypeReadonly.test.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/type-utils/tests/isTypeReadonly.test.ts b/packages/type-utils/tests/isTypeReadonly.test.ts index 5ecfc078f988..6da69896af0e 100644 --- a/packages/type-utils/tests/isTypeReadonly.test.ts +++ b/packages/type-utils/tests/isTypeReadonly.test.ts @@ -109,6 +109,50 @@ describe('isTypeReadonly', () => { ])('handles non fully readonly sets and maps', runTests); }); }); + + 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('treatMethodsAsReadonly', () => { From ba0f0ac5c8d76ef2a37bf8ac9a009dad0522a02c Mon Sep 17 00:00:00 2001 From: Rebecca Stevens Date: Tue, 11 Jan 2022 01:30:51 +1300 Subject: [PATCH 8/9] fix(type-utils): intersection types involving readonly arrays are now handled in most cases fix #4428 --- packages/type-utils/src/isTypeReadonly.ts | 32 ++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index 7f689b5b7df1..d58bf82d3d80 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -2,10 +2,10 @@ import { ESLintUtils } from '@typescript-eslint/experimental-utils'; import { isObjectType, isUnionType, - isUnionOrIntersectionType, unionTypeParts, isPropertyReadonlyInType, isSymbolFlagSet, + isIntersectionType, } from 'tsutils'; import * as ts from 'typescript'; import { getTypeOfPropertyOfType } from './propertyTypes'; @@ -198,9 +198,35 @@ function isTypeReadonlyRecurser( return readonlyness; } - // all non-object, non-intersection types are readonly. + 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; + } + } + + // all non-object are readonly. // this should only be primitive types - if (!isObjectType(type) && !isUnionOrIntersectionType(type)) { + if (!isObjectType(type)) { return Readonlyness.Readonly; } From bd475fb1f886f42651e92f65f505504a1892e767 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 17 Jan 2022 03:19:06 -0800 Subject: [PATCH 9/9] Update isTypeReadonly.test.ts --- packages/type-utils/tests/isTypeReadonly.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/type-utils/tests/isTypeReadonly.test.ts b/packages/type-utils/tests/isTypeReadonly.test.ts index f72a245cc263..f6f2cebd12bc 100644 --- a/packages/type-utils/tests/isTypeReadonly.test.ts +++ b/packages/type-utils/tests/isTypeReadonly.test.ts @@ -266,4 +266,4 @@ describe('isTypeReadonly', () => { }); }); }); -}); \ No newline at end of file +}); 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