From 3f76a428861b3ad3433ff66b01bcafe6d87f3b78 Mon Sep 17 00:00:00 2001 From: Ulrich Buchgraber Date: Wed, 22 Apr 2020 12:55:08 +0200 Subject: [PATCH 1/2] fix(eslint-plugin): [restrict-template-expressions] Add missing default value in docs --- .../eslint-plugin/docs/rules/restrict-template-expressions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.md b/packages/eslint-plugin/docs/rules/restrict-template-expressions.md index 816a0b0f9d65..4bb2a062ad94 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.md +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.md @@ -40,6 +40,7 @@ type Options = { const defaults = { allowNumber: false, allowBoolean: false, + allowAny: false, allowNullable: false, }; ``` From e49b80f6d031ebfbebba895ed422f3dc4b595554 Mon Sep 17 00:00:00 2001 From: Ulrich Buchgraber Date: Wed, 22 Apr 2020 12:50:05 +0200 Subject: [PATCH 2/2] feat(eslint-plugin): [restrict-template-expressions] Improve message (add invalid type) * Also refactor (`isInnerUnionOrIntersectionConformingTo()`) and improve its name. * Also add `TWithNoConstraint` test case. --- .../rules/restrict-template-expressions.ts | 38 ++++---- .../restrict-template-expressions.test.ts | 87 +++++++++++++++++-- 2 files changed, 96 insertions(+), 29 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 90dd363f8204..6ba846ed5980 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -27,7 +27,7 @@ export default util.createRule({ requiresTypeChecking: true, }, messages: { - invalidType: 'Invalid type of template literal expression.', + invalidType: 'Invalid type "{{type}}" of template literal expression.', }, schema: [ { @@ -90,46 +90,44 @@ export default util.createRule({ } for (const expression of node.expressions) { + const expressionType = util.getConstrainedTypeAtLocation( + typeChecker, + service.esTreeNodeToTSNodeMap.get(expression), + ); + if ( - !isUnderlyingExpressionTypeConfirmingTo( - expression, + !isInnerUnionOrIntersectionConformingTo( + expressionType, isUnderlyingTypePrimitive, ) ) { context.report({ node: expression, messageId: 'invalidType', + data: { type: typeChecker.typeToString(expressionType) }, }); } } }, }; - function isUnderlyingExpressionTypeConfirmingTo( - expression: TSESTree.Expression, + function isInnerUnionOrIntersectionConformingTo( + type: ts.Type, predicate: (underlyingType: ts.Type) => boolean, ): boolean { - return rec(getExpressionNodeType(expression)); + return rec(type); - function rec(type: ts.Type): boolean { - if (type.isUnion()) { - return type.types.every(rec); + function rec(innerType: ts.Type): boolean { + if (innerType.isUnion()) { + return innerType.types.every(rec); } - if (type.isIntersection()) { - return type.types.some(rec); + if (innerType.isIntersection()) { + return innerType.types.some(rec); } - return predicate(type); + return predicate(innerType); } } - - /** - * Helper function to extract the TS type of an TSESTree expression. - */ - function getExpressionNodeType(node: TSESTree.Expression): ts.Type { - const tsNode = service.esTreeNodeToTSNodeMap.get(node); - return util.getConstrainedTypeAtLocation(typeChecker, tsNode); - } }, }); diff --git a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts index 2f5900f3d2ac..9b991053dcb2 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -214,33 +214,68 @@ ruleTester.run('restrict-template-expressions', rule, { code: ` const msg = \`arg = \${123}\`; `, - errors: [{ messageId: 'invalidType', line: 2, column: 30 }], + errors: [ + { + messageId: 'invalidType', + data: { type: '123' }, + line: 2, + column: 30, + }, + ], }, { code: ` const msg = \`arg = \${false}\`; `, - errors: [{ messageId: 'invalidType', line: 2, column: 30 }], + errors: [ + { + messageId: 'invalidType', + data: { type: 'false' }, + line: 2, + column: 30, + }, + ], }, { code: ` const msg = \`arg = \${null}\`; `, - errors: [{ messageId: 'invalidType', line: 2, column: 30 }], + errors: [ + { + messageId: 'invalidType', + data: { type: 'null' }, + line: 2, + column: 30, + }, + ], }, { code: ` declare const arg: number; const msg = \`arg = \${arg}\`; `, - errors: [{ messageId: 'invalidType', line: 3, column: 30 }], + errors: [ + { + messageId: 'invalidType', + data: { type: 'number' }, + line: 3, + column: 30, + }, + ], }, { code: ` declare const arg: boolean; const msg = \`arg = \${arg}\`; `, - errors: [{ messageId: 'invalidType', line: 3, column: 30 }], + errors: [ + { + messageId: 'invalidType', + data: { type: 'boolean' }, + line: 3, + column: 30, + }, + ], }, { options: [{ allowNumber: true, allowBoolean: true, allowNullable: true }], @@ -248,14 +283,23 @@ ruleTester.run('restrict-template-expressions', rule, { const arg = {}; const msg = \`arg = \${arg}\`; `, - errors: [{ messageId: 'invalidType', line: 3, column: 30 }], + errors: [ + { messageId: 'invalidType', data: { type: '{}' }, line: 3, column: 30 }, + ], }, { code: ` declare const arg: { a: string } & { b: string }; const msg = \`arg = \${arg}\`; `, - errors: [{ messageId: 'invalidType', line: 3, column: 30 }], + errors: [ + { + messageId: 'invalidType', + data: { type: '{ a: string; } & { b: string; }' }, + line: 3, + column: 30, + }, + ], }, { options: [{ allowNumber: true, allowBoolean: true, allowNullable: true }], @@ -264,7 +308,25 @@ ruleTester.run('restrict-template-expressions', rule, { return \`arg = \${arg}\`; } `, - errors: [{ messageId: 'invalidType', line: 3, column: 27 }], + errors: [ + { messageId: 'invalidType', data: { type: '{}' }, line: 3, column: 27 }, + ], + }, + { + options: [{ allowNumber: true, allowBoolean: true, allowNullable: true }], + code: ` + function test(arg: T) { + return \`arg = \${arg}\`; + } + `, + errors: [ + { + messageId: 'invalidType', + data: { type: 'any' }, + line: 3, + column: 27, + }, + ], }, { options: [{ allowNumber: true, allowBoolean: true, allowNullable: true }], @@ -273,7 +335,14 @@ ruleTester.run('restrict-template-expressions', rule, { return \`arg = \${arg}\`; } `, - errors: [{ messageId: 'invalidType', line: 3, column: 27 }], + errors: [ + { + messageId: 'invalidType', + data: { type: 'any' }, + line: 3, + column: 27, + }, + ], }, ], });