tslint-immutable
Version:
TSLint rules to disable mutation in TypeScript.
180 lines • 7.29 kB
JavaScript
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
;