UNPKG

@typescript-eslint/eslint-plugin

Version:
256 lines • 11.8 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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __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"); class FunctionSignature { paramTypes; restType; parameterTypeIndex = 0; static create(checker, tsNode) { const signature = checker.getResolvedSignature(tsNode); if (!signature) { return null; } const paramTypes = []; let restType = null; const parameters = signature.getParameters(); for (let i = 0; i < parameters.length; i += 1) { const param = parameters[i]; const type = checker.getTypeOfSymbolAtLocation(param, tsNode); const decl = param.getDeclarations()?.[0]; if (decl && (0, util_1.isRestParameterDeclaration)(decl)) { // is a rest param if (checker.isArrayType(type)) { restType = { type: checker.getTypeArguments(type)[0], kind: 0 /* RestTypeKind.Array */, index: i, }; } else if (checker.isTupleType(type)) { restType = { typeArguments: checker.getTypeArguments(type), kind: 1 /* RestTypeKind.Tuple */, index: i, }; } else { restType = { type, kind: 2 /* RestTypeKind.Other */, index: i, }; } break; } paramTypes.push(type); } return new this(paramTypes, restType); } hasConsumedArguments = false; constructor(paramTypes, restType) { this.paramTypes = paramTypes; this.restType = restType; } getNextParameterType() { const index = this.parameterTypeIndex; this.parameterTypeIndex += 1; if (index >= this.paramTypes.length || this.hasConsumedArguments) { if (this.restType == null) { return null; } switch (this.restType.kind) { case 1 /* RestTypeKind.Tuple */: { const typeArguments = this.restType.typeArguments; if (this.hasConsumedArguments) { // all types consumed by a rest - just assume it's the last type // there is one edge case where this is wrong, but we ignore it because // it's rare and really complicated to handle // eg: function foo(...a: [number, ...string[], number]) return typeArguments[typeArguments.length - 1]; } const typeIndex = index - this.restType.index; if (typeIndex >= typeArguments.length) { return typeArguments[typeArguments.length - 1]; } return typeArguments[typeIndex]; } case 0 /* RestTypeKind.Array */: case 2 /* RestTypeKind.Other */: return this.restType.type; } } return this.paramTypes[index]; } consumeRemainingArguments() { this.hasConsumedArguments = true; } } exports.default = (0, util_1.createRule)({ name: 'no-unsafe-argument', meta: { type: 'problem', docs: { description: 'Disallow calling a function with a value with type `any`', recommended: 'recommended', requiresTypeChecking: true, }, messages: { unsafeArgument: 'Unsafe argument of type {{sender}} assigned to a parameter of type {{receiver}}.', unsafeTupleSpread: 'Unsafe spread of a tuple type. The argument is {{sender}} and is assigned to a parameter of type {{receiver}}.', unsafeArraySpread: 'Unsafe spread of an {{sender}} array type.', unsafeSpread: 'Unsafe spread of an {{sender}} type.', }, schema: [], }, defaultOptions: [], create(context) { const services = (0, util_1.getParserServices)(context); const checker = services.program.getTypeChecker(); function describeType(type) { if (tsutils.isIntrinsicErrorType(type)) { return 'error typed'; } return `\`${checker.typeToString(type)}\``; } function describeTypeForSpread(type) { if (checker.isArrayType(type) && tsutils.isIntrinsicErrorType(checker.getTypeArguments(type)[0])) { return 'error'; } return describeType(type); } function describeTypeForTuple(type) { if (tsutils.isIntrinsicErrorType(type)) { return 'error typed'; } return `of type \`${checker.typeToString(type)}\``; } function checkUnsafeArguments(args, callee, node) { if (args.length === 0) { return; } // ignore any-typed calls as these are caught by no-unsafe-call if ((0, util_1.isTypeAnyType)(services.getTypeAtLocation(callee))) { return; } const tsNode = services.esTreeNodeToTSNodeMap.get(node); const signature = (0, util_1.nullThrows)(FunctionSignature.create(checker, tsNode), 'Expected to a signature resolved'); if (node.type === utils_1.AST_NODE_TYPES.TaggedTemplateExpression) { // Consumes the first parameter (TemplateStringsArray) of the function called with TaggedTemplateExpression. signature.getNextParameterType(); } for (const argument of args) { switch (argument.type) { // spreads consume case utils_1.AST_NODE_TYPES.SpreadElement: { const spreadArgType = services.getTypeAtLocation(argument.argument); if ((0, util_1.isTypeAnyType)(spreadArgType)) { // foo(...any) context.report({ data: { sender: describeType(spreadArgType) }, node: argument, messageId: 'unsafeSpread', }); } else if ((0, util_1.isTypeAnyArrayType)(spreadArgType, checker)) { // foo(...any[]) // TODO - we could break down the spread and compare the array type against each argument context.report({ data: { sender: describeTypeForSpread(spreadArgType) }, node: argument, messageId: 'unsafeArraySpread', }); } else if (checker.isTupleType(spreadArgType)) { // foo(...[tuple1, tuple2]) const spreadTypeArguments = checker.getTypeArguments(spreadArgType); for (const tupleType of spreadTypeArguments) { const parameterType = signature.getNextParameterType(); if (parameterType == null) { continue; } const result = (0, util_1.isUnsafeAssignment)(tupleType, parameterType, checker, // we can't pass the individual tuple members in here as this will most likely be a spread variable // not a spread array null); if (result) { context.report({ node: argument, messageId: 'unsafeTupleSpread', data: { sender: describeTypeForTuple(tupleType), receiver: describeType(parameterType), }, }); } } if (spreadArgType.target.hasRestElement) { // the last element was a rest - so all remaining defined arguments can be considered "consumed" // all remaining arguments should be compared against the rest type (if one exists) signature.consumeRemainingArguments(); } } else { // something that's iterable // handling this will be pretty complex - so we ignore it for now // TODO - handle generic iterable case } break; } default: { const parameterType = signature.getNextParameterType(); if (parameterType == null) { continue; } const argumentType = services.getTypeAtLocation(argument); const result = (0, util_1.isUnsafeAssignment)(argumentType, parameterType, checker, argument); if (result) { context.report({ node: argument, messageId: 'unsafeArgument', data: { sender: describeType(argumentType), receiver: describeType(parameterType), }, }); } } } } } return { 'CallExpression, NewExpression'(node) { checkUnsafeArguments(node.arguments, node.callee, node); }, TaggedTemplateExpression(node) { checkUnsafeArguments(node.quasi.expressions, node.tag, node); }, }; }, }); //# sourceMappingURL=no-unsafe-argument.js.map