From e4b62276d290f17711fc832414863fde39460922 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sun, 29 Dec 2024 11:10:40 +0900 Subject: [PATCH 01/11] feat(eslint-plugin): [consistent-type-assertions] add arrayLiteralTypeAssertions options --- .../src/rules/consistent-type-assertions.ts | 153 +++-- .../rules/consistent-type-assertions.test.ts | 546 ++++++++++++++++++ 2 files changed, 661 insertions(+), 38 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts index 8713405115d9..3312541aa1ca 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts @@ -18,13 +18,17 @@ export type MessageIds = | 'angle-bracket' | 'as' | 'never' + | 'replaceArrayTypeAssertionWithAnnotation' + | 'replaceArrayTypeAssertionWithSatisfies' | 'replaceObjectTypeAssertionWithAnnotation' | 'replaceObjectTypeAssertionWithSatisfies' + | 'unexpectedArrayTypeAssertion' | 'unexpectedObjectTypeAssertion'; type OptUnion = | { assertionStyle: 'angle-bracket' | 'as'; objectLiteralTypeAssertions?: 'allow' | 'allow-as-parameter' | 'never'; + arrayLiteralTypeAssertions?: 'allow' | 'allow-as-parameter' | 'never'; } | { assertionStyle: 'never'; @@ -45,10 +49,15 @@ export default createRule({ 'angle-bracket': "Use '<{{cast}}>' instead of 'as {{cast}}'.", as: "Use 'as {{cast}}' instead of '<{{cast}}>'.", never: 'Do not use any type assertions.', + replaceArrayTypeAssertionWithAnnotation: + 'Use const x: [{cast}] = [ ... ] instead.', + replaceArrayTypeAssertionWithSatisfies: + 'Use const x = [ ... ] satisfies [{cast}] instead.', replaceObjectTypeAssertionWithAnnotation: 'Use const x: {{cast}} = { ... } instead.', replaceObjectTypeAssertionWithSatisfies: 'Use const x = { ... } satisfies {{cast}} instead.', + unexpectedArrayTypeAssertion: 'Always prefer const x: T[] = [ ... ].', unexpectedObjectTypeAssertion: 'Always prefer const x: T = { ... }.', }, schema: [ @@ -70,6 +79,11 @@ export default createRule({ type: 'object', additionalProperties: false, properties: { + arrayLiteralTypeAssertions: { + type: 'string', + description: 'TBD', + enum: ['allow', 'allow-as-parameter', 'never'], + }, assertionStyle: { type: 'string', description: 'The expected assertion style to enforce.', @@ -89,6 +103,7 @@ export default createRule({ }, defaultOptions: [ { + arrayLiteralTypeAssertions: 'allow', assertionStyle: 'as', objectLiteralTypeAssertions: 'allow', }, @@ -192,7 +207,64 @@ export default createRule({ } } - function checkExpression( + function getSuggests( + node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, + annotationMessageId: MessageIds, + satisfiesMessageId: MessageIds, + ): TSESLint.ReportSuggestionArray { + const suggest: TSESLint.ReportSuggestionArray = []; + if ( + node.parent.type === AST_NODE_TYPES.VariableDeclarator && + !node.parent.id.typeAnnotation + ) { + const { parent } = node; + suggest.push({ + messageId: annotationMessageId, + data: { cast: context.sourceCode.getText(node.typeAnnotation) }, + fix: fixer => [ + fixer.insertTextAfter( + parent.id, + `: ${context.sourceCode.getText(node.typeAnnotation)}`, + ), + fixer.replaceText( + node, + getTextWithParentheses(context.sourceCode, node.expression), + ), + ], + }); + } + suggest.push({ + messageId: satisfiesMessageId, + data: { cast: context.sourceCode.getText(node.typeAnnotation) }, + fix: fixer => [ + fixer.replaceText( + node, + getTextWithParentheses(context.sourceCode, node.expression), + ), + fixer.insertTextAfter( + node, + ` satisfies ${context.sourceCode.getText(node.typeAnnotation)}`, + ), + ], + }); + return suggest; + } + + function isAsParameter( + node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, + ): boolean { + return ( + node.parent.type === AST_NODE_TYPES.NewExpression || + node.parent.type === AST_NODE_TYPES.CallExpression || + node.parent.type === AST_NODE_TYPES.ThrowStatement || + node.parent.type === AST_NODE_TYPES.AssignmentPattern || + node.parent.type === AST_NODE_TYPES.JSXExpressionContainer || + (node.parent.type === AST_NODE_TYPES.TemplateLiteral && + node.parent.parent.type === AST_NODE_TYPES.TaggedTemplateExpression) + ); + } + + function checkExpressionForObjectAssertion( node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, ): void { if ( @@ -218,41 +290,11 @@ export default createRule({ } if (checkType(node.typeAnnotation)) { - const suggest: TSESLint.ReportSuggestionArray = []; - if ( - node.parent.type === AST_NODE_TYPES.VariableDeclarator && - !node.parent.id.typeAnnotation - ) { - const { parent } = node; - suggest.push({ - messageId: 'replaceObjectTypeAssertionWithAnnotation', - data: { cast: context.sourceCode.getText(node.typeAnnotation) }, - fix: fixer => [ - fixer.insertTextAfter( - parent.id, - `: ${context.sourceCode.getText(node.typeAnnotation)}`, - ), - fixer.replaceText( - node, - getTextWithParentheses(context.sourceCode, node.expression), - ), - ], - }); - } - suggest.push({ - messageId: 'replaceObjectTypeAssertionWithSatisfies', - data: { cast: context.sourceCode.getText(node.typeAnnotation) }, - fix: fixer => [ - fixer.replaceText( - node, - getTextWithParentheses(context.sourceCode, node.expression), - ), - fixer.insertTextAfter( - node, - ` satisfies ${context.sourceCode.getText(node.typeAnnotation)}`, - ), - ], - }); + const suggest = getSuggests( + node, + 'replaceObjectTypeAssertionWithAnnotation', + 'replaceObjectTypeAssertionWithSatisfies', + ); context.report({ node, @@ -262,6 +304,39 @@ export default createRule({ } } + function checkExpressionForArrayAssertion( + node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, + ): void { + if ( + options.assertionStyle === 'never' || + options.arrayLiteralTypeAssertions === 'allow' || + node.expression.type !== AST_NODE_TYPES.ArrayExpression + ) { + return; + } + + if ( + options.arrayLiteralTypeAssertions === 'allow-as-parameter' && + isAsParameter(node) + ) { + return; + } + + if (checkType(node.typeAnnotation)) { + const suggest = getSuggests( + node, + 'replaceArrayTypeAssertionWithAnnotation', + 'replaceArrayTypeAssertionWithSatisfies', + ); + + context.report({ + node, + messageId: 'unexpectedArrayTypeAssertion', + suggest, + }); + } + } + return { TSAsExpression(node): void { if (options.assertionStyle !== 'as') { @@ -269,7 +344,8 @@ export default createRule({ return; } - checkExpression(node); + checkExpressionForObjectAssertion(node); + checkExpressionForArrayAssertion(node); }, TSTypeAssertion(node): void { if (options.assertionStyle !== 'angle-bracket') { @@ -277,7 +353,8 @@ export default createRule({ return; } - checkExpression(node); + checkExpressionForObjectAssertion(node); + checkExpressionForArrayAssertion(node); }, }; }, diff --git a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts index efd24bd7c4ba..5d5b2e2ae283 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -85,6 +85,38 @@ print?.({ bar: 5 }) print?.call({ bar: 5 }) print\`\${{ bar: 5 }}\` `; +const ARRAY_LITERAL_AS_CASTS = ` +const x = [] as string[]; +const x = ['a'] as string[]; +const x = [] as Array; +const x = ['a'] as Array; +const x = [Math.random() ? 'a' : 'b'] as 'a'[]; +`; +const ARRAY_LITERAL_ANGLE_BRACKET_CASTS = ` +const x = []; +const x = ['a']; +const x = >[]; +const x = >['a']; +const x = <'a'[]>[Math.random() ? 'a' : 'b']; +`; +const ARRAY_LITERAL_ARGUMENT_AS_CASTS = ` +print([5] as Foo); +new print([5] as Foo); +function foo() { throw [5] as Foo } +function b(x = [5] as Foo.Bar) {} +function c(x = [5] as Foo) {} +print?.([5] as Foo); +print?.call([5] as Foo); +print\`\${[5] as Foo}\`; +`; +const ARRAY_LITERAL_ARGUMENT_BRACKET_CASTS = ` +print([5]); +new print([5]); +function foo() { throw [5] } +print?.([5]); +print?.call([5]); +print\`\${[5]}\`; +`; ruleTester.run('consistent-type-assertions', rule, { valid: [ @@ -136,6 +168,40 @@ ruleTester.run('consistent-type-assertions', rule, { }, ], }), + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_AS_CASTS, + options: [ + { + assertionStyle: 'as', + }, + ], + }), + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, + options: [ + { + assertionStyle: 'angle-bracket', + }, + ], + }), + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_ARGUMENT_AS_CASTS, + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'as', + }, + ], + }), + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_ARGUMENT_BRACKET_CASTS, + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'angle-bracket', + }, + ], + }), { code: 'const x = [1];', options: [{ assertionStyle: 'never' }] }, { code: 'const x = [1] as const;', options: [{ assertionStyle: 'never' }] }, { @@ -671,5 +737,485 @@ const bs = (x <<= y) as any; ], output: 'const ternary = (true ? x : y) as any;', }, + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_AS_CASTS, + errors: [ + { + line: 2, + messageId: 'never', + }, + { + line: 3, + messageId: 'never', + }, + { + line: 4, + messageId: 'never', + }, + { + line: 5, + messageId: 'never', + }, + { + line: 6, + messageId: 'never', + }, + ], + options: [ + { + assertionStyle: 'never', + }, + ], + }), + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, + errors: [ + { + line: 2, + messageId: 'never', + }, + { + line: 3, + messageId: 'never', + }, + { + line: 4, + messageId: 'never', + }, + { + line: 5, + messageId: 'never', + }, + { + line: 6, + messageId: 'never', + }, + ], + options: [ + { + assertionStyle: 'never', + }, + ], + }), + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_AS_CASTS, + errors: [ + { + line: 2, + messageId: 'angle-bracket', + }, + { + line: 3, + messageId: 'angle-bracket', + }, + { + line: 4, + messageId: 'angle-bracket', + }, + { + line: 5, + messageId: 'angle-bracket', + }, + { + line: 6, + messageId: 'angle-bracket', + }, + ], + options: [ + { + assertionStyle: 'angle-bracket', + }, + ], + output: null, + }), + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, + errors: [ + { + line: 2, + messageId: 'as', + }, + { + line: 3, + messageId: 'as', + }, + { + line: 4, + messageId: 'as', + }, + { + line: 5, + messageId: 'as', + }, + { + line: 6, + messageId: 'as', + }, + ], + options: [ + { + assertionStyle: 'as', + }, + ], + output: ARRAY_LITERAL_AS_CASTS, + }), + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_AS_CASTS, + errors: [ + { + line: 2, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithAnnotation', + output: 'const x: string[] = [];', + }, + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: 'const x = [] satisfies string[];', + }, + ], + }, + { + line: 3, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithAnnotation', + output: `const x: string[] = ['a'];`, + }, + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `const x = ['a'] satisfies string[];`, + }, + ], + }, + { + line: 4, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithAnnotation', + output: 'const x: Array = [];', + }, + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: 'const x = [] satisfies Array;', + }, + ], + }, + { + line: 5, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithAnnotation', + output: `const x: Array = ['a'];`, + }, + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `const x = ['a'] satisfies Array;`, + }, + ], + }, + { + line: 6, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithAnnotation', + output: `const x: 'a'[] = [Math.random() ? 'a' : 'b'];`, + }, + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `const x = [Math.random() ? 'a' : 'b'] satisfies 'a'[];`, + }, + ], + }, + ], + options: [ + { + arrayLiteralTypeAssertions: 'never', + assertionStyle: 'as', + }, + ], + }), + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, + errors: [ + { + line: 2, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithAnnotation', + output: 'const x: string[] = [];', + }, + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: 'const x = [] satisfies string[];', + }, + ], + }, + { + line: 3, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithAnnotation', + output: `const x: string[] = ['a'];`, + }, + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `const x = ['a'] satisfies string[];`, + }, + ], + }, + { + line: 4, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithAnnotation', + output: 'const x: Array = [];', + }, + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: 'const x = [] satisfies Array;', + }, + ], + }, + { + line: 5, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithAnnotation', + output: `const x: Array = ['a'];`, + }, + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `const x = ['a'] satisfies Array;`, + }, + ], + }, + { + line: 6, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithAnnotation', + output: `const x: 'a'[] = [Math.random() ? 'a' : 'b'];`, + }, + { + data: { cast: 'string' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `const x = [Math.random() ? 'a' : 'b'] satisfies 'a'[];`, + }, + ], + }, + ], + options: [ + { + arrayLiteralTypeAssertions: 'never', + assertionStyle: 'angle-bracket', + }, + ], + }), + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_ARGUMENT_AS_CASTS, + errors: [ + { + line: 2, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `print([5] satisfies Foo);`, + }, + ], + }, + { + line: 3, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `new print([5] satisfies Foo);`, + }, + ], + }, + { + line: 4, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `function foo() { throw [5] satisfies Foo }`, + }, + ], + }, + { + line: 5, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `function b(x = [5] satisfies Foo.Bar) {}`, + }, + ], + }, + { + line: 6, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `function c(x = [5] satisfies Foo) {}`, + }, + ], + }, + { + line: 7, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `print?.([5] satisfies Foo);`, + }, + ], + }, + { + line: 8, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `print?.call([5] satisfies Foo);`, + }, + ], + }, + { + line: 9, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `print\`\${[5] satisfies Foo}\`;`, + }, + ], + }, + ], + options: [ + { + arrayLiteralTypeAssertions: 'never', + assertionStyle: 'as', + }, + ], + }), + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_ARGUMENT_BRACKET_CASTS, + errors: [ + { + line: 2, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `print([5] satisfies Foo);`, + }, + ], + }, + { + line: 3, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `new print([5] satisfies Foo);`, + }, + ], + }, + { + line: 4, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `function foo() { throw [5] satisfies Foo }`, + }, + ], + }, + { + line: 5, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `print?.([5] satisfies Foo);`, + }, + ], + }, + { + line: 6, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `print?.call([5] satisfies Foo);`, + }, + ], + }, + { + line: 7, + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: `print\`\${[5] satisfies Foo}\`;`, + }, + ], + }, + ], + options: [ + { + arrayLiteralTypeAssertions: 'never', + assertionStyle: 'angle-bracket', + }, + ], + }), ], }); From 9dfe2e889bed7448b2e01b396aa8b2ab1194a420 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sun, 29 Dec 2024 16:04:42 +0900 Subject: [PATCH 02/11] add docs --- .../docs/rules/consistent-type-assertions.mdx | 68 +++++++++++++++++++ .../src/rules/consistent-type-assertions.ts | 3 +- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx b/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx index 0b13d9df2677..37aba4cada8c 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx +++ b/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx @@ -115,6 +115,74 @@ const foo = ; +### `arrayLiteralTypeAssertions` + +Always prefer `const x: T[] = [ ... ];` to `const x = [ ... ] as T[];` (or similar with angle brackets). The rationale for this is exactly the same as for `objectLiteralTypeAssertions`. + +The const assertion `const x = [1, 2, 3] as const`, introduced in TypeScript 3.4, is considered beneficial and is ignored by this option. + +Assertions to `any` are also ignored by this option. + +Examples of code for `{ assertionStyle: 'as', arrayLiteralTypeAssertions: 'never' }`: + + + + +```ts option='{ "assertionStyle": "as", "arrayLiteralTypeAssertions": "never" }' +const x = ['foo'] as T; + +function bar() { + return ['foo'] as T; +} +``` + + + + +```ts option='{ "assertionStyle": "as", "arrayLiteralTypeAssertions": "never" }' +const x: T = ['foo']; +const y = ['foo'] as any; +const z = ['foo'] as unknown; + +function bar(): T { + return ['foo']; +} +``` + + + + +Examples of code for `{ assertionStyle: 'as', arrayLiteralTypeAssertions: 'allow-as-parameter' }`: + + + + +```ts option='{ "assertionStyle": "as", "arrayLiteralTypeAssertions": "allow-as-parameter" }' +const x = ['foo'] as T; + +function bar() { + return ['foo'] as T; +} +``` + + + + +```tsx option='{ "assertionStyle": "as", "arrayLiteralTypeAssertions": "allow-as-parameter" }' +const x: T = ['foo']; +const y = ['foo'] as any; +const z = ['foo'] as unknown; +bar(['foo'] as T); +new Clazz(['foo'] as T); +function bar() { + throw ['foo'] as Foo; +} +const foo = ; +``` + + + + ## When Not To Use It If you do not want to enforce consistent type assertions. diff --git a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts index 3312541aa1ca..12d15552cb2a 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts @@ -81,7 +81,8 @@ export default createRule({ properties: { arrayLiteralTypeAssertions: { type: 'string', - description: 'TBD', + description: + 'Whether to always prefer type declarations for array literals used as variable initializers, rather than type assertions.', enum: ['allow', 'allow-as-parameter', 'never'], }, assertionStyle: { From 880789a9005f7c941311b509176150a2e6931ed2 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sun, 29 Dec 2024 16:17:29 +0900 Subject: [PATCH 03/11] Update consistent-type-assertions.mdx --- .../eslint-plugin/docs/rules/consistent-type-assertions.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx b/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx index 37aba4cada8c..b423f614b4cd 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx +++ b/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx @@ -117,6 +117,8 @@ const foo = ; ### `arrayLiteralTypeAssertions` +{/* insert option description */} + Always prefer `const x: T[] = [ ... ];` to `const x = [ ... ] as T[];` (or similar with angle brackets). The rationale for this is exactly the same as for `objectLiteralTypeAssertions`. The const assertion `const x = [1, 2, 3] as const`, introduced in TypeScript 3.4, is considered beneficial and is ignored by this option. From 8fcf9816ecc9d3a0791912f206153a2d06b42d25 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sun, 29 Dec 2024 18:43:29 +0900 Subject: [PATCH 04/11] update snapshot --- .../consistent-type-assertions.shot | 58 +++++++++++++++++++ .../consistent-type-assertions.shot | 11 ++++ 2 files changed, 69 insertions(+) diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/consistent-type-assertions.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/consistent-type-assertions.shot index 755dc6eb472d..7289fa881592 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/consistent-type-assertions.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/consistent-type-assertions.shot @@ -57,3 +57,61 @@ function bar() { const foo = ; " `; + +exports[`Validating rule docs consistent-type-assertions.mdx code examples ESLint output 5`] = ` +"Incorrect +Options: { "assertionStyle": "as", "arrayLiteralTypeAssertions": "never" } + +const x = ['foo'] as T; + ~~~~~~~~~~~~ Always prefer const x: T[] = [ ... ]. + +function bar() { + return ['foo'] as T; + ~~~~~~~~~~~~ Always prefer const x: T[] = [ ... ]. +} +" +`; + +exports[`Validating rule docs consistent-type-assertions.mdx code examples ESLint output 6`] = ` +"Correct +Options: { "assertionStyle": "as", "arrayLiteralTypeAssertions": "never" } + +const x: T = ['foo']; +const y = ['foo'] as any; +const z = ['foo'] as unknown; + +function bar(): T { + return ['foo']; +} +" +`; + +exports[`Validating rule docs consistent-type-assertions.mdx code examples ESLint output 7`] = ` +"Incorrect +Options: { "assertionStyle": "as", "arrayLiteralTypeAssertions": "allow-as-parameter" } + +const x = ['foo'] as T; + ~~~~~~~~~~~~ Always prefer const x: T[] = [ ... ]. + +function bar() { + return ['foo'] as T; + ~~~~~~~~~~~~ Always prefer const x: T[] = [ ... ]. +} +" +`; + +exports[`Validating rule docs consistent-type-assertions.mdx code examples ESLint output 8`] = ` +"Correct +Options: { "assertionStyle": "as", "arrayLiteralTypeAssertions": "allow-as-parameter" } + +const x: T = ['foo']; +const y = ['foo'] as any; +const z = ['foo'] as unknown; +bar(['foo'] as T); +new Clazz(['foo'] as T); +function bar() { + throw ['foo'] as Foo; +} +const foo = ; +" +`; diff --git a/packages/eslint-plugin/tests/schema-snapshots/consistent-type-assertions.shot b/packages/eslint-plugin/tests/schema-snapshots/consistent-type-assertions.shot index 7845d5b791bb..a999506e695e 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/consistent-type-assertions.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/consistent-type-assertions.shot @@ -22,6 +22,11 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos { "additionalProperties": false, "properties": { + "arrayLiteralTypeAssertions": { + "description": "Whether to always prefer type declarations for array literals used as variable initializers, rather than type assertions.", + "enum": ["allow", "allow-as-parameter", "never"], + "type": "string" + }, "assertionStyle": { "description": "The expected assertion style to enforce.", "enum": ["angle-bracket", "as"], @@ -49,6 +54,12 @@ type Options = [ 'never'; } | { + /** Whether to always prefer type declarations for array literals used as variable initializers, rather than type assertions. */ + arrayLiteralTypeAssertions?: + | 'allow-as-parameter' + | 'never' + /** Whether to always prefer type declarations for array literals used as variable initializers, rather than type assertions. */ + | 'allow'; /** The expected assertion style to enforce. */ assertionStyle?: | 'as' From 7d377d192595c98c7570e35fa60683dcdc3a737f Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Mon, 30 Dec 2024 19:34:02 +0900 Subject: [PATCH 05/11] fix wrong data format --- .../eslint-plugin/src/rules/consistent-type-assertions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts index 12d15552cb2a..49470c64c826 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts @@ -50,9 +50,9 @@ export default createRule({ as: "Use 'as {{cast}}' instead of '<{{cast}}>'.", never: 'Do not use any type assertions.', replaceArrayTypeAssertionWithAnnotation: - 'Use const x: [{cast}] = [ ... ] instead.', + 'Use const x: {{cast}} = [ ... ] instead.', replaceArrayTypeAssertionWithSatisfies: - 'Use const x = [ ... ] satisfies [{cast}] instead.', + 'Use const x = [ ... ] satisfies {{cast}} instead.', replaceObjectTypeAssertionWithAnnotation: 'Use const x: {{cast}} = { ... } instead.', replaceObjectTypeAssertionWithSatisfies: From ee8a2ef0d5dbe9168dc2ba321f520a8e0ddca590 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Mon, 30 Dec 2024 20:27:19 +0900 Subject: [PATCH 06/11] fix --- .../rules/consistent-type-assertions.test.ts | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts index 5d5b2e2ae283..13d6dcfb3a35 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -867,12 +867,12 @@ const bs = (x <<= y) as any; messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { - data: { cast: 'string' }, + data: { cast: 'string[]' }, messageId: 'replaceArrayTypeAssertionWithAnnotation', output: 'const x: string[] = [];', }, { - data: { cast: 'string' }, + data: { cast: 'string[]' }, messageId: 'replaceArrayTypeAssertionWithSatisfies', output: 'const x = [] satisfies string[];', }, @@ -883,12 +883,12 @@ const bs = (x <<= y) as any; messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { - data: { cast: 'string' }, + data: { cast: 'string[]' }, messageId: 'replaceArrayTypeAssertionWithAnnotation', output: `const x: string[] = ['a'];`, }, { - data: { cast: 'string' }, + data: { cast: 'string[]' }, messageId: 'replaceArrayTypeAssertionWithSatisfies', output: `const x = ['a'] satisfies string[];`, }, @@ -899,12 +899,12 @@ const bs = (x <<= y) as any; messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { - data: { cast: 'string' }, + data: { cast: 'Array' }, messageId: 'replaceArrayTypeAssertionWithAnnotation', output: 'const x: Array = [];', }, { - data: { cast: 'string' }, + data: { cast: 'Array' }, messageId: 'replaceArrayTypeAssertionWithSatisfies', output: 'const x = [] satisfies Array;', }, @@ -915,12 +915,12 @@ const bs = (x <<= y) as any; messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { - data: { cast: 'string' }, + data: { cast: 'Array' }, messageId: 'replaceArrayTypeAssertionWithAnnotation', output: `const x: Array = ['a'];`, }, { - data: { cast: 'string' }, + data: { cast: 'Array' }, messageId: 'replaceArrayTypeAssertionWithSatisfies', output: `const x = ['a'] satisfies Array;`, }, @@ -931,12 +931,12 @@ const bs = (x <<= y) as any; messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { - data: { cast: 'string' }, + data: { cast: "'a'[]" }, messageId: 'replaceArrayTypeAssertionWithAnnotation', output: `const x: 'a'[] = [Math.random() ? 'a' : 'b'];`, }, { - data: { cast: 'string' }, + data: { cast: "'a'[]" }, messageId: 'replaceArrayTypeAssertionWithSatisfies', output: `const x = [Math.random() ? 'a' : 'b'] satisfies 'a'[];`, }, @@ -958,12 +958,12 @@ const bs = (x <<= y) as any; messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { - data: { cast: 'string' }, + data: { cast: 'string[]' }, messageId: 'replaceArrayTypeAssertionWithAnnotation', output: 'const x: string[] = [];', }, { - data: { cast: 'string' }, + data: { cast: 'string[]' }, messageId: 'replaceArrayTypeAssertionWithSatisfies', output: 'const x = [] satisfies string[];', }, @@ -974,12 +974,12 @@ const bs = (x <<= y) as any; messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { - data: { cast: 'string' }, + data: { cast: 'string[]' }, messageId: 'replaceArrayTypeAssertionWithAnnotation', output: `const x: string[] = ['a'];`, }, { - data: { cast: 'string' }, + data: { cast: 'string[]' }, messageId: 'replaceArrayTypeAssertionWithSatisfies', output: `const x = ['a'] satisfies string[];`, }, @@ -990,12 +990,12 @@ const bs = (x <<= y) as any; messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { - data: { cast: 'string' }, + data: { cast: 'Array' }, messageId: 'replaceArrayTypeAssertionWithAnnotation', output: 'const x: Array = [];', }, { - data: { cast: 'string' }, + data: { cast: 'Array' }, messageId: 'replaceArrayTypeAssertionWithSatisfies', output: 'const x = [] satisfies Array;', }, @@ -1006,12 +1006,12 @@ const bs = (x <<= y) as any; messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { - data: { cast: 'string' }, + data: { cast: 'Array' }, messageId: 'replaceArrayTypeAssertionWithAnnotation', output: `const x: Array = ['a'];`, }, { - data: { cast: 'string' }, + data: { cast: 'Array' }, messageId: 'replaceArrayTypeAssertionWithSatisfies', output: `const x = ['a'] satisfies Array;`, }, @@ -1022,12 +1022,12 @@ const bs = (x <<= y) as any; messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { - data: { cast: 'string' }, + data: { cast: "'a'[]" }, messageId: 'replaceArrayTypeAssertionWithAnnotation', output: `const x: 'a'[] = [Math.random() ? 'a' : 'b'];`, }, { - data: { cast: 'string' }, + data: { cast: "'a'[]" }, messageId: 'replaceArrayTypeAssertionWithSatisfies', output: `const x = [Math.random() ? 'a' : 'b'] satisfies 'a'[];`, }, @@ -1082,7 +1082,7 @@ const bs = (x <<= y) as any; messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { - data: { cast: 'Foo' }, + data: { cast: 'Foo.Bar' }, messageId: 'replaceArrayTypeAssertionWithSatisfies', output: `function b(x = [5] satisfies Foo.Bar) {}`, }, From 0af07a9eaf28974472ac6fdaf8738e3dbfda8719 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Tue, 31 Dec 2024 14:17:35 +0900 Subject: [PATCH 07/11] review - rename getSuggests -> getSuggestions --- .../src/rules/consistent-type-assertions.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts index 49470c64c826..1a33f2e46729 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts @@ -208,18 +208,18 @@ export default createRule({ } } - function getSuggests( + function getSuggestions( node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, annotationMessageId: MessageIds, satisfiesMessageId: MessageIds, ): TSESLint.ReportSuggestionArray { - const suggest: TSESLint.ReportSuggestionArray = []; + const suggestions: TSESLint.ReportSuggestionArray = []; if ( node.parent.type === AST_NODE_TYPES.VariableDeclarator && !node.parent.id.typeAnnotation ) { const { parent } = node; - suggest.push({ + suggestions.push({ messageId: annotationMessageId, data: { cast: context.sourceCode.getText(node.typeAnnotation) }, fix: fixer => [ @@ -234,7 +234,7 @@ export default createRule({ ], }); } - suggest.push({ + suggestions.push({ messageId: satisfiesMessageId, data: { cast: context.sourceCode.getText(node.typeAnnotation) }, fix: fixer => [ @@ -248,7 +248,7 @@ export default createRule({ ), ], }); - return suggest; + return suggestions; } function isAsParameter( @@ -291,7 +291,7 @@ export default createRule({ } if (checkType(node.typeAnnotation)) { - const suggest = getSuggests( + const suggest = getSuggestions( node, 'replaceObjectTypeAssertionWithAnnotation', 'replaceObjectTypeAssertionWithSatisfies', @@ -324,7 +324,7 @@ export default createRule({ } if (checkType(node.typeAnnotation)) { - const suggest = getSuggests( + const suggest = getSuggestions( node, 'replaceArrayTypeAssertionWithAnnotation', 'replaceArrayTypeAssertionWithSatisfies', From b64df60905edb416f087981c0f62695bdd68fac4 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Tue, 31 Dec 2024 14:26:50 +0900 Subject: [PATCH 08/11] review - docs --- .../eslint-plugin/docs/rules/consistent-type-assertions.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx b/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx index b423f614b4cd..9445bcf8b19b 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx +++ b/packages/eslint-plugin/docs/rules/consistent-type-assertions.mdx @@ -119,7 +119,10 @@ const foo = ; {/* insert option description */} -Always prefer `const x: T[] = [ ... ];` to `const x = [ ... ] as T[];` (or similar with angle brackets). The rationale for this is exactly the same as for `objectLiteralTypeAssertions`. +Always prefer `const x: T[] = [ ... ];` to `const x = [ ... ] as T[];` (or similar with angle brackets). + +The compiler will warn for excess properties of elements with this syntax, but not missing _required_ fields of those objects. +For example: `const x: {foo: number}[] = [{}];` will fail to compile, but `const x = [{}] as [{ foo: number }]` will succeed. The const assertion `const x = [1, 2, 3] as const`, introduced in TypeScript 3.4, is considered beneficial and is ignored by this option. From c3f66e53ace962a8bdb075f18e2d6723a23bc839 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Thu, 2 Jan 2025 21:00:43 +0900 Subject: [PATCH 09/11] fix tests --- .../rules/consistent-type-assertions.test.ts | 470 ++++-------------- 1 file changed, 100 insertions(+), 370 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts index 13d6dcfb3a35..90b130750bae 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -85,38 +85,6 @@ print?.({ bar: 5 }) print?.call({ bar: 5 }) print\`\${{ bar: 5 }}\` `; -const ARRAY_LITERAL_AS_CASTS = ` -const x = [] as string[]; -const x = ['a'] as string[]; -const x = [] as Array; -const x = ['a'] as Array; -const x = [Math.random() ? 'a' : 'b'] as 'a'[]; -`; -const ARRAY_LITERAL_ANGLE_BRACKET_CASTS = ` -const x = []; -const x = ['a']; -const x = >[]; -const x = >['a']; -const x = <'a'[]>[Math.random() ? 'a' : 'b']; -`; -const ARRAY_LITERAL_ARGUMENT_AS_CASTS = ` -print([5] as Foo); -new print([5] as Foo); -function foo() { throw [5] as Foo } -function b(x = [5] as Foo.Bar) {} -function c(x = [5] as Foo) {} -print?.([5] as Foo); -print?.call([5] as Foo); -print\`\${[5] as Foo}\`; -`; -const ARRAY_LITERAL_ARGUMENT_BRACKET_CASTS = ` -print([5]); -new print([5]); -function foo() { throw [5] } -print?.([5]); -print?.call([5]); -print\`\${[5]}\`; -`; ruleTester.run('consistent-type-assertions', rule, { valid: [ @@ -168,40 +136,72 @@ ruleTester.run('consistent-type-assertions', rule, { }, ], }), - ...batchedSingleLineTests({ - code: ARRAY_LITERAL_AS_CASTS, + { + code: ` +const x = [] as string[]; +const x = ['a'] as string[]; +const x = [] as Array; +const x = ['a'] as Array; +const x = [Math.random() ? 'a' : 'b'] as 'a'[]; + `, options: [ { assertionStyle: 'as', }, ], - }), - ...batchedSingleLineTests({ - code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, + }, + { + code: ` +const x = []; +const x = ['a']; +const x = >[]; +const x = >['a']; +const x = <'a'[]>[Math.random() ? 'a' : 'b']; + `, options: [ { assertionStyle: 'angle-bracket', }, ], - }), - ...batchedSingleLineTests({ - code: ARRAY_LITERAL_ARGUMENT_AS_CASTS, + }, + { + code: ` +print([5] as Foo); +new print([5] as Foo); +function foo() { + throw [5] as Foo; +} +function b(x = [5] as Foo.Bar) {} +function c(x = [5] as Foo) {} +print?.([5] as Foo); +print?.call([5] as Foo); +print\`\${[5] as Foo}\`; + `, options: [ { arrayLiteralTypeAssertions: 'allow-as-parameter', assertionStyle: 'as', }, ], - }), - ...batchedSingleLineTests({ - code: ARRAY_LITERAL_ARGUMENT_BRACKET_CASTS, + }, + { + code: ` +print([5]); +new print([5]); +function foo() { + throw [5]; +} +print?.([5]); +print?.call([5]); +print\`\${[5]}\`; + `, options: [ { arrayLiteralTypeAssertions: 'allow-as-parameter', assertionStyle: 'angle-bracket', }, ], - }), + }, { code: 'const x = [1];', options: [{ assertionStyle: 'never' }] }, { code: 'const x = [1] as const;', options: [{ assertionStyle: 'never' }] }, { @@ -737,27 +737,10 @@ const bs = (x <<= y) as any; ], output: 'const ternary = (true ? x : y) as any;', }, - ...batchedSingleLineTests({ - code: ARRAY_LITERAL_AS_CASTS, + { + code: 'const x = [] as string[];', errors: [ { - line: 2, - messageId: 'never', - }, - { - line: 3, - messageId: 'never', - }, - { - line: 4, - messageId: 'never', - }, - { - line: 5, - messageId: 'never', - }, - { - line: 6, messageId: 'never', }, ], @@ -766,28 +749,11 @@ const bs = (x <<= y) as any; assertionStyle: 'never', }, ], - }), - ...batchedSingleLineTests({ - code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, + }, + { + code: 'const x = [];', errors: [ { - line: 2, - messageId: 'never', - }, - { - line: 3, - messageId: 'never', - }, - { - line: 4, - messageId: 'never', - }, - { - line: 5, - messageId: 'never', - }, - { - line: 6, messageId: 'never', }, ], @@ -796,28 +762,11 @@ const bs = (x <<= y) as any; assertionStyle: 'never', }, ], - }), - ...batchedSingleLineTests({ - code: ARRAY_LITERAL_AS_CASTS, + }, + { + code: 'const x = [] as string[];', errors: [ { - line: 2, - messageId: 'angle-bracket', - }, - { - line: 3, - messageId: 'angle-bracket', - }, - { - line: 4, - messageId: 'angle-bracket', - }, - { - line: 5, - messageId: 'angle-bracket', - }, - { - line: 6, messageId: 'angle-bracket', }, ], @@ -826,29 +775,11 @@ const bs = (x <<= y) as any; assertionStyle: 'angle-bracket', }, ], - output: null, - }), - ...batchedSingleLineTests({ - code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, + }, + { + code: 'const x = [];', errors: [ { - line: 2, - messageId: 'as', - }, - { - line: 3, - messageId: 'as', - }, - { - line: 4, - messageId: 'as', - }, - { - line: 5, - messageId: 'as', - }, - { - line: 6, messageId: 'as', }, ], @@ -857,13 +788,12 @@ const bs = (x <<= y) as any; assertionStyle: 'as', }, ], - output: ARRAY_LITERAL_AS_CASTS, - }), - ...batchedSingleLineTests({ - code: ARRAY_LITERAL_AS_CASTS, + output: 'const x = [] as string[];', + }, + { + code: 'const x = [] as string[];', errors: [ { - line: 2, messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { @@ -878,70 +808,6 @@ const bs = (x <<= y) as any; }, ], }, - { - line: 3, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'string[]' }, - messageId: 'replaceArrayTypeAssertionWithAnnotation', - output: `const x: string[] = ['a'];`, - }, - { - data: { cast: 'string[]' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `const x = ['a'] satisfies string[];`, - }, - ], - }, - { - line: 4, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Array' }, - messageId: 'replaceArrayTypeAssertionWithAnnotation', - output: 'const x: Array = [];', - }, - { - data: { cast: 'Array' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: 'const x = [] satisfies Array;', - }, - ], - }, - { - line: 5, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Array' }, - messageId: 'replaceArrayTypeAssertionWithAnnotation', - output: `const x: Array = ['a'];`, - }, - { - data: { cast: 'Array' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `const x = ['a'] satisfies Array;`, - }, - ], - }, - { - line: 6, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: "'a'[]" }, - messageId: 'replaceArrayTypeAssertionWithAnnotation', - output: `const x: 'a'[] = [Math.random() ? 'a' : 'b'];`, - }, - { - data: { cast: "'a'[]" }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `const x = [Math.random() ? 'a' : 'b'] satisfies 'a'[];`, - }, - ], - }, ], options: [ { @@ -949,12 +815,11 @@ const bs = (x <<= y) as any; assertionStyle: 'as', }, ], - }), - ...batchedSingleLineTests({ - code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, + }, + { + code: 'const x = [];', errors: [ { - line: 2, messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { @@ -969,70 +834,6 @@ const bs = (x <<= y) as any; }, ], }, - { - line: 3, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'string[]' }, - messageId: 'replaceArrayTypeAssertionWithAnnotation', - output: `const x: string[] = ['a'];`, - }, - { - data: { cast: 'string[]' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `const x = ['a'] satisfies string[];`, - }, - ], - }, - { - line: 4, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Array' }, - messageId: 'replaceArrayTypeAssertionWithAnnotation', - output: 'const x: Array = [];', - }, - { - data: { cast: 'Array' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: 'const x = [] satisfies Array;', - }, - ], - }, - { - line: 5, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Array' }, - messageId: 'replaceArrayTypeAssertionWithAnnotation', - output: `const x: Array = ['a'];`, - }, - { - data: { cast: 'Array' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `const x = ['a'] satisfies Array;`, - }, - ], - }, - { - line: 6, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: "'a'[]" }, - messageId: 'replaceArrayTypeAssertionWithAnnotation', - output: `const x: 'a'[] = [Math.random() ? 'a' : 'b'];`, - }, - { - data: { cast: "'a'[]" }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `const x = [Math.random() ? 'a' : 'b'] satisfies 'a'[];`, - }, - ], - }, ], options: [ { @@ -1040,12 +841,11 @@ const bs = (x <<= y) as any; assertionStyle: 'angle-bracket', }, ], - }), - ...batchedSingleLineTests({ - code: ARRAY_LITERAL_ARGUMENT_AS_CASTS, + }, + { + code: 'print([5] as Foo);', errors: [ { - line: 2, messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { @@ -1055,8 +855,18 @@ const bs = (x <<= y) as any; }, ], }, + ], + options: [ + { + arrayLiteralTypeAssertions: 'never', + assertionStyle: 'as', + }, + ], + }, + { + code: 'new print([5] as Foo);', + errors: [ { - line: 3, messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { @@ -1066,19 +876,18 @@ const bs = (x <<= y) as any; }, ], }, + ], + options: [ { - line: 4, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Foo' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `function foo() { throw [5] satisfies Foo }`, - }, - ], + arrayLiteralTypeAssertions: 'never', + assertionStyle: 'as', }, + ], + }, + { + code: 'function b(x = [5] as Foo.Bar) {}', + errors: [ { - line: 5, messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { @@ -1088,50 +897,6 @@ const bs = (x <<= y) as any; }, ], }, - { - line: 6, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Foo' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `function c(x = [5] satisfies Foo) {}`, - }, - ], - }, - { - line: 7, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Foo' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `print?.([5] satisfies Foo);`, - }, - ], - }, - { - line: 8, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Foo' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `print?.call([5] satisfies Foo);`, - }, - ], - }, - { - line: 9, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Foo' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `print\`\${[5] satisfies Foo}\`;`, - }, - ], - }, ], options: [ { @@ -1139,23 +904,11 @@ const bs = (x <<= y) as any; assertionStyle: 'as', }, ], - }), - ...batchedSingleLineTests({ - code: ARRAY_LITERAL_ARGUMENT_BRACKET_CASTS, + }, + { + code: 'new print([5]);', errors: [ { - line: 2, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Foo' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `print([5] satisfies Foo);`, - }, - ], - }, - { - line: 3, messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { @@ -1165,47 +918,24 @@ const bs = (x <<= y) as any; }, ], }, + ], + options: [ { - line: 4, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Foo' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `function foo() { throw [5] satisfies Foo }`, - }, - ], - }, - { - line: 5, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Foo' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `print?.([5] satisfies Foo);`, - }, - ], - }, - { - line: 6, - messageId: 'unexpectedArrayTypeAssertion', - suggestions: [ - { - data: { cast: 'Foo' }, - messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `print?.call([5] satisfies Foo);`, - }, - ], + arrayLiteralTypeAssertions: 'never', + assertionStyle: 'angle-bracket', }, + ], + }, + { + code: 'function b(x = [5]) {}', + errors: [ { - line: 7, messageId: 'unexpectedArrayTypeAssertion', suggestions: [ { - data: { cast: 'Foo' }, + data: { cast: 'Foo.Bar' }, messageId: 'replaceArrayTypeAssertionWithSatisfies', - output: `print\`\${[5] satisfies Foo}\`;`, + output: `function b(x = [5] satisfies Foo.Bar) {}`, }, ], }, @@ -1216,6 +946,6 @@ const bs = (x <<= y) as any; assertionStyle: 'angle-bracket', }, ], - }), + }, ], }); From fdcd4ced0442e476a7286ff24f0b90a6ff9f7730 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Fri, 3 Jan 2025 09:12:26 +0900 Subject: [PATCH 10/11] Update consistent-type-assertions.test.ts --- .../rules/consistent-type-assertions.test.ts | 234 ++++++++++++++++-- 1 file changed, 208 insertions(+), 26 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts index 90b130750bae..866d52b5e190 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -137,13 +137,7 @@ ruleTester.run('consistent-type-assertions', rule, { ], }), { - code: ` -const x = [] as string[]; -const x = ['a'] as string[]; -const x = [] as Array; -const x = ['a'] as Array; -const x = [Math.random() ? 'a' : 'b'] as 'a'[]; - `, + code: 'const x = [] as string[];', options: [ { assertionStyle: 'as', @@ -151,31 +145,43 @@ const x = [Math.random() ? 'a' : 'b'] as 'a'[]; ], }, { - code: ` -const x = []; -const x = ['a']; -const x = >[]; -const x = >['a']; -const x = <'a'[]>[Math.random() ? 'a' : 'b']; - `, + code: "const x = ['a'] as Array;", + options: [ + { + assertionStyle: 'as', + }, + ], + }, + { + code: 'const x = [];', options: [ { assertionStyle: 'angle-bracket', }, ], }, + { + code: 'const x = >[];', + options: [ + { + assertionStyle: 'angle-bracket', + }, + ], + }, + { + code: 'print([5] as Foo);', + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'as', + }, + ], + }, { code: ` -print([5] as Foo); -new print([5] as Foo); function foo() { throw [5] as Foo; } -function b(x = [5] as Foo.Bar) {} -function c(x = [5] as Foo) {} -print?.([5] as Foo); -print?.call([5] as Foo); -print\`\${[5] as Foo}\`; `, options: [ { @@ -184,16 +190,56 @@ print\`\${[5] as Foo}\`; }, ], }, + { + code: 'function b(x = [5] as Foo.Bar) {}', + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'as', + }, + ], + }, + { + code: 'print?.([5] as Foo);', + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'as', + }, + ], + }, + { + code: 'print?.call([5] as Foo);', + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'as', + }, + ], + }, + { + code: 'print`${[5] as Foo}`;', + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'as', + }, + ], + }, + { + code: 'print([5]);', + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'angle-bracket', + }, + ], + }, { code: ` -print([5]); -new print([5]); function foo() { throw [5]; } -print?.([5]); -print?.call([5]); -print\`\${[5]}\`; `, options: [ { @@ -202,6 +248,42 @@ print\`\${[5]}\`; }, ], }, + { + code: 'function b(x = [5]) {}', + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'angle-bracket', + }, + ], + }, + { + code: 'print?.([5]);', + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'angle-bracket', + }, + ], + }, + { + code: 'print?.call([5]);', + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'angle-bracket', + }, + ], + }, + { + code: 'print`${[5]}`;', + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'angle-bracket', + }, + ], + }, { code: 'const x = [1];', options: [{ assertionStyle: 'never' }] }, { code: 'const x = [1] as const;', options: [{ assertionStyle: 'never' }] }, { @@ -905,6 +987,56 @@ const bs = (x <<= y) as any; }, ], }, + { + code: ` +function foo() { + throw [5] as Foo; +} + `, + errors: [ + { + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: ` +function foo() { + throw [5] satisfies Foo; +} + `, + }, + ], + }, + ], + options: [ + { + arrayLiteralTypeAssertions: 'never', + assertionStyle: 'as', + }, + ], + }, + { + code: 'print`${[5] as Foo}`;', + errors: [ + { + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: 'print`${[5] satisfies Foo}`;', + }, + ], + }, + ], + options: [ + { + arrayLiteralTypeAssertions: 'never', + assertionStyle: 'as', + }, + ], + }, { code: 'new print([5]);', errors: [ @@ -947,5 +1079,55 @@ const bs = (x <<= y) as any; }, ], }, + { + code: ` +function foo() { + throw [5]; +} + `, + errors: [ + { + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: ` +function foo() { + throw [5] satisfies Foo; +} + `, + }, + ], + }, + ], + options: [ + { + arrayLiteralTypeAssertions: 'never', + assertionStyle: 'angle-bracket', + }, + ], + }, + { + code: 'print`${[5]}`;', + errors: [ + { + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: 'print`${[5] satisfies Foo}`;', + }, + ], + }, + ], + options: [ + { + arrayLiteralTypeAssertions: 'never', + assertionStyle: 'angle-bracket', + }, + ], + }, ], }); From f78ba9915661e8d2255900f55110de24045d63bc Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Fri, 10 Jan 2025 19:39:37 +0900 Subject: [PATCH 11/11] apply review --- .../src/rules/consistent-type-assertions.ts | 25 +++---- .../rules/consistent-type-assertions.test.ts | 75 +++++++++++++++++++ 2 files changed, 85 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts index 1a33f2e46729..3f460f7d11da 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts @@ -35,6 +35,10 @@ type OptUnion = }; export type Options = readonly [OptUnion]; +type AsExpressionOrTypeAssertion = + | TSESTree.TSAsExpression + | TSESTree.TSTypeAssertion; + export default createRule({ name: 'consistent-type-assertions', meta: { @@ -122,7 +126,7 @@ export default createRule({ } function reportIncorrectAssertionType( - node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, + node: AsExpressionOrTypeAssertion, ): void { const messageId = options.assertionStyle; @@ -209,7 +213,7 @@ export default createRule({ } function getSuggestions( - node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, + node: AsExpressionOrTypeAssertion, annotationMessageId: MessageIds, satisfiesMessageId: MessageIds, ): TSESLint.ReportSuggestionArray { @@ -251,9 +255,7 @@ export default createRule({ return suggestions; } - function isAsParameter( - node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, - ): boolean { + function isAsParameter(node: AsExpressionOrTypeAssertion): boolean { return ( node.parent.type === AST_NODE_TYPES.NewExpression || node.parent.type === AST_NODE_TYPES.CallExpression || @@ -266,7 +268,7 @@ export default createRule({ } function checkExpressionForObjectAssertion( - node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, + node: AsExpressionOrTypeAssertion, ): void { if ( options.assertionStyle === 'never' || @@ -278,14 +280,7 @@ export default createRule({ if ( options.objectLiteralTypeAssertions === 'allow-as-parameter' && - (node.parent.type === AST_NODE_TYPES.NewExpression || - node.parent.type === AST_NODE_TYPES.CallExpression || - node.parent.type === AST_NODE_TYPES.ThrowStatement || - node.parent.type === AST_NODE_TYPES.AssignmentPattern || - node.parent.type === AST_NODE_TYPES.JSXExpressionContainer || - (node.parent.type === AST_NODE_TYPES.TemplateLiteral && - node.parent.parent.type === - AST_NODE_TYPES.TaggedTemplateExpression)) + isAsParameter(node) ) { return; } @@ -306,7 +301,7 @@ export default createRule({ } function checkExpressionForArrayAssertion( - node: TSESTree.TSAsExpression | TSESTree.TSTypeAssertion, + node: AsExpressionOrTypeAssertion, ): void { if ( options.assertionStyle === 'never' || diff --git a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts index 866d52b5e190..b2a1c47fff3f 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -226,6 +226,25 @@ function foo() { }, ], }, + { + code: 'new Print([5] as Foo);', + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'as', + }, + ], + }, + { + code: 'const bar = ;', + languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }, + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'as', + }, + ], + }, { code: 'print([5]);', options: [ @@ -284,6 +303,15 @@ function foo() { }, ], }, + { + code: 'new Print([5]);', + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'angle-bracket', + }, + ], + }, { code: 'const x = [1];', options: [{ assertionStyle: 'never' }] }, { code: 'const x = [1] as const;', options: [{ assertionStyle: 'never' }] }, { @@ -1037,6 +1065,27 @@ function foo() { }, ], }, + { + code: 'const foo = () => [5] as Foo;', + errors: [ + { + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: 'const foo = () => [5] satisfies Foo;', + }, + ], + }, + ], + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'as', + }, + ], + }, { code: 'new print([5]);', errors: [ @@ -1129,5 +1178,31 @@ function foo() { }, ], }, + { + code: 'const foo = [5];', + errors: [ + { + messageId: 'unexpectedArrayTypeAssertion', + suggestions: [ + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithAnnotation', + output: 'const foo: Foo = [5];', + }, + { + data: { cast: 'Foo' }, + messageId: 'replaceArrayTypeAssertionWithSatisfies', + output: 'const foo = [5] satisfies Foo;', + }, + ], + }, + ], + options: [ + { + arrayLiteralTypeAssertions: 'allow-as-parameter', + assertionStyle: 'angle-bracket', + }, + ], + }, ], });