UNPKG

tslint-immutable

Version:

TSLint rules to disable mutation in TypeScript.

180 lines 7.29 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var ts = require("typescript"); var utils = require("tsutils/typeguard/2.8"); var util_1 = require("tsutils/util"); var check_node_1 = require("./shared/check-node"); var Ignore = require("./shared/ignore"); var typeguard_1 = require("./shared/typeguard"); // tslint:disable-next-line:variable-name exports.Rule = check_node_1.createCheckNodeTypedRule(checkTypedNode, "Mutating an array is not allowed."); function isArrayType(type) { return Boolean(type.symbol && type.symbol.name === "Array"); } exports.isArrayType = isArrayType; function isArrayConstructorType(type) { return Boolean(type.symbol && type.symbol.name === "ArrayConstructor"); } exports.isArrayConstructorType = isArrayConstructorType; var forbidUnaryOps = [ ts.SyntaxKind.PlusPlusToken, ts.SyntaxKind.MinusMinusToken ]; /** * Methods that mutate an array. * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Methods#Mutator_methods */ var mutatorMethods = [ "copyWithin", "fill", "pop", "push", "reverse", "shift", "sort", "splice", "unshift" ]; /** * Methods that return a new array without mutating the original. * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Methods#Accessor_methods * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Iteration_methods */ var newArrayReturningMethods = [ "concat", "slice", "filter", "map", "reduce", "reduceRight" ]; /** * Functions that create a new array. * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Methods */ var constructorFunctions = ["from", "of"]; function checkTypedNode(node, ctx, checker) { if (utils.isBinaryExpression(node)) { return checkBinaryExpression(node, ctx, checker); } if (utils.isDeleteExpression(node)) { return checkDeleteExpression(node, ctx, checker); } if (utils.isPrefixUnaryExpression(node)) { return checkPrefixUnaryExpression(node, ctx, checker); } if (utils.isPostfixUnaryExpression(node)) { return checkPostfixUnaryExpression(node, ctx, checker); } if (utils.isCallExpression(node)) { return checkCallExpression(node, ctx, checker); } return { invalidNodes: [] }; } /** * No assignment with array[index] on the left. * No assignment with array.property on the left (e.g. array.length). */ function checkBinaryExpression(node, ctx, checker) { if (!Ignore.isIgnoredPrefix(node.getText(node.getSourceFile()), ctx.options.ignorePrefix) && util_1.isAssignmentKind(node.operatorToken.kind) && typeguard_1.isAccessExpression(node.left)) { var leftExpressionType = checker.getTypeAtLocation(getRootAccessExpression(node.left).expression); if (isArrayType(leftExpressionType)) { return { invalidNodes: [check_node_1.createInvalidNode(node, [])] }; } } return { invalidNodes: [] }; } /** * No deleting array properties/values. */ function checkDeleteExpression(node, ctx, checker) { if (!Ignore.isIgnoredPrefix(node.expression.getText(node.getSourceFile()), ctx.options.ignorePrefix) && typeguard_1.isAccessExpression(node.expression)) { var expressionType = checker.getTypeAtLocation(getRootAccessExpression(node.expression).expression); if (isArrayType(expressionType)) { return { invalidNodes: [check_node_1.createInvalidNode(node, [])] }; } } return { invalidNodes: [] }; } /** * No prefix inc/dec. */ function checkPrefixUnaryExpression(node, ctx, checker) { if (!Ignore.isIgnoredPrefix(node.operand.getText(node.getSourceFile()), ctx.options.ignorePrefix) && typeguard_1.isAccessExpression(node.operand) && forbidUnaryOps.some(function (o) { return o === node.operator; })) { var operandExpressionType = checker.getTypeAtLocation(getRootAccessExpression(node.operand).expression); if (isArrayType(operandExpressionType)) { return { invalidNodes: [check_node_1.createInvalidNode(node, [])] }; } } return { invalidNodes: [] }; } /** * No postfix inc/dec. */ function checkPostfixUnaryExpression(node, ctx, checker) { if (!Ignore.isIgnoredPrefix(node.getText(node.getSourceFile()), ctx.options.ignorePrefix) && typeguard_1.isAccessExpression(node.operand) && forbidUnaryOps.some(function (o) { return o === node.operator; })) { var operandExpressionType = checker.getTypeAtLocation(getRootAccessExpression(node.operand).expression); if (isArrayType(operandExpressionType)) { return { invalidNodes: [check_node_1.createInvalidNode(node, [])] }; } } return { invalidNodes: [] }; } /** * No calls to array mutating methods. */ function checkCallExpression(node, ctx, checker) { if (!Ignore.isIgnoredPrefix(node.getText(node.getSourceFile()), ctx.options.ignorePrefix) && utils.isPropertyAccessExpression(node.expression) && (!(ctx.options.ignoreNewArray || ctx.options.ignoreMutationFollowingAccessor) || !isInChainCallAndFollowsNew(node.expression, checker)) && mutatorMethods.some(function (m) { return m === node.expression.name.text; })) { // Do the type checking as late as possible (as it is expensive). var expressionType = checker.getTypeAtLocation(getRootAccessExpression(node.expression).expression); if (isArrayType(expressionType)) { return { invalidNodes: [check_node_1.createInvalidNode(node, [])] }; } } return { invalidNodes: [] }; } /** * Check if the given the given PropertyAccessExpression is part of a chain and * immediately follows a method/function call that returns a new array. * * If this is the case, then the given PropertyAccessExpression is allowed to be a mutator method call. */ function isInChainCallAndFollowsNew(node, checker) { return (utils.isArrayLiteralExpression(node.expression) || (utils.isNewExpression(node.expression) && isArrayConstructorType(checker.getTypeAtLocation(node.expression.expression))) || (utils.isCallExpression(node.expression) && utils.isPropertyAccessExpression(node.expression.expression) && constructorFunctions.some(isExpected(node.expression.expression.name.text)) && isArrayConstructorType(checker.getTypeAtLocation(node.expression.expression.expression))) || (utils.isCallExpression(node.expression) && utils.isPropertyAccessExpression(node.expression.expression) && newArrayReturningMethods.some(isExpected(node.expression.expression.name.text)))); } /** * Returns a function that checks if the given value is the same as the expected value. */ function isExpected(expected) { return function (actual) { return actual === expected; }; } function getRootAccessExpression(n) { if (typeguard_1.isAccessExpression(n.expression)) { return getRootAccessExpression(n.expression); } return n; } //# sourceMappingURL=noArrayMutationRule.js.map