200 lines
9.4 KiB
JavaScript
200 lines
9.4 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2016 Palantir Technologies, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
"use strict";
|
|
var __extends = (this && this.__extends) || (function () {
|
|
var extendStatics = Object.setPrototypeOf ||
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
return function (d, b) {
|
|
extendStatics(d, b);
|
|
function __() { this.constructor = d; }
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
var ts = require("typescript");
|
|
var Lint = require("../index");
|
|
var utils_1 = require("../language/utils");
|
|
var Rule = (function (_super) {
|
|
__extends(Rule, _super);
|
|
function Rule() {
|
|
return _super !== null && _super.apply(this, arguments) || this;
|
|
}
|
|
Rule.prototype.apply = function (sourceFile) {
|
|
return this.applyWithWalker(new PreferForOfWalker(sourceFile, this.getOptions()));
|
|
};
|
|
return Rule;
|
|
}(Lint.Rules.AbstractRule));
|
|
/* tslint:disable:object-literal-sort-keys */
|
|
Rule.metadata = {
|
|
ruleName: "prefer-for-of",
|
|
description: "Recommends a 'for-of' loop over a standard 'for' loop if the index is only used to access the array being iterated.",
|
|
rationale: "A for(... of ...) loop is easier to implement and read when the index is not needed.",
|
|
optionsDescription: "Not configurable.",
|
|
options: null,
|
|
optionExamples: ["true"],
|
|
type: "typescript",
|
|
typescriptOnly: false,
|
|
};
|
|
/* tslint:enable:object-literal-sort-keys */
|
|
Rule.FAILURE_STRING = "Expected a 'for-of' loop instead of a 'for' loop with this simple iteration";
|
|
exports.Rule = Rule;
|
|
var PreferForOfWalker = (function (_super) {
|
|
__extends(PreferForOfWalker, _super);
|
|
function PreferForOfWalker() {
|
|
return _super !== null && _super.apply(this, arguments) || this;
|
|
}
|
|
PreferForOfWalker.prototype.createScope = function () { }; // tslint:disable-line:no-empty
|
|
PreferForOfWalker.prototype.createBlockScope = function () {
|
|
return new Map();
|
|
};
|
|
PreferForOfWalker.prototype.visitForStatement = function (node) {
|
|
var arrayNodeInfo = this.getForLoopHeaderInfo(node);
|
|
var currentBlockScope = this.getCurrentBlockScope();
|
|
var indexVariableName;
|
|
if (node.incrementor != null && arrayNodeInfo != null) {
|
|
var indexVariable = arrayNodeInfo.indexVariable, arrayToken = arrayNodeInfo.arrayToken;
|
|
indexVariableName = indexVariable.getText();
|
|
// store `for` loop state
|
|
currentBlockScope.set(indexVariableName, {
|
|
arrayToken: arrayToken,
|
|
forLoopEndPosition: node.incrementor.end + 1,
|
|
onlyArrayReadAccess: true,
|
|
});
|
|
}
|
|
_super.prototype.visitForStatement.call(this, node);
|
|
if (indexVariableName != null) {
|
|
var incrementorState = currentBlockScope.get(indexVariableName);
|
|
if (incrementorState.onlyArrayReadAccess) {
|
|
this.addFailureFromStartToEnd(node.getStart(), incrementorState.forLoopEndPosition, Rule.FAILURE_STRING);
|
|
}
|
|
// remove current `for` loop state
|
|
currentBlockScope.delete(indexVariableName);
|
|
}
|
|
};
|
|
PreferForOfWalker.prototype.visitIdentifier = function (node) {
|
|
var incrementorScope = this.findBlockScope(function (scope) { return scope.has(node.text); });
|
|
if (incrementorScope != null) {
|
|
var incrementorState = incrementorScope.get(node.text);
|
|
// check if the identifier is an iterator and is currently in the `for` loop body
|
|
if (incrementorState != null && incrementorState.arrayToken != null && incrementorState.forLoopEndPosition < node.getStart()) {
|
|
// check if iterator is used for something other than reading data from array
|
|
if (node.parent.kind === ts.SyntaxKind.ElementAccessExpression) {
|
|
var elementAccess = node.parent;
|
|
var arrayIdentifier = utils_1.unwrapParentheses(elementAccess.expression);
|
|
if (incrementorState.arrayToken.getText() !== arrayIdentifier.getText()) {
|
|
// iterator used in array other than one iterated over
|
|
incrementorState.onlyArrayReadAccess = false;
|
|
}
|
|
else if (elementAccess.parent != null && utils_1.isAssignment(elementAccess.parent)) {
|
|
// array position is assigned a new value
|
|
incrementorState.onlyArrayReadAccess = false;
|
|
}
|
|
}
|
|
else {
|
|
incrementorState.onlyArrayReadAccess = false;
|
|
}
|
|
}
|
|
_super.prototype.visitIdentifier.call(this, node);
|
|
}
|
|
};
|
|
// returns the iterator and array of a `for` loop if the `for` loop is basic. Otherwise, `null`
|
|
PreferForOfWalker.prototype.getForLoopHeaderInfo = function (forLoop) {
|
|
var indexVariableName;
|
|
var indexVariable;
|
|
// assign `indexVariableName` if initializer is simple and starts at 0
|
|
if (forLoop.initializer != null && forLoop.initializer.kind === ts.SyntaxKind.VariableDeclarationList) {
|
|
var syntaxList = forLoop.initializer.getChildAt(1);
|
|
if (syntaxList.kind === ts.SyntaxKind.SyntaxList && syntaxList.getChildCount() === 1) {
|
|
var assignment = syntaxList.getChildAt(0);
|
|
if (assignment.kind === ts.SyntaxKind.VariableDeclaration && assignment.getChildCount() === 3) {
|
|
var value = assignment.getChildAt(2).getText();
|
|
if (value === "0") {
|
|
indexVariable = assignment.getChildAt(0);
|
|
indexVariableName = indexVariable.getText();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// ensure `for` condition
|
|
if (indexVariableName == null
|
|
|| forLoop.condition == null
|
|
|| forLoop.condition.kind !== ts.SyntaxKind.BinaryExpression
|
|
|| forLoop.condition.getChildAt(0).getText() !== indexVariableName
|
|
|| forLoop.condition.getChildAt(1).getText() !== "<") {
|
|
return null;
|
|
}
|
|
if (forLoop.incrementor == null || !this.isIncremented(forLoop.incrementor, indexVariableName)) {
|
|
return null;
|
|
}
|
|
// ensure that the condition checks a `length` property
|
|
var conditionRight = forLoop.condition.getChildAt(2);
|
|
if (conditionRight.kind === ts.SyntaxKind.PropertyAccessExpression) {
|
|
var propertyAccess = conditionRight;
|
|
if (indexVariable != null && propertyAccess.name.getText() === "length") {
|
|
return { indexVariable: indexVariable, arrayToken: utils_1.unwrapParentheses(propertyAccess.expression) };
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
PreferForOfWalker.prototype.isIncremented = function (node, indexVariableName) {
|
|
if (node == null) {
|
|
return false;
|
|
}
|
|
// ensure variable is incremented
|
|
if (node.kind === ts.SyntaxKind.PrefixUnaryExpression) {
|
|
var incrementor = node;
|
|
if (incrementor.operator === ts.SyntaxKind.PlusPlusToken && incrementor.operand.getText() === indexVariableName) {
|
|
// x++
|
|
return true;
|
|
}
|
|
}
|
|
else if (node.kind === ts.SyntaxKind.PostfixUnaryExpression) {
|
|
var incrementor = node;
|
|
if (incrementor.operator === ts.SyntaxKind.PlusPlusToken && incrementor.operand.getText() === indexVariableName) {
|
|
// ++x
|
|
return true;
|
|
}
|
|
}
|
|
else if (node.kind === ts.SyntaxKind.BinaryExpression) {
|
|
var binaryExpression = node;
|
|
if (binaryExpression.operatorToken.getText() === "+="
|
|
&& binaryExpression.left.getText() === indexVariableName
|
|
&& binaryExpression.right.getText() === "1") {
|
|
// x += 1
|
|
return true;
|
|
}
|
|
if (binaryExpression.operatorToken.getText() === "="
|
|
&& binaryExpression.left.getText() === indexVariableName) {
|
|
var addExpression = binaryExpression.right;
|
|
if (addExpression.operatorToken.getText() === "+") {
|
|
if (addExpression.right.getText() === indexVariableName && addExpression.left.getText() === "1") {
|
|
// x = 1 + x
|
|
return true;
|
|
}
|
|
else if (addExpression.left.getText() === indexVariableName && addExpression.right.getText() === "1") {
|
|
// x = x + 1
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
return PreferForOfWalker;
|
|
}(Lint.BlockScopeAwareRuleWalker));
|