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', + }, + ], + }, ], }); 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