From 33e72d94911c4dc0b43cc14cd8ab2c3a9c429b30 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Tue, 26 Mar 2024 01:52:50 +0900 Subject: [PATCH 1/4] fix(eslint-plugin): [no-unnecessary-type-assertion] handle exactOptionalPropertyTypes compiler option --- .../rules/no-unnecessary-type-assertion.ts | 32 ++++++- .../no-unnecessary-type-assertion.test.ts | 86 +++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index fdb957b4602e..6984f54ecc7a 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -128,6 +128,36 @@ export default createRule({ ); } + function isTypeChanged(uncast: ts.Type, cast: ts.Type): boolean { + if (uncast === cast) { + return true; + } else if ( + isTypeFlagSet(uncast, ts.TypeFlags.Undefined) && + isTypeFlagSet(cast, ts.TypeFlags.Undefined) && + tsutils.isCompilerOptionEnabled( + compilerOptions, + 'exactOptionalPropertyTypes', + ) + ) { + const uncastParts = tsutils + .unionTypeParts(uncast) + .filter(part => !isTypeFlagSet(part, ts.TypeFlags.Undefined)); + + const castParts = tsutils + .unionTypeParts(cast) + .filter(part => !isTypeFlagSet(part, ts.TypeFlags.Undefined)); + + if (uncastParts.length !== castParts.length) { + return false; + } + + const castPartsSet = new Set(castParts); + uncastParts.forEach(part => castPartsSet.delete(part)); + return castPartsSet.size === 0; + } + return false; + } + return { TSNonNullExpression(node): void { if ( @@ -232,7 +262,7 @@ export default createRule({ const castType = services.getTypeAtLocation(node); const uncastType = services.getTypeAtLocation(node.expression); - const typeIsUnchanged = uncastType === castType; + const typeIsUnchanged = isTypeChanged(uncastType, castType); const wouldSameTypeBeInferred = castType.isLiteral() ? isLiteralVariableDeclarationChangingTypeWithConst(node) diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index a0dd0755239e..bc5cffee6d0d 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -19,6 +19,11 @@ const optionsWithOnUncheckedIndexedAccess = { project: './tsconfig.noUncheckedIndexedAccess.json', }; +const optionsWithExactOptionalPropertyTypes = { + tsconfigRootDir: rootDir, + project: './tsconfig.exactOptionalPropertyTypes.json', +}; + ruleTester.run('no-unnecessary-type-assertion', rule, { valid: [ ` @@ -287,6 +292,42 @@ const templateLiteral = \`\${myString}-somethingElse\` as const; const myString = 'foo'; const templateLiteral = \`\${myString}-somethingElse\`; `, + { + code: ` +declare const foo: { + a?: string; +}; +const bar = foo.a as string; + `, + parserOptions: optionsWithExactOptionalPropertyTypes, + }, + { + code: ` +declare const foo: { + a?: string | undefined; +}; +const bar = foo.a as string; + `, + parserOptions: optionsWithExactOptionalPropertyTypes, + }, + { + code: ` +declare const foo: { + a: string; +}; +const bar = foo.a as string | undefined; + `, + parserOptions: optionsWithExactOptionalPropertyTypes, + }, + { + code: ` +declare const foo: { + a: string | null | number; +}; +const bar = foo.a as string | undefined; + `, + parserOptions: optionsWithExactOptionalPropertyTypes, + }, ], invalid: [ @@ -934,5 +975,50 @@ function bar(items: string[]) { }, ], }, + // exactOptionalPropertyTypes = true + { + code: ` +declare const foo: { + a?: string; +}; +const bar = foo.a as string | undefined; + `, + output: ` +declare const foo: { + a?: string; +}; +const bar = foo.a; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 5, + column: 13, + }, + ], + parserOptions: optionsWithExactOptionalPropertyTypes, + }, + { + code: ` +declare const foo: { + a?: string | undefined; +}; +const bar = foo.a as string | undefined; + `, + output: ` +declare const foo: { + a?: string | undefined; +}; +const bar = foo.a; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 5, + column: 13, + }, + ], + parserOptions: optionsWithExactOptionalPropertyTypes, + }, ], }); From e702eb7cbc89784233ba92cc060ba46728cb29ad Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Tue, 26 Mar 2024 02:23:58 +0900 Subject: [PATCH 2/4] rename function --- .../eslint-plugin/src/rules/no-unnecessary-type-assertion.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 6984f54ecc7a..2d41e1df5879 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -128,7 +128,7 @@ export default createRule({ ); } - function isTypeChanged(uncast: ts.Type, cast: ts.Type): boolean { + function isTypeUnchanged(uncast: ts.Type, cast: ts.Type): boolean { if (uncast === cast) { return true; } else if ( @@ -262,7 +262,7 @@ export default createRule({ const castType = services.getTypeAtLocation(node); const uncastType = services.getTypeAtLocation(node.expression); - const typeIsUnchanged = isTypeChanged(uncastType, castType); + const typeIsUnchanged = isTypeUnchanged(uncastType, castType); const wouldSameTypeBeInferred = castType.isLiteral() ? isLiteralVariableDeclarationChangingTypeWithConst(node) From a48a580821f4282d31ce459ac5745c99f8525b41 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Tue, 26 Mar 2024 02:44:40 +0900 Subject: [PATCH 3/4] edit tc --- .../tests/rules/no-unnecessary-type-assertion.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index bc5cffee6d0d..bd65853c6d1c 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -322,7 +322,7 @@ const bar = foo.a as string | undefined; { code: ` declare const foo: { - a: string | null | number; + a?: string | null | number; }; const bar = foo.a as string | undefined; `, From 72a9a55a04dab5e35829a9dcb504440b6b21a6bb Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Wed, 27 Mar 2024 00:02:17 +0900 Subject: [PATCH 4/4] apply review --- .../src/rules/no-unnecessary-type-assertion.ts | 10 ++++++---- .../tests/rules/no-unnecessary-type-assertion.test.ts | 9 +++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 2d41e1df5879..a73962b9acbd 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -131,7 +131,9 @@ export default createRule({ function isTypeUnchanged(uncast: ts.Type, cast: ts.Type): boolean { if (uncast === cast) { return true; - } else if ( + } + + if ( isTypeFlagSet(uncast, ts.TypeFlags.Undefined) && isTypeFlagSet(cast, ts.TypeFlags.Undefined) && tsutils.isCompilerOptionEnabled( @@ -151,10 +153,10 @@ export default createRule({ return false; } - const castPartsSet = new Set(castParts); - uncastParts.forEach(part => castPartsSet.delete(part)); - return castPartsSet.size === 0; + const uncastPartsSet = new Set(uncastParts); + return castParts.every(part => uncastPartsSet.has(part)); } + return false; } diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index bd65853c6d1c..17efb3ff0baf 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -328,6 +328,15 @@ const bar = foo.a as string | undefined; `, parserOptions: optionsWithExactOptionalPropertyTypes, }, + { + code: ` +declare const foo: { + a?: string | number; +}; +const bar = foo.a as string | undefined | bigint; + `, + parserOptions: optionsWithExactOptionalPropertyTypes, + }, ], invalid: [ 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