UNPKG

@typescript-eslint/eslint-plugin

Version:
321 lines (320 loc) • 15.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("@typescript-eslint/utils"); const tsutils = __importStar(require("ts-api-utils")); const util_1 = require("../util"); var ComparisonType; (function (ComparisonType) { /** Do no assignment comparison */ ComparisonType[ComparisonType["None"] = 0] = "None"; /** Use the receiver's type for comparison */ ComparisonType[ComparisonType["Basic"] = 1] = "Basic"; /** Use the sender's contextual type for comparison */ ComparisonType[ComparisonType["Contextual"] = 2] = "Contextual"; })(ComparisonType || (ComparisonType = {})); exports.default = (0, util_1.createRule)({ name: 'no-unsafe-assignment', meta: { type: 'problem', docs: { description: 'Disallow assigning a value with type `any` to variables and properties', recommended: 'recommended', requiresTypeChecking: true, }, messages: { anyAssignment: 'Unsafe assignment of an {{sender}} value.', anyAssignmentThis: [ 'Unsafe assignment of an {{sender}} value. `this` is typed as `any`.', 'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.', ].join('\n'), unsafeArrayPattern: 'Unsafe array destructuring of an {{sender}} array value.', unsafeArrayPatternFromTuple: 'Unsafe array destructuring of a tuple element with an {{sender}} value.', unsafeArraySpread: 'Unsafe spread of an {{sender}} value in an array.', unsafeAssignment: 'Unsafe assignment of type {{sender}} to a variable of type {{receiver}}.', }, schema: [], }, defaultOptions: [], create(context) { const services = (0, util_1.getParserServices)(context); const checker = services.program.getTypeChecker(); const compilerOptions = services.program.getCompilerOptions(); const isNoImplicitThis = tsutils.isStrictCompilerOptionEnabled(compilerOptions, 'noImplicitThis'); // returns true if the assignment reported function checkArrayDestructureHelper(receiverNode, senderNode) { if (receiverNode.type !== utils_1.AST_NODE_TYPES.ArrayPattern) { return false; } const senderTsNode = services.esTreeNodeToTSNodeMap.get(senderNode); const senderType = services.getTypeAtLocation(senderNode); return checkArrayDestructure(receiverNode, senderType, senderTsNode); } // returns true if the assignment reported function checkArrayDestructure(receiverNode, senderType, senderNode) { // any array // const [x] = ([] as any[]); if ((0, util_1.isTypeAnyArrayType)(senderType, checker)) { context.report({ node: receiverNode, messageId: 'unsafeArrayPattern', data: createData(senderType), }); return false; } if (!checker.isTupleType(senderType)) { return true; } const tupleElements = checker.getTypeArguments(senderType); // tuple with any // const [x] = [1 as any]; let didReport = false; for (let receiverIndex = 0; receiverIndex < receiverNode.elements.length; receiverIndex += 1) { const receiverElement = receiverNode.elements[receiverIndex]; if (!receiverElement) { continue; } if (receiverElement.type === utils_1.AST_NODE_TYPES.RestElement) { // don't handle rests as they're not a 1:1 assignment continue; } const senderType = tupleElements[receiverIndex]; if (!senderType) { continue; } // check for the any type first so we can handle [[[x]]] = [any] if ((0, util_1.isTypeAnyType)(senderType)) { context.report({ node: receiverElement, messageId: 'unsafeArrayPatternFromTuple', data: createData(senderType), }); // we want to report on every invalid element in the tuple didReport = true; } else if (receiverElement.type === utils_1.AST_NODE_TYPES.ArrayPattern) { didReport = checkArrayDestructure(receiverElement, senderType, senderNode); } else if (receiverElement.type === utils_1.AST_NODE_TYPES.ObjectPattern) { didReport = checkObjectDestructure(receiverElement, senderType, senderNode); } } return didReport; } // returns true if the assignment reported function checkObjectDestructureHelper(receiverNode, senderNode) { if (receiverNode.type !== utils_1.AST_NODE_TYPES.ObjectPattern) { return false; } const senderTsNode = services.esTreeNodeToTSNodeMap.get(senderNode); const senderType = services.getTypeAtLocation(senderNode); return checkObjectDestructure(receiverNode, senderType, senderTsNode); } // returns true if the assignment reported function checkObjectDestructure(receiverNode, senderType, senderNode) { const properties = new Map(senderType .getProperties() .map(property => [ property.getName(), checker.getTypeOfSymbolAtLocation(property, senderNode), ])); let didReport = false; for (const receiverProperty of receiverNode.properties) { if (receiverProperty.type === utils_1.AST_NODE_TYPES.RestElement) { // don't bother checking rest continue; } let key; if (!receiverProperty.computed) { key = receiverProperty.key.type === utils_1.AST_NODE_TYPES.Identifier ? receiverProperty.key.name : String(receiverProperty.key.value); } else if (receiverProperty.key.type === utils_1.AST_NODE_TYPES.Literal) { key = String(receiverProperty.key.value); } else if (receiverProperty.key.type === utils_1.AST_NODE_TYPES.TemplateLiteral && receiverProperty.key.quasis.length === 1) { key = String(receiverProperty.key.quasis[0].value.cooked); } else { // can't figure out the name, so skip it continue; } const senderType = properties.get(key); if (!senderType) { continue; } // check for the any type first so we can handle {x: {y: z}} = {x: any} if ((0, util_1.isTypeAnyType)(senderType)) { context.report({ node: receiverProperty.value, messageId: 'unsafeArrayPatternFromTuple', data: createData(senderType), }); didReport = true; } else if (receiverProperty.value.type === utils_1.AST_NODE_TYPES.ArrayPattern) { didReport = checkArrayDestructure(receiverProperty.value, senderType, senderNode); } else if (receiverProperty.value.type === utils_1.AST_NODE_TYPES.ObjectPattern) { didReport = checkObjectDestructure(receiverProperty.value, senderType, senderNode); } } return didReport; } // returns true if the assignment reported function checkAssignment(receiverNode, senderNode, reportingNode, comparisonType) { const receiverTsNode = services.esTreeNodeToTSNodeMap.get(receiverNode); const receiverType = comparisonType === ComparisonType.Contextual ? ((0, util_1.getContextualType)(checker, receiverTsNode) ?? services.getTypeAtLocation(receiverNode)) : services.getTypeAtLocation(receiverNode); const senderType = services.getTypeAtLocation(senderNode); if ((0, util_1.isTypeAnyType)(senderType)) { // handle cases when we assign any ==> unknown. if ((0, util_1.isTypeUnknownType)(receiverType)) { return false; } let messageId = 'anyAssignment'; if (!isNoImplicitThis) { // `var foo = this` const thisExpression = (0, util_1.getThisExpression)(senderNode); if (thisExpression && (0, util_1.isTypeAnyType)((0, util_1.getConstrainedTypeAtLocation)(services, thisExpression))) { messageId = 'anyAssignmentThis'; } } context.report({ node: reportingNode, messageId, data: createData(senderType), }); return true; } if (comparisonType === ComparisonType.None) { return false; } const result = (0, util_1.isUnsafeAssignment)(senderType, receiverType, checker, senderNode); if (!result) { return false; } const { receiver, sender } = result; context.report({ node: reportingNode, messageId: 'unsafeAssignment', data: createData(sender, receiver), }); return true; } function getComparisonType(typeAnnotation) { return typeAnnotation ? // if there's a type annotation, we can do a comparison ComparisonType.Basic : // no type annotation means the variable's type will just be inferred, thus equal ComparisonType.None; } function createData(senderType, receiverType) { if (receiverType) { return { receiver: `\`${checker.typeToString(receiverType)}\``, sender: `\`${checker.typeToString(senderType)}\``, }; } return { sender: tsutils.isIntrinsicErrorType(senderType) ? 'error typed' : '`any`', }; } return { 'AccessorProperty[value != null]'(node) { checkAssignment(node.key, node.value, node, getComparisonType(node.typeAnnotation)); }, 'AssignmentExpression[operator = "="], AssignmentPattern'(node) { let didReport = checkAssignment(node.left, node.right, node, // the variable already has some form of a type to compare against ComparisonType.Basic); if (!didReport) { didReport = checkArrayDestructureHelper(node.left, node.right); } if (!didReport) { checkObjectDestructureHelper(node.left, node.right); } }, 'PropertyDefinition[value != null]'(node) { checkAssignment(node.key, node.value, node, getComparisonType(node.typeAnnotation)); }, 'VariableDeclarator[init != null]'(node) { const init = (0, util_1.nullThrows)(node.init, util_1.NullThrowsReasons.MissingToken(node.type, 'init')); let didReport = checkAssignment(node.id, init, node, getComparisonType(node.id.typeAnnotation)); if (!didReport) { didReport = checkArrayDestructureHelper(node.id, init); } if (!didReport) { checkObjectDestructureHelper(node.id, init); } }, // object pattern props are checked via assignments ':not(ObjectPattern) > Property'(node) { if (node.value.type === utils_1.AST_NODE_TYPES.AssignmentPattern || node.value.type === utils_1.AST_NODE_TYPES.TSEmptyBodyFunctionExpression) { // handled by other selector return; } checkAssignment(node.key, node.value, node, ComparisonType.Contextual); }, 'ArrayExpression > SpreadElement'(node) { const restType = services.getTypeAtLocation(node.argument); if ((0, util_1.isTypeAnyType)(restType) || (0, util_1.isTypeAnyArrayType)(restType, checker)) { context.report({ node, messageId: 'unsafeArraySpread', data: createData(restType), }); } }, 'JSXAttribute[value != null]'(node) { const value = (0, util_1.nullThrows)(node.value, util_1.NullThrowsReasons.MissingToken(node.type, 'value')); if (value.type !== utils_1.AST_NODE_TYPES.JSXExpressionContainer || value.expression.type === utils_1.AST_NODE_TYPES.JSXEmptyExpression) { return; } checkAssignment(node.name, value.expression, value.expression, ComparisonType.Contextual); }, }; }, });