summaryrefslogtreecommitdiff
path: root/tools/eslint/lib/rules/indent.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/eslint/lib/rules/indent.js')
-rw-r--r--tools/eslint/lib/rules/indent.js464
1 files changed, 464 insertions, 0 deletions
diff --git a/tools/eslint/lib/rules/indent.js b/tools/eslint/lib/rules/indent.js
new file mode 100644
index 0000000000..b0e838e74b
--- /dev/null
+++ b/tools/eslint/lib/rules/indent.js
@@ -0,0 +1,464 @@
+/**
+ * @fileoverview This option sets a specific tab width for your code
+ * This rule has been ported and modified from JSCS.
+ * @author Dmitriy Shekhovtsov
+ * @copyright 2015 Dmitriy Shekhovtsov. All rights reserved.
+ * @copyright 2013 Dulin Marat and other contributors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+/*eslint no-use-before-define:[2, "nofunc"]*/
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function (context) {
+ // indentation defaults: 4 spaces
+ var indentChar = " ";
+ var indentSize = 4;
+ var options = {indentSwitchCase: false};
+
+ var lines = null;
+ var indentStack = [0];
+ var linesToCheck = null;
+ var breakIndents = null;
+
+ if (context.options.length) {
+ if (context.options[0] === "tab") {
+ indentChar = "\t";
+ indentSize = 1;
+ } else if (typeof context.options[0] === "number") {
+ indentSize = context.options[0];
+ }
+
+ if (context.options[1]) {
+ var opts = context.options[1];
+ options.indentSwitchCase = opts.indentSwitchCase === true;
+ }
+ }
+
+ var blockParents = [
+ "IfStatement",
+ "WhileStatement",
+ "DoWhileStatement",
+ "ForStatement",
+ "ForInStatement",
+ "ForOfStatement",
+ "FunctionDeclaration",
+ "FunctionExpression",
+ "ArrowExpression",
+ "CatchClause",
+ "WithStatement"
+ ];
+
+ var indentableNodes = {
+ BlockStatement: "body",
+ Program: "body",
+ ObjectExpression: "properties",
+ ArrayExpression: "elements",
+ SwitchStatement: "cases"
+ };
+
+ if (options.indentSwitchCase) {
+ indentableNodes.SwitchCase = "consequent";
+ }
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Mark line to be checked
+ * @param {Number} line - line number
+ * @returns {void}
+ */
+ function markCheckLine(line) {
+ linesToCheck[line].check = true;
+ }
+
+ /**
+ * Mark line with targeted node to be checked
+ * @param {ASTNode} checkNode - targeted node
+ * @returns {void}
+ */
+ function markCheck(checkNode) {
+ markCheckLine(checkNode.loc.start.line - 1);
+ }
+
+ /**
+ * Sets pushing indent of current node
+ * @param {ASTNode} node - targeted node
+ * @param {Number} indents - indents count to push
+ * @returns {void}
+ */
+ function markPush(node, indents) {
+ linesToCheck[node.loc.start.line - 1].push.push(indents);
+ }
+
+ /**
+ * Marks line as outdent, end of block statement for example
+ * @param {ASTNode} node - targeted node
+ * @param {Number} outdents - count of outedents in targeted line
+ * @returns {void}
+ */
+ function markPop(node, outdents) {
+ linesToCheck[node.loc.end.line - 1].pop.push(outdents);
+ }
+
+ /**
+ * Set alt push for current node
+ * @param {ASTNode} node - targeted node
+ * @returns {void}
+ */
+ function markPushAlt(node) {
+ linesToCheck[node.loc.start.line - 1].pushAltLine.push(node.loc.end.line - 1);
+ }
+
+ /**
+ * Marks end of node block to be checked
+ * and marks targeted node as indent pushing
+ * @param {ASTNode} pushNode - targeted node
+ * @param {Number} indents - indent count to push
+ * @returns {void}
+ */
+ function markPushAndEndCheck(pushNode, indents) {
+ markPush(pushNode, indents);
+ markCheckLine(pushNode.loc.end.line - 1);
+ }
+
+ /**
+ * Mark node as switch case statement
+ * and set push\pop indentation changes
+ * @param {ASTNode} caseNode - targeted node
+ * @param {ASTNode[]} children - consequent child nodes of case node
+ * @returns {void}
+ */
+ function markCase(caseNode, children) {
+ var outdentNode = getCaseOutdent(children);
+
+ if (outdentNode) {
+ // If a case statement has a `break` as a direct child and it is the
+ // first one encountered, use it as the example for all future case indentation
+ if (breakIndents === null) {
+ breakIndents = (caseNode.loc.start.column === outdentNode.loc.start.column) ? 1 : 0;
+ }
+ markPop(outdentNode, breakIndents);
+ } else {
+ markPop(caseNode, 0);
+ }
+ }
+
+ /**
+ * Mark child nodes to be checked later of targeted node,
+ * only if child node not in same line as targeted one
+ * (if child and parent nodes wrote in single line)
+ * @param {ASTNode} node - targeted node
+ * @returns {void}
+ */
+ function markChildren(node) {
+ getChildren(node).forEach(function(childNode) {
+ if (childNode.loc.start.line !== node.loc.start.line || node.type === "Program") {
+ markCheck(childNode);
+ }
+ });
+ }
+
+ /**
+ * Mark child block as scope pushing and mark to check
+ * @param {ASTNode} node - target node
+ * @param {String} property - target node property containing child
+ * @returns {void}
+ */
+ function markAlternateBlockStatement(node, property) {
+ var child = node[property];
+ if (child && child.type === "BlockStatement") {
+ markCheck(child);
+ }
+ }
+
+ /**
+ * Checks whether node is multiline or single line
+ * @param {ASTNode} node - target node
+ * @returns {boolean} - is multiline node
+ */
+ function isMultiline(node) {
+ return node.loc.start.line !== node.loc.end.line;
+ }
+
+ /**
+ * Get switch case statement outdent node if any
+ * @param {ASTNode[]} caseChildren - case statement childs
+ * @returns {ASTNode} - outdent node
+ */
+ function getCaseOutdent(caseChildren) {
+ var outdentNode;
+ caseChildren.some(function(node) {
+ if (node.type === "BreakStatement") {
+ outdentNode = node;
+ return true;
+ }
+ });
+
+ return outdentNode;
+ }
+
+ /**
+ * Returns block containing node
+ * @param {ASTNode} node - targeted node
+ * @returns {ASTNode} - block node
+ */
+ function getBlockNodeToMark(node) {
+ var parent = node.parent;
+
+ // The parent of an else is the entire if/else block. To avoid over indenting
+ // in the case of a non-block if with a block else, mark push where the else starts,
+ // not where the if starts!
+ if (parent.type === "IfStatement" && parent.alternate === node) {
+ return node;
+ }
+
+ // The end line to check of a do while statement needs to be the location of the
+ // closing curly brace, not the while statement, to avoid marking the last line of
+ // a multiline while as a line to check.
+ if (parent.type === "DoWhileStatement") {
+ return node;
+ }
+
+ // Detect bare blocks: a block whose parent doesn"t expect blocks in its syntax specifically.
+ if (blockParents.indexOf(parent.type) === -1) {
+ return node;
+ }
+
+ return parent;
+ }
+
+ /**
+ * Get node's children
+ * @param {ASTNode} node - current node
+ * @returns {ASTNode[]} - children
+ */
+ function getChildren(node) {
+ var childrenProperty = indentableNodes[node.type];
+ return node[childrenProperty];
+ }
+
+ /**
+ * Gets indentation in line `i`
+ * @param {Number} i - number of line to get indentation
+ * @returns {Number} - count of indentation symbols
+ */
+ function getIndentationFromLine(i) {
+ var rNotIndentChar = new RegExp("[^" + indentChar + "]");
+ var firstContent = lines[i].search(rNotIndentChar);
+ if (firstContent === -1) {
+ firstContent = lines[i].length;
+ }
+ return firstContent;
+ }
+
+ /**
+ * Compares expected and actual indentation
+ * and reports any violations
+ * @param {ASTNode} node - node used only for reporting
+ * @returns {void}
+ */
+ function checkIndentations(node) {
+ linesToCheck.forEach(function(line, i) {
+ var actualIndentation = getIndentationFromLine(i);
+ var expectedIndentation = getExpectedIndentation(line, actualIndentation);
+
+ if (line.check) {
+
+ if (actualIndentation !== expectedIndentation) {
+ context.report(node,
+ {line: i + 1, column: expectedIndentation},
+ "Expected indentation of " + expectedIndentation + " characters.");
+ // correct the indentation so that future lines
+ // can be validated appropriately
+ actualIndentation = expectedIndentation;
+ }
+ }
+
+ if (line.push.length) {
+ pushExpectedIndentations(line, actualIndentation);
+ }
+ });
+ }
+
+ /**
+ * Counts expected indentation for given line number
+ * @param {Number} line - line number
+ * @param {Number} actual - actual indentation
+ * @returns {number} - expected indentation
+ */
+ function getExpectedIndentation(line, actual) {
+ var outdent = indentSize * Math.max.apply(null, line.pop);
+
+ var idx = indentStack.length - 1;
+ var expected = indentStack[idx];
+
+ if (!Array.isArray(expected)) {
+ expected = [expected];
+ }
+
+ expected = expected.map(function(value) {
+ if (line.pop.length) {
+ value -= outdent;
+ }
+
+ return value;
+ }).reduce(function(previous, current) {
+ // when the expected is an array, resolve the value
+ // back into a Number by checking both values are the actual indentation
+ return actual === current ? current : previous;
+ });
+
+ indentStack[idx] = expected;
+
+ line.pop.forEach(function() {
+ indentStack.pop();
+ });
+
+ return expected;
+ }
+
+ /**
+ * Store in stack expected indentations
+ * @param {Number} line - current line
+ * @param {Number} actualIndentation - actual indentation at current line
+ * @returns {void}
+ */
+ function pushExpectedIndentations(line, actualIndentation) {
+ var indents = Math.max.apply(null, line.push);
+ var expected = actualIndentation + (indentSize * indents);
+
+ // when a line has alternate indentations, push an array of possible values
+ // on the stack, to be resolved when checked against an actual indentation
+ if (line.pushAltLine.length) {
+ expected = [expected];
+ line.pushAltLine.forEach(function(altLine) {
+ expected.push(getIndentationFromLine(altLine) + (indentSize * indents));
+ });
+ }
+
+ line.push.forEach(function() {
+ indentStack.push(expected);
+ });
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "Program": function (node) {
+ lines = context.getSourceLines();
+ linesToCheck = lines.map(function () {
+ return {
+ push: [],
+ pushAltLine: [],
+ pop: [],
+ check: false
+ };
+ });
+
+ if (!isMultiline(node)) {
+ return;
+ }
+
+ markChildren(node);
+ },
+ "Program:exit": function (node) {
+ checkIndentations(node);
+ },
+
+ "BlockStatement": function (node) {
+ if (!isMultiline(node)) {
+ return;
+ }
+
+ markChildren(node);
+ markPop(node, 1);
+
+ markPushAndEndCheck(getBlockNodeToMark(node), 1);
+ },
+
+ "IfStatement": function (node) {
+ markAlternateBlockStatement(node, "alternate");
+ },
+
+ "TryStatement": function (node) {
+ markAlternateBlockStatement(node, "handler");
+ markAlternateBlockStatement(node, "finalizer");
+ },
+
+ "SwitchStatement": function (node) {
+ if (!isMultiline(node)) {
+ return;
+ }
+
+ var indents = 1;
+ var children = getChildren(node);
+
+ if (children.length && node.loc.start.column === children[0].loc.start.column) {
+ indents = 0;
+ }
+
+ markChildren(node);
+ markPop(node, indents);
+ markPushAndEndCheck(node, indents);
+ },
+
+ "SwitchCase": function (node) {
+ if (!options.indentSwitchCase) {
+ return;
+ }
+
+ if (!isMultiline(node)) {
+ return;
+ }
+
+ var children = getChildren(node);
+
+ if (children.length === 1 && children[0].type === "BlockStatement") {
+ return;
+ }
+
+ markPush(node, 1);
+ markCheck(node);
+ markChildren(node);
+
+ markCase(node, children);
+ },
+
+ // indentations inside of function expressions can be offset from
+ // either the start of the function or the end of the function, therefore
+ // mark all starting lines of functions as potential indentations
+ "FunctionDeclaration": function (node) {
+ markPushAlt(node);
+ },
+ "FunctionExpression": function (node) {
+ markPushAlt(node);
+ }
+ };
+
+};