From a920f19ae224278e90c4a62b044ffd358b9486ce Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Thu, 23 Mar 2023 16:15:13 -0400 Subject: [PATCH 01/11] passing tests with array assertions --- .../rules/consistent-type-assertions.test.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) 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 45f672af9e4d..10203118bfac 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -62,6 +62,15 @@ print?.({ bar: 5 }) print?.call({ bar: 5 }) `; +const ARRAY_LITERAL_AS_CASTS = ` +const errorMessages = [] as string[]; +const errorMessages = ['a'] as string[]; +`; +const ARRAY_LITERAL_ANGLE_BRACKET_CASTS = ` +const errorMessages = []; +const errorMessages = ['a']; +`; + ruleTester.run('consistent-type-assertions', rule, { valid: [ ...batchedSingleLineTests({ @@ -148,6 +157,22 @@ ruleTester.run('consistent-type-assertions', rule, { }, ], }, + { + code: ARRAY_LITERAL_AS_CASTS, + options: [ + { + assertionStyle: 'as', + }, + ], + }, + { + code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, + options: [ + { + assertionStyle: 'angle-bracket', + }, + ], + }, ], invalid: [ ...batchedSingleLineTests({ From f1e9378bf6f3db198ed9b0bc69949be63845ec5b Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Thu, 23 Mar 2023 16:23:14 -0400 Subject: [PATCH 02/11] Add some invalid tests --- .../rules/consistent-type-assertions.test.ts | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 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 10203118bfac..59b1b90e1335 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -63,12 +63,15 @@ print?.call({ bar: 5 }) `; const ARRAY_LITERAL_AS_CASTS = ` -const errorMessages = [] as string[]; -const errorMessages = ['a'] as string[]; +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 errorMessages = []; -const errorMessages = ['a']; +const x = []; +const x = ['a']; `; ruleTester.run('consistent-type-assertions', rule, { @@ -735,5 +738,53 @@ ruleTester.run('consistent-type-assertions', rule, { }, ], }, + { + code: ARRAY_LITERAL_AS_CASTS, + options: [ + { + assertionStyle: 'never', + }, + ], + errors: [ + { + messageId: 'never', + line: 2, + }, + { + messageId: 'never', + line: 3, + }, + { + messageId: 'never', + line: 4, + }, + { + messageId: 'never', + line: 5, + }, + { + messageId: 'never', + line: 6, + }, + ], + }, + { + code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, + options: [ + { + assertionStyle: 'never', + }, + ], + errors: [ + { + messageId: 'never', + line: 2, + }, + { + messageId: 'never', + line: 3, + }, + ], + }, ], }); From 5eada0f723e0dba960db1ff5187f5af7b3464dbb Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Thu, 23 Mar 2023 16:25:12 -0400 Subject: [PATCH 03/11] Add some invalid tests --- .../rules/consistent-type-assertions.test.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) 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 59b1b90e1335..d2e98965a31a 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -786,5 +786,53 @@ ruleTester.run('consistent-type-assertions', rule, { }, ], }, + { + code: ARRAY_LITERAL_AS_CASTS, + options: [ + { + assertionStyle: 'angle', + }, + ], + errors: [ + { + messageId: 'angle-bracket', + line: 2, + }, + { + messageId: 'angle-bracket', + line: 3, + }, + { + messageId: 'angle-bracket', + line: 4, + }, + { + messageId: 'angle-bracket', + line: 5, + }, + { + messageId: 'angle-bracket', + line: 6, + }, + ], + }, + { + code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, + options: [ + { + assertionStyle: 'as', + }, + ], + errors: [ + { + messageId: 'as', + line: 2, + }, + { + messageId: 'as', + line: 3, + }, + ], + }, ], }); From 2df38372339ae50c9ec381277d6f71990f9a0761 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Thu, 23 Mar 2023 16:37:35 -0400 Subject: [PATCH 04/11] angle -> as auto-fixer --- .../rules/consistent-type-assertions.test.ts | 70 ++++++++++++++----- 1 file changed, 53 insertions(+), 17 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 d2e98965a31a..34e47ab5a021 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -72,6 +72,16 @@ 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_DECLARATIONS = ` +const x: string[] = []; +const x: string[] = ['a']; +const x: Array = []; +const x: Array = ['a']; +const x: 'a'[] = [Math.random() ? 'a' : 'b']; `; ruleTester.run('consistent-type-assertions', rule, { @@ -130,6 +140,22 @@ 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', + }, + ], + }), { code: 'const x = [1];', options: [ @@ -160,22 +186,6 @@ ruleTester.run('consistent-type-assertions', rule, { }, ], }, - { - code: ARRAY_LITERAL_AS_CASTS, - options: [ - { - assertionStyle: 'as', - }, - ], - }, - { - code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, - options: [ - { - assertionStyle: 'angle-bracket', - }, - ], - }, ], invalid: [ ...batchedSingleLineTests({ @@ -784,13 +794,25 @@ ruleTester.run('consistent-type-assertions', rule, { messageId: 'never', line: 3, }, + { + messageId: 'never', + line: 4, + }, + { + messageId: 'never', + line: 5, + }, + { + messageId: 'never', + line: 6, + }, ], }, { code: ARRAY_LITERAL_AS_CASTS, options: [ { - assertionStyle: 'angle', + assertionStyle: 'angle-bracket', }, ], errors: [ @@ -815,6 +837,7 @@ ruleTester.run('consistent-type-assertions', rule, { line: 6, }, ], + output: null, }, { code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, @@ -832,7 +855,20 @@ ruleTester.run('consistent-type-assertions', rule, { messageId: 'as', line: 3, }, + { + messageId: 'as', + line: 4, + }, + { + messageId: 'as', + line: 5, + }, + { + messageId: 'as', + line: 6, + }, ], + output: ARRAY_LITERAL_AS_CASTS, }, ], }); From f0b4dc6741288ddb7aa66b2b83e2b21924f1e6fb Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Thu, 23 Mar 2023 16:47:39 -0400 Subject: [PATCH 05/11] arrayLiteralTypeAssertions --- .../src/rules/consistent-type-assertions.ts | 72 ++++++++++++++++++- .../rules/consistent-type-assertions.test.ts | 33 +++++++++ 2 files changed, 102 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts index 58403fe8a141..0f7d98db8d99 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts @@ -9,12 +9,14 @@ type MessageIds = | 'angle-bracket' | 'never' | 'unexpectedObjectTypeAssertion' + | 'unexpectedArrayTypeAssertion' | 'replaceObjectTypeAssertionWithAnnotation' | 'replaceObjectTypeAssertionWithSatisfies'; type OptUnion = | { assertionStyle: 'as' | 'angle-bracket'; objectLiteralTypeAssertions?: 'allow' | 'allow-as-parameter' | 'never'; + arrayLiteralTypeAssertions?: 'allow' | 'never'; } | { assertionStyle: 'never'; @@ -36,6 +38,7 @@ export default util.createRule({ 'angle-bracket': "Use '<{{cast}}>' instead of 'as {{cast}}'.", never: 'Do not use any type assertions.', unexpectedObjectTypeAssertion: 'Always prefer const x: T = { ... }.', + unexpectedArrayTypeAssertion: 'Always prefer const x: T[] = [ ... ].', replaceObjectTypeAssertionWithAnnotation: 'Use const x: {{cast}} = { ... } instead.', replaceObjectTypeAssertionWithSatisfies: @@ -63,6 +66,9 @@ export default util.createRule({ objectLiteralTypeAssertions: { enum: ['allow', 'allow-as-parameter', 'never'], }, + arrayLiteralTypeAssertions: { + enum: ['allow', 'never'], + }, }, additionalProperties: false, required: ['assertionStyle'], @@ -75,6 +81,7 @@ export default util.createRule({ { assertionStyle: 'as', objectLiteralTypeAssertions: 'allow', + arrayLiteralTypeAssertions: 'allow', }, ], create(context, [options]) { @@ -164,7 +171,7 @@ export default util.createRule({ } } - function checkExpression( + function checkExpressionForObjectAssertion( node: TSESTree.TSTypeAssertion | TSESTree.TSAsExpression, ): void { if ( @@ -231,6 +238,63 @@ export default util.createRule({ } } + function checkExpressionForArrayAssertion( + node: TSESTree.TSTypeAssertion | TSESTree.TSAsExpression, + ): void { + if ( + options.assertionStyle === 'never' || + options.arrayLiteralTypeAssertions === 'allow' || + node.expression.type !== AST_NODE_TYPES.ArrayExpression + ) { + return; + } + + if ( + checkType(node.typeAnnotation) && + node.expression.type === AST_NODE_TYPES.ArrayExpression + ) { + /* + 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: sourceCode.getText(node.typeAnnotation) }, + fix: fixer => [ + fixer.insertTextAfter( + parent.id, + `: ${sourceCode.getText(node.typeAnnotation)}`, + ), + fixer.replaceText(node, getTextWithParentheses(node.expression)), + ], + }); + } + suggest.push({ + messageId: 'replaceObjectTypeAssertionWithSatisfies', + data: { cast: sourceCode.getText(node.typeAnnotation) }, + fix: fixer => [ + fixer.replaceText(node, getTextWithParentheses(node.expression)), + fixer.insertTextAfter( + node, + ` satisfies ${context + .getSourceCode() + .getText(node.typeAnnotation)}`, + ), + ], + }); + */ + + context.report({ + node, + messageId: 'unexpectedArrayTypeAssertion', + suggest: [], + }); + } + } + return { TSTypeAssertion(node): void { if (options.assertionStyle !== 'angle-bracket') { @@ -238,7 +302,8 @@ export default util.createRule({ return; } - checkExpression(node); + checkExpressionForObjectAssertion(node); + checkExpressionForArrayAssertion(node); }, TSAsExpression(node): void { if (options.assertionStyle !== 'as') { @@ -246,7 +311,8 @@ export default util.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 34e47ab5a021..6b7c996f81aa 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -76,6 +76,7 @@ const x = >[]; const x = >['a']; const x = <'a'[]>[Math.random() ? 'a' : 'b']; `; +/* const ARRAY_LITERAL_DECLARATIONS = ` const x: string[] = []; const x: string[] = ['a']; @@ -83,6 +84,7 @@ const x: Array = []; const x: Array = ['a']; const x: 'a'[] = [Math.random() ? 'a' : 'b']; `; +*/ ruleTester.run('consistent-type-assertions', rule, { valid: [ @@ -870,5 +872,36 @@ ruleTester.run('consistent-type-assertions', rule, { ], output: ARRAY_LITERAL_AS_CASTS, }, + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_AS_CASTS, + options: [ + { + assertionStyle: 'as', + arrayLiteralTypeAssertions: 'never', + }, + ], + errors: [ + { + messageId: 'unexpectedArrayTypeAssertion', + line: 2, + }, + { + messageId: 'unexpectedArrayTypeAssertion', + line: 3, + }, + { + messageId: 'unexpectedArrayTypeAssertion', + line: 4, + }, + { + messageId: 'unexpectedArrayTypeAssertion', + line: 5, + }, + { + messageId: 'unexpectedArrayTypeAssertion', + line: 6, + }, + ], + }), ], }); From 79772e33b40ba56f5a630bb73eecba3fafec8f46 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Thu, 23 Mar 2023 16:48:48 -0400 Subject: [PATCH 06/11] analogous test for angle-brackets --- .../rules/consistent-type-assertions.test.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) 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 6b7c996f81aa..b9374aa3badc 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -904,4 +904,35 @@ ruleTester.run('consistent-type-assertions', rule, { ], }), ], + ...batchedSingleLineTests({ + code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, + options: [ + { + assertionStyle: 'angle-brackets', + arrayLiteralTypeAssertions: 'never', + }, + ], + errors: [ + { + messageId: 'unexpectedArrayTypeAssertion', + line: 2, + }, + { + messageId: 'unexpectedArrayTypeAssertion', + line: 3, + }, + { + messageId: 'unexpectedArrayTypeAssertion', + line: 4, + }, + { + messageId: 'unexpectedArrayTypeAssertion', + line: 5, + }, + { + messageId: 'unexpectedArrayTypeAssertion', + line: 6, + }, + ], + }), }); From ea3c340b3f3e1da8c4f7179369dea651cf6af86c Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Thu, 23 Mar 2023 17:00:07 -0400 Subject: [PATCH 07/11] test for auto-fixer --- .../src/rules/consistent-type-assertions.ts | 16 ++++++++++------ .../rules/consistent-type-assertions.test.ts | 13 +++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts index 0f7d98db8d99..fb79a06c105a 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts @@ -11,7 +11,9 @@ type MessageIds = | 'unexpectedObjectTypeAssertion' | 'unexpectedArrayTypeAssertion' | 'replaceObjectTypeAssertionWithAnnotation' - | 'replaceObjectTypeAssertionWithSatisfies'; + | 'replaceObjectTypeAssertionWithSatisfies' + | 'replaceArrayTypeAssertionWithAnnotation' + | 'replaceArrayTypeAssertionWithSatisfies'; type OptUnion = | { assertionStyle: 'as' | 'angle-bracket'; @@ -43,6 +45,10 @@ export default util.createRule({ 'Use const x: {{cast}} = { ... } instead.', replaceObjectTypeAssertionWithSatisfies: 'Use const x = { ... } satisfies {{cast}} instead.', + replaceArrayTypeAssertionWithAnnotation: + 'Use const x: [{cast}] = [ ... ] instead.', + replaceArrayTypeAssertionWithSatisfies: + 'Use const x = [ ... ] satisfies [{cast}] instead.', }, schema: [ { @@ -253,7 +259,6 @@ export default util.createRule({ checkType(node.typeAnnotation) && node.expression.type === AST_NODE_TYPES.ArrayExpression ) { - /* const suggest: TSESLint.ReportSuggestionArray = []; if ( node.parent?.type === AST_NODE_TYPES.VariableDeclarator && @@ -261,7 +266,7 @@ export default util.createRule({ ) { const { parent } = node; suggest.push({ - messageId: 'replaceObjectTypeAssertionWithAnnotation', + messageId: 'replaceArrayTypeAssertionWithAnnotation', data: { cast: sourceCode.getText(node.typeAnnotation) }, fix: fixer => [ fixer.insertTextAfter( @@ -273,7 +278,7 @@ export default util.createRule({ }); } suggest.push({ - messageId: 'replaceObjectTypeAssertionWithSatisfies', + messageId: 'replaceArrayTypeAssertionWithAnnotation', data: { cast: sourceCode.getText(node.typeAnnotation) }, fix: fixer => [ fixer.replaceText(node, getTextWithParentheses(node.expression)), @@ -285,12 +290,11 @@ export default util.createRule({ ), ], }); - */ context.report({ node, messageId: 'unexpectedArrayTypeAssertion', - suggest: [], + suggest, }); } } 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 b9374aa3badc..f5b6662a4d0c 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -874,6 +874,7 @@ ruleTester.run('consistent-type-assertions', rule, { }, ...batchedSingleLineTests({ code: ARRAY_LITERAL_AS_CASTS, + only: true, options: [ { assertionStyle: 'as', @@ -884,6 +885,18 @@ ruleTester.run('consistent-type-assertions', rule, { { messageId: 'unexpectedArrayTypeAssertion', line: 2, + suggestions: [ + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: 'const x: string[] = [];', + }, + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: 'const x = [] satisfies string[];', + }, + ], }, { messageId: 'unexpectedArrayTypeAssertion', From 4855d450f942f9ecb12a4a104c62225535d07eb3 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Thu, 23 Mar 2023 17:53:50 -0400 Subject: [PATCH 08/11] full fixer tests --- .../rules/consistent-type-assertions.test.ts | 118 ++++++++++++++++-- 1 file changed, 108 insertions(+), 10 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 f5b6662a4d0c..d2d02f3a3e24 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -76,15 +76,6 @@ const x = >[]; const x = >['a']; const x = <'a'[]>[Math.random() ? 'a' : 'b']; `; -/* -const ARRAY_LITERAL_DECLARATIONS = ` -const x: string[] = []; -const x: string[] = ['a']; -const x: Array = []; -const x: Array = ['a']; -const x: 'a'[] = [Math.random() ? 'a' : 'b']; -`; -*/ ruleTester.run('consistent-type-assertions', rule, { valid: [ @@ -874,7 +865,6 @@ ruleTester.run('consistent-type-assertions', rule, { }, ...batchedSingleLineTests({ code: ARRAY_LITERAL_AS_CASTS, - only: true, options: [ { assertionStyle: 'as', @@ -901,18 +891,66 @@ ruleTester.run('consistent-type-assertions', rule, { { messageId: 'unexpectedArrayTypeAssertion', line: 3, + suggestions: [ + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: `const x: string[] = ['a'];`, + }, + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: `const x = ['a'] satisfies string[];`, + }, + ], }, { messageId: 'unexpectedArrayTypeAssertion', line: 4, + suggestions: [ + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: 'const x: Array = [];', + }, + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: 'const x = [] satisfies Array;', + }, + ], }, { messageId: 'unexpectedArrayTypeAssertion', line: 5, + suggestions: [ + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: `const x: Array = ['a'];`, + }, + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: `const x = ['a'] satisfies Array;`, + }, + ], }, { messageId: 'unexpectedArrayTypeAssertion', line: 6, + suggestions: [ + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: `const x: 'a'[] = [Math.random() ? 'a' : 'b'];`, + }, + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: `const x = [Math.random() ? 'a' : 'b'] satisfies 'a'[];`, + }, + ], }, ], }), @@ -929,22 +967,82 @@ ruleTester.run('consistent-type-assertions', rule, { { messageId: 'unexpectedArrayTypeAssertion', line: 2, + suggestions: [ + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: 'const x: string[] = [];', + }, + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: 'const x = [] satisfies string[];', + }, + ], }, { messageId: 'unexpectedArrayTypeAssertion', line: 3, + suggestions: [ + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: `const x: string[] = ['a'];`, + }, + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: `const x = ['a'] satisfies string[];`, + }, + ], }, { messageId: 'unexpectedArrayTypeAssertion', line: 4, + suggestions: [ + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: 'const x: Array = [];', + }, + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: 'const x = [] satisfies Array;', + }, + ], }, { messageId: 'unexpectedArrayTypeAssertion', line: 5, + suggestions: [ + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: `const x: Array = ['a'];`, + }, + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: `const x = ['a'] satisfies Array;`, + }, + ], }, { messageId: 'unexpectedArrayTypeAssertion', line: 6, + suggestions: [ + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: `const x: 'a'[] = [Math.random() ? 'a' : 'b'];`, + }, + { + messageId: 'replaceArrayTypeAssertionWithAnnotation', + data: { cast: 'string' }, + output: `const x = [Math.random() ? 'a' : 'b'] satisfies 'a'[];`, + }, + ], }, ], }), From 24dac63cfc353b7ae915101dd2068d7e2fdc153a Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Thu, 23 Mar 2023 17:55:44 -0400 Subject: [PATCH 09/11] batched single line tests --- .../rules/consistent-type-assertions.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 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 d2d02f3a3e24..eb25f034fcb5 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -741,7 +741,7 @@ ruleTester.run('consistent-type-assertions', rule, { }, ], }, - { + ...batchedSingleLineTests({ code: ARRAY_LITERAL_AS_CASTS, options: [ { @@ -770,8 +770,8 @@ ruleTester.run('consistent-type-assertions', rule, { line: 6, }, ], - }, - { + }), + ...batchedSingleLineTests({ code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, options: [ { @@ -800,8 +800,8 @@ ruleTester.run('consistent-type-assertions', rule, { line: 6, }, ], - }, - { + }), + ...batchedSingleLineTests({ code: ARRAY_LITERAL_AS_CASTS, options: [ { @@ -831,8 +831,8 @@ ruleTester.run('consistent-type-assertions', rule, { }, ], output: null, - }, - { + }), + ...batchedSingleLineTests({ code: ARRAY_LITERAL_ANGLE_BRACKET_CASTS, options: [ { @@ -862,7 +862,7 @@ ruleTester.run('consistent-type-assertions', rule, { }, ], output: ARRAY_LITERAL_AS_CASTS, - }, + }), ...batchedSingleLineTests({ code: ARRAY_LITERAL_AS_CASTS, options: [ From a150480ca29c763fc439b166edafcb65323ffd53 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Thu, 23 Mar 2023 18:03:32 -0400 Subject: [PATCH 10/11] factor out getReplacementSuggestions helper --- .../src/rules/consistent-type-assertions.ts | 111 ++++++++---------- .../rules/consistent-type-assertions.test.ts | 20 ++-- 2 files changed, 59 insertions(+), 72 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts index fb79a06c105a..77dbf039e565 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts @@ -177,6 +177,45 @@ export default util.createRule({ } } + function getReplacementSuggestions( + node: TSESTree.TSTypeAssertion | TSESTree.TSAsExpression, + 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: sourceCode.getText(node.typeAnnotation) }, + fix: fixer => [ + fixer.insertTextAfter( + parent.id, + `: ${sourceCode.getText(node.typeAnnotation)}`, + ), + fixer.replaceText(node, getTextWithParentheses(node.expression)), + ], + }); + } + suggest.push({ + messageId: satisfiesMessageId, + data: { cast: sourceCode.getText(node.typeAnnotation) }, + fix: fixer => [ + fixer.replaceText(node, getTextWithParentheses(node.expression)), + fixer.insertTextAfter( + node, + ` satisfies ${context + .getSourceCode() + .getText(node.typeAnnotation)}`, + ), + ], + }); + return suggest; + } + function checkExpressionForObjectAssertion( node: TSESTree.TSTypeAssertion | TSESTree.TSAsExpression, ): void { @@ -204,37 +243,11 @@ export default util.createRule({ checkType(node.typeAnnotation) && node.expression.type === AST_NODE_TYPES.ObjectExpression ) { - 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: sourceCode.getText(node.typeAnnotation) }, - fix: fixer => [ - fixer.insertTextAfter( - parent.id, - `: ${sourceCode.getText(node.typeAnnotation)}`, - ), - fixer.replaceText(node, getTextWithParentheses(node.expression)), - ], - }); - } - suggest.push({ - messageId: 'replaceObjectTypeAssertionWithSatisfies', - data: { cast: sourceCode.getText(node.typeAnnotation) }, - fix: fixer => [ - fixer.replaceText(node, getTextWithParentheses(node.expression)), - fixer.insertTextAfter( - node, - ` satisfies ${context - .getSourceCode() - .getText(node.typeAnnotation)}`, - ), - ], - }); + const suggest = getReplacementSuggestions( + node, + 'replaceObjectTypeAssertionWithAnnotation', + 'replaceObjectTypeAssertionWithSatisfies', + ); context.report({ node, @@ -259,37 +272,11 @@ export default util.createRule({ checkType(node.typeAnnotation) && node.expression.type === AST_NODE_TYPES.ArrayExpression ) { - const suggest: TSESLint.ReportSuggestionArray = []; - if ( - node.parent?.type === AST_NODE_TYPES.VariableDeclarator && - !node.parent.id.typeAnnotation - ) { - const { parent } = node; - suggest.push({ - messageId: 'replaceArrayTypeAssertionWithAnnotation', - data: { cast: sourceCode.getText(node.typeAnnotation) }, - fix: fixer => [ - fixer.insertTextAfter( - parent.id, - `: ${sourceCode.getText(node.typeAnnotation)}`, - ), - fixer.replaceText(node, getTextWithParentheses(node.expression)), - ], - }); - } - suggest.push({ - messageId: 'replaceArrayTypeAssertionWithAnnotation', - data: { cast: sourceCode.getText(node.typeAnnotation) }, - fix: fixer => [ - fixer.replaceText(node, getTextWithParentheses(node.expression)), - fixer.insertTextAfter( - node, - ` satisfies ${context - .getSourceCode() - .getText(node.typeAnnotation)}`, - ), - ], - }); + const suggest = getReplacementSuggestions( + node, + 'replaceArrayTypeAssertionWithAnnotation', + 'replaceArrayTypeAssertionWithSatisfies', + ); context.report({ 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 eb25f034fcb5..07733e7a67b2 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -882,7 +882,7 @@ ruleTester.run('consistent-type-assertions', rule, { output: 'const x: string[] = [];', }, { - messageId: 'replaceArrayTypeAssertionWithAnnotation', + messageId: 'replaceArrayTypeAssertionWithSatisfies', data: { cast: 'string' }, output: 'const x = [] satisfies string[];', }, @@ -898,7 +898,7 @@ ruleTester.run('consistent-type-assertions', rule, { output: `const x: string[] = ['a'];`, }, { - messageId: 'replaceArrayTypeAssertionWithAnnotation', + messageId: 'replaceArrayTypeAssertionWithSatisfies', data: { cast: 'string' }, output: `const x = ['a'] satisfies string[];`, }, @@ -914,7 +914,7 @@ ruleTester.run('consistent-type-assertions', rule, { output: 'const x: Array = [];', }, { - messageId: 'replaceArrayTypeAssertionWithAnnotation', + messageId: 'replaceArrayTypeAssertionWithSatisfies', data: { cast: 'string' }, output: 'const x = [] satisfies Array;', }, @@ -930,7 +930,7 @@ ruleTester.run('consistent-type-assertions', rule, { output: `const x: Array = ['a'];`, }, { - messageId: 'replaceArrayTypeAssertionWithAnnotation', + messageId: 'replaceArrayTypeAssertionWithSatisfies', data: { cast: 'string' }, output: `const x = ['a'] satisfies Array;`, }, @@ -946,7 +946,7 @@ ruleTester.run('consistent-type-assertions', rule, { output: `const x: 'a'[] = [Math.random() ? 'a' : 'b'];`, }, { - messageId: 'replaceArrayTypeAssertionWithAnnotation', + messageId: 'replaceArrayTypeAssertionWithSatisfies', data: { cast: 'string' }, output: `const x = [Math.random() ? 'a' : 'b'] satisfies 'a'[];`, }, @@ -974,7 +974,7 @@ ruleTester.run('consistent-type-assertions', rule, { output: 'const x: string[] = [];', }, { - messageId: 'replaceArrayTypeAssertionWithAnnotation', + messageId: 'replaceArrayTypeAssertionWithSatisfies', data: { cast: 'string' }, output: 'const x = [] satisfies string[];', }, @@ -990,7 +990,7 @@ ruleTester.run('consistent-type-assertions', rule, { output: `const x: string[] = ['a'];`, }, { - messageId: 'replaceArrayTypeAssertionWithAnnotation', + messageId: 'replaceArrayTypeAssertionWithSatisfies', data: { cast: 'string' }, output: `const x = ['a'] satisfies string[];`, }, @@ -1006,7 +1006,7 @@ ruleTester.run('consistent-type-assertions', rule, { output: 'const x: Array = [];', }, { - messageId: 'replaceArrayTypeAssertionWithAnnotation', + messageId: 'replaceArrayTypeAssertionWithSatisfies', data: { cast: 'string' }, output: 'const x = [] satisfies Array;', }, @@ -1022,7 +1022,7 @@ ruleTester.run('consistent-type-assertions', rule, { output: `const x: Array = ['a'];`, }, { - messageId: 'replaceArrayTypeAssertionWithAnnotation', + messageId: 'replaceArrayTypeAssertionWithSatisfies', data: { cast: 'string' }, output: `const x = ['a'] satisfies Array;`, }, @@ -1038,7 +1038,7 @@ ruleTester.run('consistent-type-assertions', rule, { output: `const x: 'a'[] = [Math.random() ? 'a' : 'b'];`, }, { - messageId: 'replaceArrayTypeAssertionWithAnnotation', + messageId: 'replaceArrayTypeAssertionWithSatisfies', data: { cast: 'string' }, output: `const x = [Math.random() ? 'a' : 'b'] satisfies 'a'[];`, }, From fd20281c03bb49e86a8555f0537c4f5d07977b75 Mon Sep 17 00:00:00 2001 From: Dan Vanderkam Date: Thu, 23 Mar 2023 18:07:51 -0400 Subject: [PATCH 11/11] add some docs --- .../docs/rules/consistent-type-assertions.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/consistent-type-assertions.md b/packages/eslint-plugin/docs/rules/consistent-type-assertions.md index 4c7f87ee9558..19f96878c7f6 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-assertions.md +++ b/packages/eslint-plugin/docs/rules/consistent-type-assertions.md @@ -103,6 +103,34 @@ 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' }`: + + + +#### ❌ Incorrect + +```ts +const x = [] as string[]; +const y = ['a'] as string[]; +``` + +#### ✅ Correct + +```ts +const x: string[] = []; +const y: string[] = ['a']; +``` + + + ## When Not To Use It If you do not want to enforce consistent type assertions. 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