Skip to content

Commit

Permalink
feat(eslint-plugin): [restrict-template-expressions] improve error me…
Browse files Browse the repository at this point in the history
…ssage (#1926)
  • Loading branch information
ulrichb committed May 17, 2020
1 parent ce344d9 commit 1af59ba
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 29 deletions.
Expand Up @@ -40,6 +40,7 @@ type Options = {
const defaults = {
allowNumber: false,
allowBoolean: false,
allowAny: false,
allowNullable: false,
};
```
Expand Down
38 changes: 18 additions & 20 deletions packages/eslint-plugin/src/rules/restrict-template-expressions.ts
Expand Up @@ -27,7 +27,7 @@ export default util.createRule<Options, MessageId>({
requiresTypeChecking: true,
},
messages: {
invalidType: 'Invalid type of template literal expression.',
invalidType: 'Invalid type "{{type}}" of template literal expression.',
},
schema: [
{
Expand Down Expand Up @@ -90,46 +90,44 @@ export default util.createRule<Options, MessageId>({
}

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);
}
},
});
Expand Up @@ -214,48 +214,92 @@ 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 }],
code: `
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 }],
Expand All @@ -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<TWithNoConstraint>(arg: T) {
return \`arg = \${arg}\`;
}
`,
errors: [
{
messageId: 'invalidType',
data: { type: 'any' },
line: 3,
column: 27,
},
],
},
{
options: [{ allowNumber: true, allowBoolean: true, allowNullable: true }],
Expand All @@ -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,
},
],
},
],
});

0 comments on commit 1af59ba

Please sign in to comment.