diff options
Diffstat (limited to 'tools/eslint/lib/rules/indent.js')
-rw-r--r-- | tools/eslint/lib/rules/indent.js | 220 |
1 files changed, 155 insertions, 65 deletions
diff --git a/tools/eslint/lib/rules/indent.js b/tools/eslint/lib/rules/indent.js index 3c0c9827d8..b24f942d1a 100644 --- a/tools/eslint/lib/rules/indent.js +++ b/tools/eslint/lib/rules/indent.js @@ -11,8 +11,8 @@ //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -var util = require("util"); -var lodash = require("lodash"); +let util = require("util"); +let lodash = require("lodash"); module.exports = { meta: { @@ -71,6 +71,10 @@ module.exports = { outerIIFEBody: { type: "integer", minimum: 0 + }, + MemberExpression: { + type: "integer", + minimum: 0 } }, additionalProperties: false @@ -80,12 +84,12 @@ module.exports = { create: function(context) { - var MESSAGE = "Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}."; - var DEFAULT_VARIABLE_INDENT = 1; + let MESSAGE = "Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}."; + let DEFAULT_VARIABLE_INDENT = 1; - var indentType = "space"; - var indentSize = 4; - var options = { + let indentType = "space"; + let indentSize = 4; + let options = { SwitchCase: 0, VariableDeclarator: { var: DEFAULT_VARIABLE_INDENT, @@ -95,7 +99,7 @@ module.exports = { outerIIFEBody: null }; - var sourceCode = context.getSourceCode(); + let sourceCode = context.getSourceCode(); if (context.options.length) { if (context.options[0] === "tab") { @@ -107,10 +111,10 @@ module.exports = { } if (context.options[1]) { - var opts = context.options[1]; + let opts = context.options[1]; options.SwitchCase = opts.SwitchCase || 0; - var variableDeclaratorRules = opts.VariableDeclarator; + let variableDeclaratorRules = opts.VariableDeclarator; if (typeof variableDeclaratorRules === "number") { options.VariableDeclarator = { @@ -125,15 +129,19 @@ module.exports = { if (typeof opts.outerIIFEBody === "number") { options.outerIIFEBody = opts.outerIIFEBody; } + + if (typeof opts.MemberExpression === "number") { + options.MemberExpression = opts.MemberExpression; + } } } - var indentPattern = { + let indentPattern = { normal: indentType === "space" ? /^ +/ : /^\t+/, excludeCommas: indentType === "space" ? /^[ ,]+/ : /^[\t,]+/ }; - var caseIndentStore = {}; + let caseIndentStore = {}; /** * Reports a given indent violation and properly pluralizes the message @@ -145,13 +153,13 @@ module.exports = { * @returns {void} */ function report(node, needed, gotten, loc, isLastNodeCheck) { - var msgContext = { + let msgContext = { needed: needed, type: indentType, characters: needed === 1 ? "character" : "characters", gotten: gotten }; - var indentChar = indentType === "space" ? " " : "\t"; + let indentChar = indentType === "space" ? " " : "\t"; /** * Responsible for fixing the indentation issue fix @@ -159,10 +167,10 @@ module.exports = { * @private */ function getFixerFunction() { - var rangeToFix = []; + let rangeToFix = []; if (needed > gotten) { - var spaces = "" + new Array(needed - gotten + 1).join(indentChar); // replace with repeat in future + let spaces = "" + new Array(needed - gotten + 1).join(indentChar); // replace with repeat in future if (isLastNodeCheck === true) { rangeToFix = [ @@ -224,10 +232,10 @@ module.exports = { * @returns {int} Indent */ function getNodeIndent(node, byLastLine, excludeCommas) { - var token = byLastLine ? sourceCode.getLastToken(node) : sourceCode.getFirstToken(node); - var src = sourceCode.getText(token, token.loc.start.column); - var regExp = excludeCommas ? indentPattern.excludeCommas : indentPattern.normal; - var indent = regExp.exec(src); + let token = byLastLine ? sourceCode.getLastToken(node) : sourceCode.getFirstToken(node); + let src = sourceCode.getText(token, token.loc.start.column); + let regExp = excludeCommas ? indentPattern.excludeCommas : indentPattern.normal; + let indent = regExp.exec(src); return indent ? indent[0].length : 0; } @@ -239,7 +247,7 @@ module.exports = { * @returns {boolean} true if its the first in the its start line */ function isNodeFirstInLine(node, byEndLocation) { - var firstToken = byEndLocation === true ? sourceCode.getLastToken(node, 1) : sourceCode.getTokenBefore(node), + let firstToken = byEndLocation === true ? sourceCode.getLastToken(node, 1) : sourceCode.getTokenBefore(node), startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line, endLine = firstToken ? firstToken.loc.end.line : -1; @@ -254,7 +262,7 @@ module.exports = { * @returns {void} */ function checkNodeIndent(node, indent, excludeCommas) { - var nodeIndent = getNodeIndent(node, false, excludeCommas); + let nodeIndent = getNodeIndent(node, false, excludeCommas); if ( node.type !== "ArrayExpression" && node.type !== "ObjectExpression" && @@ -274,7 +282,7 @@ module.exports = { function checkNodesIndent(nodes, indent, excludeCommas) { nodes.forEach(function(node) { if (node.type === "IfStatement" && node.alternate) { - var elseToken = sourceCode.getTokenBefore(node.alternate); + let elseToken = sourceCode.getTokenBefore(node.alternate); checkNodeIndent(elseToken, indent, excludeCommas); } @@ -289,8 +297,8 @@ module.exports = { * @returns {void} */ function checkLastNodeLineIndent(node, lastLineIndent) { - var lastToken = sourceCode.getLastToken(node); - var endIndent = getNodeIndent(lastToken, true); + let lastToken = sourceCode.getLastToken(node); + let endIndent = getNodeIndent(lastToken, true); if (endIndent !== lastLineIndent && isNodeFirstInLine(node, true)) { report( @@ -310,7 +318,7 @@ module.exports = { * @returns {void} */ function checkFirstNodeLineIndent(node, firstLineIndent) { - var startIndent = getNodeIndent(node, false); + let startIndent = getNodeIndent(node, false); if (startIndent !== firstLineIndent && isNodeFirstInLine(node)) { report( @@ -323,19 +331,40 @@ module.exports = { } /** - * Returns the VariableDeclarator based on the current node + * Returns a parent node of given node based on a specified type * if not present then return null * @param {ASTNode} node node to examine + * @param {string} type type that is being looked for * @returns {ASTNode|void} if found then node otherwise null */ - function getVariableDeclaratorNode(node) { - var parent = node.parent; + function getParentNodeByType(node, type) { + let parent = node.parent; - while (parent.type !== "VariableDeclarator" && parent.type !== "Program") { + while (parent.type !== type && parent.type !== "Program") { parent = parent.parent; } - return parent.type === "VariableDeclarator" ? parent : null; + return parent.type === type ? parent : null; + } + + /** + * Returns the VariableDeclarator based on the current node + * if not present then return null + * @param {ASTNode} node node to examine + * @returns {ASTNode|void} if found then node otherwise null + */ + function getVariableDeclaratorNode(node) { + return getParentNodeByType(node, "VariableDeclarator"); + } + + /** + * Returns the ExpressionStatement based on the current node + * if not present then return null + * @param {ASTNode} node node to examine + * @returns {ASTNode|void} if found then node otherwise null + */ + function getAssignmentExpressionNode(node) { + return getParentNodeByType(node, "AssignmentExpression"); } /** @@ -358,7 +387,7 @@ module.exports = { * @returns {boolean} True if arguments are multi-line */ function isArgBeforeCalleeNodeMultiline(node) { - var parent = node.parent; + let parent = node.parent; if (parent.arguments.length >= 2 && parent.arguments[1] === node) { return parent.arguments[0].loc.end.line > parent.arguments[0].loc.start.line; @@ -367,19 +396,46 @@ module.exports = { return false; } - /** + /** * Check to see if the node is a file level IIFE * @param {ASTNode} node The function node to check. * @returns {boolean} True if the node is the outer IIFE */ function isOuterIIFE(node) { - var parent = node.parent; + let parent = node.parent; + let stmt = parent.parent; - return ( - parent.type === "CallExpression" && - parent.callee === node && - parent.parent.type === "ExpressionStatement" && - parent.parent.parent && parent.parent.parent.type === "Program" + /* + * Verify that the node is an IIEF + */ + if ( + parent.type !== "CallExpression" || + parent.callee !== node) { + + return false; + } + + /* + * Navigate legal ancestors to determine whether this IIEF is outer + */ + while ( + stmt.type === "UnaryExpression" && ( + stmt.operator === "!" || + stmt.operator === "~" || + stmt.operator === "+" || + stmt.operator === "-") || + stmt.type === "AssignmentExpression" || + stmt.type === "LogicalExpression" || + stmt.type === "SequenceExpression" || + stmt.type === "VariableDeclarator") { + + stmt = stmt.parent; + } + + return (( + stmt.type === "ExpressionStatement" || + stmt.type === "VariableDeclaration") && + stmt.parent && stmt.parent.type === "Program" ); } @@ -403,8 +459,8 @@ module.exports = { * * Looks for 'Models' */ - var calleeNode = node.parent; // FunctionExpression - var indent; + let calleeNode = node.parent; // FunctionExpression + let indent; if (calleeNode.parent && (calleeNode.parent.type === "Property" || @@ -419,7 +475,7 @@ module.exports = { } if (calleeNode.parent.type === "CallExpression") { - var calleeParent = calleeNode.parent; + let calleeParent = calleeNode.parent; if (calleeNode.type !== "FunctionExpression" && calleeNode.type !== "ArrowFunctionExpression") { if (calleeParent && calleeParent.loc.start.line < node.loc.start.line) { @@ -436,7 +492,7 @@ module.exports = { // function body indent should be indent + indent size, unless this // is the outer IIFE and that option is enabled. - var functionOffset = indentSize; + let functionOffset = indentSize; if (options.outerIIFEBody !== null && isOuterIIFE(calleeNode)) { functionOffset = options.outerIIFEBody * indentSize; @@ -444,7 +500,7 @@ module.exports = { indent += functionOffset; // check if the node is inside a variable - var parentVarNode = getVariableDeclaratorNode(node); + let parentVarNode = getVariableDeclaratorNode(node); if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) { indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind]; @@ -464,7 +520,7 @@ module.exports = { * @returns {boolean} Whether or not the block starts and ends on the same line. */ function isSingleLineNode(node) { - var lastToken = sourceCode.getLastToken(node), + let lastToken = sourceCode.getLastToken(node), startLine = node.loc.start.line, endLine = lastToken.loc.end.line; @@ -497,7 +553,7 @@ module.exports = { return; } - var elements = (node.type === "ArrayExpression") ? node.elements : node.properties; + let elements = (node.type === "ArrayExpression") ? node.elements : node.properties; // filter out empty elements example would be [ , 2] so remove first element as espree considers it as null elements = elements.filter(function(elem) { @@ -509,14 +565,14 @@ module.exports = { return; } - var nodeIndent; - var elementsIndent; - var parentVarNode = getVariableDeclaratorNode(node); + let nodeIndent; + let elementsIndent; + let parentVarNode = getVariableDeclaratorNode(node); // TODO - come up with a better strategy in future if (isNodeFirstInLine(node)) { - var parent = node.parent; - var effectiveParent = parent; + let parent = node.parent; + let effectiveParent = parent; if (parent.type === "MemberExpression") { if (isNodeFirstInLine(parent)) { @@ -605,14 +661,14 @@ module.exports = { return; } - var indent; - var nodesToCheck = []; + let indent; + let nodesToCheck = []; /* * For this statements we should check indent from statement beginning, * not from the beginning of the block. */ - var statementsWithProperties = [ + let statementsWithProperties = [ "IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration" ]; @@ -647,7 +703,7 @@ module.exports = { */ function filterOutSameLineVars(node) { return node.declarations.reduce(function(finalCollection, elem) { - var lastElem = finalCollection[finalCollection.length - 1]; + let lastElem = finalCollection[finalCollection.length - 1]; if ((elem.loc.start.line !== node.loc.start.line && !lastElem) || (lastElem && lastElem.loc.start.line !== elem.loc.start.line)) { @@ -664,11 +720,11 @@ module.exports = { * @returns {void} */ function checkIndentInVariableDeclarations(node) { - var elements = filterOutSameLineVars(node); - var nodeIndent = getNodeIndent(node); - var lastElement = elements[elements.length - 1]; + let elements = filterOutSameLineVars(node); + let nodeIndent = getNodeIndent(node); + let lastElement = elements[elements.length - 1]; - var elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind]; + let elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind]; // Comma can be placed before declaration checkNodesIndent(elements, elementsIndent, true); @@ -678,7 +734,7 @@ module.exports = { return; } - var tokenBeforeLastElement = sourceCode.getTokenBefore(lastElement); + let tokenBeforeLastElement = sourceCode.getTokenBefore(lastElement); if (tokenBeforeLastElement.value === ",") { @@ -708,8 +764,8 @@ module.exports = { * @returns {int} indent size */ function expectedCaseIndent(node, switchIndent) { - var switchNode = (node.type === "SwitchStatement") ? node : node.parent; - var caseIndent; + let switchNode = (node.type === "SwitchStatement") ? node : node.parent; + let caseIndent; if (caseIndentStore[switchNode.loc.start.line]) { return caseIndentStore[switchNode.loc.start.line]; @@ -772,11 +828,45 @@ module.exports = { checkIndentInArrayOrObjectBlock(node); }, + MemberExpression: function(node) { + if (typeof options.MemberExpression === "undefined") { + return; + } + + if (isSingleLineNode(node)) { + return; + } + + // The typical layout of variable declarations and assignments + // alter the expectation of correct indentation. Skip them. + // TODO: Add appropriate configuration options for variable + // declarations and assignments. + if (getVariableDeclaratorNode(node)) { + return; + } + + if (getAssignmentExpressionNode(node)) { + return; + } + + let propertyIndent = getNodeIndent(node) + indentSize * options.MemberExpression; + + let checkNodes = [node.property]; + + let dot = context.getTokenBefore(node.property); + + if (dot.type === "Punctuator" && dot.value === ".") { + checkNodes.push(dot); + } + + checkNodesIndent(checkNodes, propertyIndent); + }, + SwitchStatement: function(node) { // Switch is not a 'BlockStatement' - var switchIndent = getNodeIndent(node); - var caseIndent = expectedCaseIndent(node, switchIndent); + let switchIndent = getNodeIndent(node); + let caseIndent = expectedCaseIndent(node, switchIndent); checkNodesIndent(node.cases, caseIndent); @@ -790,7 +880,7 @@ module.exports = { if (isSingleLineNode(node)) { return; } - var caseIndent = expectedCaseIndent(node); + let caseIndent = expectedCaseIndent(node); checkNodesIndent(node.consequent, caseIndent + indentSize); } |