UNPKG

6.95 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3var ts = require("typescript");
4var utils = require("tsutils/typeguard/2.8");
5var util_1 = require("tsutils/util");
6var check_node_1 = require("./shared/check-node");
7var Ignore = require("./shared/ignore");
8var typeguard_1 = require("./shared/typeguard");
9// tslint:disable-next-line:variable-name
10exports.Rule = check_node_1.createCheckNodeTypedRule(checkTypedNode, "Mutating an array is not allowed.");
11function isArrayType(type) {
12 return Boolean(type.symbol && type.symbol.name === "Array");
13}
14exports.isArrayType = isArrayType;
15function isArrayConstructorType(type) {
16 return Boolean(type.symbol && type.symbol.name === "ArrayConstructor");
17}
18exports.isArrayConstructorType = isArrayConstructorType;
19var forbidUnaryOps = [
20 ts.SyntaxKind.PlusPlusToken,
21 ts.SyntaxKind.MinusMinusToken
22];
23/**
24 * Methods that mutate an array.
25 *
26 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Methods#Mutator_methods
27 */
28var mutatorMethods = [
29 "copyWithin",
30 "fill",
31 "pop",
32 "push",
33 "reverse",
34 "shift",
35 "sort",
36 "splice",
37 "unshift"
38];
39/**
40 * Methods that return a new array without mutating the original.
41 *
42 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Methods#Accessor_methods
43 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Iteration_methods
44 */
45var newArrayReturningMethods = [
46 "concat",
47 "slice",
48 "filter",
49 "map",
50 "reduce",
51 "reduceRight"
52];
53/**
54 * Functions that create a new array.
55 *
56 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Methods
57 */
58var constructorFunctions = ["from", "of"];
59function checkTypedNode(node, ctx, checker) {
60 return { invalidNodes: getInvalidNodes(node, ctx, checker) };
61}
62function getInvalidNodes(node, ctx, checker) {
63 if (utils.isBinaryExpression(node)) {
64 return checkBinaryExpression(node, ctx, checker);
65 }
66 if (utils.isDeleteExpression(node)) {
67 return checkDeleteExpression(node, ctx, checker);
68 }
69 if (utils.isPrefixUnaryExpression(node)) {
70 return checkPrefixUnaryExpression(node, ctx, checker);
71 }
72 if (utils.isPostfixUnaryExpression(node)) {
73 return checkPostfixUnaryExpression(node, ctx, checker);
74 }
75 if (utils.isCallExpression(node)) {
76 return checkCallExpression(node, ctx, checker);
77 }
78 return [];
79}
80/**
81 * No assignment with array[index] on the left.
82 * No assignment with array.property on the left (e.g. array.length).
83 */
84function checkBinaryExpression(node, ctx, checker) {
85 if (!Ignore.isIgnoredPrefix(node.getText(node.getSourceFile()), ctx.options.ignorePrefix) &&
86 utils.isBinaryExpression(node) &&
87 util_1.isAssignmentKind(node.operatorToken.kind) &&
88 typeguard_1.isAccessExpression(node.left)) {
89 var leftExpressionType = checker.getTypeAtLocation(node.left.expression);
90 if (isArrayType(leftExpressionType)) {
91 return [check_node_1.createInvalidNode(node, [])];
92 }
93 }
94 return [];
95}
96/**
97 * No deleting array properties/values.
98 */
99function checkDeleteExpression(node, ctx, checker) {
100 if (!Ignore.isIgnoredPrefix(node.expression.getText(node.getSourceFile()), ctx.options.ignorePrefix) &&
101 typeguard_1.isAccessExpression(node.expression)) {
102 var expressionType = checker.getTypeAtLocation(node.expression.expression);
103 if (isArrayType(expressionType)) {
104 return [check_node_1.createInvalidNode(node, [])];
105 }
106 }
107 return [];
108}
109/**
110 * No prefix inc/dec.
111 */
112function checkPrefixUnaryExpression(node, ctx, checker) {
113 if (!Ignore.isIgnoredPrefix(node.operand.getText(node.getSourceFile()), ctx.options.ignorePrefix) &&
114 typeguard_1.isAccessExpression(node.operand) &&
115 forbidUnaryOps.some(function (o) { return o === node.operator; })) {
116 var operandExpressionType = checker.getTypeAtLocation(node.operand.expression);
117 if (isArrayType(operandExpressionType)) {
118 return [check_node_1.createInvalidNode(node, [])];
119 }
120 }
121 return [];
122}
123/**
124 * No postfix inc/dec.
125 */
126function checkPostfixUnaryExpression(node, ctx, checker) {
127 if (!Ignore.isIgnoredPrefix(node.getText(node.getSourceFile()), ctx.options.ignorePrefix) &&
128 typeguard_1.isAccessExpression(node.operand) &&
129 forbidUnaryOps.some(function (o) { return o === node.operator; })) {
130 var operandExpressionType = checker.getTypeAtLocation(node.operand.expression);
131 if (isArrayType(operandExpressionType)) {
132 return [check_node_1.createInvalidNode(node, [])];
133 }
134 }
135 return [];
136}
137/**
138 * No calls to array mutating methods.
139 */
140function checkCallExpression(node, ctx, checker) {
141 if (!Ignore.isIgnoredPrefix(node.getText(node.getSourceFile()), ctx.options.ignorePrefix) &&
142 utils.isPropertyAccessExpression(node.expression) &&
143 (!(ctx.options.ignoreNewArray || ctx.options.ignoreMutationFollowingAccessor) ||
144 !isInChainCallAndFollowsNew(node.expression, checker)) &&
145 mutatorMethods.some(function (m) { return m === node.expression.name.text; })) {
146 // Do the type checking as late as possible (as it is expensive).
147 var expressionType = checker.getTypeAtLocation(node.expression.expression);
148 if (isArrayType(expressionType)) {
149 return [check_node_1.createInvalidNode(node, [])];
150 }
151 }
152 return [];
153}
154/**
155 * Check if the given the given PropertyAccessExpression is part of a chain and
156 * immediately follows a method/function call that returns a new array.
157 *
158 * If this is the case, then the given PropertyAccessExpression is allowed to be a mutator method call.
159 */
160function isInChainCallAndFollowsNew(node, checker) {
161 return (utils.isArrayLiteralExpression(node.expression) ||
162 (utils.isNewExpression(node.expression) &&
163 isArrayConstructorType(checker.getTypeAtLocation(node.expression.expression))) ||
164 (utils.isCallExpression(node.expression) &&
165 utils.isPropertyAccessExpression(node.expression.expression) &&
166 constructorFunctions.some(isExpected(node.expression.expression.name.text)) &&
167 isArrayConstructorType(checker.getTypeAtLocation(node.expression.expression.expression))) ||
168 (utils.isCallExpression(node.expression) &&
169 utils.isPropertyAccessExpression(node.expression.expression) &&
170 newArrayReturningMethods.some(isExpected(node.expression.expression.name.text))));
171}
172/**
173 * Returns a function that checks if the given value is the same as the expected value.
174 */
175function isExpected(expected) {
176 return function (actual) { return actual === expected; };
177}
178//# sourceMappingURL=noArrayMutationRule.js.map
\No newline at end of file