UNPKG

@typescript-eslint/eslint-plugin

Version:
120 lines (119 loc) 4.85 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const type_utils_1 = require("@typescript-eslint/type-utils"); const utils_1 = require("@typescript-eslint/utils"); const typescript_1 = require("typescript"); const util_1 = require("../util"); const testTypeFlag = (flagsToCheck) => type => (0, util_1.isTypeFlagSet)(type, flagsToCheck); const optionTesters = [ ['Any', util_1.isTypeAnyType], [ 'Array', (type, checker, recursivelyCheckType) => (checker.isArrayType(type) || checker.isTupleType(type)) && // eslint-disable-next-line @typescript-eslint/no-non-null-assertion recursivelyCheckType(type.getNumberIndexType()), ], // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum ['Boolean', testTypeFlag(typescript_1.TypeFlags.BooleanLike)], ['Nullish', testTypeFlag(typescript_1.TypeFlags.Null | typescript_1.TypeFlags.Undefined)], ['Number', testTypeFlag(typescript_1.TypeFlags.NumberLike | typescript_1.TypeFlags.BigIntLike)], [ 'RegExp', (type, checker) => (0, util_1.getTypeName)(checker, type) === 'RegExp', ], ['Never', util_1.isTypeNeverType], ].map(([type, tester]) => ({ type, option: `allow${type}`, tester, })); exports.default = (0, util_1.createRule)({ name: 'restrict-template-expressions', meta: { type: 'problem', docs: { description: 'Enforce template literal expressions to be of `string` type', recommended: { recommended: true, strict: [ { allowAny: false, allowBoolean: false, allowNever: false, allowNullish: false, allowNumber: false, allowRegExp: false, }, ], }, requiresTypeChecking: true, }, messages: { invalidType: 'Invalid type "{{type}}" of template literal expression.', }, schema: [ { type: 'object', additionalProperties: false, properties: { ...Object.fromEntries(optionTesters.map(({ type, option }) => [ option, { type: 'boolean', description: `Whether to allow \`${type.toLowerCase()}\` typed values in template expressions.`, }, ])), allow: { description: `Types to allow in template expressions.`, ...type_utils_1.typeOrValueSpecifiersSchema, }, }, }, ], }, defaultOptions: [ { allow: [{ name: ['Error', 'URL', 'URLSearchParams'], from: 'lib' }], allowAny: true, allowBoolean: true, allowNullish: true, allowNumber: true, allowRegExp: true, }, ], create(context, [{ allow, ...options }]) { const services = (0, util_1.getParserServices)(context); const { program } = services; const checker = program.getTypeChecker(); const enabledOptionTesters = optionTesters.filter(({ option }) => options[option]); return { TemplateLiteral(node) { // don't check tagged template literals if (node.parent.type === utils_1.AST_NODE_TYPES.TaggedTemplateExpression) { return; } for (const expression of node.expressions) { const expressionType = (0, util_1.getConstrainedTypeAtLocation)(services, expression); if (!recursivelyCheckType(expressionType)) { context.report({ node: expression, messageId: 'invalidType', data: { type: checker.typeToString(expressionType) }, }); } } }, }; function recursivelyCheckType(innerType) { if (innerType.isUnion()) { return innerType.types.every(recursivelyCheckType); } if (innerType.isIntersection()) { return innerType.types.some(recursivelyCheckType); } return ((0, util_1.isTypeFlagSet)(innerType, typescript_1.TypeFlags.StringLike) || (0, type_utils_1.typeMatchesSomeSpecifier)(innerType, allow, program) || enabledOptionTesters.some(({ tester }) => tester(innerType, checker, recursivelyCheckType))); } }, });