summaryrefslogtreecommitdiff
path: root/tools/eslint/lib/rules
diff options
context:
space:
mode:
Diffstat (limited to 'tools/eslint/lib/rules')
-rw-r--r--tools/eslint/lib/rules/block-scoped-var.js318
-rw-r--r--tools/eslint/lib/rules/brace-style.js204
-rw-r--r--tools/eslint/lib/rules/camelcase.js99
-rw-r--r--tools/eslint/lib/rules/comma-dangle.js58
-rw-r--r--tools/eslint/lib/rules/comma-spacing.js159
-rw-r--r--tools/eslint/lib/rules/comma-style.js177
-rw-r--r--tools/eslint/lib/rules/complexity.js88
-rw-r--r--tools/eslint/lib/rules/consistent-return.js73
-rw-r--r--tools/eslint/lib/rules/consistent-this.js108
-rw-r--r--tools/eslint/lib/rules/curly.js103
-rw-r--r--tools/eslint/lib/rules/default-case.js64
-rw-r--r--tools/eslint/lib/rules/dot-notation.js104
-rw-r--r--tools/eslint/lib/rules/eol-last.js36
-rw-r--r--tools/eslint/lib/rules/eqeqeq.js90
-rw-r--r--tools/eslint/lib/rules/func-names.js43
-rw-r--r--tools/eslint/lib/rules/func-style.js43
-rw-r--r--tools/eslint/lib/rules/generator-star-spacing.js83
-rw-r--r--tools/eslint/lib/rules/generator-star.js70
-rw-r--r--tools/eslint/lib/rules/global-strict.js43
-rw-r--r--tools/eslint/lib/rules/guard-for-in.js30
-rw-r--r--tools/eslint/lib/rules/handle-callback-err.js118
-rw-r--r--tools/eslint/lib/rules/indent.js464
-rw-r--r--tools/eslint/lib/rules/key-spacing.js307
-rw-r--r--tools/eslint/lib/rules/max-depth.js83
-rw-r--r--tools/eslint/lib/rules/max-len.js65
-rw-r--r--tools/eslint/lib/rules/max-nested-callbacks.js67
-rw-r--r--tools/eslint/lib/rules/max-params.js39
-rw-r--r--tools/eslint/lib/rules/max-statements.js55
-rw-r--r--tools/eslint/lib/rules/new-cap.js197
-rw-r--r--tools/eslint/lib/rules/new-parens.js27
-rw-r--r--tools/eslint/lib/rules/newline-after-var.js121
-rw-r--r--tools/eslint/lib/rules/no-alert.js54
-rw-r--r--tools/eslint/lib/rules/no-array-constructor.js29
-rw-r--r--tools/eslint/lib/rules/no-bitwise.js55
-rw-r--r--tools/eslint/lib/rules/no-caller.js27
-rw-r--r--tools/eslint/lib/rules/no-catch-shadow.js50
-rw-r--r--tools/eslint/lib/rules/no-comma-dangle.js43
-rw-r--r--tools/eslint/lib/rules/no-cond-assign.js117
-rw-r--r--tools/eslint/lib/rules/no-console.js25
-rw-r--r--tools/eslint/lib/rules/no-constant-condition.js71
-rw-r--r--tools/eslint/lib/rules/no-continue.js21
-rw-r--r--tools/eslint/lib/rules/no-control-regex.js56
-rw-r--r--tools/eslint/lib/rules/no-debugger.js20
-rw-r--r--tools/eslint/lib/rules/no-delete-var.js23
-rw-r--r--tools/eslint/lib/rules/no-div-regex.js25
-rw-r--r--tools/eslint/lib/rules/no-dupe-args.js83
-rw-r--r--tools/eslint/lib/rules/no-dupe-keys.js41
-rw-r--r--tools/eslint/lib/rules/no-duplicate-case.js59
-rw-r--r--tools/eslint/lib/rules/no-else-return.js123
-rw-r--r--tools/eslint/lib/rules/no-empty-class.js43
-rw-r--r--tools/eslint/lib/rules/no-empty-label.js25
-rw-r--r--tools/eslint/lib/rules/no-empty.js47
-rw-r--r--tools/eslint/lib/rules/no-eq-null.js27
-rw-r--r--tools/eslint/lib/rules/no-eval.js24
-rw-r--r--tools/eslint/lib/rules/no-ex-assign.js40
-rw-r--r--tools/eslint/lib/rules/no-extend-native.js77
-rw-r--r--tools/eslint/lib/rules/no-extra-bind.js79
-rw-r--r--tools/eslint/lib/rules/no-extra-boolean-cast.js69
-rw-r--r--tools/eslint/lib/rules/no-extra-parens.js299
-rw-r--r--tools/eslint/lib/rules/no-extra-semi.js21
-rw-r--r--tools/eslint/lib/rules/no-extra-strict.js84
-rw-r--r--tools/eslint/lib/rules/no-fallthrough.js95
-rw-r--r--tools/eslint/lib/rules/no-floating-decimal.js28
-rw-r--r--tools/eslint/lib/rules/no-func-assign.js81
-rw-r--r--tools/eslint/lib/rules/no-implied-eval.js74
-rw-r--r--tools/eslint/lib/rules/no-inline-comments.js47
-rw-r--r--tools/eslint/lib/rules/no-inner-declarations.js72
-rw-r--r--tools/eslint/lib/rules/no-invalid-regexp.js51
-rw-r--r--tools/eslint/lib/rules/no-irregular-whitespace.js92
-rw-r--r--tools/eslint/lib/rules/no-iterator.js26
-rw-r--r--tools/eslint/lib/rules/no-label-var.js62
-rw-r--r--tools/eslint/lib/rules/no-labels.js42
-rw-r--r--tools/eslint/lib/rules/no-lone-blocks.js25
-rw-r--r--tools/eslint/lib/rules/no-lonely-if.js28
-rw-r--r--tools/eslint/lib/rules/no-loop-func.js49
-rw-r--r--tools/eslint/lib/rules/no-mixed-requires.js159
-rw-r--r--tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js68
-rw-r--r--tools/eslint/lib/rules/no-multi-spaces.js101
-rw-r--r--tools/eslint/lib/rules/no-multi-str.js41
-rw-r--r--tools/eslint/lib/rules/no-multiple-empty-lines.js60
-rw-r--r--tools/eslint/lib/rules/no-native-reassign.js37
-rw-r--r--tools/eslint/lib/rules/no-negated-in-lhs.js23
-rw-r--r--tools/eslint/lib/rules/no-nested-ternary.js22
-rw-r--r--tools/eslint/lib/rules/no-new-func.js23
-rw-r--r--tools/eslint/lib/rules/no-new-object.js23
-rw-r--r--tools/eslint/lib/rules/no-new-require.js23
-rw-r--r--tools/eslint/lib/rules/no-new-wrappers.js24
-rw-r--r--tools/eslint/lib/rules/no-new.js25
-rw-r--r--tools/eslint/lib/rules/no-obj-calls.js26
-rw-r--r--tools/eslint/lib/rules/no-octal-escape.js37
-rw-r--r--tools/eslint/lib/rules/no-octal.js23
-rw-r--r--tools/eslint/lib/rules/no-param-reassign.js85
-rw-r--r--tools/eslint/lib/rules/no-path-concat.js37
-rw-r--r--tools/eslint/lib/rules/no-plusplus.js22
-rw-r--r--tools/eslint/lib/rules/no-process-env.js28
-rw-r--r--tools/eslint/lib/rules/no-process-exit.js31
-rw-r--r--tools/eslint/lib/rules/no-proto.js26
-rw-r--r--tools/eslint/lib/rules/no-redeclare.js58
-rw-r--r--tools/eslint/lib/rules/no-regex-spaces.js33
-rw-r--r--tools/eslint/lib/rules/no-reserved-keys.js54
-rw-r--r--tools/eslint/lib/rules/no-restricted-modules.js72
-rw-r--r--tools/eslint/lib/rules/no-return-assign.js22
-rw-r--r--tools/eslint/lib/rules/no-script-url.js32
-rw-r--r--tools/eslint/lib/rules/no-self-compare.js27
-rw-r--r--tools/eslint/lib/rules/no-sequences.js92
-rw-r--r--tools/eslint/lib/rules/no-shadow-restricted-names.js49
-rw-r--r--tools/eslint/lib/rules/no-shadow.js72
-rw-r--r--tools/eslint/lib/rules/no-space-before-semi.js96
-rw-r--r--tools/eslint/lib/rules/no-spaced-func.js35
-rw-r--r--tools/eslint/lib/rules/no-sparse-arrays.js31
-rw-r--r--tools/eslint/lib/rules/no-sync.js28
-rw-r--r--tools/eslint/lib/rules/no-ternary.js22
-rw-r--r--tools/eslint/lib/rules/no-throw-literal.js31
-rw-r--r--tools/eslint/lib/rules/no-trailing-spaces.js46
-rw-r--r--tools/eslint/lib/rules/no-undef-init.js26
-rw-r--r--tools/eslint/lib/rules/no-undef.js90
-rw-r--r--tools/eslint/lib/rules/no-undefined.js25
-rw-r--r--tools/eslint/lib/rules/no-underscore-dangle.js71
-rw-r--r--tools/eslint/lib/rules/no-unreachable.js96
-rw-r--r--tools/eslint/lib/rules/no-unused-expressions.js74
-rw-r--r--tools/eslint/lib/rules/no-unused-vars.js180
-rw-r--r--tools/eslint/lib/rules/no-use-before-define.js67
-rw-r--r--tools/eslint/lib/rules/no-var.js24
-rw-r--r--tools/eslint/lib/rules/no-void.js26
-rw-r--r--tools/eslint/lib/rules/no-warning-comments.js84
-rw-r--r--tools/eslint/lib/rules/no-with.js20
-rw-r--r--tools/eslint/lib/rules/no-wrap-func.js44
-rw-r--r--tools/eslint/lib/rules/object-shorthand.js63
-rw-r--r--tools/eslint/lib/rules/one-var.js175
-rw-r--r--tools/eslint/lib/rules/operator-assignment.js112
-rw-r--r--tools/eslint/lib/rules/operator-linebreak.js100
-rw-r--r--tools/eslint/lib/rules/padded-blocks.js92
-rw-r--r--tools/eslint/lib/rules/quote-props.js66
-rw-r--r--tools/eslint/lib/rules/quotes.js83
-rw-r--r--tools/eslint/lib/rules/radix.js39
-rw-r--r--tools/eslint/lib/rules/semi-spacing.js152
-rw-r--r--tools/eslint/lib/rules/semi.js130
-rw-r--r--tools/eslint/lib/rules/sort-vars.js37
-rw-r--r--tools/eslint/lib/rules/space-after-function-name.js43
-rw-r--r--tools/eslint/lib/rules/space-after-keywords.js76
-rw-r--r--tools/eslint/lib/rules/space-before-blocks.js85
-rw-r--r--tools/eslint/lib/rules/space-before-function-paren.js117
-rw-r--r--tools/eslint/lib/rules/space-before-function-parentheses.js117
-rw-r--r--tools/eslint/lib/rules/space-in-brackets.js272
-rw-r--r--tools/eslint/lib/rules/space-in-parens.js262
-rw-r--r--tools/eslint/lib/rules/space-infix-ops.js94
-rw-r--r--tools/eslint/lib/rules/space-return-throw-case.js36
-rw-r--r--tools/eslint/lib/rules/space-unary-ops.js118
-rw-r--r--tools/eslint/lib/rules/spaced-line-comment.js71
-rw-r--r--tools/eslint/lib/rules/strict.js236
-rw-r--r--tools/eslint/lib/rules/use-isnan.js24
-rw-r--r--tools/eslint/lib/rules/valid-jsdoc.js191
-rw-r--r--tools/eslint/lib/rules/valid-typeof.js40
-rw-r--r--tools/eslint/lib/rules/vars-on-top.js113
-rw-r--r--tools/eslint/lib/rules/wrap-iife.js42
-rw-r--r--tools/eslint/lib/rules/wrap-regex.js36
-rw-r--r--tools/eslint/lib/rules/yoda.js210
157 files changed, 11868 insertions, 0 deletions
diff --git a/tools/eslint/lib/rules/block-scoped-var.js b/tools/eslint/lib/rules/block-scoped-var.js
new file mode 100644
index 0000000000..6b23167fd1
--- /dev/null
+++ b/tools/eslint/lib/rules/block-scoped-var.js
@@ -0,0 +1,318 @@
+/**
+ * @fileoverview Rule to check for "block scoped" variables by binding context
+ * @author Matt DuVall <http://www.mattduvall.com>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var scopeStack = [];
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Determines whether an identifier is in declaration position or is a non-declaration reference.
+ * @param {ASTNode} id The identifier.
+ * @param {ASTNode} parent The identifier's parent AST node.
+ * @returns {Boolean} true when the identifier is in declaration position.
+ */
+ function isDeclaration(id, parent) {
+ switch (parent.type) {
+ case "FunctionDeclaration":
+ case "FunctionExpression":
+ return parent.params.indexOf(id) > -1 || id === parent.id;
+
+ case "VariableDeclarator":
+ return id === parent.id;
+
+ case "CatchClause":
+ return id === parent.param;
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Determines whether an identifier is in property position.
+ * @param {ASTNode} id The identifier.
+ * @param {ASTNode} parent The identifier's parent AST node.
+ * @returns {Boolean} true when the identifier is in property position.
+ */
+ function isProperty(id, parent) {
+ switch (parent.type) {
+ case "MemberExpression":
+ return id === parent.property && !parent.computed;
+
+ case "Property":
+ return id === parent.key;
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Pushes a new scope object on the scope stack.
+ * @returns {void}
+ */
+ function pushScope() {
+ scopeStack.push([]);
+ }
+
+ /**
+ * Removes the topmost scope object from the scope stack.
+ * @returns {void}
+ */
+ function popScope() {
+ scopeStack.pop();
+ }
+
+ /**
+ * Declares the given names in the topmost scope object.
+ * @param {[String]} names A list of names to declare.
+ * @returns {void}
+ */
+ function declare(names) {
+ [].push.apply(scopeStack[scopeStack.length - 1], names);
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ /**
+ * Declares all relevant identifiers for module imports.
+ * @param {ASTNode} node The AST node representing an import.
+ * @returns {void}
+ * @private
+ */
+ function declareImports(node) {
+ declare([node.local.name]);
+
+ if (node.imported && node.imported.name !== node.local.name) {
+ declare([node.imported.name]);
+ }
+ }
+
+ /**
+ * Declares all relevant identifiers for classes.
+ * @param {ASTNode} node The AST node representing a class.
+ * @returns {void}
+ * @private
+ */
+ function declareClass(node) {
+
+ if (node.id) {
+ declare([node.id.name]);
+ }
+
+ pushScope();
+ }
+
+ /**
+ * Declares all relevant identifiers for classes.
+ * @param {ASTNode} node The AST node representing a class.
+ * @returns {void}
+ * @private
+ */
+ function declareClassMethod(node) {
+ pushScope();
+
+ declare([node.key.name]);
+ }
+
+ /**
+ * Add declarations based on the type of node being passed.
+ * @param {ASTNode} node The node containing declarations.
+ * @returns {void}
+ * @private
+ */
+ function declareByNodeType(node) {
+
+ var declarations = [];
+
+ switch (node.type) {
+ case "Identifier":
+ declarations.push(node.name);
+ break;
+
+ case "ObjectPattern":
+ node.properties.forEach(function(property) {
+ declarations.push(property.key.name);
+ if (property.value) {
+ declarations.push(property.value.name);
+ }
+ });
+ break;
+
+ case "ArrayPattern":
+ node.elements.forEach(function(element) {
+ if (element) {
+ declarations.push(element.name);
+ }
+ });
+ break;
+
+ case "AssignmentPattern":
+ declareByNodeType(node.left);
+ break;
+
+ case "RestElement":
+ declareByNodeType(node.argument);
+ break;
+
+ // no default
+ }
+
+ declare(declarations);
+
+ }
+
+ /**
+ * Adds declarations of the function parameters and pushes the scope
+ * @param {ASTNode} node The node containing declarations.
+ * @returns {void}
+ * @private
+ */
+ function functionHandler(node) {
+ pushScope();
+
+ node.params.forEach(function(param) {
+ declareByNodeType(param);
+ });
+
+ declare(node.rest ? [node.rest.name] : []);
+ declare(["arguments"]);
+ }
+
+ /**
+ * Adds declaration of the function name in its parent scope then process the function
+ * @param {ASTNode} node The node containing declarations.
+ * @returns {void}
+ * @private
+ */
+ function functionDeclarationHandler(node) {
+ declare(node.id ? [node.id.name] : []);
+ functionHandler(node);
+ }
+
+ /**
+ * Process function declarations and declares its name in its own scope
+ * @param {ASTNode} node The node containing declarations.
+ * @returns {void}
+ * @private
+ */
+ function functionExpressionHandler(node) {
+ functionHandler(node);
+ declare(node.id ? [node.id.name] : []);
+ }
+
+ function variableDeclarationHandler(node) {
+ node.declarations.forEach(function(declaration) {
+ declareByNodeType(declaration.id);
+ });
+
+ }
+
+ return {
+ "Program": function() {
+ var scope = context.getScope();
+ scopeStack = [scope.variables.map(function(v) {
+ return v.name;
+ })];
+
+ // global return creates another scope
+ if (context.ecmaFeatures.globalReturn) {
+ scope = scope.childScopes[0];
+ scopeStack.push(scope.variables.map(function(v) {
+ return v.name;
+ }));
+ }
+ },
+
+ "ImportSpecifier": declareImports,
+ "ImportDefaultSpecifier": declareImports,
+ "ImportNamespaceSpecifier": declareImports,
+
+ "BlockStatement": function(node) {
+ var statements = node.body;
+ pushScope();
+ statements.forEach(function(stmt) {
+ if (stmt.type === "VariableDeclaration") {
+ variableDeclarationHandler(stmt);
+ } else if (stmt.type === "FunctionDeclaration") {
+ declare([stmt.id.name]);
+ }
+ });
+ },
+
+ "VariableDeclaration": function (node) {
+ variableDeclarationHandler(node);
+ },
+
+ "BlockStatement:exit": popScope,
+
+ "CatchClause": function(node) {
+ pushScope();
+ declare([node.param.name]);
+ },
+ "CatchClause:exit": popScope,
+
+ "FunctionDeclaration": functionDeclarationHandler,
+ "FunctionDeclaration:exit": popScope,
+
+ "ClassDeclaration": declareClass,
+ "ClassDeclaration:exit": popScope,
+
+ "ClassExpression": declareClass,
+ "ClassExpression:exit": popScope,
+
+ "MethodDefinition": declareClassMethod,
+ "MethodDefinition:exit": popScope,
+
+ "FunctionExpression": functionExpressionHandler,
+ "FunctionExpression:exit": popScope,
+
+ // Arrow functions cannot have names
+ "ArrowFunctionExpression": functionHandler,
+ "ArrowFunctionExpression:exit": popScope,
+
+ "ForStatement": function() {
+ pushScope();
+ },
+ "ForStatement:exit": popScope,
+
+ "ForInStatement": function() {
+ pushScope();
+ },
+ "ForInStatement:exit": popScope,
+
+ "ForOfStatement": function() {
+ pushScope();
+ },
+ "ForOfStatement:exit": popScope,
+
+ "Identifier": function(node) {
+ var ancestor = context.getAncestors().pop();
+ if (isDeclaration(node, ancestor) || isProperty(node, ancestor) || ancestor.type === "LabeledStatement") {
+ return;
+ }
+
+ for (var i = 0, l = scopeStack.length; i < l; i++) {
+ if (scopeStack[i].indexOf(node.name) > -1) {
+ return;
+ }
+ }
+
+ context.report(node, "\"" + node.name + "\" used outside of binding context.");
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/brace-style.js b/tools/eslint/lib/rules/brace-style.js
new file mode 100644
index 0000000000..599e86f68b
--- /dev/null
+++ b/tools/eslint/lib/rules/brace-style.js
@@ -0,0 +1,204 @@
+/**
+ * @fileoverview Rule to flag block statements that do not use the one true brace style
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+ var style = context.options[0] || "1tbs";
+ var params = context.options[1] || {};
+
+ var OPEN_MESSAGE = "Opening curly brace does not appear on the same line as controlling statement.",
+ BODY_MESSAGE = "Statement inside of curly braces should be on next line.",
+ CLOSE_MESSAGE = "Closing curly brace does not appear on the same line as the subsequent block.",
+ CLOSE_MESSAGE_SINGLE = "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.",
+ CLOSE_MESSAGE_STROUSTRUP = "Closing curly brace appears on the same line as the subsequent block.";
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Determines if a given node is a block statement.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} True if the node is a block statement, false if not.
+ * @private
+ */
+ function isBlock(node) {
+ return node && node.type === "BlockStatement";
+ }
+
+
+ /**
+ * Binds a list of properties to a function that verifies that the opening
+ * curly brace is on the same line as its controlling statement of a given
+ * node.
+ * @param {...string} The properties to check on the node.
+ * @returns {Function} A function that will perform the check on a node
+ * @private
+ */
+ function checkBlock() {
+ var blockProperties = arguments;
+
+ return function(node) {
+ [].forEach.call(blockProperties, function(blockProp) {
+ var block = node[blockProp], previousToken, curlyToken, curlyTokenEnd, curlyTokensOnSameLine;
+ block = node[blockProp];
+
+ if (isBlock(block)) {
+
+ previousToken = context.getTokenBefore(block);
+ curlyToken = context.getFirstToken(block);
+ curlyTokenEnd = context.getLastToken(block);
+ curlyTokensOnSameLine = curlyToken.loc.start.line === curlyTokenEnd.loc.start.line;
+
+ if (previousToken.loc.start.line !== curlyToken.loc.start.line) {
+ context.report(node, OPEN_MESSAGE);
+ } else if (block.body.length && params.allowSingleLine) {
+
+ if (curlyToken.loc.start.line === block.body[0].loc.start.line && !curlyTokensOnSameLine) {
+ context.report(block.body[0], BODY_MESSAGE);
+ } else if (curlyTokenEnd.loc.start.line === block.body[block.body.length - 1].loc.start.line && !curlyTokensOnSameLine) {
+ context.report(block.body[block.body.length - 1], CLOSE_MESSAGE_SINGLE);
+ }
+
+ } else if (block.body.length && curlyToken.loc.start.line === block.body[0].loc.start.line) {
+ context.report(block.body[0], BODY_MESSAGE);
+ }
+ }
+ });
+ };
+ }
+
+ /**
+ * Enforces the configured brace style on IfStatements
+ * @param {ASTNode} node An IfStatement node.
+ * @returns {void}
+ * @private
+ */
+ function checkIfStatement(node) {
+ var tokens,
+ alternateIsBlock = false,
+ alternateIsIfBlock = false;
+
+ checkBlock("consequent", "alternate")(node);
+
+ if (node.alternate) {
+
+ alternateIsBlock = isBlock(node.alternate);
+ alternateIsIfBlock = node.alternate.type === "IfStatement" && isBlock(node.alternate.consequent);
+
+ if (alternateIsBlock || alternateIsIfBlock) {
+ tokens = context.getTokensBefore(node.alternate, 2);
+
+ if (style === "1tbs") {
+ if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
+ context.report(node.alternate, CLOSE_MESSAGE);
+ }
+ } else if (style === "stroustrup") {
+ if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
+ context.report(node.alternate, CLOSE_MESSAGE_STROUSTRUP);
+ }
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Enforces the configured brace style on TryStatements
+ * @param {ASTNode} node A TryStatement node.
+ * @returns {void}
+ * @private
+ */
+ function checkTryStatement(node) {
+ var tokens;
+
+ checkBlock("block", "finalizer")(node);
+
+ if (isBlock(node.finalizer)) {
+ tokens = context.getTokensBefore(node.finalizer, 2);
+ if (style === "1tbs") {
+ if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
+ context.report(node.finalizer, CLOSE_MESSAGE);
+ }
+ } else if (style === "stroustrup") {
+ if (tokens[0].loc.start.line === tokens[1].loc.start.line) {
+ context.report(node.finalizer, CLOSE_MESSAGE_STROUSTRUP);
+ }
+ }
+ }
+ }
+
+ /**
+ * Enforces the configured brace style on CatchClauses
+ * @param {ASTNode} node A CatchClause node.
+ * @returns {void}
+ * @private
+ */
+ function checkCatchClause(node) {
+ var previousToken = context.getTokenBefore(node),
+ firstToken = context.getFirstToken(node);
+
+ checkBlock("body")(node);
+
+ if (isBlock(node.body)) {
+ if (style === "1tbs") {
+ if (previousToken.loc.start.line !== firstToken.loc.start.line) {
+ context.report(node, CLOSE_MESSAGE);
+ }
+ } else if (style === "stroustrup") {
+ if (previousToken.loc.start.line === firstToken.loc.start.line) {
+ context.report(node, CLOSE_MESSAGE_STROUSTRUP);
+ }
+ }
+ }
+ }
+
+ /**
+ * Enforces the configured brace style on SwitchStatements
+ * @param {ASTNode} node A SwitchStatement node.
+ * @returns {void}
+ * @private
+ */
+ function checkSwitchStatement(node) {
+ var tokens;
+ if (node.cases && node.cases.length) {
+ tokens = context.getTokensBefore(node.cases[0], 2);
+ if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
+ context.report(node, OPEN_MESSAGE);
+ }
+ } else {
+ tokens = context.getLastTokens(node, 3);
+ if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
+ context.report(node, OPEN_MESSAGE);
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+ "FunctionDeclaration": checkBlock("body"),
+ "FunctionExpression": checkBlock("body"),
+ "ArrowFunctionExpression": checkBlock("body"),
+ "IfStatement": checkIfStatement,
+ "TryStatement": checkTryStatement,
+ "CatchClause": checkCatchClause,
+ "DoWhileStatement": checkBlock("body"),
+ "WhileStatement": checkBlock("body"),
+ "WithStatement": checkBlock("body"),
+ "ForStatement": checkBlock("body"),
+ "ForInStatement": checkBlock("body"),
+ "ForOfStatement": checkBlock("body"),
+ "SwitchStatement": checkSwitchStatement
+ };
+
+};
diff --git a/tools/eslint/lib/rules/camelcase.js b/tools/eslint/lib/rules/camelcase.js
new file mode 100644
index 0000000000..5b1b8020da
--- /dev/null
+++ b/tools/eslint/lib/rules/camelcase.js
@@ -0,0 +1,99 @@
+/**
+ * @fileoverview Rule to flag non-camelcased identifiers
+ * @author Nicholas C. Zakas
+ * @copyright 2015 Dieter Oberkofler. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Checks if a string contains an underscore and isn't all upper-case
+ * @param {String} name The string to check.
+ * @returns {boolean} if the string is underscored
+ * @private
+ */
+ function isUnderscored(name) {
+
+ // if there's an underscore, it might be A_CONSTANT, which is okay
+ return name.indexOf("_") > -1 && name !== name.toUpperCase();
+ }
+
+ /**
+ * Reports an AST node as a rule violation.
+ * @param {ASTNode} node The node to report.
+ * @returns {void}
+ * @private
+ */
+ function report(node) {
+ context.report(node, "Identifier '{{name}}' is not in camel case.", { name: node.name });
+ }
+
+ var options = context.options[0] || {},
+ properties = options.properties || "";
+
+ if (properties !== "always" && properties !== "never") {
+ properties = "always";
+ }
+
+ return {
+
+ "Identifier": function(node) {
+
+ // Leading and trailing underscores are commonly used to flag private/protected identifiers, strip them
+ var name = node.name.replace(/^_+|_+$/g, ""),
+ effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
+
+ // MemberExpressions get special rules
+ if (node.parent.type === "MemberExpression") {
+
+ // "never" check properties
+ if (properties === "never") {
+ return;
+ }
+
+ // Always report underscored object names
+ if (node.parent.object.type === "Identifier" &&
+ node.parent.object.name === node.name &&
+ isUnderscored(name)) {
+ report(node);
+
+ // Report AssignmentExpressions only if they are the left side of the assignment
+ } else if (effectiveParent.type === "AssignmentExpression" &&
+ isUnderscored(name) &&
+ (effectiveParent.right.type !== "MemberExpression" ||
+ effectiveParent.left.type === "MemberExpression" &&
+ effectiveParent.left.property.name === node.name)) {
+ report(node);
+ }
+
+ // Properties have their own rules
+ } else if (node.parent.type === "Property") {
+
+ // "never" check properties
+ if (properties === "never") {
+ return;
+ }
+
+ if (isUnderscored(name) && effectiveParent.type !== "CallExpression") {
+ report(node);
+ }
+
+ // Report anything that is underscored that isn't a CallExpression
+ } else if (isUnderscored(name) && effectiveParent.type !== "CallExpression") {
+ report(node);
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/comma-dangle.js b/tools/eslint/lib/rules/comma-dangle.js
new file mode 100644
index 0000000000..da3776d424
--- /dev/null
+++ b/tools/eslint/lib/rules/comma-dangle.js
@@ -0,0 +1,58 @@
+/**
+ * @fileoverview Rule to forbid or enforce dangling commas.
+ * @author Ian Christian Myers
+ * @copyright 2015 Mathias Schreck
+ * @copyright 2013 Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function (context) {
+ var allowDangle = context.options[0];
+ var forbidDangle = allowDangle !== "always-multiline" && allowDangle !== "always";
+ var UNEXPECTED_MESSAGE = "Unexpected trailing comma.";
+ var MISSING_MESSAGE = "Missing trailing comma.";
+
+ /**
+ * Checks the given node for trailing comma and reports violations.
+ * @param {ASTNode} node The node of an ObjectExpression or ArrayExpression
+ * @returns {void}
+ */
+ function checkForTrailingComma(node) {
+ var items = node.properties || node.elements,
+ length = items.length,
+ nodeIsMultiLine = node.loc.start.line !== node.loc.end.line,
+ lastItem,
+ penultimateToken,
+ hasDanglingComma;
+
+ if (length) {
+ lastItem = items[length - 1];
+ if (lastItem) {
+ penultimateToken = context.getLastToken(node, 1);
+ hasDanglingComma = penultimateToken.value === ",";
+
+ if (forbidDangle && hasDanglingComma) {
+ context.report(lastItem, penultimateToken.loc.start, UNEXPECTED_MESSAGE);
+ } else if (allowDangle === "always-multiline") {
+ if (hasDanglingComma && !nodeIsMultiLine) {
+ context.report(lastItem, penultimateToken.loc.start, UNEXPECTED_MESSAGE);
+ } else if (!hasDanglingComma && nodeIsMultiLine) {
+ context.report(lastItem, penultimateToken.loc.end, MISSING_MESSAGE);
+ }
+ } else if (allowDangle === "always" && !hasDanglingComma) {
+ context.report(lastItem, lastItem.loc.end, MISSING_MESSAGE);
+ }
+ }
+ }
+ }
+
+ return {
+ "ObjectExpression": checkForTrailingComma,
+ "ArrayExpression": checkForTrailingComma
+ };
+};
diff --git a/tools/eslint/lib/rules/comma-spacing.js b/tools/eslint/lib/rules/comma-spacing.js
new file mode 100644
index 0000000000..32136a3523
--- /dev/null
+++ b/tools/eslint/lib/rules/comma-spacing.js
@@ -0,0 +1,159 @@
+/**
+ * @fileoverview Comma spacing - validates spacing before and after comma
+ * @author Vignesh Anand aka vegetableman.
+ * @copyright 2014 Vignesh Anand. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var options = {
+ before: context.options[0] ? !!context.options[0].before : false,
+ after: context.options[0] ? !!context.options[0].after : true
+ };
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ // the index of the last comment that was checked
+ var lastCommentIndex = 0;
+
+ /**
+ * Determines whether two adjacent tokens have whitespace between them.
+ * @param {Object} left - The left token object.
+ * @param {Object} right - The right token object.
+ * @returns {boolean} Whether or not there is space between the tokens.
+ */
+ function isSpaced(left, right) {
+ var punctuationLength = context.getTokensBetween(left, right).length; // the length of any parenthesis
+ return (left.range[1] + punctuationLength) < right.range[0];
+ }
+
+ /**
+ * Checks whether two tokens are on the same line.
+ * @param {ASTNode} left The leftmost token.
+ * @param {ASTNode} right The rightmost token.
+ * @returns {boolean} True if the tokens are on the same line, false if not.
+ * @private
+ */
+ function isSameLine(left, right) {
+ return left.loc.end.line === right.loc.start.line;
+ }
+
+ /**
+ * Determines if a given token is a comma operator.
+ * @param {ASTNode} token The token to check.
+ * @returns {boolean} True if the token is a comma, false if not.
+ * @private
+ */
+ function isComma(token) {
+ return !!token && (token.type === "Punctuator") && (token.value === ",");
+ }
+
+ /**
+ * Reports a spacing error with an appropriate message.
+ * @param {ASTNode} node The binary expression node to report.
+ * @param {string} dir Is the error "before" or "after" the comma?
+ * @returns {void}
+ * @private
+ */
+ function report(node, dir) {
+ context.report(node, options[dir] ?
+ "A space is required " + dir + " ','." :
+ "There should be no space " + dir + " ','.");
+ }
+
+ /**
+ * Validates the spacing around a comma token.
+ * @param {Object} tokens - The tokens to be validated.
+ * @param {Token} tokens.comma The token representing the comma.
+ * @param {Token} [tokens.left] The last token before the comma.
+ * @param {Token} [tokens.right] The first token after the comma.
+ * @param {Token|ASTNode} reportItem The item to use when reporting an error.
+ * @returns {void}
+ * @private
+ */
+ function validateCommaItemSpacing(tokens, reportItem) {
+ if (tokens.left && isSameLine(tokens.left, tokens.comma) &&
+ (options.before !== isSpaced(tokens.left, tokens.comma))
+ ) {
+ report(reportItem, "before");
+ }
+ if (tokens.right && isSameLine(tokens.comma, tokens.right) &&
+ (options.after !== isSpaced(tokens.comma, tokens.right))
+ ) {
+ report(reportItem, "after");
+ }
+ }
+
+ /**
+ * Determines if a given source index is in a comment or not by checking
+ * the index against the comment range. Since the check goes straight
+ * through the file, once an index is passed a certain comment, we can
+ * go to the next comment to check that.
+ * @param {int} index The source index to check.
+ * @param {ASTNode[]} comments An array of comment nodes.
+ * @returns {boolean} True if the index is within a comment, false if not.
+ * @private
+ */
+ function isIndexInComment(index, comments) {
+
+ var comment;
+
+ while (lastCommentIndex < comments.length) {
+
+ comment = comments[lastCommentIndex];
+
+ if (comment.range[0] <= index && index < comment.range[1]) {
+ return true;
+ } else if (index > comment.range[1]) {
+ lastCommentIndex++;
+ } else {
+ break;
+ }
+
+ }
+
+ return false;
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "Program": function() {
+
+ var source = context.getSource(),
+ allComments = context.getAllComments(),
+ pattern = /,/g,
+ commaToken,
+ previousToken,
+ nextToken;
+
+ while (pattern.test(source)) {
+
+ // do not flag anything inside of comments
+ if (!isIndexInComment(pattern.lastIndex, allComments)) {
+ commaToken = context.getTokenByRangeStart(pattern.lastIndex - 1);
+
+ if (commaToken && commaToken.type !== "JSXText") {
+ previousToken = context.getTokenBefore(commaToken);
+ nextToken = context.getTokenAfter(commaToken);
+ validateCommaItemSpacing({
+ comma: commaToken,
+ left: isComma(previousToken) ? null : previousToken,
+ right: isComma(nextToken) ? null : nextToken
+ }, commaToken);
+ }
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/comma-style.js b/tools/eslint/lib/rules/comma-style.js
new file mode 100644
index 0000000000..4b8d145775
--- /dev/null
+++ b/tools/eslint/lib/rules/comma-style.js
@@ -0,0 +1,177 @@
+/**
+ * @fileoverview Comma style - enforces comma styles of two types: last and first
+ * @author Vignesh Anand aka vegetableman
+ * @copyright 2014 Vignesh Anand. All rights reserved.
+ * @copyright 2015 Evan Simmons. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var style = context.options[0] || "last",
+ exceptions = {};
+
+ if (context.options.length === 2 && context.options[1].hasOwnProperty("exceptions")) {
+ exceptions = context.options[1].exceptions;
+ }
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Checks whether two tokens are on the same line.
+ * @param {ASTNode} left The leftmost token.
+ * @param {ASTNode} right The rightmost token.
+ * @returns {boolean} True if the tokens are on the same line, false if not.
+ * @private
+ */
+ function isSameLine(left, right) {
+ return left.loc.end.line === right.loc.start.line;
+ }
+
+ /**
+ * Determines if a given token is a comma operator.
+ * @param {ASTNode} token The token to check.
+ * @returns {boolean} True if the token is a comma, false if not.
+ * @private
+ */
+ function isComma(token) {
+ return !!token && (token.type === "Punctuator") && (token.value === ",");
+ }
+
+ /**
+ * Validates the spacing around single items in lists.
+ * @param {Token} previousItemToken The last token from the previous item.
+ * @param {Token} commaToken The token representing the comma.
+ * @param {Token} currentItemToken The first token of the current item.
+ * @param {Token} reportItem The item to use when reporting an error.
+ * @returns {void}
+ * @private
+ */
+ function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) {
+
+ // if single line
+ if (isSameLine(commaToken, currentItemToken) &&
+ isSameLine(previousItemToken, commaToken)) {
+
+ return;
+
+ } else if (!isSameLine(commaToken, currentItemToken) &&
+ !isSameLine(previousItemToken, commaToken)) {
+
+ // lone comma
+ context.report(reportItem, {
+ line: commaToken.loc.end.line,
+ column: commaToken.loc.start.column
+ }, "Bad line breaking before and after ','.");
+
+ } else if (style === "first" && !isSameLine(commaToken, currentItemToken)) {
+
+ context.report(reportItem, "',' should be placed first.");
+
+ } else if (style === "last" && isSameLine(commaToken, currentItemToken)) {
+
+ context.report(reportItem, {
+ line: commaToken.loc.end.line,
+ column: commaToken.loc.end.column
+ }, "',' should be placed last.");
+ }
+ }
+
+ /**
+ * Checks the comma placement with regards to a declaration/property/element
+ * @param {ASTNode} node The binary expression node to check
+ * @param {string} property The property of the node containing child nodes.
+ * @private
+ * @returns {void}
+ */
+ function validateComma(node, property) {
+ var items = node[property],
+ arrayLiteral = (node.type === "ArrayExpression"),
+ previousItemToken;
+
+ if (items.length > 1 || arrayLiteral) {
+
+ // seed as opening [
+ previousItemToken = context.getFirstToken(node);
+
+ items.forEach(function(item) {
+ var commaToken = item ? context.getTokenBefore(item) : previousItemToken,
+ currentItemToken = item ? context.getFirstToken(item) : context.getTokenAfter(commaToken),
+ reportItem = item || currentItemToken;
+
+ /*
+ * This works by comparing three token locations:
+ * - previousItemToken is the last token of the previous item
+ * - commaToken is the location of the comma before the current item
+ * - currentItemToken is the first token of the current item
+ *
+ * These values get switched around if item is undefined.
+ * previousItemToken will refer to the last token not belonging
+ * to the current item, which could be a comma or an opening
+ * square bracket. currentItemToken could be a comma.
+ *
+ * All comparisons are done based on these tokens directly, so
+ * they are always valid regardless of an undefined item.
+ */
+ if (isComma(commaToken)) {
+ validateCommaItemSpacing(previousItemToken, commaToken,
+ currentItemToken, reportItem);
+ }
+
+ previousItemToken = item ? context.getLastToken(item) : previousItemToken;
+ });
+
+ /*
+ * Special case for array literals that have empty last items, such
+ * as [ 1, 2, ]. These arrays only have two items show up in the
+ * AST, so we need to look at the token to verify that there's no
+ * dangling comma.
+ */
+ if (arrayLiteral) {
+
+ var lastToken = context.getLastToken(node),
+ nextToLastToken = context.getTokenBefore(lastToken);
+
+ if (isComma(nextToLastToken)) {
+ validateCommaItemSpacing(
+ context.getTokenBefore(nextToLastToken),
+ nextToLastToken,
+ lastToken,
+ lastToken
+ );
+ }
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ var nodes = {};
+
+ if (!exceptions.VariableDeclaration) {
+ nodes.VariableDeclaration = function(node) {
+ validateComma(node, "declarations");
+ };
+ }
+ if (!exceptions.ObjectExpression) {
+ nodes.ObjectExpression = function(node) {
+ validateComma(node, "properties");
+ };
+ }
+ if (!exceptions.ArrayExpression) {
+ nodes.ArrayExpression = function(node) {
+ validateComma(node, "elements");
+ };
+ }
+
+ return nodes;
+};
diff --git a/tools/eslint/lib/rules/complexity.js b/tools/eslint/lib/rules/complexity.js
new file mode 100644
index 0000000000..36c9c1f467
--- /dev/null
+++ b/tools/eslint/lib/rules/complexity.js
@@ -0,0 +1,88 @@
+/**
+ * @fileoverview Counts the cyclomatic complexity of each function of the script. See http://en.wikipedia.org/wiki/Cyclomatic_complexity.
+ * Counts the number of if, conditional, for, whilte, try, switch/case,
+ * @author Patrick Brosset
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var THRESHOLD = context.options[0];
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ // Using a stack to store complexity (handling nested functions)
+ var fns = [];
+
+ // When parsing a new function, store it in our function stack
+ function startFunction() {
+ fns.push(1);
+ }
+
+ function endFunction(node) {
+ var complexity = fns.pop(),
+ name = "anonymous";
+
+ if (node.id) {
+ name = node.id.name;
+ } else if (node.parent.type === "MethodDefinition") {
+ name = node.parent.key.name;
+ }
+
+ if (complexity > THRESHOLD) {
+ context.report(node, "Function '{{name}}' has a complexity of {{complexity}}.", { name: name, complexity: complexity });
+ }
+ }
+
+ function increaseComplexity() {
+ if (fns.length) {
+ fns[fns.length - 1] ++;
+ }
+ }
+
+ function increaseSwitchComplexity(node) {
+ // Avoiding `default`
+ if (node.test) {
+ increaseComplexity(node);
+ }
+ }
+
+ function increaseLogicalComplexity(node) {
+ // Avoiding &&
+ if (node.operator === "||") {
+ increaseComplexity(node);
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+ "FunctionDeclaration": startFunction,
+ "FunctionExpression": startFunction,
+ "ArrowFunctionExpression": startFunction,
+ "FunctionDeclaration:exit": endFunction,
+ "FunctionExpression:exit": endFunction,
+ "ArrowFunctionExpression:exit": endFunction,
+
+ "CatchClause": increaseComplexity,
+ "ConditionalExpression": increaseComplexity,
+ "LogicalExpression": increaseLogicalComplexity,
+ "ForStatement": increaseComplexity,
+ "ForInStatement": increaseComplexity,
+ "ForOfStatement": increaseComplexity,
+ "IfStatement": increaseComplexity,
+ "SwitchCase": increaseSwitchComplexity,
+ "WhileStatement": increaseComplexity,
+ "DoWhileStatement": increaseComplexity
+ };
+
+};
diff --git a/tools/eslint/lib/rules/consistent-return.js b/tools/eslint/lib/rules/consistent-return.js
new file mode 100644
index 0000000000..38adba7f31
--- /dev/null
+++ b/tools/eslint/lib/rules/consistent-return.js
@@ -0,0 +1,73 @@
+/**
+ * @fileoverview Rule to flag consistent return values
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var functions = [];
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Marks entrance into a function by pushing a new object onto the functions
+ * stack.
+ * @returns {void}
+ * @private
+ */
+ function enterFunction() {
+ functions.push({});
+ }
+
+ /**
+ * Marks exit of a function by popping off the functions stack.
+ * @returns {void}
+ * @private
+ */
+ function exitFunction() {
+ functions.pop();
+ }
+
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "Program": enterFunction,
+ "FunctionDeclaration": enterFunction,
+ "FunctionExpression": enterFunction,
+ "ArrowFunctionExpression": enterFunction,
+
+ "Program:exit": exitFunction,
+ "FunctionDeclaration:exit": exitFunction,
+ "FunctionExpression:exit": exitFunction,
+ "ArrowFunctionExpression:exit": exitFunction,
+
+ "ReturnStatement": function(node) {
+
+ var returnInfo = functions[functions.length - 1],
+ returnTypeDefined = "type" in returnInfo;
+
+ if (returnTypeDefined) {
+
+ if (returnInfo.type !== !!node.argument) {
+ context.report(node, "Expected " + (returnInfo.type ? "a" : "no") + " return value.");
+ }
+
+ } else {
+ returnInfo.type = !!node.argument;
+ }
+
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/consistent-this.js b/tools/eslint/lib/rules/consistent-this.js
new file mode 100644
index 0000000000..3efb2cd372
--- /dev/null
+++ b/tools/eslint/lib/rules/consistent-this.js
@@ -0,0 +1,108 @@
+/**
+ * @fileoverview Rule to enforce consistent naming of "this" context variables
+ * @author Raphael Pigulla
+ * @copyright 2015 Timothy Jones. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+ var alias = context.options[0];
+
+ /**
+ * Reports that a variable declarator or assignment expression is assigning
+ * a non-'this' value to the specified alias.
+ * @param {ASTNode} node - The assigning node.
+ * @returns {void}
+ */
+ function reportBadAssignment(node) {
+ context.report(node,
+ "Designated alias '{{alias}}' is not assigned to 'this'.",
+ { alias: alias });
+ }
+
+ /**
+ * Checks that an assignment to an identifier only assigns 'this' to the
+ * appropriate alias, and the alias is only assigned to 'this'.
+ * @param {ASTNode} node - The assigning node.
+ * @param {Identifier} name - The name of the variable assigned to.
+ * @param {Expression} value - The value of the assignment.
+ * @returns {void}
+ */
+ function checkAssignment(node, name, value) {
+ var isThis = value.type === "ThisExpression";
+
+ if (name === alias) {
+ if (!isThis || node.operator && node.operator !== "=") {
+ reportBadAssignment(node);
+ }
+ } else if (isThis) {
+ context.report(node,
+ "Unexpected alias '{{name}}' for 'this'.", { name: name });
+ }
+ }
+
+ /**
+ * Ensures that a variable declaration of the alias in a program or function
+ * is assigned to the correct value.
+ * @returns {void}
+ */
+ function ensureWasAssigned() {
+ var scope = context.getScope();
+
+ scope.variables.some(function (variable) {
+ var lookup;
+
+ if (variable.name === alias) {
+ if (variable.defs.some(function (def) {
+ return def.node.type === "VariableDeclarator" &&
+ def.node.init !== null;
+ })) {
+ return true;
+ }
+
+ lookup = scope.type === "global" ? scope : variable;
+
+ // The alias has been declared and not assigned: check it was
+ // assigned later in the same scope.
+ if (!lookup.references.some(function (reference) {
+ var write = reference.writeExpr;
+
+ if (reference.from === scope &&
+ write && write.type === "ThisExpression" &&
+ write.parent.operator === "=") {
+ return true;
+ }
+ })) {
+ variable.defs.map(function (def) {
+ return def.node;
+ }).forEach(reportBadAssignment);
+ }
+
+ return true;
+ }
+ });
+ }
+
+ return {
+ "Program:exit": ensureWasAssigned,
+ "FunctionExpression:exit": ensureWasAssigned,
+ "FunctionDeclaration:exit": ensureWasAssigned,
+
+ "VariableDeclarator": function (node) {
+ if (node.init !== null) {
+ checkAssignment(node, node.id.name, node.init);
+ }
+ },
+
+ "AssignmentExpression": function (node) {
+ if (node.left.type === "Identifier") {
+ checkAssignment(node, node.left.name, node.right);
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/curly.js b/tools/eslint/lib/rules/curly.js
new file mode 100644
index 0000000000..2bfcfb2798
--- /dev/null
+++ b/tools/eslint/lib/rules/curly.js
@@ -0,0 +1,103 @@
+/**
+ * @fileoverview Rule to flag statements without curly braces
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var multiOnly = (context.options[0] === "multi");
+ var multiLine = (context.options[0] === "multi-line");
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Determines if a given node is a one-liner that's on the same line as it's preceding code.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code.
+ * @private
+ */
+ function isCollapsedOneLiner(node) {
+ var before = context.getTokenBefore(node),
+ last = context.getLastToken(node);
+ return before.loc.start.line === last.loc.end.line;
+ }
+
+ /**
+ * Checks the body of a node to see if it's a block statement. Depending on
+ * the rule options, reports the appropriate problems.
+ * @param {ASTNode} node The node to report if there's a problem.
+ * @param {ASTNode} body The body node to check for blocks.
+ * @param {string} name The name to report if there's a problem.
+ * @param {string} suffix Additional string to add to the end of a report.
+ * @returns {void}
+ */
+ function checkBody(node, body, name, suffix) {
+ var hasBlock = (body.type === "BlockStatement");
+
+ if (multiOnly) {
+ if (hasBlock && body.body.length === 1) {
+ context.report(node, "Unnecessary { after '{{name}}'{{suffix}}.",
+ {
+ name: name,
+ suffix: (suffix ? " " + suffix : "")
+ }
+ );
+ }
+ } else if (multiLine) {
+ if (!hasBlock && !isCollapsedOneLiner(body)) {
+ context.report(node, "Expected { after '{{name}}'{{suffix}}.",
+ {
+ name: name,
+ suffix: (suffix ? " " + suffix : "")
+ }
+ );
+ }
+ } else {
+ if (!hasBlock) {
+ context.report(node, "Expected { after '{{name}}'{{suffix}}.",
+ {
+ name: name,
+ suffix: (suffix ? " " + suffix : "")
+ }
+ );
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "IfStatement": function(node) {
+
+ checkBody(node, node.consequent, "if", "condition");
+
+ if (node.alternate && node.alternate.type !== "IfStatement") {
+ checkBody(node, node.alternate, "else");
+ }
+
+ },
+
+ "WhileStatement": function(node) {
+ checkBody(node, node.body, "while", "condition");
+ },
+
+ "DoWhileStatement": function (node) {
+ checkBody(node, node.body, "do");
+ },
+
+ "ForStatement": function(node) {
+ checkBody(node, node.body, "for", "condition");
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/default-case.js b/tools/eslint/lib/rules/default-case.js
new file mode 100644
index 0000000000..da0da1ad7d
--- /dev/null
+++ b/tools/eslint/lib/rules/default-case.js
@@ -0,0 +1,64 @@
+/**
+ * @fileoverview require default case in switch statements
+ * @author Aliaksei Shytkin
+ */
+"use strict";
+
+var COMMENT_VALUE = "no default";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Shortcut to get last element of array
+ * @param {*[]} collection Array
+ * @returns {*} Last element
+ */
+ function last(collection) {
+ return collection[collection.length - 1];
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "SwitchStatement": function(node) {
+
+ if (!node.cases.length) {
+ // skip check of empty switch because there is no easy way
+ // to extract comments inside it now
+ return;
+ }
+
+ var hasDefault = node.cases.some(function(v) {
+ return v.test === null;
+ });
+
+ if (!hasDefault) {
+
+ var comment;
+ var comments;
+
+ var lastCase = last(node.cases);
+ comments = context.getComments(lastCase).trailing;
+
+ if (comments.length) {
+ comment = last(comments);
+ }
+
+ if (!comment || comment.value.trim() !== COMMENT_VALUE) {
+ context.report(node, "Expected a default case.");
+ }
+ }
+ }
+ };
+};
diff --git a/tools/eslint/lib/rules/dot-notation.js b/tools/eslint/lib/rules/dot-notation.js
new file mode 100644
index 0000000000..bf89cbfdf8
--- /dev/null
+++ b/tools/eslint/lib/rules/dot-notation.js
@@ -0,0 +1,104 @@
+/**
+ * @fileoverview Rule to warn about using dot notation instead of square bracket notation when possible.
+ * @author Josh Perez
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+var validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
+var keywords = [
+ "this",
+ "function",
+ "if",
+ "return",
+ "var",
+ "else",
+ "for",
+ "new",
+ "in",
+ "typeof",
+ "while",
+ "case",
+ "break",
+ "try",
+ "catch",
+ "delete",
+ "throw",
+ "switch",
+ "continue",
+ "default",
+ "instanceof",
+ "do",
+ "void",
+ "finally",
+ "with",
+ "debugger",
+ "implements",
+ "interface",
+ "package",
+ "private",
+ "protected",
+ "public",
+ "static",
+ "class",
+ "enum",
+ "export",
+ "extends",
+ "import",
+ "super",
+ "true",
+ "false",
+ "null",
+ "abstract",
+ "boolean",
+ "byte",
+ "char",
+ "const",
+ "double",
+ "final",
+ "float",
+ "goto",
+ "int",
+ "long",
+ "native",
+ "short",
+ "synchronized",
+ "throws",
+ "transient",
+ "volatile"
+];
+
+module.exports = function(context) {
+ var options = context.options[0] || {};
+ var allowKeywords = options.allowKeywords === void 0 || !!options.allowKeywords;
+
+ var allowPattern;
+ if (options.allowPattern) {
+ allowPattern = new RegExp(options.allowPattern);
+ }
+
+ return {
+ "MemberExpression": function(node) {
+ if (
+ node.computed &&
+ node.property.type === "Literal" &&
+ validIdentifier.test(node.property.value) &&
+ (allowKeywords || keywords.indexOf("" + node.property.value) === -1)
+ ) {
+ if (!(allowPattern && allowPattern.test(node.property.value))) {
+ context.report(node, "[" + JSON.stringify(node.property.value) + "] is better written in dot notation.");
+ }
+ }
+ if (
+ !allowKeywords &&
+ !node.computed &&
+ keywords.indexOf("" + node.property.name) !== -1
+ ) {
+ context.report(node, "." + node.property.name + " is a syntax error.");
+ }
+ }
+ };
+};
diff --git a/tools/eslint/lib/rules/eol-last.js b/tools/eslint/lib/rules/eol-last.js
new file mode 100644
index 0000000000..96c78c18b4
--- /dev/null
+++ b/tools/eslint/lib/rules/eol-last.js
@@ -0,0 +1,36 @@
+/**
+ * @fileoverview Require file to end with single newline.
+ * @author Nodeca Team <https://github.com/nodeca>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "Program": function checkBadEOF(node) {
+ // Get the whole source code, not for node only.
+ var src = context.getSource(), location = {column: 1};
+
+ if (src.length === 0) {
+ return;
+ }
+
+ if (src[src.length - 1] !== "\n") {
+ // file is not newline-terminated
+ location.line = src.split(/\n/g).length;
+ context.report(node, location, "Newline required at end of file but not found.");
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/eqeqeq.js b/tools/eslint/lib/rules/eqeqeq.js
new file mode 100644
index 0000000000..e847a8327e
--- /dev/null
+++ b/tools/eslint/lib/rules/eqeqeq.js
@@ -0,0 +1,90 @@
+/**
+ * @fileoverview Rule to flag statements that use != and == instead of !== and ===
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Checks if an expression is a typeof expression
+ * @param {ASTNode} node The node to check
+ * @returns {boolean} if the node is a typeof expression
+ */
+ function isTypeOf(node) {
+ return node.type === "UnaryExpression" && node.operator === "typeof";
+ }
+
+ /**
+ * Checks if either operand of a binary expression is a typeof operation
+ * @param {ASTNode} node The node to check
+ * @returns {boolean} if one of the operands is typeof
+ * @private
+ */
+ function isTypeOfBinary(node) {
+ return isTypeOf(node.left) || isTypeOf(node.right);
+ }
+
+ /**
+ * Checks if operands are literals of the same type (via typeof)
+ * @param {ASTNode} node The node to check
+ * @returns {boolean} if operands are of same type
+ * @private
+ */
+ function areLiteralsAndSameType(node) {
+ return node.left.type === "Literal" && node.right.type === "Literal" &&
+ typeof node.left.value === typeof node.right.value;
+ }
+
+ /**
+ * Checks if one of the operands is a literal null
+ * @param {ASTNode} node The node to check
+ * @returns {boolean} if operands are null
+ * @private
+ */
+ function isNullCheck(node) {
+ return (node.right.type === "Literal" && node.right.value === null) ||
+ (node.left.type === "Literal" && node.left.value === null);
+ }
+
+ /**
+ * Gets the location (line and column) of the binary expression's operator
+ * @param {ASTNode} node The binary expression node to check
+ * @param {String} operator The operator to find
+ * @returns {Object} { line, column } location of operator
+ * @private
+ */
+ function getOperatorLocation(node) {
+ var opToken = context.getTokenAfter(node.left);
+ return {line: opToken.loc.start.line, column: opToken.loc.start.column};
+ }
+
+ return {
+ "BinaryExpression": function(node) {
+ if (node.operator !== "==" && node.operator !== "!=") {
+ return;
+ }
+
+ if (context.options[0] === "smart" && (isTypeOfBinary(node) ||
+ areLiteralsAndSameType(node)) || isNullCheck(node)) {
+ return;
+ }
+
+ if (context.options[0] === "allow-null" && isNullCheck(node)) {
+ return;
+ }
+
+ context.report(
+ node, getOperatorLocation(node),
+ "Expected '{{op}}=' and instead saw '{{op}}'.",
+ {op: node.operator}
+ );
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/func-names.js b/tools/eslint/lib/rules/func-names.js
new file mode 100644
index 0000000000..eb4d9be10a
--- /dev/null
+++ b/tools/eslint/lib/rules/func-names.js
@@ -0,0 +1,43 @@
+/**
+ * @fileoverview Rule to warn when a function expression does not have a name.
+ * @author Kyle T. Nunery
+ * @copyright 2015 Brandon Mills. All rights reserved.
+ * @copyright 2014 Kyle T. Nunery. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Determines whether the current FunctionExpression node is a get, set, or
+ * shorthand method in an object literal or a class.
+ * @returns {boolean} True if the node is a get, set, or shorthand method.
+ */
+ function isObjectOrClassMethod() {
+ var parent = context.getAncestors().pop();
+
+ return (parent.type === "MethodDefinition" || (
+ parent.type === "Property" && (
+ parent.method ||
+ parent.kind === "get" ||
+ parent.kind === "set"
+ )
+ ));
+ }
+
+ return {
+ "FunctionExpression": function(node) {
+
+ var name = node.id && node.id.name;
+
+ if (!name && !isObjectOrClassMethod()) {
+ context.report(node, "Missing function expression name.");
+ }
+ }
+ };
+};
diff --git a/tools/eslint/lib/rules/func-style.js b/tools/eslint/lib/rules/func-style.js
new file mode 100644
index 0000000000..f54350e765
--- /dev/null
+++ b/tools/eslint/lib/rules/func-style.js
@@ -0,0 +1,43 @@
+/**
+ * @fileoverview Rule to enforce a particular function style
+ * @author Nicholas C. Zakas
+ * @copyright 2013 Nicholas C. Zakas. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var style = context.options[0],
+ enforceDeclarations = (style === "declaration");
+
+ return {
+
+ "FunctionDeclaration": function(node) {
+ if (!enforceDeclarations) {
+ context.report(node, "Expected a function expression.");
+ }
+ },
+
+ "FunctionExpression": function() {
+ var parent = context.getAncestors().pop();
+
+ if (enforceDeclarations && parent.type === "VariableDeclarator") {
+ context.report(parent, "Expected a function declaration.");
+ }
+ },
+
+ "ArrowFunctionExpression": function() {
+ var parent = context.getAncestors().pop();
+
+ if (enforceDeclarations && parent.type === "VariableDeclarator") {
+ context.report(parent, "Expected a function declaration.");
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/generator-star-spacing.js b/tools/eslint/lib/rules/generator-star-spacing.js
new file mode 100644
index 0000000000..fe0b82bd19
--- /dev/null
+++ b/tools/eslint/lib/rules/generator-star-spacing.js
@@ -0,0 +1,83 @@
+/**
+ * @fileoverview Rule to check the spacing around the * in generator functions.
+ * @author Jamund Ferguson
+ * @copyright 2015 Brandon Mills. All rights reserved.
+ * @copyright 2014 Jamund Ferguson. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var mode = {
+ before: { before: true, after: false },
+ after: { before: false, after: true },
+ both: { before: true, after: true },
+ neither: { before: false, after: false }
+ }[context.options[0] || "before"];
+
+ /**
+ * Checks the spacing between two tokens before or after the star token.
+ * @param {string} side Either "before" or "after".
+ * @param {Token} leftToken `function` keyword token if side is "before", or
+ * star token if side is "after".
+ * @param {Token} rightToken Star token if side is "before", or identifier
+ * token if side is "after".
+ * @returns {void}
+ */
+ function checkSpacing(side, leftToken, rightToken) {
+ if (!!(rightToken.range[0] - leftToken.range[1]) !== mode[side]) {
+ context.report(
+ leftToken.value === "*" ? leftToken : rightToken,
+ "{{type}} space {{side}} *.",
+ {
+ type: mode[side] ? "Missing" : "Unexpected",
+ side: side
+ }
+ );
+ }
+ }
+
+ /**
+ * Enforces the spacing around the star if node is a generator function.
+ * @param {ASTNode} node A function expression or declaration node.
+ * @returns {void}
+ */
+ function checkFunction(node) {
+ var isMethod, starToken, tokenBefore, tokenAfter;
+
+ if (!node.generator) {
+ return;
+ }
+
+ isMethod = !!context.getAncestors().pop().method;
+
+ if (isMethod) {
+ starToken = context.getTokenBefore(node, 1);
+ } else {
+ starToken = context.getFirstToken(node, 1);
+ }
+
+ // Only check before when preceded by `function` keyword
+ tokenBefore = context.getTokenBefore(starToken);
+ if (tokenBefore.value === "function") {
+ checkSpacing("before", tokenBefore, starToken);
+ }
+
+ // Only check after when followed by an identifier
+ tokenAfter = context.getTokenAfter(starToken);
+ if (tokenAfter.type === "Identifier") {
+ checkSpacing("after", starToken, tokenAfter);
+ }
+ }
+
+ return {
+ "FunctionDeclaration": checkFunction,
+ "FunctionExpression": checkFunction
+ };
+
+};
diff --git a/tools/eslint/lib/rules/generator-star.js b/tools/eslint/lib/rules/generator-star.js
new file mode 100644
index 0000000000..4541e6711c
--- /dev/null
+++ b/tools/eslint/lib/rules/generator-star.js
@@ -0,0 +1,70 @@
+/**
+ * @fileoverview Rule to check for the position of the * in your generator functions
+ * @author Jamund Ferguson
+ * @copyright 2014 Jamund Ferguson. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var position = context.options[0] || "end";
+
+ /**
+ * Check the position of the star compared to the expected position.
+ * @param {ASTNode} node - the entire function node
+ * @returns {void}
+ */
+ function checkStarPosition(node) {
+ var starToken;
+
+ if (!node.generator) {
+ return;
+ }
+
+ // Blocked, pending decision to fix or work around in eslint/espree#36
+ if (context.getAncestors().pop().method) {
+ return;
+ }
+
+ starToken = context.getFirstToken(node, 1);
+
+ // check for function *name() {}
+ if (position === "end") {
+
+ // * starts where the next identifier begins
+ if (starToken.range[1] !== context.getTokenAfter(starToken).range[0]) {
+ context.report(node, "Expected a space before *.");
+ }
+ }
+
+ // check for function* name() {}
+ if (position === "start") {
+
+ // * begins where the previous identifier ends
+ if (starToken.range[0] !== context.getTokenBefore(starToken).range[1]) {
+ context.report(node, "Expected no space before *.");
+ }
+ }
+
+ // check for function * name() {}
+ if (position === "middle") {
+
+ // must be a space before and afer the *
+ if (starToken.range[0] <= context.getTokenBefore(starToken).range[1] ||
+ starToken.range[1] >= context.getTokenAfter(starToken).range[0]) {
+ context.report(node, "Expected spaces around *.");
+ }
+ }
+ }
+
+ return {
+ "FunctionDeclaration": checkStarPosition,
+ "FunctionExpression": checkStarPosition
+ };
+
+};
diff --git a/tools/eslint/lib/rules/global-strict.js b/tools/eslint/lib/rules/global-strict.js
new file mode 100644
index 0000000000..c16c62db92
--- /dev/null
+++ b/tools/eslint/lib/rules/global-strict.js
@@ -0,0 +1,43 @@
+/**
+ * @fileoverview Rule to flag or require global strict mode.
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var mode = context.options[0];
+
+ if (mode === "always") {
+
+ return {
+ "Program": function(node) {
+ if (node.body.length > 0) {
+ var statement = node.body[0];
+
+ if (!(statement.type === "ExpressionStatement" && statement.expression.value === "use strict")) {
+ context.report(node, "Use the global form of \"use strict\".");
+ }
+ }
+ }
+ };
+
+ } else { // mode = "never"
+
+ return {
+ "ExpressionStatement": function(node) {
+ var parent = context.getAncestors().pop();
+
+ if (node.expression.value === "use strict" && parent.type === "Program") {
+ context.report(node, "Use the function form of \"use strict\".");
+ }
+ }
+ };
+
+ }
+
+};
diff --git a/tools/eslint/lib/rules/guard-for-in.js b/tools/eslint/lib/rules/guard-for-in.js
new file mode 100644
index 0000000000..d79651d38c
--- /dev/null
+++ b/tools/eslint/lib/rules/guard-for-in.js
@@ -0,0 +1,30 @@
+/**
+ * @fileoverview Rule to flag for-in loops without if statements inside
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "ForInStatement": function(node) {
+
+ /*
+ * If the for-in statement has {}, then the real body is the body
+ * of the BlockStatement. Otherwise, just use body as provided.
+ */
+ var body = node.body.type === "BlockStatement" ? node.body.body[0] : node.body;
+
+ if (body && body.type !== "IfStatement") {
+ context.report(node, "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/handle-callback-err.js b/tools/eslint/lib/rules/handle-callback-err.js
new file mode 100644
index 0000000000..f57e4d1752
--- /dev/null
+++ b/tools/eslint/lib/rules/handle-callback-err.js
@@ -0,0 +1,118 @@
+/**
+ * @fileoverview Ensure handling of errors when we know they exist.
+ * @author Jamund Ferguson
+ * @copyright 2014 Jamund Ferguson. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var errorArgument = context.options[0] || "err";
+ var callbacks = [];
+ var scopes = 0;
+
+ /**
+ * Checks if the given argument should be interpreted as a regexp pattern.
+ * @param {string} stringToCheck The string which should be checked.
+ * @returns {boolean} Whether or not the string should be interpreted as a pattern.
+ */
+ function isPattern(stringToCheck) {
+ var firstChar = stringToCheck[0];
+ return firstChar === "^";
+ }
+
+ /**
+ * Checks if the given name matches the configured error argument.
+ * @param {string} name The name which should be compared.
+ * @returns {boolean} Whether or not the given name matches the configured error variable name.
+ */
+ function matchesConfiguredErrorName(name) {
+ if (isPattern(errorArgument)) {
+ var regexp = new RegExp(errorArgument);
+ return regexp.test(name);
+ }
+ return name === errorArgument;
+ }
+
+ /**
+ * Check the arguments to see if we need to start tracking the error object.
+ * @param {ASTNode} node The AST node to check.
+ * @returns {void}
+ */
+ function startFunction(node) {
+
+ // keep track of nested scopes
+ scopes++;
+
+ // check if the first argument matches our argument name
+ var firstArg = node.params && node.params[0];
+ if (firstArg && matchesConfiguredErrorName(firstArg.name)) {
+ callbacks.push({handled: false, depth: scopes, errorVariableName: firstArg.name});
+ }
+ }
+
+ /**
+ * At the end of a function check to see if the error was handled.
+ * @param {ASTNode} node The AST node to check.
+ * @returns {void}
+ */
+ function endFunction(node) {
+
+ var callback = callbacks[callbacks.length - 1] || {};
+
+ // check if a callback is ending, if so pop it off the stack
+ if (callback.depth === scopes) {
+ callbacks.pop();
+
+ // check if there were no handled errors since the last callback
+ if (!callback.handled) {
+ context.report(node, "Expected error to be handled.");
+ }
+ }
+
+ // less nested functions
+ scopes--;
+
+ }
+
+ /**
+ * Check to see if we're handling the error object properly.
+ * @param {ASTNode} node The AST node to check.
+ * @returns {void}
+ */
+ function checkForError(node) {
+ if (callbacks.length > 0) {
+ var callback = callbacks[callbacks.length - 1] || {};
+
+ // make sure the node's name matches our error argument name
+ var isAboutError = node.name === callback.errorVariableName;
+
+ // we don't consider these use cases as "handling" the error
+ var doNotCount = ["FunctionDeclaration", "ArrowFunctionExpression", "FunctionExpression", "CatchClause"];
+
+ // make sure this identifier isn't used as part of one of them
+ var isHandled = doNotCount.indexOf(node.parent.type) === -1;
+
+ if (isAboutError && isHandled) {
+ // record that this callback handled its error
+ callback.handled = true;
+ }
+ }
+ }
+
+ return {
+ "FunctionDeclaration": startFunction,
+ "FunctionExpression": startFunction,
+ "ArrowFunctionExpression": startFunction,
+ "Identifier": checkForError,
+ "FunctionDeclaration:exit": endFunction,
+ "FunctionExpression:exit": endFunction,
+ "ArrowFunctionExpression:exit": endFunction
+ };
+
+};
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);
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/key-spacing.js b/tools/eslint/lib/rules/key-spacing.js
new file mode 100644
index 0000000000..3ffaf9302f
--- /dev/null
+++ b/tools/eslint/lib/rules/key-spacing.js
@@ -0,0 +1,307 @@
+/**
+ * @fileoverview Rule to specify spacing of object literal keys and values
+ * @author Brandon Mills
+ * @copyright 2014 Brandon Mills. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether a string contains a line terminator as defined in
+ * http://www.ecma-international.org/ecma-262/5.1/#sec-7.3
+ * @param {string} str String to test.
+ * @returns {boolean} True if str contains a line terminator.
+ */
+function containsLineTerminator(str) {
+ return /[\n\r\u2028\u2029]/.test(str);
+}
+
+/**
+ * Gets the last element of an array.
+ * @param {Array} arr An array.
+ * @returns {any} Last element of arr.
+ */
+function last(arr) {
+ return arr[arr.length - 1];
+}
+
+/**
+ * Checks whether a property is a member of the property group it follows.
+ * @param {ASTNode} lastMember The last Property known to be in the group.
+ * @param {ASTNode} candidate The next Property that might be in the group.
+ * @returns {boolean} True if the candidate property is part of the group.
+ */
+function continuesPropertyGroup(lastMember, candidate) {
+ var groupEndLine = lastMember.loc.end.line,
+ candidateStartLine = candidate.loc.start.line,
+ comments, i;
+
+ if (candidateStartLine - groupEndLine <= 1) {
+ return true;
+ }
+
+ // Check that the first comment is adjacent to the end of the group, the
+ // last comment is adjacent to the candidate property, and that successive
+ // comments are adjacent to each other.
+ comments = candidate.leadingComments;
+ if (
+ comments &&
+ comments[0].loc.start.line - groupEndLine <= 1 &&
+ candidateStartLine - last(comments).loc.end.line <= 1
+ ) {
+ for (i = 1; i < comments.length; i++) {
+ if (comments[i].loc.start.line - comments[i - 1].loc.end.line > 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Checks whether a node is contained on a single line.
+ * @param {ASTNode} node AST Node being evaluated.
+ * @returns {boolean} True if the node is a single line.
+ */
+function isSingleLine(node) {
+ return (node.loc.end.line === node.loc.start.line);
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+var messages = {
+ key: "{{error}} space after {{computed}}key \"{{key}}\".",
+ value: "{{error}} space before value for {{computed}}key \"{{key}}\"."
+};
+
+module.exports = function(context) {
+
+ /**
+ * OPTIONS
+ * "key-spacing": [2, {
+ * beforeColon: false,
+ * afterColon: true,
+ * align: "colon" // Optional, or "value"
+ * }
+ */
+
+ var options = context.options[0] || {},
+ align = options.align,
+ beforeColon = +!!options.beforeColon, // Defaults to false
+ afterColon = +!(options.afterColon === false); // Defaults to true
+
+ /**
+ * Gets an object literal property's key as the identifier name or string value.
+ * @param {ASTNode} property Property node whose key to retrieve.
+ * @returns {string} The property's key.
+ */
+ function getKey(property) {
+ var key = property.key;
+
+ if (property.computed) {
+ return context.getSource().slice(key.range[0], key.range[1]);
+ }
+
+ return property.key.name || property.key.value;
+ }
+
+ /**
+ * Reports an appropriately-formatted error if spacing is incorrect on one
+ * side of the colon.
+ * @param {ASTNode} property Key-value pair in an object literal.
+ * @param {string} side Side being verified - either "key" or "value".
+ * @param {string} whitespace Actual whitespace string.
+ * @param {int} expected Expected whitespace length.
+ * @returns {void}
+ */
+ function report(property, side, whitespace, expected) {
+ var diff = whitespace.length - expected,
+ key = property.key,
+ firstTokenAfterColon = context.getTokenAfter(key, 1),
+ location = side === "key" ? key.loc.start : firstTokenAfterColon.loc.start;
+
+ if (diff && !(expected && containsLineTerminator(whitespace))) {
+ context.report(property[side], location, messages[side], {
+ error: diff > 0 ? "Extra" : "Missing",
+ computed: property.computed ? "computed " : "",
+ key: getKey(property)
+ });
+ }
+ }
+
+ /**
+ * Gets the number of characters in a key, including quotes around string
+ * keys and braces around computed property keys.
+ * @param {ASTNode} property Property of on object literal.
+ * @returns {int} Width of the key.
+ */
+ function getKeyWidth(property) {
+ var key = property.key,
+ startToken, endToken;
+
+ // [computed]: value
+ if (property.computed) {
+ startToken = context.getTokenBefore(key);
+ endToken = context.getTokenAfter(key);
+ return endToken.range[1] - startToken.range[0];
+ }
+
+ // name: value
+ if (key.type === "Identifier") {
+ return key.name.length;
+ }
+
+ // "literal": value
+ // 42: value
+ if (key.type === "Literal") {
+ return key.raw.length;
+ }
+ }
+
+ /**
+ * Gets the whitespace around the colon in an object literal property.
+ * @param {ASTNode} property Property node from an object literal.
+ * @returns {Object} Whitespace before and after the property's colon.
+ */
+ function getPropertyWhitespace(property) {
+ var whitespace = /(\s*):(\s*)/.exec(context.getSource().slice(
+ property.key.range[1], property.value.range[0]
+ ));
+
+ if (whitespace) {
+ return {
+ beforeColon: whitespace[1],
+ afterColon: whitespace[2]
+ };
+ }
+ }
+
+ /**
+ * Creates groups of properties.
+ * @param {ASTNode} node ObjectExpression node being evaluated.
+ * @returns {Array.<ASTNode[]>} Groups of property AST node lists.
+ */
+ function createGroups(node) {
+ if (node.properties.length === 1) {
+ return [node.properties];
+ }
+
+ return node.properties.reduce(function(groups, property) {
+ var currentGroup = last(groups),
+ prev = last(currentGroup);
+
+ if (!prev || continuesPropertyGroup(prev, property)) {
+ currentGroup.push(property);
+ } else {
+ groups.push([property]);
+ }
+
+ return groups;
+ }, [[]]);
+ }
+
+ /**
+ * Verifies correct vertical alignment of a group of properties.
+ * @param {ASTNode[]} properties List of Property AST nodes.
+ * @returns {void}
+ */
+ function verifyGroupAlignment(properties) {
+ var length = properties.length,
+ widths = properties.map(getKeyWidth), // Width of keys, including quotes
+ targetWidth = Math.max.apply(null, widths),
+ i, property, whitespace, width;
+
+ // Conditionally include one space before or after colon
+ targetWidth += (align === "colon" ? beforeColon : afterColon);
+
+ for (i = 0; i < length; i++) {
+ property = properties[i];
+ whitespace = getPropertyWhitespace(property);
+
+ if (!whitespace) {
+ continue; // Object literal getters/setters lack a colon
+ }
+
+ width = widths[i];
+
+ if (align === "value") {
+ report(property, "key", whitespace.beforeColon, beforeColon);
+ report(property, "value", whitespace.afterColon, targetWidth - width);
+ } else { // align = "colon"
+ report(property, "key", whitespace.beforeColon, targetWidth - width);
+ report(property, "value", whitespace.afterColon, afterColon);
+ }
+ }
+ }
+
+ /**
+ * Verifies vertical alignment, taking into account groups of properties.
+ * @param {ASTNode} node ObjectExpression node being evaluated.
+ * @returns {void}
+ */
+ function verifyAlignment(node) {
+ createGroups(node).forEach(function(group) {
+ verifyGroupAlignment(group);
+ });
+ }
+
+ /**
+ * Verifies spacing of property conforms to specified options.
+ * @param {ASTNode} node Property node being evaluated.
+ * @returns {void}
+ */
+ function verifySpacing(node) {
+ var whitespace = getPropertyWhitespace(node);
+ if (whitespace) { // Object literal getters/setters lack colons
+ report(node, "key", whitespace.beforeColon, beforeColon);
+ report(node, "value", whitespace.afterColon, afterColon);
+ }
+ }
+
+ /**
+ * Verifies spacing of each property in a list.
+ * @param {ASTNode[]} properties List of Property AST nodes.
+ * @returns {void}
+ */
+ function verifyListSpacing(properties) {
+ var length = properties.length;
+
+ for (var i = 0; i < length; i++) {
+ verifySpacing(properties[i]);
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ if (align) { // Verify vertical alignment
+
+ return {
+ "ObjectExpression": function(node) {
+ if (isSingleLine(node)) {
+ verifyListSpacing(node.properties);
+ } else {
+ verifyAlignment(node);
+ }
+ }
+ };
+
+ } else { // Strictly obey beforeColon and afterColon in each property
+
+ return {
+ "Property": function (node) {
+ verifySpacing(node);
+ }
+ };
+
+ }
+
+};
diff --git a/tools/eslint/lib/rules/max-depth.js b/tools/eslint/lib/rules/max-depth.js
new file mode 100644
index 0000000000..cc7e366257
--- /dev/null
+++ b/tools/eslint/lib/rules/max-depth.js
@@ -0,0 +1,83 @@
+/**
+ * @fileoverview A rule to set the maximum depth block can be nested in a function.
+ * @author Ian Christian Myers
+ * @copyright 2013 Ian Christian Myers. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ var functionStack = [],
+ maxDepth = context.options[0] || 4;
+
+ function startFunction() {
+ functionStack.push(0);
+ }
+
+ function endFunction() {
+ functionStack.pop();
+ }
+
+ function pushBlock(node) {
+ var len = ++functionStack[functionStack.length - 1];
+
+ if (len > maxDepth) {
+ context.report(node, "Blocks are nested too deeply ({{depth}}).",
+ { depth: len });
+ }
+ }
+
+ function popBlock() {
+ functionStack[functionStack.length - 1]--;
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+ "Program": startFunction,
+ "FunctionDeclaration": startFunction,
+ "FunctionExpression": startFunction,
+ "ArrowFunctionExpression": startFunction,
+
+ "IfStatement": function(node) {
+ if (node.parent.type !== "IfStatement") {
+ pushBlock(node);
+ }
+ },
+ "SwitchStatement": pushBlock,
+ "TryStatement": pushBlock,
+ "DoWhileStatement": pushBlock,
+ "WhileStatement": pushBlock,
+ "WithStatement": pushBlock,
+ "ForStatement": pushBlock,
+ "ForInStatement": pushBlock,
+ "ForOfStatement": pushBlock,
+
+ "IfStatement:exit": popBlock,
+ "SwitchStatement:exit": popBlock,
+ "TryStatement:exit": popBlock,
+ "DoWhileStatement:exit": popBlock,
+ "WhileStatement:exit": popBlock,
+ "WithStatement:exit": popBlock,
+ "ForStatement:exit": popBlock,
+ "ForInStatement:exit": popBlock,
+ "ForOfStatement:exit": popBlock,
+
+ "FunctionDeclaration:exit": endFunction,
+ "FunctionExpression:exit": endFunction,
+ "ArrowFunctionExpression:exit": endFunction,
+ "Program:exit": endFunction
+ };
+
+};
diff --git a/tools/eslint/lib/rules/max-len.js b/tools/eslint/lib/rules/max-len.js
new file mode 100644
index 0000000000..a36dd73b1b
--- /dev/null
+++ b/tools/eslint/lib/rules/max-len.js
@@ -0,0 +1,65 @@
+/**
+ * @fileoverview Rule to check for max length on a line.
+ * @author Matt DuVall <http://www.mattduvall.com>
+ * @copyright 2013 Matt DuVall. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Creates a string that is made up of repeating a given string a certain
+ * number of times. This uses exponentiation of squares to achieve significant
+ * performance gains over the more traditional implementation of such
+ * functionality.
+ * @param {string} str The string to repeat.
+ * @param {int} num The number of times to repeat the string.
+ * @returns {string} The created string.
+ * @private
+ */
+ function stringRepeat(str, num) {
+ var result = "";
+ for (num |= 0; num > 0; num >>>= 1, str += str) {
+ if (num & 1) {
+ result += str;
+ }
+ }
+ return result;
+ }
+
+ var tabWidth = context.options[1];
+
+ var maxLength = context.options[0],
+ tabString = stringRepeat(" ", tabWidth);
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+ function checkProgramForMaxLength(node) {
+ var lines = context.getSourceLines();
+
+ // Replace the tabs
+ // Split (honors line-ending)
+ // Iterate
+ lines.forEach(function(line, i) {
+ if (line.replace(/\t/g, tabString).length > maxLength) {
+ context.report(node, { line: i + 1, col: 1 }, "Line " + (i + 1) + " exceeds the maximum line length of " + maxLength + ".");
+ }
+ });
+ }
+
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+ "Program": checkProgramForMaxLength
+ };
+
+};
diff --git a/tools/eslint/lib/rules/max-nested-callbacks.js b/tools/eslint/lib/rules/max-nested-callbacks.js
new file mode 100644
index 0000000000..590274ffd1
--- /dev/null
+++ b/tools/eslint/lib/rules/max-nested-callbacks.js
@@ -0,0 +1,67 @@
+/**
+ * @fileoverview Rule to enforce a maximum number of nested callbacks.
+ * @author Ian Christian Myers
+ * @copyright 2013 Ian Christian Myers. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Constants
+ //--------------------------------------------------------------------------
+
+ var THRESHOLD = context.options[0];
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ var callbackStack = [];
+
+ /**
+ * Checks a given function node for too many callbacks.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ * @private
+ */
+ function checkFunction(node) {
+ var parent = node.parent;
+
+ if (parent.type === "CallExpression") {
+ callbackStack.push(node);
+ }
+
+ if (callbackStack.length > THRESHOLD) {
+ var opts = {num: callbackStack.length, max: THRESHOLD};
+ context.report(node, "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", opts);
+ }
+ }
+
+ /**
+ * Pops the call stack.
+ * @returns {void}
+ * @private
+ */
+ function popStack() {
+ callbackStack.pop();
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+ "ArrowFunctionExpression": checkFunction,
+ "ArrowFunctionExpression:exit": popStack,
+
+ "FunctionExpression": checkFunction,
+ "FunctionExpression:exit": popStack
+ };
+
+};
diff --git a/tools/eslint/lib/rules/max-params.js b/tools/eslint/lib/rules/max-params.js
new file mode 100644
index 0000000000..931548932f
--- /dev/null
+++ b/tools/eslint/lib/rules/max-params.js
@@ -0,0 +1,39 @@
+/**
+ * @fileoverview Rule to flag when a function has too many parameters
+ * @author Ilya Volodin
+ * @copyright 2014 Nicholas C. Zakas. All rights reserved.
+ * @copyright 2013 Ilya Volodin. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var numParams = context.options[0] || 3;
+
+ /**
+ * Checks a function to see if it has too many parameters.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ * @private
+ */
+ function checkFunction(node) {
+ if (node.params.length > numParams) {
+ context.report(node, "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", {
+ count: node.params.length,
+ max: numParams
+ });
+ }
+ }
+
+ return {
+ "FunctionDeclaration": checkFunction,
+ "ArrowFunctionExpression": checkFunction,
+ "FunctionExpression": checkFunction
+ };
+
+};
diff --git a/tools/eslint/lib/rules/max-statements.js b/tools/eslint/lib/rules/max-statements.js
new file mode 100644
index 0000000000..9fe11bfd91
--- /dev/null
+++ b/tools/eslint/lib/rules/max-statements.js
@@ -0,0 +1,55 @@
+/**
+ * @fileoverview A rule to set the maximum number of statements in a function.
+ * @author Ian Christian Myers
+ * @copyright 2013 Ian Christian Myers. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ var functionStack = [],
+ maxStatements = context.options[0] || 10;
+
+ function startFunction() {
+ functionStack.push(0);
+ }
+
+ function endFunction(node) {
+ var count = functionStack.pop();
+
+ if (count > maxStatements) {
+ context.report(node, "This function has too many statements ({{count}}). Maximum allowed is {{max}}.",
+ { count: count, max: maxStatements });
+ }
+ }
+
+ function countStatements(node) {
+ functionStack[functionStack.length - 1] += node.body.length;
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+ "FunctionDeclaration": startFunction,
+ "FunctionExpression": startFunction,
+ "ArrowFunctionExpression": startFunction,
+
+ "BlockStatement": countStatements,
+
+ "FunctionDeclaration:exit": endFunction,
+ "FunctionExpression:exit": endFunction,
+ "ArrowFunctionExpression:exit": endFunction
+ };
+
+};
diff --git a/tools/eslint/lib/rules/new-cap.js b/tools/eslint/lib/rules/new-cap.js
new file mode 100644
index 0000000000..1474fc8028
--- /dev/null
+++ b/tools/eslint/lib/rules/new-cap.js
@@ -0,0 +1,197 @@
+/**
+ * @fileoverview Rule to flag use of constructors without capital letters
+ * @author Nicholas C. Zakas
+ * @copyright 2014 Jordan Harband. All rights reserved.
+ * @copyright 2013-2014 Nicholas C. Zakas. All rights reserved.
+ */
+
+"use strict";
+
+var CAPS_ALLOWED = [
+ "Array",
+ "Boolean",
+ "Date",
+ "Error",
+ "Function",
+ "Number",
+ "Object",
+ "RegExp",
+ "String",
+ "Symbol"
+];
+
+/**
+ * Ensure that if the key is provided, it must be an array.
+ * @param {Object} obj Object to check with `key`.
+ * @param {string} key Object key to check on `obj`.
+ * @param {*} fallback If obj[key] is not present, this will be returned.
+ * @returns {string[]} Returns obj[key] if it's an Array, otherwise `fallback`
+ */
+function checkArray(obj, key, fallback) {
+ if (Object.prototype.hasOwnProperty.call(obj, key) && !Array.isArray(obj[key])) {
+ throw new TypeError(key + ", if provided, must be an Array");
+ }
+ return obj[key] || fallback;
+}
+
+/**
+ * A reducer function to invert an array to an Object mapping the string form of the key, to `true`.
+ * @param {Object} map Accumulator object for the reduce.
+ * @param {string} key Object key to set to `true`.
+ * @returns {Object} Returns the updated Object for further reduction.
+ */
+function invert(map, key) {
+ map[key] = true;
+ return map;
+}
+
+/**
+ * Creates an object with the cap is new exceptions as its keys and true as their values.
+ * @param {Object} config Rule configuration
+ * @returns {Object} Object with cap is new exceptions.
+ */
+function calculateCapIsNewExceptions(config) {
+ var capIsNewExceptions = checkArray(config, "capIsNewExceptions", CAPS_ALLOWED);
+
+ if (capIsNewExceptions !== CAPS_ALLOWED) {
+ capIsNewExceptions = capIsNewExceptions.concat(CAPS_ALLOWED);
+ }
+
+ return capIsNewExceptions.reduce(invert, {});
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var config = context.options[0] || {};
+ config.newIsCap = config.newIsCap === false ? false : true;
+ config.capIsNew = config.capIsNew === false ? false : true;
+
+ var newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {});
+
+ var capIsNewExceptions = calculateCapIsNewExceptions(config);
+
+ var listeners = {};
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Get exact callee name from expression
+ * @param {ASTNode} node CallExpression or NewExpression node
+ * @returns {string} name
+ */
+ function extractNameFromExpression(node) {
+
+ var name = "",
+ property;
+
+ if (node.callee.type === "MemberExpression") {
+ property = node.callee.property;
+
+ if (property.type === "Literal" && (typeof property.value === "string")) {
+ name = property.value;
+ } else if (property.type === "Identifier" && !node.callee.computed) {
+ name = property.name;
+ }
+ } else {
+ name = node.callee.name;
+ }
+ return name;
+ }
+
+ /**
+ * Returns the capitalization state of the string -
+ * Whether the first character is uppercase, lowercase, or non-alphabetic
+ * @param {string} str String
+ * @returns {string} capitalization state: "non-alpha", "lower", or "upper"
+ */
+ function getCap(str) {
+ var firstChar = str.charAt(0);
+
+ var firstCharLower = firstChar.toLowerCase();
+ var firstCharUpper = firstChar.toUpperCase();
+
+ if (firstCharLower === firstCharUpper) {
+ // char has no uppercase variant, so it's non-alphabetic
+ return "non-alpha";
+ } else if (firstChar === firstCharLower) {
+ return "lower";
+ } else {
+ return "upper";
+ }
+ }
+
+ /**
+ * Check if capitalization is allowed for a CallExpression
+ * @param {Object} allowedMap Object mapping calleeName to a Boolean
+ * @param {ASTNode} node CallExpression node
+ * @param {string} calleeName Capitalized callee name from a CallExpression
+ * @returns {Boolean} Returns true if the callee may be capitalized
+ */
+ function isCapAllowed(allowedMap, node, calleeName) {
+ if (allowedMap[calleeName]) {
+ return true;
+ }
+ if (calleeName === "UTC" && node.callee.type === "MemberExpression") {
+ // allow if callee is Date.UTC
+ return node.callee.object.type === "Identifier" &&
+ node.callee.object.name === "Date";
+ }
+ return false;
+ }
+
+ /**
+ * Reports the given message for the given node. The location will be the start of the property or the callee.
+ * @param {ASTNode} node CallExpression or NewExpression node.
+ * @param {string} message The message to report.
+ * @returns {void}
+ */
+ function report(node, message) {
+ var callee = node.callee;
+
+ if (callee.type === "MemberExpression") {
+ callee = callee.property;
+ }
+
+ context.report(node, callee.loc.start, message);
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ if (config.newIsCap) {
+ listeners.NewExpression = function(node) {
+
+ var constructorName = extractNameFromExpression(node);
+ if (constructorName) {
+ var capitalization = getCap(constructorName);
+ var isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName);
+ if (!isAllowed) {
+ report(node, "A constructor name should not start with a lowercase letter.");
+ }
+ }
+ };
+ }
+
+ if (config.capIsNew) {
+ listeners.CallExpression = function(node) {
+
+ var calleeName = extractNameFromExpression(node);
+ if (calleeName) {
+ var capitalization = getCap(calleeName);
+ var isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName);
+ if (!isAllowed) {
+ report(node, "A function with a name starting with an uppercase letter should only be used as a constructor.");
+ }
+ }
+ };
+ }
+
+ return listeners;
+};
diff --git a/tools/eslint/lib/rules/new-parens.js b/tools/eslint/lib/rules/new-parens.js
new file mode 100644
index 0000000000..adc2f70c92
--- /dev/null
+++ b/tools/eslint/lib/rules/new-parens.js
@@ -0,0 +1,27 @@
+/**
+ * @fileoverview Rule to flag when using constructor without parentheses
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "NewExpression": function(node) {
+ var tokens = context.getTokens(node);
+ var prenticesTokens = tokens.filter(function(token) {
+ return token.value === "(" || token.value === ")";
+ });
+ if (prenticesTokens.length < 2) {
+ context.report(node, "Missing '()' invoking a constructor");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/newline-after-var.js b/tools/eslint/lib/rules/newline-after-var.js
new file mode 100644
index 0000000000..7a6c3cff2f
--- /dev/null
+++ b/tools/eslint/lib/rules/newline-after-var.js
@@ -0,0 +1,121 @@
+/**
+ * @fileoverview Rule to check empty newline after "var" statement
+ * @author Gopal Venkatesan
+ * @copyright 2015 Gopal Venkatesan. All rights reserved.
+ * @copyright 2015 Casey Visco. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var ALWAYS_MESSAGE = "Expected blank line after variable declarations.",
+ NEVER_MESSAGE = "Unexpected blank line after variable declarations.";
+
+ // Default `mode` to "always". This means that invalid options will also
+ // be treated as "always" and the only special case is "never"
+ var mode = context.options[0] === "never" ? "never" : "always";
+
+ // Cache line numbers of comments for faster lookup
+ var comments = context.getAllComments().map(function (token) {
+ return token.loc.start.line;
+ });
+
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Determine if provided keyword is a variable declaration
+ * @private
+ * @param {string} keyword - keyword to test
+ * @returns {boolean} True if `keyword` is a type of var
+ */
+ function isVar(keyword) {
+ return keyword === "var" || keyword === "let" || keyword === "const";
+ }
+
+ /**
+ * Determine if provided keyword is a variant of for specifiers
+ * @private
+ * @param {string} keyword - keyword to test
+ * @returns {boolean} True if `keyword` is a variant of for specifier
+ */
+ function isForTypeSpecifier(keyword) {
+ return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement";
+ }
+
+ /**
+ * Determine if provided keyword is an export specifiers
+ * @private
+ * @param {string} nodeType - nodeType to test
+ * @returns {boolean} True if `nodeType` is an export specifier
+ */
+ function isExportSpecifier(nodeType) {
+ return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" ||
+ nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration";
+ }
+
+ /**
+ * Checks that a blank line exists after a variable declaration when mode is
+ * set to "always", or checks that there is no blank line when mode is set
+ * to "never"
+ * @private
+ * @param {ASTNode} node - `VariableDeclaration` node to test
+ * @returns {void}
+ */
+ function checkForBlankLine(node) {
+ var lastToken = context.getLastToken(node),
+ nextToken = context.getTokenAfter(node),
+ nextLineNum = lastToken.loc.end.line + 1,
+ noNextLineToken,
+ hasNextLineComment;
+
+ // Ignore if there is no following statement
+ if (!nextToken) {
+ return;
+ }
+
+ // Ignore if parent of node is a for variant
+ if (isForTypeSpecifier(node.parent.type)) {
+ return;
+ }
+
+ // Ignore if parent of node is an export specifier
+ if (isExportSpecifier(node.parent.type)) {
+ return;
+ }
+
+ // Some coding styles use multiple `var` statements, so do nothing if
+ // the next token is a `var` statement.
+ if (nextToken.type === "Keyword" && isVar(nextToken.value)) {
+ return;
+ }
+
+ // Next statement is not a `var`...
+ noNextLineToken = nextToken.loc.start.line > nextLineNum;
+ hasNextLineComment = comments.indexOf(nextLineNum) >= 0;
+
+ if (mode === "never" && noNextLineToken && !hasNextLineComment) {
+ context.report(node, NEVER_MESSAGE, { identifier: node.name });
+ }
+
+ if (mode === "always" && (!noNextLineToken || hasNextLineComment)) {
+ context.report(node, ALWAYS_MESSAGE, { identifier: node.name });
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "VariableDeclaration": checkForBlankLine
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-alert.js b/tools/eslint/lib/rules/no-alert.js
new file mode 100644
index 0000000000..1f14b533d7
--- /dev/null
+++ b/tools/eslint/lib/rules/no-alert.js
@@ -0,0 +1,54 @@
+/**
+ * @fileoverview Rule to flag use of alert, confirm, prompt
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+function matchProhibited(name) {
+ return name.match(/^(alert|confirm|prompt)$/);
+}
+
+function report(context, node, result) {
+ context.report(node, "Unexpected {{name}}.", { name: result[1] });
+}
+
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "CallExpression": function(node) {
+
+ var result;
+
+ // without window.
+ if (node.callee.type === "Identifier") {
+
+ result = matchProhibited(node.callee.name);
+
+ if (result) {
+ report(context, node, result);
+ }
+
+ } else if (node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier") {
+
+ result = matchProhibited(node.callee.property.name);
+
+ if (result && node.callee.object.name === "window") {
+ report(context, node, result);
+ }
+
+ }
+
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-array-constructor.js b/tools/eslint/lib/rules/no-array-constructor.js
new file mode 100644
index 0000000000..a37d674c80
--- /dev/null
+++ b/tools/eslint/lib/rules/no-array-constructor.js
@@ -0,0 +1,29 @@
+/**
+ * @fileoverview Disallow construction of dense arrays using the Array constructor
+ * @author Matt DuVall <http://www.mattduvall.com/>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ function check(node) {
+ if (
+ node.arguments.length !== 1 &&
+ node.callee.type === "Identifier" &&
+ node.callee.name === "Array"
+ ) {
+ context.report(node, "The array literal notation [] is preferrable.");
+ }
+ }
+
+ return {
+ "CallExpression": check,
+ "NewExpression": check
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-bitwise.js b/tools/eslint/lib/rules/no-bitwise.js
new file mode 100644
index 0000000000..942317f3d0
--- /dev/null
+++ b/tools/eslint/lib/rules/no-bitwise.js
@@ -0,0 +1,55 @@
+/**
+ * @fileoverview Rule to flag bitwise identifiers
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var BITWISE_OPERATORS = [
+ "^", "|", "&", "<<", ">>", ">>>",
+ "^=", "|=", "&=", "<<=", ">>=", ">>>=",
+ "~"
+ ];
+
+ /**
+ * Reports an unexpected use of a bitwise operator.
+ * @param {ASTNode} node Node which contains the bitwise operator.
+ * @returns {void}
+ */
+ function report(node) {
+ context.report(node, "Unexpected use of '{{operator}}'.", { operator: node.operator });
+ }
+
+ /**
+ * Checks if the given node has a bitwise operator.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} Whether or not the node has a bitwise operator.
+ */
+ function hasBitwiseOperator(node) {
+ return BITWISE_OPERATORS.indexOf(node.operator) !== -1;
+ }
+
+ /**
+ * Report if the given node contains a bitwise operator.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ */
+ function checkNodeForBitwiseOperator(node) {
+ if (hasBitwiseOperator(node)) {
+ report(node);
+ }
+ }
+
+ return {
+ "AssignmentExpression": checkNodeForBitwiseOperator,
+ "BinaryExpression": checkNodeForBitwiseOperator,
+ "UnaryExpression": checkNodeForBitwiseOperator
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-caller.js b/tools/eslint/lib/rules/no-caller.js
new file mode 100644
index 0000000000..b489d79c54
--- /dev/null
+++ b/tools/eslint/lib/rules/no-caller.js
@@ -0,0 +1,27 @@
+/**
+ * @fileoverview Rule to flag use of arguments.callee and arguments.caller.
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "MemberExpression": function(node) {
+ var objectName = node.object.name,
+ propertyName = node.property.name;
+
+ if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/)) {
+ context.report(node, "Avoid arguments.{{property}}.", { property: propertyName });
+ }
+
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-catch-shadow.js b/tools/eslint/lib/rules/no-catch-shadow.js
new file mode 100644
index 0000000000..af07c46292
--- /dev/null
+++ b/tools/eslint/lib/rules/no-catch-shadow.js
@@ -0,0 +1,50 @@
+/**
+ * @fileoverview Rule to flag variable leak in CatchClauses in IE 8 and earlier
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ function paramIsShadowing(scope, name) {
+ var found = scope.variables.some(function(variable) {
+ return variable.name === name;
+ });
+
+ if (found) {
+ return true;
+ }
+
+ if (scope.upper) {
+ return paramIsShadowing(scope.upper, name);
+ }
+
+ return false;
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "CatchClause": function(node) {
+ var scope = context.getScope();
+
+ if (paramIsShadowing(scope, node.param.name)) {
+ context.report(node, "Value of '{{name}}' may be overwritten in IE 8 and earlier.",
+ { name: node.param.name });
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-comma-dangle.js b/tools/eslint/lib/rules/no-comma-dangle.js
new file mode 100644
index 0000000000..32b2a73658
--- /dev/null
+++ b/tools/eslint/lib/rules/no-comma-dangle.js
@@ -0,0 +1,43 @@
+/**
+ * @fileoverview Rule to flag trailing commas in object literals.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //-------------------------------------------------------------------------
+ // Helpers
+ //-------------------------------------------------------------------------
+
+ function checkForTrailingComma(node) {
+ var items = node.properties || node.elements,
+ length = items.length,
+ lastItem, penultimateToken;
+
+ if (length) {
+ lastItem = items[length - 1];
+ if (lastItem) {
+ penultimateToken = context.getLastToken(node, 1);
+ if (penultimateToken.value === ",") {
+ context.report(lastItem, penultimateToken.loc.start, "Trailing comma.");
+ }
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+ "ObjectExpression": checkForTrailingComma,
+ "ArrayExpression": checkForTrailingComma
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-cond-assign.js b/tools/eslint/lib/rules/no-cond-assign.js
new file mode 100644
index 0000000000..9b00fe4e32
--- /dev/null
+++ b/tools/eslint/lib/rules/no-cond-assign.js
@@ -0,0 +1,117 @@
+/**
+ * @fileoverview Rule to flag assignment in a conditional statement's test expression
+ * @author Stephen Murray <spmurrayzzz>
+ */
+"use strict";
+
+var NODE_DESCRIPTIONS = {
+ "DoWhileStatement": "a 'do...while' statement",
+ "ForStatement": "a 'for' statement",
+ "IfStatement": "an 'if' statement",
+ "WhileStatement": "a 'while' statement"
+};
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var prohibitAssign = (context.options[0] || "except-parens");
+
+ /**
+ * Check whether an AST node is the test expression for a conditional statement.
+ * @param {!Object} node The node to test.
+ * @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`.
+ */
+ function isConditionalTestExpression(node) {
+ return node.parent &&
+ node.parent.test &&
+ node === node.parent.test;
+ }
+
+ /**
+ * Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement.
+ * @param {!Object} node The node to use at the start of the search.
+ * @returns {?Object} The closest ancestor node that represents a conditional statement.
+ */
+ function findConditionalAncestor(node) {
+ var currentAncestor = node;
+
+ while ((currentAncestor = currentAncestor.parent)) {
+ if (isConditionalTestExpression(currentAncestor)) {
+ return currentAncestor.parent;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Check whether the code represented by an AST node is enclosed in parentheses.
+ * @param {!Object} node The node to test.
+ * @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`.
+ */
+ function isParenthesised(node) {
+ var previousToken = context.getTokenBefore(node),
+ nextToken = context.getTokenAfter(node);
+
+ return previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
+ nextToken.value === ")" && nextToken.range[0] >= node.range[1];
+ }
+
+ /**
+ * Check whether the code represented by an AST node is enclosed in two sets of parentheses.
+ * @param {!Object} node The node to test.
+ * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`.
+ */
+ function isParenthesisedTwice(node) {
+ var previousToken = context.getTokenBefore(node, 1),
+ nextToken = context.getTokenAfter(node, 1);
+
+ return isParenthesised(node) &&
+ previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
+ nextToken.value === ")" && nextToken.range[0] >= node.range[1];
+ }
+
+ /**
+ * Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses.
+ * @param {!Object} node The node for the conditional statement.
+ * @returns {void}
+ */
+ function testForAssign(node) {
+ if (node.test && (node.test.type === "AssignmentExpression") && !isParenthesisedTwice(node.test)) {
+ // must match JSHint's error message
+ context.report(node, "Expected a conditional expression and instead saw an assignment.");
+ }
+ }
+
+ /**
+ * Check whether an assignment expression is descended from a conditional statement's test expression.
+ * @param {!Object} node The node for the assignment expression.
+ * @returns {void}
+ */
+ function testForConditionalAncestor(node) {
+ var ancestor = findConditionalAncestor(node);
+
+ if (ancestor) {
+ context.report(ancestor, "Unexpected assignment within {{type}}.", {
+ type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
+ });
+ }
+ }
+
+ if (prohibitAssign === "always") {
+ return {
+ "AssignmentExpression": testForConditionalAncestor
+ };
+ }
+
+ return {
+ "DoWhileStatement": testForAssign,
+ "ForStatement": testForAssign,
+ "IfStatement": testForAssign,
+ "WhileStatement": testForAssign
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-console.js b/tools/eslint/lib/rules/no-console.js
new file mode 100644
index 0000000000..929f00045e
--- /dev/null
+++ b/tools/eslint/lib/rules/no-console.js
@@ -0,0 +1,25 @@
+/**
+ * @fileoverview Rule to flag use of console object
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "MemberExpression": function(node) {
+
+ if (node.object.name === "console") {
+ context.report(node, "Unexpected console statement.");
+ }
+
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-constant-condition.js b/tools/eslint/lib/rules/no-constant-condition.js
new file mode 100644
index 0000000000..394a9c9bb8
--- /dev/null
+++ b/tools/eslint/lib/rules/no-constant-condition.js
@@ -0,0 +1,71 @@
+/**
+ * @fileoverview Rule to flag use constant conditions
+ * @author Christian Schulz <http://rndm.de>
+ * @copyright 2014 Christian Schulz. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Checks if a node has a constant truthiness value.
+ * @param {ASTNode} node The AST node to check.
+ * @returns {Bool} true when node's truthiness is constant
+ * @private
+ */
+ function isConstant(node) {
+ switch (node.type) {
+ case "Literal":
+ case "ArrowFunctionExpression":
+ case "FunctionExpression":
+ case "ObjectExpression":
+ case "ArrayExpression":
+ return true;
+ case "UnaryExpression":
+ return isConstant(node.argument);
+ case "BinaryExpression":
+ case "LogicalExpression":
+ return isConstant(node.left) && isConstant(node.right);
+ case "AssignmentExpression":
+ return (node.operator === "=") && isConstant(node.right);
+ case "SequenceExpression":
+ return isConstant(node.expressions[node.expressions.length - 1]);
+ // no default
+ }
+ return false;
+ }
+
+ /**
+ * Reports when the given node contains a constant condition.
+ * @param {ASTNode} node The AST node to check.
+ * @returns {void}
+ * @private
+ */
+ function checkConstantCondition(node) {
+ if (node.test && isConstant(node.test)) {
+ context.report(node, "Unexpected constant condition.");
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "ConditionalExpression": checkConstantCondition,
+ "IfStatement": checkConstantCondition,
+ "WhileStatement": checkConstantCondition,
+ "DoWhileStatement": checkConstantCondition,
+ "ForStatement": checkConstantCondition
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-continue.js b/tools/eslint/lib/rules/no-continue.js
new file mode 100644
index 0000000000..b481013005
--- /dev/null
+++ b/tools/eslint/lib/rules/no-continue.js
@@ -0,0 +1,21 @@
+/**
+ * @fileoverview Rule to flag use of continue statement
+ * @author Borislav Zhivkov
+ * @copyright 2015 Borislav Zhivkov. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ "ContinueStatement": function(node) {
+ context.report(node, "Unexpected use of continue statement");
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-control-regex.js b/tools/eslint/lib/rules/no-control-regex.js
new file mode 100644
index 0000000000..a800acc87f
--- /dev/null
+++ b/tools/eslint/lib/rules/no-control-regex.js
@@ -0,0 +1,56 @@
+/**
+ * @fileoverview Rule to forbid control charactes from regular expressions.
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ function getRegExp(node) {
+
+ if (node.value instanceof RegExp) {
+ return node.value;
+ } else if (typeof node.value === "string") {
+
+ var parent = context.getAncestors().pop();
+ if ((parent.type === "NewExpression" || parent.type === "CallExpression") &&
+ parent.callee.type === "Identifier" && parent.callee.name === "RegExp") {
+
+ // there could be an invalid regular expression string
+ try {
+ return new RegExp(node.value);
+ } catch (ex) {
+ return null;
+ }
+
+ }
+ } else {
+ return null;
+ }
+
+ }
+
+
+
+ return {
+
+ "Literal": function(node) {
+
+ var computedValue,
+ regex = getRegExp(node);
+
+ if (regex) {
+ computedValue = regex.toString();
+ if (/[\x00-\x1f]/.test(computedValue)) {
+ context.report(node, "Unexpected control character in regular expression.");
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-debugger.js b/tools/eslint/lib/rules/no-debugger.js
new file mode 100644
index 0000000000..1a9a7e7d70
--- /dev/null
+++ b/tools/eslint/lib/rules/no-debugger.js
@@ -0,0 +1,20 @@
+/**
+ * @fileoverview Rule to flag use of a debugger statement
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ "DebuggerStatement": function(node) {
+ context.report(node, "Unexpected 'debugger' statement.");
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-delete-var.js b/tools/eslint/lib/rules/no-delete-var.js
new file mode 100644
index 0000000000..a863d6897a
--- /dev/null
+++ b/tools/eslint/lib/rules/no-delete-var.js
@@ -0,0 +1,23 @@
+/**
+ * @fileoverview Rule to flag when deleting variables
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "UnaryExpression": function(node) {
+ if (node.operator === "delete" && node.argument.type === "Identifier") {
+ context.report(node, "Variables should not be deleted.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-div-regex.js b/tools/eslint/lib/rules/no-div-regex.js
new file mode 100644
index 0000000000..17347480c6
--- /dev/null
+++ b/tools/eslint/lib/rules/no-div-regex.js
@@ -0,0 +1,25 @@
+/**
+ * @fileoverview Rule to check for ambiguous div operator in regexes
+ * @author Matt DuVall <http://www.mattduvall.com>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "Literal": function(node) {
+ var token = context.getFirstToken(node);
+
+ if (token.type === "RegularExpression" && token.value[1] === "=") {
+ context.report(node, "A regular expression literal can be confused with '/='.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-dupe-args.js b/tools/eslint/lib/rules/no-dupe-args.js
new file mode 100644
index 0000000000..458622e5e0
--- /dev/null
+++ b/tools/eslint/lib/rules/no-dupe-args.js
@@ -0,0 +1,83 @@
+/**
+ * @fileoverview Rule to flag duplicate arguments
+ * @author Jamund Ferguson
+ * @copyright 2015 Jamund Ferguson. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Determines if a given node has duplicate parameters.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ * @private
+ */
+ function checkParams(node) {
+ var params = {},
+ dups = {};
+
+
+ /**
+ * Marks a given param as either seen or duplicated.
+ * @param {string} name The name of the param to mark.
+ * @returns {void}
+ * @private
+ */
+ function markParam(name) {
+ if (params.hasOwnProperty(name)) {
+ dups[name] = 1;
+ } else {
+ params[name] = 1;
+ }
+ }
+
+ // loop through and find each duplicate param
+ node.params.forEach(function(param) {
+
+ switch (param.type) {
+ case "Identifier":
+ markParam(param.name);
+ break;
+
+ case "ObjectPattern":
+ param.properties.forEach(function(property) {
+ markParam(property.key.name);
+ });
+ break;
+
+ case "ArrayPattern":
+ param.elements.forEach(function(element) {
+ markParam(element.name);
+ });
+ break;
+
+ // no default
+ }
+ });
+
+ // log an error for each duplicate (not 2 for each)
+ Object.keys(dups).forEach(function(currentParam) {
+ context.report(node, "Duplicate param '{{key}}'.", { key: currentParam });
+ });
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+ "FunctionDeclaration": checkParams,
+ "FunctionExpression": checkParams
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-dupe-keys.js b/tools/eslint/lib/rules/no-dupe-keys.js
new file mode 100644
index 0000000000..522f6ace6d
--- /dev/null
+++ b/tools/eslint/lib/rules/no-dupe-keys.js
@@ -0,0 +1,41 @@
+/**
+ * @fileoverview Rule to flag use of duplicate keys in an object.
+ * @author Ian Christian Myers
+ * @copyright 2013 Ian Christian Myers. All rights reserved.
+ * @copyright 2013 Nicholas C. Zakas. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "ObjectExpression": function(node) {
+
+ // Object that will be a map of properties--safe because we will
+ // prefix all of the keys.
+ var nodeProps = Object.create(null);
+
+ node.properties.forEach(function(property) {
+ var keyName = property.key.name || property.key.value,
+ key = property.kind + "-" + keyName,
+ checkProperty = (!property.computed || property.key.type === "Literal");
+
+ if (checkProperty) {
+ if (nodeProps[key]) {
+ context.report(node, "Duplicate key '{{key}}'.", { key: keyName });
+ } else {
+ nodeProps[key] = true;
+ }
+ }
+ });
+
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-duplicate-case.js b/tools/eslint/lib/rules/no-duplicate-case.js
new file mode 100644
index 0000000000..89de7174fc
--- /dev/null
+++ b/tools/eslint/lib/rules/no-duplicate-case.js
@@ -0,0 +1,59 @@
+/**
+ * @fileoverview Rule to disallow a duplicate case label.
+ * @author Dieter Oberkofler
+ * @copyright 2015 Dieter Oberkofler. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Get a hash value for the node
+ * @param {ASTNode} node The node.
+ * @returns {string} A hash value for the node.
+ * @private
+ */
+ function getHash(node) {
+ if (node.type === "Literal") {
+ return node.type + typeof node.value + node.value;
+ } else if (node.type === "Identifier") {
+ return node.type + typeof node.name + node.name;
+ } else if (node.type === "MemberExpression") {
+ return node.type + getHash(node.object) + getHash(node.property);
+ }
+ }
+
+ var switchStatement = [];
+
+ return {
+
+ "SwitchStatement": function(/*node*/) {
+ switchStatement.push({});
+ },
+
+ "SwitchStatement:exit": function(/*node*/) {
+ switchStatement.pop();
+ },
+
+ "SwitchCase": function(node) {
+ var currentSwitch = switchStatement[switchStatement.length - 1],
+ hashValue;
+
+ if (node.test) {
+ hashValue = getHash(node.test);
+ if (typeof hashValue !== "undefined" && currentSwitch.hasOwnProperty(hashValue)) {
+ context.report(node, "Duplicate case label.");
+ } else {
+ currentSwitch[hashValue] = true;
+ }
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-else-return.js b/tools/eslint/lib/rules/no-else-return.js
new file mode 100644
index 0000000000..2496095365
--- /dev/null
+++ b/tools/eslint/lib/rules/no-else-return.js
@@ -0,0 +1,123 @@
+/**
+ * @fileoverview Rule to flag `else` after a `return` in `if`
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Display the context report if rule is violated
+ *
+ * @param {Node} node The 'else' node
+ * @returns {void}
+ */
+ function displayReport(node) {
+ context.report(node, "Unexpected 'else' after 'return'.");
+ }
+
+ /**
+ * Check to see if the node is a ReturnStatement
+ *
+ * @param {Node} node The node being evaluated
+ * @returns {boolean} True if node is a return
+ */
+ function checkForReturn(node) {
+ return node.type === "ReturnStatement";
+ }
+
+ /**
+ * Naive return checking, does not iterate through the whole
+ * BlockStatement because we make the assumption that the ReturnStatement
+ * will be the last node in the body of the BlockStatement.
+ *
+ * @param {Node} node The consequent/alternate node
+ * @returns {boolean} True if it has a return
+ */
+ function naiveHasReturn(node) {
+ if (node.type === "BlockStatement") {
+ var body = node.body,
+ lastChildNode = body[body.length - 1];
+
+ return lastChildNode && checkForReturn(lastChildNode);
+ }
+ return checkForReturn(node);
+ }
+
+ /**
+ * Check to see if the node is valid for evaluation,
+ * meaning it has an else and not an else-if
+ *
+ * @param {Node} node The node being evaluated
+ * @returns {boolean} True if the node is valid
+ */
+ function hasElse(node) {
+ return node.alternate && node.consequent && node.alternate.type !== "IfStatement";
+ }
+
+ /**
+ * If the consequent is an IfStatement, check to see if it has an else
+ * and both its consequent and alternate path return, meaning this is
+ * a nested case of rule violation. If-Else not considered currently.
+ *
+ * @param {Node} node The consequent node
+ * @returns {boolean} True if this is a nested rule violation
+ */
+ function checkForIf(node) {
+ return node.type === "IfStatement" && hasElse(node) &&
+ naiveHasReturn(node.alternate) && naiveHasReturn(node.consequent);
+ }
+
+ /**
+ * Check the consequent/body node to make sure it is not
+ * a ReturnStatement or an IfStatement that returns on both
+ * code paths. If it is, display the context report.
+ *
+ * @param {Node} node The consequent or body node
+ * @param {Node} alternate The alternate node
+ * @returns {void}
+ */
+ function checkForReturnOrIf(node, alternate) {
+ if (checkForReturn(node) || checkForIf(node)) {
+ displayReport(alternate);
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "IfStatement": function (node) {
+ // Don't bother finding a ReturnStatement, if there's no `else`
+ // or if the alternate is also an if (indicating an else if).
+ if (hasElse(node)) {
+ var consequent = node.consequent,
+ alternate = node.alternate;
+ // If we have a BlockStatement, check each consequent body node.
+ if (consequent.type === "BlockStatement") {
+ var body = consequent.body;
+ body.forEach(function (bodyNode) {
+ checkForReturnOrIf(bodyNode, alternate);
+ });
+ // If not a block statement, make sure the consequent isn't a ReturnStatement
+ // or an IfStatement with returns on both paths
+ } else {
+ checkForReturnOrIf(consequent, alternate);
+ }
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-empty-class.js b/tools/eslint/lib/rules/no-empty-class.js
new file mode 100644
index 0000000000..009c1446b4
--- /dev/null
+++ b/tools/eslint/lib/rules/no-empty-class.js
@@ -0,0 +1,43 @@
+/**
+ * @fileoverview Rule to flag the use of empty character classes in regular expressions
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/*
+plain-English description of the following regexp:
+0. `^` fix the match at the beginning of the string
+1. `\/`: the `/` that begins the regexp
+2. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following
+ 2.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes)
+ 2.1. `\\.`: an escape sequence
+ 2.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty
+3. `\/` the `/` that ends the regexp
+4. `[gimy]*`: optional regexp flags
+5. `$`: fix the match at the end of the string
+*/
+var regex = /^\/([^\\[]|\\.|\[([^\\\]]|\\.)+\])*\/[gimy]*$/;
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "Literal": function(node) {
+ var token = context.getFirstToken(node);
+ if (token.type === "RegularExpression" && !regex.test(token.value)) {
+ context.report(node, "Empty class.");
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-empty-label.js b/tools/eslint/lib/rules/no-empty-label.js
new file mode 100644
index 0000000000..371c629a82
--- /dev/null
+++ b/tools/eslint/lib/rules/no-empty-label.js
@@ -0,0 +1,25 @@
+/**
+ * @fileoverview Rule to flag when label is not used for a loop or switch
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "LabeledStatement": function(node) {
+ var type = node.body.type;
+
+ if (type !== "ForStatement" && type !== "WhileStatement" && type !== "DoWhileStatement" && type !== "SwitchStatement" && type !== "ForInStatement" && type !== "ForOfStatement") {
+ context.report(node, "Unexpected label {{l}}", {l: node.label.name});
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-empty.js b/tools/eslint/lib/rules/no-empty.js
new file mode 100644
index 0000000000..1c2307d63f
--- /dev/null
+++ b/tools/eslint/lib/rules/no-empty.js
@@ -0,0 +1,47 @@
+/**
+ * @fileoverview Rule to flag use of an empty block statement
+ * @author Nicholas C. Zakas
+ * @copyright Nicholas C. Zakas. All rights reserved.
+ * @copyright 2015 Dieter Oberkofler. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "BlockStatement": function(node) {
+ var parent = node.parent,
+ parentType = parent.type;
+
+ // if the body is not empty, we can just return immediately
+ if (node.body.length !== 0) {
+ return;
+ }
+
+ // a function is generally allowed to be empty
+ if (parentType === "FunctionDeclaration" || parentType === "FunctionExpression" || parentType === "ArrowFunctionExpression") {
+ return;
+ }
+
+ // any other block is only allowed to be empty, if it contains a comment
+ if (context.getComments(node).trailing.length > 0) {
+ return;
+ }
+
+ context.report(node, "Empty block statement.");
+ },
+
+ "SwitchStatement": function(node) {
+
+ if (typeof node.cases === "undefined" || node.cases.length === 0) {
+ context.report(node, "Empty switch statement.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-eq-null.js b/tools/eslint/lib/rules/no-eq-null.js
new file mode 100644
index 0000000000..deee40c36a
--- /dev/null
+++ b/tools/eslint/lib/rules/no-eq-null.js
@@ -0,0 +1,27 @@
+/**
+ * @fileoverview Rule to flag comparisons to null without a type-checking
+ * operator.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "BinaryExpression": function(node) {
+ var badOperator = node.operator === "==" || node.operator === "!=";
+
+ if (node.right.type === "Literal" && node.right.raw === "null" && badOperator ||
+ node.left.type === "Literal" && node.left.raw === "null" && badOperator) {
+ context.report(node, "Use ‘===’ to compare with ‘null’.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-eval.js b/tools/eslint/lib/rules/no-eval.js
new file mode 100644
index 0000000000..8eced727db
--- /dev/null
+++ b/tools/eslint/lib/rules/no-eval.js
@@ -0,0 +1,24 @@
+/**
+ * @fileoverview Rule to flag use of eval() statement
+ * @author Nicholas C. Zakas
+ * @copyright 2015 Mathias Schreck. All rights reserved.
+ * @copyright 2013 Nicholas C. Zakas. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ "CallExpression": function(node) {
+ if (node.callee.name === "eval") {
+ context.report(node, "eval can be harmful.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-ex-assign.js b/tools/eslint/lib/rules/no-ex-assign.js
new file mode 100644
index 0000000000..6ea5a22360
--- /dev/null
+++ b/tools/eslint/lib/rules/no-ex-assign.js
@@ -0,0 +1,40 @@
+/**
+ * @fileoverview Rule to flag assignment of the exception parameter
+ * @author Stephen Murray <spmurrayzzz>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var catchStack = [];
+
+ return {
+
+ "CatchClause": function(node) {
+ catchStack.push(node.param.name);
+ },
+
+ "CatchClause:exit": function() {
+ catchStack.pop();
+ },
+
+ "AssignmentExpression": function(node) {
+
+ if (catchStack.length > 0) {
+
+ var exceptionName = catchStack[catchStack.length - 1];
+
+ if (node.left.name && node.left.name === exceptionName) {
+ context.report(node, "Do not assign to the exception parameter.");
+ }
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-extend-native.js b/tools/eslint/lib/rules/no-extend-native.js
new file mode 100644
index 0000000000..a600de09be
--- /dev/null
+++ b/tools/eslint/lib/rules/no-extend-native.js
@@ -0,0 +1,77 @@
+/**
+ * @fileoverview Rule to flag adding properties to native object's prototypes.
+ * @author David Nelson
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+var BUILTINS = [
+ "Object", "Function", "Array", "String", "Boolean", "Number", "Date",
+ "RegExp", "Error", "EvalError", "RangeError", "ReferenceError",
+ "SyntaxError", "TypeError", "URIError"
+];
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ // handle the Array.prototype.extra style case
+ "AssignmentExpression": function(node) {
+ var lhs = node.left, affectsProto;
+
+ if (lhs.type !== "MemberExpression" || lhs.object.type !== "MemberExpression") {
+ return;
+ }
+
+ affectsProto = lhs.object.computed ?
+ lhs.object.property.type === "Literal" && lhs.object.property.value === "prototype" :
+ lhs.object.property.name === "prototype";
+
+ if (!affectsProto) {
+ return;
+ }
+
+ BUILTINS.forEach(function(builtin) {
+ if (lhs.object.object.name === builtin) {
+ context.report(node, builtin + " prototype is read only, properties should not be added.");
+ }
+ });
+ },
+
+ // handle the Object.defineProperty(Array.prototype) case
+ "CallExpression": function(node) {
+
+ var callee = node.callee,
+ subject,
+ object;
+
+ // only worry about Object.defineProperty
+ if (callee.type === "MemberExpression" &&
+ callee.object.name === "Object" &&
+ callee.property.name === "defineProperty") {
+
+ // verify the object being added to is a native prototype
+ subject = node.arguments[0];
+ object = subject.object;
+
+ if (object &&
+ object.type === "Identifier" &&
+ (BUILTINS.indexOf(object.name) > -1) &&
+ subject.property.name === "prototype") {
+
+ context.report(node, object.name + " prototype is read only, properties should not be added.");
+ }
+ }
+
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-extra-bind.js b/tools/eslint/lib/rules/no-extra-bind.js
new file mode 100644
index 0000000000..b585730633
--- /dev/null
+++ b/tools/eslint/lib/rules/no-extra-bind.js
@@ -0,0 +1,79 @@
+/**
+ * @fileoverview Rule to flag unnecessary bind calls
+ * @author Bence Dányi <bence@danyi.me>
+ * @copyright 2014 Bence Dányi. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var scope = [{
+ depth: -1,
+ found: 0
+ }];
+
+ /**
+ * Get the topmost scope
+ * @returns {Object} The topmost scope
+ */
+ function getTopScope() {
+ return scope[scope.length - 1];
+ }
+
+ /**
+ * Increment the depth of the top scope
+ * @returns {void}
+ */
+ function incrementScopeDepth() {
+ var top = getTopScope();
+ top.depth++;
+ }
+
+ /**
+ * Decrement the depth of the top scope
+ * @returns {void}
+ */
+ function decrementScopeDepth() {
+ var top = getTopScope();
+ top.depth--;
+ }
+
+ return {
+ "CallExpression": function(node) {
+ if (node.arguments.length === 1 &&
+ node.callee.type === "MemberExpression" &&
+ node.callee.property.name === "bind" &&
+ /FunctionExpression$/.test(node.callee.object.type)) {
+ scope.push({
+ call: node,
+ depth: -1,
+ found: 0
+ });
+ }
+ },
+ "CallExpression:exit": function(node) {
+ var top = getTopScope();
+ if (top.call === node && top.found === 0) {
+ context.report(node, "The function binding is unnecessary.");
+ scope.pop();
+ }
+ },
+ "ArrowFunctionExpression": incrementScopeDepth,
+ "ArrowFunctionExpression:exit": decrementScopeDepth,
+ "FunctionExpression": incrementScopeDepth,
+ "FunctionExpression:exit": decrementScopeDepth,
+ "FunctionDeclaration": incrementScopeDepth,
+ "FunctionDeclaration:exit": decrementScopeDepth,
+ "ThisExpression": function() {
+ var top = getTopScope();
+ if (top.depth === 0) {
+ top.found++;
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-extra-boolean-cast.js b/tools/eslint/lib/rules/no-extra-boolean-cast.js
new file mode 100644
index 0000000000..605d7b1210
--- /dev/null
+++ b/tools/eslint/lib/rules/no-extra-boolean-cast.js
@@ -0,0 +1,69 @@
+/**
+ * @fileoverview Rule to flag unnecessary double negation in Boolean contexts
+ * @author Brandon Mills
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ "UnaryExpression": function (node) {
+ var ancestors = context.getAncestors(),
+ parent = ancestors.pop(),
+ grandparent = ancestors.pop();
+
+ // Exit early if it's guaranteed not to match
+ if (node.operator !== "!" ||
+ parent.type !== "UnaryExpression" ||
+ parent.operator !== "!") {
+ return;
+ }
+
+ // if (<bool>) ...
+ if (grandparent.type === "IfStatement") {
+ context.report(node, "Redundant double negation in an if statement condition.");
+
+ // do ... while (<bool>)
+ } else if (grandparent.type === "DoWhileStatement") {
+ context.report(node, "Redundant double negation in a do while loop condition.");
+
+ // while (<bool>) ...
+ } else if (grandparent.type === "WhileStatement") {
+ context.report(node, "Redundant double negation in a while loop condition.");
+
+ // <bool> ? ... : ...
+ } else if ((grandparent.type === "ConditionalExpression" &&
+ parent === grandparent.test)) {
+ context.report(node, "Redundant double negation in a ternary condition.");
+
+ // for (...; <bool>; ...) ...
+ } else if ((grandparent.type === "ForStatement" &&
+ parent === grandparent.test)) {
+ context.report(node, "Redundant double negation in a for loop condition.");
+
+ // !<bool>
+ } else if ((grandparent.type === "UnaryExpression" &&
+ grandparent.operator === "!")) {
+ context.report(node, "Redundant multiple negation.");
+
+ // Boolean(<bool>)
+ } else if ((grandparent.type === "CallExpression" &&
+ grandparent.callee.type === "Identifier" &&
+ grandparent.callee.name === "Boolean")) {
+ context.report(node, "Redundant double negation in call to Boolean().");
+
+ // new Boolean(<bool>)
+ } else if ((grandparent.type === "NewExpression" &&
+ grandparent.callee.type === "Identifier" &&
+ grandparent.callee.name === "Boolean")) {
+ context.report(node, "Redundant double negation in Boolean constructor call.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-extra-parens.js b/tools/eslint/lib/rules/no-extra-parens.js
new file mode 100644
index 0000000000..2ec8647a68
--- /dev/null
+++ b/tools/eslint/lib/rules/no-extra-parens.js
@@ -0,0 +1,299 @@
+/**
+ * @fileoverview Disallow parenthesesisng higher precedence subexpressions.
+ * @author Michael Ficarra
+ * @copyright 2014 Michael Ficarra. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ function isParenthesised(node) {
+ var previousToken = context.getTokenBefore(node),
+ nextToken = context.getTokenAfter(node);
+
+ return previousToken && nextToken &&
+ previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
+ nextToken.value === ")" && nextToken.range[0] >= node.range[1];
+ }
+
+ function isParenthesisedTwice(node) {
+ var previousToken = context.getTokenBefore(node, 1),
+ nextToken = context.getTokenAfter(node, 1);
+
+ return isParenthesised(node) && previousToken && nextToken &&
+ previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
+ nextToken.value === ")" && nextToken.range[0] >= node.range[1];
+ }
+
+ function precedence(node) {
+
+ switch (node.type) {
+ case "SequenceExpression":
+ return 0;
+
+ case "AssignmentExpression":
+ case "ArrowFunctionExpression":
+ case "YieldExpression":
+ return 1;
+
+ case "ConditionalExpression":
+ return 3;
+
+ case "LogicalExpression":
+ switch (node.operator) {
+ case "||":
+ return 4;
+ case "&&":
+ return 5;
+ // no default
+ }
+
+ /* falls through */
+ case "BinaryExpression":
+ switch (node.operator) {
+ case "|":
+ return 6;
+ case "^":
+ return 7;
+ case "&":
+ return 8;
+ case "==":
+ case "!=":
+ case "===":
+ case "!==":
+ return 9;
+ case "<":
+ case "<=":
+ case ">":
+ case ">=":
+ case "in":
+ case "instanceof":
+ return 10;
+ case "<<":
+ case ">>":
+ case ">>>":
+ return 11;
+ case "+":
+ case "-":
+ return 12;
+ case "*":
+ case "/":
+ case "%":
+ return 13;
+ // no default
+ }
+ /* falls through */
+ case "UnaryExpression":
+ return 14;
+ case "UpdateExpression":
+ return 15;
+ case "CallExpression":
+ // IIFE is allowed to have parens in any position (#655)
+ if (node.callee.type === "FunctionExpression") {
+ return -1;
+ }
+ return 16;
+ case "NewExpression":
+ return 17;
+ // no default
+ }
+ return 18;
+ }
+
+ function report(node) {
+ var previousToken = context.getTokenBefore(node);
+ context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression.");
+ }
+
+ function dryUnaryUpdate(node) {
+ if (isParenthesised(node.argument) && precedence(node.argument) >= precedence(node)) {
+ report(node.argument);
+ }
+ }
+
+ function dryCallNew(node) {
+ if (isParenthesised(node.callee) && precedence(node.callee) >= precedence(node) &&
+ !(node.type === "CallExpression" && node.callee.type === "FunctionExpression")) {
+ report(node.callee);
+ }
+ if (node.arguments.length === 1) {
+ if (isParenthesisedTwice(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({type: "AssignmentExpression"})) {
+ report(node.arguments[0]);
+ }
+ } else {
+ [].forEach.call(node.arguments, function(arg) {
+ if (isParenthesised(arg) && precedence(arg) >= precedence({type: "AssignmentExpression"})) {
+ report(arg);
+ }
+ });
+ }
+ }
+
+ function dryBinaryLogical(node) {
+ var prec = precedence(node);
+ if (isParenthesised(node.left) && precedence(node.left) >= prec) {
+ report(node.left);
+ }
+ if (isParenthesised(node.right) && precedence(node.right) > prec) {
+ report(node.right);
+ }
+ }
+
+ return {
+ "ArrayExpression": function(node) {
+ [].forEach.call(node.elements, function(e) {
+ if (e && isParenthesised(e) && precedence(e) >= precedence({type: "AssignmentExpression"})) {
+ report(e);
+ }
+ });
+ },
+ "ArrowFunctionExpression": function(node) {
+ if (node.body.type !== "BlockStatement" && isParenthesised(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) {
+ report(node.body);
+ }
+ },
+ "AssignmentExpression": function(node) {
+ if (isParenthesised(node.right) && precedence(node.right) >= precedence(node)) {
+ report(node.right);
+ }
+ },
+ "BinaryExpression": dryBinaryLogical,
+ "CallExpression": dryCallNew,
+ "ConditionalExpression": function(node) {
+ if (isParenthesised(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) {
+ report(node.test);
+ }
+ if (isParenthesised(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) {
+ report(node.consequent);
+ }
+ if (isParenthesised(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) {
+ report(node.alternate);
+ }
+ },
+ "DoWhileStatement": function(node) {
+ if (isParenthesisedTwice(node.test)) {
+ report(node.test);
+ }
+ },
+ "ExpressionStatement": function(node) {
+ var firstToken;
+ if (isParenthesised(node.expression)) {
+ firstToken = context.getFirstToken(node.expression);
+ if (firstToken.value !== "function" && firstToken.value !== "{") {
+ report(node.expression);
+ }
+ }
+ },
+ "ForInStatement": function(node) {
+ if (isParenthesised(node.right)) {
+ report(node.right);
+ }
+ },
+ "ForOfStatement": function(node) {
+ if (isParenthesised(node.right)) {
+ report(node.right);
+ }
+ },
+ "ForStatement": function(node) {
+ if (node.init && isParenthesised(node.init)) {
+ report(node.init);
+ }
+
+ if (node.test && isParenthesised(node.test)) {
+ report(node.test);
+ }
+
+ if (node.update && isParenthesised(node.update)) {
+ report(node.update);
+ }
+ },
+ "IfStatement": function(node) {
+ if (isParenthesisedTwice(node.test)) {
+ report(node.test);
+ }
+ },
+ "LogicalExpression": dryBinaryLogical,
+ "MemberExpression": function(node) {
+ if (
+ isParenthesised(node.object) &&
+ precedence(node.object) >= precedence(node) &&
+ (
+ node.computed ||
+ !(
+ (node.object.type === "Literal" &&
+ typeof node.object.value === "number" &&
+ /^[0-9]+$/.test(context.getFirstToken(node.object).value))
+ ||
+ // RegExp literal is allowed to have parens (#1589)
+ (node.object.type === "Literal" && node.object.regex)
+ )
+ )
+ ) {
+ report(node.object);
+ }
+ },
+ "NewExpression": dryCallNew,
+ "ObjectExpression": function(node) {
+ [].forEach.call(node.properties, function(e) {
+ var v = e.value;
+ if (v && isParenthesised(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) {
+ report(v);
+ }
+ });
+ },
+ "ReturnStatement": function(node) {
+ if (node.argument && isParenthesised(node.argument) &&
+ // RegExp literal is allowed to have parens (#1589)
+ !(node.argument.type === "Literal" && node.argument.regex)) {
+ report(node.argument);
+ }
+ },
+ "SequenceExpression": function(node) {
+ [].forEach.call(node.expressions, function(e) {
+ if (isParenthesised(e) && precedence(e) >= precedence(node)) {
+ report(e);
+ }
+ });
+ },
+ "SwitchCase": function(node) {
+ if (node.test && isParenthesised(node.test)) {
+ report(node.test);
+ }
+ },
+ "SwitchStatement": function(node) {
+ if (isParenthesisedTwice(node.discriminant)) {
+ report(node.discriminant);
+ }
+ },
+ "ThrowStatement": function(node) {
+ if (isParenthesised(node.argument)) {
+ report(node.argument);
+ }
+ },
+ "UnaryExpression": dryUnaryUpdate,
+ "UpdateExpression": dryUnaryUpdate,
+ "VariableDeclarator": function(node) {
+ if (node.init && isParenthesised(node.init) &&
+ precedence(node.init) >= precedence({type: "AssignmentExpression"}) &&
+ // RegExp literal is allowed to have parens (#1589)
+ !(node.init.type === "Literal" && node.init.regex)) {
+ report(node.init);
+ }
+ },
+ "WhileStatement": function(node) {
+ if (isParenthesisedTwice(node.test)) {
+ report(node.test);
+ }
+ },
+ "WithStatement": function(node) {
+ if (isParenthesisedTwice(node.object)) {
+ report(node.object);
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-extra-semi.js b/tools/eslint/lib/rules/no-extra-semi.js
new file mode 100644
index 0000000000..e155d982e5
--- /dev/null
+++ b/tools/eslint/lib/rules/no-extra-semi.js
@@ -0,0 +1,21 @@
+/**
+ * @fileoverview Rule to flag use of unnecessary semicolons
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "EmptyStatement": function(node) {
+ context.report(node, "Unnecessary semicolon.");
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-extra-strict.js b/tools/eslint/lib/rules/no-extra-strict.js
new file mode 100644
index 0000000000..4c6ac16a42
--- /dev/null
+++ b/tools/eslint/lib/rules/no-extra-strict.js
@@ -0,0 +1,84 @@
+/**
+ * @fileoverview Rule to flag unnecessary strict directives.
+ * @author Ian Christian Myers
+ * @copyright 2014 Ian Christian Myers. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ function directives(block) {
+ var ds = [], body = block.body, e, i, l;
+
+ if (body) {
+ for (i = 0, l = body.length; i < l; ++i) {
+ e = body[i];
+
+ if (
+ e.type === "ExpressionStatement" &&
+ e.expression.type === "Literal" &&
+ typeof e.expression.value === "string"
+ ) {
+ ds.push(e.expression);
+ } else {
+ break;
+ }
+ }
+ }
+
+ return ds;
+ }
+
+ function isStrict(directive) {
+ return directive.value === "use strict";
+ }
+
+ function checkForUnnecessaryUseStrict(node) {
+ var useStrictDirectives = directives(node).filter(isStrict),
+ scope,
+ upper;
+
+ switch (useStrictDirectives.length) {
+ case 0:
+ break;
+
+ case 1:
+ scope = context.getScope();
+ upper = scope.upper;
+
+ if (upper && upper.functionExpressionScope) {
+ upper = upper.upper;
+ }
+
+ if (upper && upper.isStrict) {
+ context.report(useStrictDirectives[0], "Unnecessary 'use strict'.");
+ }
+ break;
+
+ default:
+ context.report(useStrictDirectives[1], "Multiple 'use strict' directives.");
+ }
+ }
+
+ return {
+
+ "Program": checkForUnnecessaryUseStrict,
+
+ "ArrowFunctionExpression": function(node) {
+ checkForUnnecessaryUseStrict(node.body);
+ },
+
+ "FunctionExpression": function(node) {
+ checkForUnnecessaryUseStrict(node.body);
+ },
+
+ "FunctionDeclaration": function(node) {
+ checkForUnnecessaryUseStrict(node.body);
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-fallthrough.js b/tools/eslint/lib/rules/no-fallthrough.js
new file mode 100644
index 0000000000..a137fb3f80
--- /dev/null
+++ b/tools/eslint/lib/rules/no-fallthrough.js
@@ -0,0 +1,95 @@
+/**
+ * @fileoverview Rule to flag fall-through cases in switch statements.
+ * @author Matt DuVall <http://mattduvall.com/>
+ */
+"use strict";
+
+
+var FALLTHROUGH_COMMENT = /falls\sthrough/;
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var switches = [];
+
+ return {
+
+ "SwitchCase": function(node) {
+
+ var consequent = node.consequent,
+ switchData = switches[switches.length - 1],
+ i,
+ comments,
+ comment;
+
+ /*
+ * Some developers wrap case bodies in blocks, so if there is just one
+ * node and it's a block statement, check inside.
+ */
+ if (consequent.length === 1 && consequent[0].type === "BlockStatement") {
+ consequent = consequent[0];
+ }
+
+ // checking on previous case
+ if (!switchData.lastCaseClosed) {
+
+ // a fall through comment will be the last trailing comment of the last case
+ comments = context.getComments(switchData.lastCase).trailing;
+ comment = comments[comments.length - 1];
+
+ // unless the user doesn't like semicolons, in which case it's first leading comment of this case
+ if (!comment) {
+ comments = context.getComments(node).leading;
+ comment = comments[comments.length - 1];
+ }
+
+ // check for comment
+ if (!comment || !FALLTHROUGH_COMMENT.test(comment.value)) {
+
+ context.report(switchData.lastCase,
+ "Expected a \"break\" statement before \"{{code}}\".",
+ { code: node.test ? "case" : "default" });
+ }
+ }
+
+ // now dealing with the current case
+ switchData.lastCaseClosed = false;
+ switchData.lastCase = node;
+
+ // try to verify using statements - go backwards as a fast path for the search
+ if (consequent.length) {
+ for (i = consequent.length - 1; i >= 0; i--) {
+ if (/(?:Break|Continue|Return|Throw)Statement/.test(consequent[i].type)) {
+ switchData.lastCaseClosed = true;
+ break;
+ }
+ }
+ } else {
+ // the case statement has no statements, so it must logically fall through
+ switchData.lastCaseClosed = true;
+ }
+
+ /*
+ * Any warnings are triggered when the next SwitchCase occurs.
+ * There is no need to warn on the last SwitchCase, since it can't
+ * fall through to anything.
+ */
+ },
+
+ "SwitchStatement": function(node) {
+ switches.push({
+ node: node,
+ lastCaseClosed: true,
+ lastCase: null
+ });
+ },
+
+ "SwitchStatement:exit": function() {
+ switches.pop();
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-floating-decimal.js b/tools/eslint/lib/rules/no-floating-decimal.js
new file mode 100644
index 0000000000..c6380d5460
--- /dev/null
+++ b/tools/eslint/lib/rules/no-floating-decimal.js
@@ -0,0 +1,28 @@
+/**
+ * @fileoverview Rule to flag use of a leading/trailing decimal point in a numeric literal
+ * @author James Allardice
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ "Literal": function(node) {
+
+ if (typeof node.value === "number") {
+ if (node.raw.indexOf(".") === 0) {
+ context.report(node, "A leading decimal point can be confused with a dot.");
+ }
+ if (node.raw.indexOf(".") === node.raw.length - 1) {
+ context.report(node, "A trailing decimal point can be confused with a dot.");
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-func-assign.js b/tools/eslint/lib/rules/no-func-assign.js
new file mode 100644
index 0000000000..a9f2858126
--- /dev/null
+++ b/tools/eslint/lib/rules/no-func-assign.js
@@ -0,0 +1,81 @@
+/**
+ * @fileoverview Rule to flag use of function declaration identifiers as variables.
+ * @author Ian Christian Myers
+ * @copyright 2013 Ian Christian Myers. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /*
+ * Walk the scope chain looking for either a FunctionDeclaration or a
+ * VariableDeclaration with the same name as left-hand side of the
+ * AssignmentExpression. If we find the FunctionDeclaration first, then we
+ * warn, because a FunctionDeclaration is trying to become a Variable or a
+ * FunctionExpression. If we find a VariableDeclaration first, then we have
+ * a legitimate shadow variable.
+ */
+ function checkIfIdentifierIsFunction(scope, name) {
+ var variable,
+ def,
+ i,
+ j;
+
+ // Loop over all of the identifiers available in scope.
+ for (i = 0; i < scope.variables.length; i++) {
+ variable = scope.variables[i];
+
+ // For each identifier, see if it was defined in _this_ scope.
+ for (j = 0; j < variable.defs.length; j++) {
+ def = variable.defs[j];
+
+ // Identifier is a function and was declared in this scope
+ if (def.type === "FunctionName" && def.name.name === name) {
+ return true;
+ }
+
+ // Identifier is a variable and was declared in this scope. This
+ // is a legitimate shadow variable.
+ if (def.name && def.name.name === name) {
+ return false;
+ }
+ }
+ }
+
+ // Check the upper scope.
+ if (scope.upper) {
+ return checkIfIdentifierIsFunction(scope.upper, name);
+ }
+
+ // We've reached the global scope and haven't found anything.
+ return false;
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "AssignmentExpression": function(node) {
+ var scope = context.getScope(),
+ name = node.left.name;
+
+ if (checkIfIdentifierIsFunction(scope, name)) {
+ context.report(node, "'{{name}}' is a function.", { name: name });
+ }
+
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-implied-eval.js b/tools/eslint/lib/rules/no-implied-eval.js
new file mode 100644
index 0000000000..f798dcdade
--- /dev/null
+++ b/tools/eslint/lib/rules/no-implied-eval.js
@@ -0,0 +1,74 @@
+/**
+ * @fileoverview Rule to flag use of implied eval via setTimeout and setInterval
+ * @author James Allardice
+ * @copyright 2015 Mathias Schreck. All rights reserved.
+ * @copyright 2013 James Allardice. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+ var IMPLIED_EVAL = /set(?:Timeout|Interval)/;
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Checks if the first argument of a given CallExpression node is a string literal.
+ * @param {ASTNode} node The CallExpression node the check.
+ * @returns {boolean} True if the first argument is a string literal, false if not.
+ */
+ function hasStringLiteralArgument(node) {
+ var firstArgument = node.arguments[0];
+
+ return firstArgument && firstArgument.type === "Literal" && typeof firstArgument.value === "string";
+ }
+
+ /**
+ * Checks if the given MemberExpression node is window.setTimeout or window.setInterval.
+ * @param {ASTNode} node The MemberExpression node to check.
+ * @returns {boolean} Whether or not the given node is window.set*.
+ */
+ function isSetMemberExpression(node) {
+ var object = node.object,
+ property = node.property,
+ hasSetPropertyName = IMPLIED_EVAL.test(property.name) || IMPLIED_EVAL.test(property.value);
+
+ return object.name === "window" && hasSetPropertyName;
+
+ }
+
+ /**
+ * Determines if a node represents a call to setTimeout/setInterval with
+ * a string argument.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} True if the node matches, false if not.
+ * @private
+ */
+ function isImpliedEval(node) {
+ var isMemberExpression = (node.callee.type === "MemberExpression"),
+ isIdentifier = (node.callee.type === "Identifier"),
+ isSetMethod = (isIdentifier && IMPLIED_EVAL.test(node.callee.name)) ||
+ (isMemberExpression && isSetMemberExpression(node.callee));
+
+ return isSetMethod && hasStringLiteralArgument(node);
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "CallExpression": function(node) {
+ if (isImpliedEval(node)) {
+ context.report(node, "Implied eval. Consider passing a function instead of a string.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-inline-comments.js b/tools/eslint/lib/rules/no-inline-comments.js
new file mode 100644
index 0000000000..091ad9bee4
--- /dev/null
+++ b/tools/eslint/lib/rules/no-inline-comments.js
@@ -0,0 +1,47 @@
+/**
+ * @fileoverview Enforces or disallows inline comments.
+ * @author Greg Cochard
+ * @copyright 2014 Greg Cochard. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Will check that comments are not on lines starting with or ending with code
+ * @param {ASTNode} node The comment node to check
+ * @private
+ * @returns {void}
+ */
+ function testCodeAroundComment(node) {
+
+ // Get the whole line and cut it off at the start of the comment
+ var startLine = String(context.getSourceLines()[node.loc.start.line - 1]);
+ var endLine = String(context.getSourceLines()[node.loc.end.line - 1]);
+
+ var preamble = startLine.slice(0, node.loc.start.column).trim();
+
+ // Also check after the comment
+ var postamble = endLine.slice(node.loc.end.column).trim();
+
+ // Should be empty if there was only whitespace around the comment
+ if (preamble || postamble) {
+ context.report(node, "Unexpected comment inline with code.");
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "LineComment": testCodeAroundComment,
+ "BlockComment": testCodeAroundComment
+
+ };
+};
diff --git a/tools/eslint/lib/rules/no-inner-declarations.js b/tools/eslint/lib/rules/no-inner-declarations.js
new file mode 100644
index 0000000000..664639d575
--- /dev/null
+++ b/tools/eslint/lib/rules/no-inner-declarations.js
@@ -0,0 +1,72 @@
+/**
+ * @fileoverview Rule to enforce declarations in program or function body root.
+ * @author Brandon Mills
+ * @copyright 2014 Brandon Mills. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Find the nearest Program or Function ancestor node.
+ * @returns {Object} Ancestor's type and distance from node.
+ */
+ function nearestBody() {
+ var ancestors = context.getAncestors(),
+ ancestor = ancestors.pop(),
+ generation = 1;
+
+ while (ancestor && ["Program", "FunctionDeclaration",
+ "FunctionExpression", "ArrowFunctionExpression"
+ ].indexOf(ancestor.type) < 0) {
+ generation += 1;
+ ancestor = ancestors.pop();
+ }
+
+ return {
+ // Type of containing ancestor
+ type: ancestor.type,
+ // Separation between ancestor and node
+ distance: generation
+ };
+ }
+
+ /**
+ * Ensure that a given node is at a program or function body's root.
+ * @param {ASTNode} node Declaration node to check.
+ * @returns {void}
+ */
+ function check(node) {
+ var body = nearestBody(node),
+ valid = ((body.type === "Program" && body.distance === 1) ||
+ body.distance === 2);
+
+ if (!valid) {
+ context.report(node, "Move {{type}} declaration to {{body}} root.",
+ {
+ type: (node.type === "FunctionDeclaration" ?
+ "function" : "variable"),
+ body: (body.type === "Program" ?
+ "program" : "function body")
+ }
+ );
+ }
+ }
+
+ return {
+
+ "FunctionDeclaration": check,
+ "VariableDeclaration": function(node) {
+ if (context.options[0] === "both" && node.kind === "var") {
+ check(node);
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-invalid-regexp.js b/tools/eslint/lib/rules/no-invalid-regexp.js
new file mode 100644
index 0000000000..cea5372f15
--- /dev/null
+++ b/tools/eslint/lib/rules/no-invalid-regexp.js
@@ -0,0 +1,51 @@
+/**
+ * @fileoverview Validate strings passed to the RegExp constructor
+ * @author Michael Ficarra
+ * @copyright 2014 Michael Ficarra. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+var espree = require("espree");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ function isString(node) {
+ return node && node.type === "Literal" && typeof node.value === "string";
+ }
+
+ function check(node) {
+ if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(node.arguments[0])) {
+ var flags = isString(node.arguments[1]) ? node.arguments[1].value : "";
+
+ try {
+ void new RegExp(node.arguments[0].value);
+ } catch(e) {
+ context.report(node, e.message);
+ }
+
+ if (flags) {
+
+ try {
+ espree.parse("/./" + flags, { ecmaFeatures: context.ecmaFeatures });
+ } catch (ex) {
+ context.report(node, "Invalid flags supplied to RegExp constructor '" + flags + "'");
+ }
+ }
+
+ }
+ }
+
+ return {
+ "CallExpression": check,
+ "NewExpression": check
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-irregular-whitespace.js b/tools/eslint/lib/rules/no-irregular-whitespace.js
new file mode 100644
index 0000000000..3599f0cecd
--- /dev/null
+++ b/tools/eslint/lib/rules/no-irregular-whitespace.js
@@ -0,0 +1,92 @@
+/**
+ * @fileoverview Rule to disalow whitespace that is not a tab or space, whitespace inside strings and comments are allowed
+ * @author Jonathan Kingston
+ * @copyright 2014 Jonathan Kingston. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var irregularWhitespace = /[\u0085\u00A0\ufeff\f\v\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f\u205f\u3000]+/mg;
+
+ // Module store of errors that we have found
+ var errors = [];
+
+ /**
+ * Removes errors that occur inside a string node
+ * @param {ASTNode} node to check for matching errors.
+ * @returns {void}
+ * @private
+ */
+ function removeStringError(node) {
+ var locStart = node.loc.start;
+ var locEnd = node.loc.end;
+
+ errors = errors.filter(function (error) {
+ var errorLoc = error[1];
+ if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) {
+ if (errorLoc.column >= locStart.column && errorLoc.column <= locEnd.column) {
+ return false;
+ }
+ }
+ return true;
+ });
+ }
+
+ /**
+ * Checks nodes for errors that we are choosing to ignore and calls the relevent methods to remove the errors
+ * @param {ASTNode} node to check for matching errors.
+ * @returns {void}
+ * @private
+ */
+ function removeInvalidNodeErrors(node) {
+ if (typeof node.value === "string") {
+ // If we have irregular characters remove them from the errors list
+ if (node.value.match(irregularWhitespace)) {
+ removeStringError(node);
+ }
+ }
+ }
+
+ return {
+ "Program": function (node) {
+ /**
+ * As we can easily fire warnings for all white space issues with all the source its simpler to fire them here
+ * This means we can check all the application code without having to worry about issues caused in the parser tokens
+ * When writing this code also evaluating per node was missing out connecting tokens in some cases
+ * We can later filter the errors when they are found to be not an issue in nodes we don't care about
+ */
+ var sourceLines = context.getSourceLines();
+
+ sourceLines.forEach(function (sourceLine, lineIndex) {
+ var location,
+ match = irregularWhitespace.exec(sourceLine);
+
+ if (match !== null) {
+ location = {
+ line: lineIndex + 1,
+ column: match.index
+ };
+
+ errors.push([node, location, "Irregular whitespace not allowed"]);
+ }
+ });
+ },
+ "Identifier": removeInvalidNodeErrors,
+ "Literal": removeInvalidNodeErrors,
+ "Statement": removeInvalidNodeErrors,
+ "Expression": removeInvalidNodeErrors,
+ "Program:exit": function () {
+
+ // If we have any errors remaining report on them
+ errors.forEach(function (error) {
+ context.report.apply(this, error);
+ });
+ }
+ };
+};
diff --git a/tools/eslint/lib/rules/no-iterator.js b/tools/eslint/lib/rules/no-iterator.js
new file mode 100644
index 0000000000..564c09abfa
--- /dev/null
+++ b/tools/eslint/lib/rules/no-iterator.js
@@ -0,0 +1,26 @@
+/**
+ * @fileoverview Rule to flag usage of __iterator__ property
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "MemberExpression": function(node) {
+
+ if (node.property &&
+ (node.property.type === "Identifier" && node.property.name === "__iterator__" && !node.computed) ||
+ (node.property.type === "Literal" && node.property.value === "__iterator__")) {
+ context.report(node, "Reserved name '__iterator__'.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-label-var.js b/tools/eslint/lib/rules/no-label-var.js
new file mode 100644
index 0000000000..b82bd0af60
--- /dev/null
+++ b/tools/eslint/lib/rules/no-label-var.js
@@ -0,0 +1,62 @@
+/**
+ * @fileoverview Rule to flag labels that are the same as an identifier
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ function findIdentifier(scope, identifier) {
+ var found = false;
+
+ scope.variables.forEach(function(variable) {
+ if (variable.name === identifier) {
+ found = true;
+ }
+ });
+
+ scope.references.forEach(function(reference) {
+ if (reference.identifier.name === identifier) {
+ found = true;
+ }
+ });
+
+ // If we have not found the identifier in this scope, check the parent
+ // scope.
+ if (scope.upper && !found) {
+ return findIdentifier(scope.upper, identifier);
+ }
+
+ return found;
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "LabeledStatement": function(node) {
+
+ // Fetch the innermost scope.
+ var scope = context.getScope();
+
+ // Recursively find the identifier walking up the scope, starting
+ // with the innermost scope.
+ if (findIdentifier(scope, node.label.name)) {
+ context.report(node, "Found identifier with same name as label.");
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-labels.js b/tools/eslint/lib/rules/no-labels.js
new file mode 100644
index 0000000000..8b5086a295
--- /dev/null
+++ b/tools/eslint/lib/rules/no-labels.js
@@ -0,0 +1,42 @@
+/**
+ * @fileoverview Disallow Labeled Statements
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "LabeledStatement": function(node) {
+ context.report(node, "Unexpected labeled statement.");
+ },
+
+ "BreakStatement": function(node) {
+
+ if (node.label) {
+ context.report(node, "Unexpected label in break statement.");
+ }
+
+ },
+
+ "ContinueStatement": function(node) {
+
+ if (node.label) {
+ context.report(node, "Unexpected label in continue statement.");
+ }
+
+ }
+
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-lone-blocks.js b/tools/eslint/lib/rules/no-lone-blocks.js
new file mode 100644
index 0000000000..25d8c34f00
--- /dev/null
+++ b/tools/eslint/lib/rules/no-lone-blocks.js
@@ -0,0 +1,25 @@
+/**
+ * @fileoverview Rule to flag blocks with no reason to exist
+ * @author Brandon Mills
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ "BlockStatement": function (node) {
+ // Check for any occurrence of BlockStatement > BlockStatement or
+ // Program > BlockStatement
+ var parent = context.getAncestors().pop();
+ if (parent.type === "BlockStatement" || parent.type === "Program") {
+ context.report(node, "Block is nested inside another block.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-lonely-if.js b/tools/eslint/lib/rules/no-lonely-if.js
new file mode 100644
index 0000000000..59d807da30
--- /dev/null
+++ b/tools/eslint/lib/rules/no-lonely-if.js
@@ -0,0 +1,28 @@
+/**
+ * @fileoverview Rule to disallow if as the only statmenet in an else block
+ * @author Brandon Mills
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ "IfStatement": function(node) {
+ var ancestors = context.getAncestors(),
+ parent = ancestors.pop(),
+ grandparent = ancestors.pop();
+
+ if (parent && parent.type === "BlockStatement" &&
+ parent.body.length === 1 && grandparent &&
+ grandparent.type === "IfStatement" &&
+ parent === grandparent.alternate) {
+ context.report(node, "Unexpected if as the only statement in an else block.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-loop-func.js b/tools/eslint/lib/rules/no-loop-func.js
new file mode 100644
index 0000000000..ec68611b72
--- /dev/null
+++ b/tools/eslint/lib/rules/no-loop-func.js
@@ -0,0 +1,49 @@
+/**
+ * @fileoverview Rule to flag creation of function inside a loop
+ * @author Ilya Volodin
+ * @copyright 2013 Ilya Volodin. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+ var loopNodeTypes = [
+ "ForStatement",
+ "WhileStatement",
+ "ForInStatement",
+ "ForOfStatement",
+ "DoWhileStatement"
+ ];
+
+ /**
+ * Checks if the given node is a loop.
+ * @param {ASTNode} node The AST node to check.
+ * @returns {boolean} Whether or not the node is a loop.
+ */
+ function isLoop(node) {
+ return loopNodeTypes.indexOf(node.type) > -1;
+ }
+
+ /**
+ * Reports if the given node has an ancestor node which is a loop.
+ * @param {ASTNode} node The AST node to check.
+ * @returns {boolean} Whether or not the node is within a loop.
+ */
+ function checkForLoops(node) {
+ var ancestors = context.getAncestors();
+
+ if (ancestors.some(isLoop)) {
+ context.report(node, "Don't make functions within a loop");
+ }
+ }
+
+ return {
+ "ArrowFunctionExpression": checkForLoops,
+ "FunctionExpression": checkForLoops,
+ "FunctionDeclaration": checkForLoops
+ };
+};
diff --git a/tools/eslint/lib/rules/no-mixed-requires.js b/tools/eslint/lib/rules/no-mixed-requires.js
new file mode 100644
index 0000000000..d75fc56abb
--- /dev/null
+++ b/tools/eslint/lib/rules/no-mixed-requires.js
@@ -0,0 +1,159 @@
+/**
+ * @fileoverview Rule to enforce grouped require statements for Node.JS
+ * @author Raphael Pigulla
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Returns the list of built-in modules.
+ *
+ * @returns {string[]} An array of built-in Node.js modules.
+ */
+ function getBuiltinModules() {
+ // This list is generated using `require("repl")._builtinLibs.concat('repl').sort()`
+ // This particular list is as per nodejs v0.12.2 and iojs v0.7.1
+ return [
+ "assert", "buffer", "child_process", "cluster", "crypto",
+ "dgram", "dns", "domain", "events", "fs", "http", "https",
+ "net", "os", "path", "punycode", "querystring", "readline",
+ "repl", "smalloc", "stream", "string_decoder", "tls", "tty",
+ "url", "util", "v8", "vm", "zlib"
+ ];
+ }
+
+ var BUILTIN_MODULES = getBuiltinModules();
+
+ var DECL_REQUIRE = "require",
+ DECL_UNINITIALIZED = "uninitialized",
+ DECL_OTHER = "other";
+
+ var REQ_CORE = "core",
+ REQ_FILE = "file",
+ REQ_MODULE = "module",
+ REQ_COMPUTED = "computed";
+
+ /**
+ * Determines the type of a declaration statement.
+ * @param {ASTNode} initExpression The init node of the VariableDeclarator.
+ * @returns {string} The type of declaration represented by the expression.
+ */
+ function getDeclarationType(initExpression) {
+ if (!initExpression) {
+ // "var x;"
+ return DECL_UNINITIALIZED;
+ }
+
+ if (initExpression.type === "CallExpression" &&
+ initExpression.callee.type === "Identifier" &&
+ initExpression.callee.name === "require"
+ ) {
+ // "var x = require('util');"
+ return DECL_REQUIRE;
+ } else if (initExpression.type === "MemberExpression") {
+ // "var x = require('glob').Glob;"
+ return getDeclarationType(initExpression.object);
+ }
+
+ // "var x = 42;"
+ return DECL_OTHER;
+ }
+
+ /**
+ * Determines the type of module that is loaded via require.
+ * @param {ASTNode} initExpression The init node of the VariableDeclarator.
+ * @returns {string} The module type.
+ */
+ function inferModuleType(initExpression) {
+ if (initExpression.type === "MemberExpression") {
+ // "var x = require('glob').Glob;"
+ return inferModuleType(initExpression.object);
+ } else if (initExpression.arguments.length === 0) {
+ // "var x = require();"
+ return REQ_COMPUTED;
+ }
+
+ var arg = initExpression.arguments[0];
+
+ if (arg.type !== "Literal" || typeof arg.value !== "string") {
+ // "var x = require(42);"
+ return REQ_COMPUTED;
+ }
+
+ if (BUILTIN_MODULES.indexOf(arg.value) !== -1) {
+ // "var fs = require('fs');"
+ return REQ_CORE;
+ } else if (/^\.{0,2}\//.test(arg.value)) {
+ // "var utils = require('./utils');"
+ return REQ_FILE;
+ } else {
+ // "var async = require('async');"
+ return REQ_MODULE;
+ }
+ }
+
+ /**
+ * Check if the list of variable declarations is mixed, i.e. whether it
+ * contains both require and other declarations.
+ * @param {ASTNode} declarations The list of VariableDeclarators.
+ * @returns {boolean} True if the declarations are mixed, false if not.
+ */
+ function isMixed(declarations) {
+ var contains = {};
+
+ declarations.forEach(function (declaration) {
+ var type = getDeclarationType(declaration.init);
+ contains[type] = true;
+ });
+
+ return !!(
+ contains[DECL_REQUIRE] &&
+ (contains[DECL_UNINITIALIZED] || contains[DECL_OTHER])
+ );
+ }
+
+ /**
+ * Check if all require declarations in the given list are of the same
+ * type.
+ * @param {ASTNode} declarations The list of VariableDeclarators.
+ * @returns {boolean} True if the declarations are grouped, false if not.
+ */
+ function isGrouped(declarations) {
+ var found = {};
+
+ declarations.forEach(function (declaration) {
+ if (getDeclarationType(declaration.init) === DECL_REQUIRE) {
+ found[inferModuleType(declaration.init)] = true;
+ }
+ });
+
+ return Object.keys(found).length <= 1;
+ }
+
+
+ return {
+
+ "VariableDeclaration": function(node) {
+ var grouping = !!context.options[0];
+
+ if (isMixed(node.declarations)) {
+ context.report(
+ node,
+ "Do not mix 'require' and other declarations."
+ );
+ } else if (grouping && !isGrouped(node.declarations)) {
+ context.report(
+ node,
+ "Do not mix core, module, file and computed requires."
+ );
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js b/tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js
new file mode 100644
index 0000000000..7b76d76db9
--- /dev/null
+++ b/tools/eslint/lib/rules/no-mixed-spaces-and-tabs.js
@@ -0,0 +1,68 @@
+/**
+ * @fileoverview Disallow mixed spaces and tabs for indentation
+ * @author Jary Niebur
+ * @copyright 2014 Nicholas C. Zakas. All rights reserved.
+ * @copyright 2014 Jary Niebur. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var smartTabs;
+
+ switch (context.options[0]) {
+ case true: // Support old syntax, maybe add deprecation warning here
+ case "smart-tabs":
+ smartTabs = true;
+ break;
+ default:
+ smartTabs = false;
+ }
+
+ var COMMENT_START = /^\s*\/\*/,
+ MAYBE_COMMENT = /^\s*\*/;
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "Program": function(node) {
+ /*
+ * At least one space followed by a tab
+ * or the reverse before non-tab/-space
+ * characters begin.
+ */
+ var regex = /^(?=[\t ]*(\t | \t))/,
+ match,
+ lines = context.getSourceLines();
+
+ if (smartTabs) {
+ /*
+ * At least one space followed by a tab
+ * before non-tab/-space characters begin.
+ */
+ regex = /^(?=[\t ]* \t)/;
+ }
+
+ lines.forEach(function(line, i) {
+ match = regex.exec(line);
+
+ if (match) {
+
+ if (!MAYBE_COMMENT.test(line) && !COMMENT_START.test(lines[i - 1])) {
+ context.report(node, { line: i + 1, column: match.index + 1 }, "Mixed spaces and tabs.");
+ }
+
+ }
+ });
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-multi-spaces.js b/tools/eslint/lib/rules/no-multi-spaces.js
new file mode 100644
index 0000000000..9e247c1533
--- /dev/null
+++ b/tools/eslint/lib/rules/no-multi-spaces.js
@@ -0,0 +1,101 @@
+/**
+ * @fileoverview Disallow use of multiple spaces.
+ * @author Nicholas C. Zakas
+ * @copyright 2015 Brandon Mills. All rights reserved.
+ * @copyright 2015 Nicholas C. Zakas. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ // the index of the last comment that was checked
+ var exceptions = { "Property": true },
+ hasExceptions = true,
+ options = context.options[0],
+ lastCommentIndex = 0;
+
+ if (options && options.exceptions) {
+ Object.keys(options.exceptions).forEach(function (key) {
+ if (options.exceptions[key]) {
+ exceptions[key] = true;
+ } else {
+ delete exceptions[key];
+ }
+ });
+ hasExceptions = Object.keys(exceptions).length > 0;
+ }
+
+ /**
+ * Determines if a given source index is in a comment or not by checking
+ * the index against the comment range. Since the check goes straight
+ * through the file, once an index is passed a certain comment, we can
+ * go to the next comment to check that.
+ * @param {int} index The source index to check.
+ * @param {ASTNode[]} comments An array of comment nodes.
+ * @returns {boolean} True if the index is within a comment, false if not.
+ * @private
+ */
+ function isIndexInComment(index, comments) {
+
+ var comment;
+
+ while (lastCommentIndex < comments.length) {
+
+ comment = comments[lastCommentIndex];
+
+ if (comment.range[0] <= index && index < comment.range[1]) {
+ return true;
+ } else if (index > comment.range[1]) {
+ lastCommentIndex++;
+ } else {
+ break;
+ }
+
+ }
+
+ return false;
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "Program": function() {
+
+ var source = context.getSource(),
+ allComments = context.getAllComments(),
+ pattern = /[^\n\r\u2028\u2029 ] {2,}/g, // note: repeating space
+ token,
+ parent;
+
+ while (pattern.test(source)) {
+
+ // do not flag anything inside of comments
+ if (!isIndexInComment(pattern.lastIndex, allComments)) {
+
+ token = context.getTokenByRangeStart(pattern.lastIndex);
+
+ if (token) {
+ if (hasExceptions) {
+ parent = context.getNodeByRangeIndex(pattern.lastIndex - 1);
+ }
+
+ if (!parent || !exceptions[parent.type]) {
+ context.report(token, token.loc.start,
+ "Multiple spaces found before '{{value}}'.",
+ { value: token.value });
+ }
+ }
+
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-multi-str.js b/tools/eslint/lib/rules/no-multi-str.js
new file mode 100644
index 0000000000..1d4810b97d
--- /dev/null
+++ b/tools/eslint/lib/rules/no-multi-str.js
@@ -0,0 +1,41 @@
+/**
+ * @fileoverview Rule to flag when using multiline strings
+ * @author Ilya Volodin
+ * @copyright 2014 Nicholas C. Zakas. All rights reserved.
+ * @copyright 2013 Ilya Volodin. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Determines if a given node is part of JSX syntax.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} True if the node is a JSX node, false if not.
+ * @private
+ */
+ function isJSXElement(node) {
+ return node.type.indexOf("JSX") === 0;
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "Literal": function(node) {
+ var lineBreak = /\n/;
+
+ if (lineBreak.test(node.raw) && !isJSXElement(node.parent)) {
+ context.report(node, "Multiline support is limited to browsers supporting ES5 only.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-multiple-empty-lines.js b/tools/eslint/lib/rules/no-multiple-empty-lines.js
new file mode 100644
index 0000000000..9cfea63daa
--- /dev/null
+++ b/tools/eslint/lib/rules/no-multiple-empty-lines.js
@@ -0,0 +1,60 @@
+/**
+ * @fileoverview Disallows multiple blank lines.
+ * implementation adapted from the no-trailing-spaces rule.
+ * @author Greg Cochard
+ * @copyright 2014 Greg Cochard. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ // Use options.max or 2 as default
+ var numLines = 2;
+
+ if (context.options.length) {
+ numLines = context.options[0].max;
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "Program": function checkBlankLines(node) {
+ var lines = context.getSourceLines(),
+ currentLocation = -1,
+ lastLocation,
+ blankCounter = 0,
+ location,
+ trimmedLines = lines.map(function(str) {
+ return str.trim();
+ });
+
+ // Aggregate and count blank lines
+ do {
+ lastLocation = currentLocation;
+ currentLocation = trimmedLines.indexOf("", currentLocation + 1);
+ if (lastLocation === currentLocation - 1) {
+ blankCounter++;
+ } else {
+ if (blankCounter >= numLines) {
+ location = {
+ line: lastLocation + 1,
+ column: lines[lastLocation].length
+ };
+ context.report(node, location, "Multiple blank lines not allowed.");
+ }
+
+ // Finally, reset the blank counter
+ blankCounter = 0;
+ }
+ } while (currentLocation !== -1);
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-native-reassign.js b/tools/eslint/lib/rules/no-native-reassign.js
new file mode 100644
index 0000000000..29ff4a3836
--- /dev/null
+++ b/tools/eslint/lib/rules/no-native-reassign.js
@@ -0,0 +1,37 @@
+/**
+ * @fileoverview Rule to flag when re-assigning native objects
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var nativeObjects = ["Array", "Boolean", "Date", "decodeURI",
+ "decodeURIComponent", "encodeURI", "encodeURIComponent",
+ "Error", "eval", "EvalError", "Function", "isFinite",
+ "isNaN", "JSON", "Math", "Number", "Object", "parseInt",
+ "parseFloat", "RangeError", "ReferenceError", "RegExp",
+ "String", "SyntaxError", "TypeError", "URIError",
+ "Map", "NaN", "Set", "WeakMap", "Infinity", "undefined"];
+
+ return {
+
+ "AssignmentExpression": function(node) {
+ if (nativeObjects.indexOf(node.left.name) >= 0) {
+ context.report(node, node.left.name + " is a read-only native object.");
+ }
+ },
+
+ "VariableDeclarator": function(node) {
+ if (nativeObjects.indexOf(node.id.name) >= 0) {
+ context.report(node, "Redefinition of '{{nativeObject}}'.", { nativeObject: node.id.name });
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-negated-in-lhs.js b/tools/eslint/lib/rules/no-negated-in-lhs.js
new file mode 100644
index 0000000000..75bdf73192
--- /dev/null
+++ b/tools/eslint/lib/rules/no-negated-in-lhs.js
@@ -0,0 +1,23 @@
+/**
+ * @fileoverview A rule to disallow negated left operands of the `in` operator
+ * @author Michael Ficarra
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "BinaryExpression": function(node) {
+ if (node.operator === "in" && node.left.type === "UnaryExpression" && node.left.operator === "!") {
+ context.report(node, "The `in` expression's left operand is negated");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-nested-ternary.js b/tools/eslint/lib/rules/no-nested-ternary.js
new file mode 100644
index 0000000000..10b0683f95
--- /dev/null
+++ b/tools/eslint/lib/rules/no-nested-ternary.js
@@ -0,0 +1,22 @@
+/**
+ * @fileoverview Rule to flag nested ternary expressions
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ "ConditionalExpression": function(node) {
+ if (node.alternate.type === "ConditionalExpression" ||
+ node.consequent.type === "ConditionalExpression") {
+ context.report(node, "Do not nest ternary expressions");
+ }
+ }
+ };
+};
diff --git a/tools/eslint/lib/rules/no-new-func.js b/tools/eslint/lib/rules/no-new-func.js
new file mode 100644
index 0000000000..c0e64350ed
--- /dev/null
+++ b/tools/eslint/lib/rules/no-new-func.js
@@ -0,0 +1,23 @@
+/**
+ * @fileoverview Rule to flag when using new Function
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "NewExpression": function(node) {
+ if (node.callee.name === "Function") {
+ context.report(node, "The Function constructor is eval.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-new-object.js b/tools/eslint/lib/rules/no-new-object.js
new file mode 100644
index 0000000000..426d7129da
--- /dev/null
+++ b/tools/eslint/lib/rules/no-new-object.js
@@ -0,0 +1,23 @@
+/**
+ * @fileoverview A rule to disallow calls to the Object constructor
+ * @author Matt DuVall <http://www.mattduvall.com/>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "NewExpression": function(node) {
+ if (node.callee.name === "Object") {
+ context.report(node, "The object literal notation {} is preferrable.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-new-require.js b/tools/eslint/lib/rules/no-new-require.js
new file mode 100644
index 0000000000..27d25d22af
--- /dev/null
+++ b/tools/eslint/lib/rules/no-new-require.js
@@ -0,0 +1,23 @@
+/**
+ * @fileoverview Rule to disallow use of new operator with the `require` function
+ * @author Wil Moore III
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "NewExpression": function(node) {
+ if (node.callee.type === "Identifier" && node.callee.name === "require") {
+ context.report(node, "Unexpected use of new with require.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-new-wrappers.js b/tools/eslint/lib/rules/no-new-wrappers.js
new file mode 100644
index 0000000000..4dbc60d5b4
--- /dev/null
+++ b/tools/eslint/lib/rules/no-new-wrappers.js
@@ -0,0 +1,24 @@
+/**
+ * @fileoverview Rule to flag when using constructor for wrapper objects
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "NewExpression": function(node) {
+ var wrapperObjects = ["String", "Number", "Boolean", "Math", "JSON"];
+ if (wrapperObjects.indexOf(node.callee.name) > -1) {
+ context.report(node, "Do not use {{fn}} as a constructor.", { fn: node.callee.name });
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-new.js b/tools/eslint/lib/rules/no-new.js
new file mode 100644
index 0000000000..914a649303
--- /dev/null
+++ b/tools/eslint/lib/rules/no-new.js
@@ -0,0 +1,25 @@
+/**
+ * @fileoverview Rule to flag statements with function invocation preceded by
+ * "new" and not part of assignment
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "ExpressionStatement": function(node) {
+
+ if (node.expression.type === "NewExpression") {
+ context.report(node, "Do not use 'new' for side effects.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-obj-calls.js b/tools/eslint/lib/rules/no-obj-calls.js
new file mode 100644
index 0000000000..246720c2c4
--- /dev/null
+++ b/tools/eslint/lib/rules/no-obj-calls.js
@@ -0,0 +1,26 @@
+/**
+ * @fileoverview Rule to flag use of an object property of the global object (Math and JSON) as a function
+ * @author James Allardice
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ "CallExpression": function(node) {
+
+ if (node.callee.type === "Identifier") {
+ var name = node.callee.name;
+ if (name === "Math" || name === "JSON") {
+ context.report(node, "'{{name}}' is not a function.", { name: name });
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-octal-escape.js b/tools/eslint/lib/rules/no-octal-escape.js
new file mode 100644
index 0000000000..d6c75a868d
--- /dev/null
+++ b/tools/eslint/lib/rules/no-octal-escape.js
@@ -0,0 +1,37 @@
+/**
+ * @fileoverview Rule to flag octal escape sequences in string literals.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "Literal": function(node) {
+ if (typeof node.value !== "string") {
+ return;
+ }
+
+ var match = node.raw.match(/^([^\\]|\\[^0-7])*\\([0-3][0-7]{1,2}|[4-7][0-7]|[0-7])/),
+ octalDigit;
+
+ if (match) {
+ octalDigit = match[2];
+
+ // \0 is actually not considered an octal
+ if (match[2] !== "0" || typeof match[3] !== "undefined") {
+ context.report(node, "Don't use octal: '\\{{octalDigit}}'. Use '\\u....' instead.",
+ { octalDigit: octalDigit });
+ }
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-octal.js b/tools/eslint/lib/rules/no-octal.js
new file mode 100644
index 0000000000..dae4f7fe94
--- /dev/null
+++ b/tools/eslint/lib/rules/no-octal.js
@@ -0,0 +1,23 @@
+/**
+ * @fileoverview Rule to flag when initializing octal literal
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "Literal": function(node) {
+ if (typeof node.value === "number" && /^0[0-7]/.test(node.raw)) {
+ context.report(node, "Octal literals should not be used.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-param-reassign.js b/tools/eslint/lib/rules/no-param-reassign.js
new file mode 100644
index 0000000000..c38f0ef6f4
--- /dev/null
+++ b/tools/eslint/lib/rules/no-param-reassign.js
@@ -0,0 +1,85 @@
+/**
+ * @fileoverview Disallow reassignment of function parameters.
+ * @author Nat Burns
+ * @copyright 2014 Nat Burns. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Finds the declaration for a given variable by name, searching up the scope tree.
+ * @param {Scope} scope The scope in which to search.
+ * @param {String} name The name of the variable.
+ * @returns {Variable} The declaration information for the given variable, or null if no declaration was found.
+ */
+ function findDeclaration(scope, name) {
+ var variables = scope.variables;
+
+ for (var i = 0; i < variables.length; i++) {
+ if (variables[i].name === name) {
+ return variables[i];
+ }
+ }
+
+ if (scope.upper) {
+ return findDeclaration(scope.upper, name);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Determines if a given variable is declared as a function parameter.
+ * @param {Variable} variable The variable declaration.
+ * @returns {boolean} True if the variable is a function parameter, false otherwise.
+ */
+ function isParameter(variable) {
+ var defs = variable.defs;
+
+ for (var i = 0; i < defs.length; i++) {
+ if (defs[i].type === "Parameter") {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether a given node is an assignment to a function parameter.
+ * If so, a linting error will be reported.
+ * @param {ASTNode} node The node to check.
+ * @param {String} name The name of the variable being assigned to.
+ * @returns {void}
+ */
+ function checkParameter(node, name) {
+ var declaration = findDeclaration(context.getScope(), name);
+
+ if (declaration && isParameter(declaration)) {
+ context.report(node, "Assignment to function parameter '{{name}}'.", { name: name });
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "AssignmentExpression": function(node) {
+ checkParameter(node, node.left.name);
+ },
+
+ "UpdateExpression": function(node) {
+ checkParameter(node, node.argument.name);
+ }
+ };
+};
diff --git a/tools/eslint/lib/rules/no-path-concat.js b/tools/eslint/lib/rules/no-path-concat.js
new file mode 100644
index 0000000000..b555589157
--- /dev/null
+++ b/tools/eslint/lib/rules/no-path-concat.js
@@ -0,0 +1,37 @@
+/**
+ * @fileoverview Disallow string concatenation when using __dirname and __filename
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var MATCHER = /^__(?:dir|file)name$/;
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "BinaryExpression": function(node) {
+
+ var left = node.left,
+ right = node.right;
+
+ if (node.operator === "+" &&
+ ((left.type === "Identifier" && MATCHER.test(left.name)) ||
+ (right.type === "Identifier" && MATCHER.test(right.name)))
+ ) {
+
+ context.report(node, "Use path.join() or path.resolve() instead of + to create paths.");
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-plusplus.js b/tools/eslint/lib/rules/no-plusplus.js
new file mode 100644
index 0000000000..268af7ee64
--- /dev/null
+++ b/tools/eslint/lib/rules/no-plusplus.js
@@ -0,0 +1,22 @@
+/**
+ * @fileoverview Rule to flag use of unary increment and decrement operators.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "UpdateExpression": function(node) {
+ context.report(node, "Unary operator '" + node.operator + "' used.");
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-process-env.js b/tools/eslint/lib/rules/no-process-env.js
new file mode 100644
index 0000000000..54a0cb4928
--- /dev/null
+++ b/tools/eslint/lib/rules/no-process-env.js
@@ -0,0 +1,28 @@
+/**
+ * @fileoverview Disallow the use of process.env()
+ * @author Vignesh Anand
+ * @copyright 2014 Vignesh Anand. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "MemberExpression": function(node) {
+ var objectName = node.object.name,
+ propertyName = node.property.name;
+
+ if (objectName === "process" && !node.computed && propertyName && propertyName === "env") {
+ context.report(node, "Unexpected use of process.env.");
+ }
+
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-process-exit.js b/tools/eslint/lib/rules/no-process-exit.js
new file mode 100644
index 0000000000..84006f2dbd
--- /dev/null
+++ b/tools/eslint/lib/rules/no-process-exit.js
@@ -0,0 +1,31 @@
+/**
+ * @fileoverview Disallow the use of process.exit()
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "CallExpression": function(node) {
+ var callee = node.callee;
+
+ if (callee.type === "MemberExpression" && callee.object.name === "process" &&
+ callee.property.name === "exit"
+ ) {
+ context.report(node, "Don't use process.exit(); throw an error instead.");
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-proto.js b/tools/eslint/lib/rules/no-proto.js
new file mode 100644
index 0000000000..0ab434c1f5
--- /dev/null
+++ b/tools/eslint/lib/rules/no-proto.js
@@ -0,0 +1,26 @@
+/**
+ * @fileoverview Rule to flag usage of __proto__ property
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "MemberExpression": function(node) {
+
+ if (node.property &&
+ (node.property.type === "Identifier" && node.property.name === "__proto__" && !node.computed) ||
+ (node.property.type === "Literal" && node.property.value === "__proto__")) {
+ context.report(node, "The '__proto__' property is deprecated.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-redeclare.js b/tools/eslint/lib/rules/no-redeclare.js
new file mode 100644
index 0000000000..da1b7bff4c
--- /dev/null
+++ b/tools/eslint/lib/rules/no-redeclare.js
@@ -0,0 +1,58 @@
+/**
+ * @fileoverview Rule to flag when the same variable is declared more then once.
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Find variables in a given scope and flag redeclared ones.
+ * @param {Scope} scope An escope scope object.
+ * @returns {void}
+ * @private
+ */
+ function findVariablesInScope(scope) {
+ scope.variables.forEach(function(variable) {
+
+ if (variable.identifiers && variable.identifiers.length > 1) {
+ variable.identifiers.sort(function(a, b) {
+ return a.range[1] - b.range[1];
+ });
+
+ for (var i = 1, l = variable.identifiers.length; i < l; i++) {
+ context.report(variable.identifiers[i], "{{a}} is already defined", {a: variable.name});
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Find variables in a given node's associated scope.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ * @private
+ */
+ function findVariables(node) {
+ var scope = context.getScope();
+
+ findVariablesInScope(scope);
+
+ // globalReturn means one extra scope to check
+ if (node.type === "Program" && context.ecmaFeatures.globalReturn) {
+ findVariablesInScope(scope.childScopes[0]);
+ }
+ }
+
+ return {
+ "Program": findVariables,
+ "FunctionExpression": findVariables,
+ "FunctionDeclaration": findVariables
+ };
+};
diff --git a/tools/eslint/lib/rules/no-regex-spaces.js b/tools/eslint/lib/rules/no-regex-spaces.js
new file mode 100644
index 0000000000..edbf76d513
--- /dev/null
+++ b/tools/eslint/lib/rules/no-regex-spaces.js
@@ -0,0 +1,33 @@
+/**
+ * @fileoverview Rule to count multiple spaces in regular expressions
+ * @author Matt DuVall <http://www.mattduvall.com/>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "Literal": function(node) {
+ var token = context.getFirstToken(node),
+ nodeType = token.type,
+ nodeValue = token.value,
+ multipleSpacesRegex = /( {2,})+?/,
+ regexResults;
+
+ if (nodeType === "RegularExpression") {
+ regexResults = multipleSpacesRegex.exec(nodeValue);
+
+ if (regexResults !== null) {
+ context.report(node, "Spaces are hard to count. Use {" + regexResults[0].length + "}.");
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-reserved-keys.js b/tools/eslint/lib/rules/no-reserved-keys.js
new file mode 100644
index 0000000000..d94dcffb97
--- /dev/null
+++ b/tools/eslint/lib/rules/no-reserved-keys.js
@@ -0,0 +1,54 @@
+/**
+ * @fileoverview Rule to disallow reserved words being used as keys
+ * @author Emil Bay
+ * @copyright 2014 Emil Bay. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var MESSAGE = "Reserved word '{{key}}' used as key.";
+
+ var reservedWords = [
+ "abstract",
+ "boolean", "break", "byte",
+ "case", "catch", "char", "class", "const", "continue",
+ "debugger", "default", "delete", "do", "double",
+ "else", "enum", "export", "extends",
+ "final", "finally", "float", "for", "function",
+ "goto",
+ "if", "implements", "import", "in", "instanceof", "int", "interface",
+ "long",
+ "native", "new",
+ "package", "private", "protected", "public",
+ "return",
+ "short", "static", "super", "switch", "synchronized",
+ "this", "throw", "throws", "transient", "try", "typeof",
+ "var", "void", "volatile",
+ "while", "with"
+ ];
+
+ return {
+
+ "ObjectExpression": function(node) {
+ node.properties.forEach(function(property) {
+
+ if (property.key.type === "Identifier") {
+ var keyName = property.key.name;
+
+ if (reservedWords.indexOf("" + keyName) !== -1) {
+ context.report(node, MESSAGE, { key: keyName });
+ }
+ }
+
+ });
+
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-restricted-modules.js b/tools/eslint/lib/rules/no-restricted-modules.js
new file mode 100644
index 0000000000..a4c13517df
--- /dev/null
+++ b/tools/eslint/lib/rules/no-restricted-modules.js
@@ -0,0 +1,72 @@
+/**
+ * @fileoverview Restrict usage of specified node modules.
+ * @author Christian Schulz
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function (context) {
+ // trim restricted module names
+ var restrictedModules = context.options;
+
+ // if no modules are restricted we don't need to check the CallExpressions
+ if (restrictedModules.length === 0) {
+ return {};
+ }
+
+ /**
+ * Function to check if a node is a string literal.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} If the node is a string literal.
+ */
+ function isString(node) {
+ return node && node.type === "Literal" && typeof node.value === "string";
+ }
+
+ /**
+ * Function to check if a node is a require call.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} If the node is a require call.
+ */
+ function isRequireCall(node) {
+ return node.callee.type === "Identifier" && node.callee.name === "require";
+ }
+
+ /**
+ * Function to check if a node has an argument that is an restricted module and return its name.
+ * @param {ASTNode} node The node to check
+ * @returns {undefined|String} restricted module name or undefined if node argument isn't restricted.
+ */
+ function getRestrictedModuleName(node) {
+ var moduleName;
+
+ // node has arguments and first argument is string
+ if (node.arguments.length && isString(node.arguments[0])) {
+ var argumentValue = node.arguments[0].value.trim();
+
+ // check if argument value is in restricted modules array
+ if (restrictedModules.indexOf(argumentValue) !== -1) {
+ moduleName = argumentValue;
+ }
+ }
+
+ return moduleName;
+ }
+
+ return {
+ "CallExpression": function (node) {
+ if (isRequireCall(node)) {
+ var restrictedModuleName = getRestrictedModuleName(node);
+
+ if (restrictedModuleName) {
+ context.report(node, "'{{moduleName}}' module is restricted from being used.", {
+ moduleName: restrictedModuleName
+ });
+ }
+ }
+ }
+ };
+};
diff --git a/tools/eslint/lib/rules/no-return-assign.js b/tools/eslint/lib/rules/no-return-assign.js
new file mode 100644
index 0000000000..65a751c0e8
--- /dev/null
+++ b/tools/eslint/lib/rules/no-return-assign.js
@@ -0,0 +1,22 @@
+/**
+ * @fileoverview Rule to flag when return statement contains assignment
+ * @author Ilya Volodin
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "ReturnStatement": function(node) {
+ if (node.argument && node.argument.type === "AssignmentExpression") {
+ context.report(node, "Return statement should not contain assignment.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-script-url.js b/tools/eslint/lib/rules/no-script-url.js
new file mode 100644
index 0000000000..9ae9ba1a3a
--- /dev/null
+++ b/tools/eslint/lib/rules/no-script-url.js
@@ -0,0 +1,32 @@
+/**
+ * @fileoverview Rule to flag when using javascript: urls
+ * @author Ilya Volodin
+ */
+/*jshint scripturl: true */
+/*eslint no-script-url: 0*/
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "Literal": function(node) {
+
+ var value;
+
+ if (node.value && typeof node.value === "string") {
+ value = node.value.toLowerCase();
+
+ if (value.indexOf("javascript:") === 0) {
+ context.report(node, "Script URL is a form of eval.");
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-self-compare.js b/tools/eslint/lib/rules/no-self-compare.js
new file mode 100644
index 0000000000..dc68c50046
--- /dev/null
+++ b/tools/eslint/lib/rules/no-self-compare.js
@@ -0,0 +1,27 @@
+/**
+ * @fileoverview Rule to flag comparison where left part is the same as the right
+ * part.
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "BinaryExpression": function(node) {
+ var operators = ["===", "==", "!==", "!=", ">", "<", ">=", "<="];
+ if (operators.indexOf(node.operator) > -1 &&
+ (node.left.type === "Identifier" && node.right.type === "Identifier" && node.left.name === node.right.name ||
+ node.left.type === "Literal" && node.right.type === "Literal" && node.left.value === node.right.value)) {
+ context.report(node, "Comparing to itself is potentially pointless.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-sequences.js b/tools/eslint/lib/rules/no-sequences.js
new file mode 100644
index 0000000000..7edcd40d70
--- /dev/null
+++ b/tools/eslint/lib/rules/no-sequences.js
@@ -0,0 +1,92 @@
+/**
+ * @fileoverview Rule to flag use of comma operator
+ * @author Brandon Mills
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Parts of the grammar that are required to have parens.
+ */
+ var parenthesized = {
+ "DoWhileStatement": "test",
+ "IfStatement": "test",
+ "SwitchStatement": "discriminant",
+ "WhileStatement": "test",
+ "WithStatement": "object"
+
+ // Omitting CallExpression - commas are parsed as argument separators
+ // Omitting NewExpression - commas are parsed as argument separators
+ // Omitting ForInStatement - parts aren't individually parenthesised
+ // Omitting ForStatement - parts aren't individually parenthesised
+ };
+
+ /**
+ * Determines whether a node is required by the grammar to be wrapped in
+ * parens, e.g. the test of an if statement.
+ * @param {ASTNode} node - The AST node
+ * @returns {boolean} True if parens around node belong to parent node.
+ */
+ function requiresExtraParens(node) {
+ return node.parent && parenthesized[node.parent.type] != null &&
+ node === node.parent[parenthesized[node.parent.type]];
+ }
+
+ /**
+ * Check if a node is wrapped in parens.
+ * @param {ASTNode} node - The AST node
+ * @returns {boolean} True if the node has a paren on each side.
+ */
+ function isParenthesised(node) {
+ var previousToken = context.getTokenBefore(node),
+ nextToken = context.getTokenAfter(node);
+
+ return previousToken && nextToken &&
+ previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
+ nextToken.value === ")" && nextToken.range[0] >= node.range[1];
+ }
+
+ /**
+ * Check if a node is wrapped in two levels of parens.
+ * @param {ASTNode} node - The AST node
+ * @returns {boolean} True if two parens surround the node on each side.
+ */
+ function isParenthesisedTwice(node) {
+ var previousToken = context.getTokenBefore(node, 1),
+ nextToken = context.getTokenAfter(node, 1);
+
+ return isParenthesised(node) && previousToken && nextToken &&
+ previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
+ nextToken.value === ")" && nextToken.range[0] >= node.range[1];
+ }
+
+ return {
+ "SequenceExpression": function(node) {
+ // Always allow sequences in for statement update
+ if (node.parent.type === "ForStatement" &&
+ (node === node.parent.init || node === node.parent.update)) {
+ return;
+ }
+
+ // Wrapping a sequence in extra parens indicates intent
+ if (requiresExtraParens(node)) {
+ if (isParenthesisedTwice(node)) {
+ return;
+ }
+ } else {
+ if (isParenthesised(node)) {
+ return;
+ }
+ }
+
+ context.report(node, "Unexpected use of comma operator.");
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-shadow-restricted-names.js b/tools/eslint/lib/rules/no-shadow-restricted-names.js
new file mode 100644
index 0000000000..e720a0a153
--- /dev/null
+++ b/tools/eslint/lib/rules/no-shadow-restricted-names.js
@@ -0,0 +1,49 @@
+/**
+ * @fileoverview Disallow shadowing of NaN, undefined, and Infinity (ES5 section 15.1.1)
+ * @author Michael Ficarra
+ * @copyright 2013 Michael Ficarra. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var RESTRICTED = ["undefined", "NaN", "Infinity", "arguments", "eval"];
+
+ function checkForViolation(id) {
+ if (RESTRICTED.indexOf(id.name) > -1) {
+ context.report(id, "Shadowing of global property \"" + id.name + "\".");
+ }
+ }
+
+ return {
+ "VariableDeclarator": function(node) {
+ checkForViolation(node.id);
+ },
+ "ArrowFunctionExpression": function(node) {
+ if (node.id) {
+ checkForViolation(node.id);
+ }
+ [].map.call(node.params, checkForViolation);
+ },
+ "FunctionExpression": function(node) {
+ if (node.id) {
+ checkForViolation(node.id);
+ }
+ [].map.call(node.params, checkForViolation);
+ },
+ "FunctionDeclaration": function(node) {
+ if (node.id) {
+ checkForViolation(node.id);
+ [].map.call(node.params, checkForViolation);
+ }
+ },
+ "CatchClause": function(node) {
+ checkForViolation(node.param);
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-shadow.js b/tools/eslint/lib/rules/no-shadow.js
new file mode 100644
index 0000000000..4906fa58f2
--- /dev/null
+++ b/tools/eslint/lib/rules/no-shadow.js
@@ -0,0 +1,72 @@
+/**
+ * @fileoverview Rule to flag on declaring variables already declared in the outer scope
+ * @author Ilya Volodin
+ * @copyright 2013 Ilya Volodin. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Checks if a variable is contained in the list of given scope variables.
+ * @param {Object} variable The variable to check.
+ * @param {Array} scopeVars The scope variables to look for.
+ * @returns {boolean} Whether or not the variable is contains in the list of scope variables.
+ */
+ function isContainedInScopeVars(variable, scopeVars) {
+ return scopeVars.some(function (scopeVar) {
+ if (scopeVar.identifiers.length > 0) {
+ return variable.name === scopeVar.name;
+ }
+ return false;
+ });
+ }
+
+ /**
+ * Checks if the given variables are shadowed in the given scope.
+ * @param {Array} variables The variables to look for
+ * @param {Object} scope The scope to be checked.
+ * @returns {void}
+ */
+ function checkShadowsInScope(variables, scope) {
+ variables.forEach(function (variable) {
+ if (isContainedInScopeVars(variable, scope.variables) &&
+ // "arguments" is a special case that has no identifiers (#1759)
+ variable.identifiers.length > 0 &&
+
+ // function names are exempt
+ variable.defs.length && variable.defs[0].type !== "FunctionName"
+ ) {
+ context.report(variable.identifiers[0], "{{a}} is already declared in the upper scope.", {a: variable.name});
+ }
+ });
+ }
+
+ /**
+ * Checks the current context for shadowed variables.
+ * @returns {void}
+ */
+ function checkForShadows() {
+ var scope = context.getScope(),
+ variables = scope.variables;
+
+ // iterate through the array of variables and find duplicates with the upper scope
+ var upper = scope.upper;
+ while (upper) {
+ checkShadowsInScope(variables, upper);
+ upper = upper.upper;
+ }
+ }
+
+ return {
+ "FunctionDeclaration": checkForShadows,
+ "FunctionExpression": checkForShadows,
+ "ArrowFunctionExpression": checkForShadows
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-space-before-semi.js b/tools/eslint/lib/rules/no-space-before-semi.js
new file mode 100644
index 0000000000..33926741e0
--- /dev/null
+++ b/tools/eslint/lib/rules/no-space-before-semi.js
@@ -0,0 +1,96 @@
+/**
+ * @fileoverview Rule to disallow whitespace before the semicolon
+ * @author Jonathan Kingston
+ * @copyright 2015 Mathias Schreck
+ * @copyright 2014 Jonathan Kingston
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Determines whether two adjacent tokens are have whitespace between them.
+ * @param {Object} left - The left token object.
+ * @param {Object} right - The right token object.
+ * @returns {boolean} Whether or not there is space between the tokens.
+ */
+ function isSpaced(left, right) {
+ return left.range[1] < right.range[0];
+ }
+
+ /**
+ * Checks whether two tokens are on the same line.
+ * @param {Object} left The leftmost token.
+ * @param {Object} right The rightmost token.
+ * @returns {boolean} True if the tokens are on the same line, false if not.
+ * @private
+ */
+ function isSameLine(left, right) {
+ return left.loc.end.line === right.loc.start.line;
+ }
+
+ /**
+ * Checks if a given token has leading whitespace.
+ * @param {Object} token The token to check.
+ * @returns {boolean} True if the given token has leading space, false if not.
+ */
+ function hasLeadingSpace(token) {
+ var tokenBefore = context.getTokenBefore(token);
+ return isSameLine(tokenBefore, token) && isSpaced(tokenBefore, token);
+ }
+
+ /**
+ * Checks if the given token is a semicolon.
+ * @param {Token} token The token to check.
+ * @returns {boolean} Whether or not the given token is a semicolon.
+ */
+ function isSemicolon(token) {
+ return token.type === "Punctuator" && token.value === ";";
+ }
+
+ /**
+ * Reports if the given token has leading space.
+ * @param {Token} token The semicolon token to check.
+ * @param {ASTNode} node The corresponding node of the token.
+ * @returns {void}
+ */
+ function checkSemiTokenForLeadingSpace(token, node) {
+ if (isSemicolon(token) && hasLeadingSpace(token)) {
+ context.report(node, token.loc.start, "Unexpected whitespace before semicolon.");
+ }
+ }
+
+ /**
+ * Checks leading space before the semicolon with the assumption that the last token is the semicolon.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ */
+ function checkNode(node) {
+ var token = context.getLastToken(node);
+ checkSemiTokenForLeadingSpace(token, node);
+ }
+
+ return {
+ "VariableDeclaration": checkNode,
+ "ExpressionStatement": checkNode,
+ "BreakStatement": checkNode,
+ "ContinueStatement": checkNode,
+ "DebuggerStatement": checkNode,
+ "ReturnStatement": checkNode,
+ "ThrowStatement": checkNode,
+ "ForStatement": function (node) {
+ if (node.init) {
+ checkSemiTokenForLeadingSpace(context.getTokenAfter(node.init), node);
+ }
+
+ if (node.test) {
+ checkSemiTokenForLeadingSpace(context.getTokenAfter(node.test), node);
+ }
+ }
+ };
+};
diff --git a/tools/eslint/lib/rules/no-spaced-func.js b/tools/eslint/lib/rules/no-spaced-func.js
new file mode 100644
index 0000000000..7fb9fc2864
--- /dev/null
+++ b/tools/eslint/lib/rules/no-spaced-func.js
@@ -0,0 +1,35 @@
+/**
+ * @fileoverview Rule to check that spaced function application
+ * @author Matt DuVall <http://www.mattduvall.com>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ function detectOpenSpaces(node) {
+ var lastCalleeToken = context.getLastToken(node.callee);
+ var tokens = context.getTokens(node);
+ var i = tokens.indexOf(lastCalleeToken), l = tokens.length;
+ while (i < l && tokens[i].value !== "(") {
+ ++i;
+ }
+ if (i >= l) {
+ return;
+ }
+ // look for a space between the callee and the open paren
+ if (tokens[i - 1].range[1] !== tokens[i].range[0]) {
+ context.report(node, "Unexpected space between function name and paren.");
+ }
+ }
+
+ return {
+ "CallExpression": detectOpenSpaces,
+ "NewExpression": detectOpenSpaces
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-sparse-arrays.js b/tools/eslint/lib/rules/no-sparse-arrays.js
new file mode 100644
index 0000000000..143097713f
--- /dev/null
+++ b/tools/eslint/lib/rules/no-sparse-arrays.js
@@ -0,0 +1,31 @@
+/**
+ * @fileoverview Disallow sparse arrays
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "ArrayExpression": function(node) {
+
+ var emptySpot = node.elements.indexOf(null) > -1;
+
+ if (emptySpot) {
+ context.report(node, "Unexpected comma in middle of array.");
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-sync.js b/tools/eslint/lib/rules/no-sync.js
new file mode 100644
index 0000000000..1a9d27d637
--- /dev/null
+++ b/tools/eslint/lib/rules/no-sync.js
@@ -0,0 +1,28 @@
+/**
+ * @fileoverview Rule to check for properties whose identifier ends with the string Sync
+ * @author Matt DuVall<http://mattduvall.com/>
+ */
+
+/*jshint node:true*/
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "MemberExpression": function(node) {
+ var propertyName = node.property.name,
+ syncRegex = /.*Sync$/;
+
+ if (syncRegex.exec(propertyName) !== null) {
+ context.report(node, "Unexpected sync method: '" + propertyName + "'.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-ternary.js b/tools/eslint/lib/rules/no-ternary.js
new file mode 100644
index 0000000000..6ae8e9a272
--- /dev/null
+++ b/tools/eslint/lib/rules/no-ternary.js
@@ -0,0 +1,22 @@
+/**
+ * @fileoverview Rule to flag use of ternary operators.
+ * @author Ian Christian Myers
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "ConditionalExpression": function(node) {
+ context.report(node, "Ternary operator used.");
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-throw-literal.js b/tools/eslint/lib/rules/no-throw-literal.js
new file mode 100644
index 0000000000..d02e7786b6
--- /dev/null
+++ b/tools/eslint/lib/rules/no-throw-literal.js
@@ -0,0 +1,31 @@
+/**
+ * @fileoverview Rule to restrict what can be thrown as an exception.
+ * @author Dieter Oberkofler
+ * @copyright 2015 Dieter Oberkofler. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "ThrowStatement": function(node) {
+
+ if (node.argument.type === "Literal") {
+ context.report(node, "Do not throw a literal.");
+ } else if (node.argument.type === "Identifier") {
+ if (node.argument.name === "undefined") {
+ context.report(node, "Do not throw undefined.");
+ }
+ }
+
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-trailing-spaces.js b/tools/eslint/lib/rules/no-trailing-spaces.js
new file mode 100644
index 0000000000..d299eb5151
--- /dev/null
+++ b/tools/eslint/lib/rules/no-trailing-spaces.js
@@ -0,0 +1,46 @@
+/**
+ * @fileoverview Disallow trailing spaces at the end of lines.
+ * @author Nodeca Team <https://github.com/nodeca>
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var TRAILER = "[ \t\u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]+$";
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "Program": function checkTrailingSpaces(node) {
+
+ // Let's hack. Since Esprima does not return whitespace nodes,
+ // fetch the source code and do black magic via regexps.
+
+ var src = context.getSource(),
+ re = new RegExp(TRAILER, "mg"),
+ match, lines, location;
+
+ while ((match = re.exec(src)) !== null) {
+ lines = src.slice(0, re.lastIndex).split(/\r?\n/g);
+
+ location = {
+ line: lines.length,
+ column: lines[lines.length - 1].length - match[0].length + 1
+ };
+
+ // Passing node is a bit dirty, because message data will contain
+ // big text in `source`. But... who cares :) ?
+ // One more kludge will not make worse the bloody wizardry of this plugin.
+ context.report(node, location, "Trailing spaces not allowed.");
+ }
+ }
+
+ };
+};
diff --git a/tools/eslint/lib/rules/no-undef-init.js b/tools/eslint/lib/rules/no-undef-init.js
new file mode 100644
index 0000000000..60ad5700f8
--- /dev/null
+++ b/tools/eslint/lib/rules/no-undef-init.js
@@ -0,0 +1,26 @@
+/**
+ * @fileoverview Rule to flag when initializing to undefined
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "VariableDeclarator": function(node) {
+ var name = node.id.name;
+ var init = node.init && node.init.name;
+
+ if (init === "undefined") {
+ context.report(node, "It's not necessary to initialize '{{name}}' to undefined.", { name: name });
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-undef.js b/tools/eslint/lib/rules/no-undef.js
new file mode 100644
index 0000000000..5ee07f3124
--- /dev/null
+++ b/tools/eslint/lib/rules/no-undef.js
@@ -0,0 +1,90 @@
+/**
+ * @fileoverview Rule to flag references to undeclared variables.
+ * @author Mark Macdonald
+ * @copyright 2015 Nicholas C. Zakas. All rights reserved.
+ * @copyright 2013 Mark Macdonald. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+// none!
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+function isImplicitGlobal(variable) {
+ return variable.defs.every(function(def) {
+ return def.type === "ImplicitGlobalVariable";
+ });
+}
+
+/**
+ * Gets the declared variable, defined in `scope`, that `ref` refers to.
+ * @param {Scope} scope The scope in which to search.
+ * @param {Reference} ref The reference to find in the scope.
+ * @returns {Variable} The variable, or null if ref refers to an undeclared variable.
+ */
+function getDeclaredGlobalVariable(scope, ref) {
+ var declaredGlobal = null;
+ scope.variables.some(function(variable) {
+ if (variable.name === ref.identifier.name) {
+ // If it's an implicit global, it must have a `writeable` field (indicating it was declared)
+ if (!isImplicitGlobal(variable) || {}.hasOwnProperty.call(variable, "writeable")) {
+ declaredGlobal = variable;
+ return true;
+ }
+ }
+ return false;
+ });
+ return declaredGlobal;
+}
+
+/**
+ * Checks if the given node is the argument of a typeof operator.
+ * @param {ASTNode} node The AST node being checked.
+ * @returns {boolean} Whether or not the node is the argument of a typeof operator.
+ */
+function hasTypeOfOperator(node) {
+ var parent = node.parent;
+ return parent.type === "UnaryExpression" && parent.operator === "typeof";
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var NOT_DEFINED_MESSAGE = "\"{{name}}\" is not defined.",
+ READ_ONLY_MESSAGE = "\"{{name}}\" is read only.";
+
+ return {
+
+ "Program:exit": function(/*node*/) {
+
+ var globalScope = context.getScope();
+
+ globalScope.through.forEach(function(ref) {
+ var variable = getDeclaredGlobalVariable(globalScope, ref),
+ name = ref.identifier.name;
+
+ if (hasTypeOfOperator(ref.identifier)) {
+ return;
+ }
+
+ if (!variable) {
+ context.report(ref.identifier, NOT_DEFINED_MESSAGE, { name: name });
+ } else if (ref.isWrite() && variable.writeable === false) {
+ context.report(ref.identifier, READ_ONLY_MESSAGE, { name: name });
+ }
+ });
+
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-undefined.js b/tools/eslint/lib/rules/no-undefined.js
new file mode 100644
index 0000000000..4d96eb3e92
--- /dev/null
+++ b/tools/eslint/lib/rules/no-undefined.js
@@ -0,0 +1,25 @@
+/**
+ * @fileoverview Rule to flag references to the undefined variable.
+ * @author Michael Ficarra
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "Identifier": function(node) {
+ if (node.name === "undefined") {
+ var parent = context.getAncestors().pop();
+ if (!parent || parent.type !== "MemberExpression" || node !== parent.property || parent.computed) {
+ context.report(node, "Unexpected use of undefined.");
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-underscore-dangle.js b/tools/eslint/lib/rules/no-underscore-dangle.js
new file mode 100644
index 0000000000..354c1e4cda
--- /dev/null
+++ b/tools/eslint/lib/rules/no-underscore-dangle.js
@@ -0,0 +1,71 @@
+/**
+ * @fileoverview Rule to flag trailing underscores in variable declarations.
+ * @author Matt DuVall <http://www.mattduvall.com>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //-------------------------------------------------------------------------
+ // Helpers
+ //-------------------------------------------------------------------------
+
+ function hasTrailingUnderscore(identifier) {
+ var len = identifier.length;
+
+ return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_");
+ }
+
+ function isSpecialCaseIdentifierForMemberExpression(identifier) {
+ return identifier === "__proto__";
+ }
+
+ function isSpecialCaseIdentifierInVariableExpression(identifier) {
+ // Checks for the underscore library usage here
+ return identifier === "_";
+ }
+
+ function checkForTrailingUnderscoreInFunctionDeclaration(node) {
+ if (node.id) {
+ var identifier = node.id.name;
+
+ if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier)) {
+ context.report(node, "Unexpected dangling \"_\" in \"" + identifier + "\".");
+ }
+ }
+ }
+
+ function checkForTrailingUnderscoreInVariableExpression(node) {
+ var identifier = node.id.name;
+
+ if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) &&
+ !isSpecialCaseIdentifierInVariableExpression(identifier)) {
+ context.report(node, "Unexpected dangling \"_\" in \"" + identifier + "\".");
+ }
+ }
+
+ function checkForTrailingUnderscoreInMemberExpression(node) {
+ var identifier = node.property.name;
+
+ if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) &&
+ !isSpecialCaseIdentifierForMemberExpression(identifier)) {
+ context.report(node, "Unexpected dangling \"_\" in \"" + identifier + "\".");
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+ "FunctionDeclaration": checkForTrailingUnderscoreInFunctionDeclaration,
+ "VariableDeclarator": checkForTrailingUnderscoreInVariableExpression,
+ "MemberExpression": checkForTrailingUnderscoreInMemberExpression
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-unreachable.js b/tools/eslint/lib/rules/no-unreachable.js
new file mode 100644
index 0000000000..6606b6adae
--- /dev/null
+++ b/tools/eslint/lib/rules/no-unreachable.js
@@ -0,0 +1,96 @@
+/**
+ * @fileoverview Checks for unreachable code due to return, throws, break, and continue.
+ * @author Joel Feenstra
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+
+function report(context, node, unreachableType) {
+ var keyword;
+ switch (unreachableType) {
+ case "BreakStatement":
+ keyword = "break";
+ break;
+ case "ContinueStatement":
+ keyword = "continue";
+ break;
+ case "ReturnStatement":
+ keyword = "return";
+ break;
+ case "ThrowStatement":
+ keyword = "throw";
+ break;
+ default:
+ return;
+ }
+ context.report(node, "Found unexpected statement after a {{type}}.", { type: keyword });
+}
+
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Checks if a node is an exception for no-unreachable because of variable/function hoisting
+ * @param {ASTNode} node The AST node to check.
+ * @returns {boolean} if the node doesn't trigger unreachable
+ * @private
+ */
+ function isUnreachableAllowed(node) {
+ return node.type === "FunctionDeclaration" ||
+ node.type === "VariableDeclaration" &&
+ node.declarations.every(function(declaration) {
+ return declaration.type === "VariableDeclarator" && declaration.init === null;
+ });
+ }
+
+ /*
+ * Verifies that the given node is the last node or followed exclusively by
+ * hoisted declarations
+ * @param {ASTNode} node Node that should be the last node
+ * @returns {void}
+ * @private
+ */
+ function checkNode(node) {
+ var parent = context.getAncestors().pop();
+ var field, i, sibling;
+
+ switch (parent.type) {
+ case "SwitchCase":
+ field = "consequent";
+ break;
+ case "Program":
+ case "BlockStatement":
+ field = "body";
+ break;
+ default:
+ return;
+ }
+
+ for (i = parent[field].length - 1; i >= 0; i--) {
+ sibling = parent[field][i];
+ if (sibling === node) {
+ return; // Found the last reachable statement, all done
+ }
+
+ if (!isUnreachableAllowed(sibling)) {
+ report(context, sibling, node.type);
+ }
+ }
+ }
+
+ return {
+ "ReturnStatement": checkNode,
+ "ThrowStatement": checkNode,
+ "ContinueStatement": checkNode,
+ "BreakStatement": checkNode
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-unused-expressions.js b/tools/eslint/lib/rules/no-unused-expressions.js
new file mode 100644
index 0000000000..7036f90e7c
--- /dev/null
+++ b/tools/eslint/lib/rules/no-unused-expressions.js
@@ -0,0 +1,74 @@
+/**
+ * @fileoverview Flag expressions in statement position that do not side effect
+ * @author Michael Ficarra
+ * @copyright 2013 Michael Ficarra. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * @param {ASTNode} node - any node
+ * @returns {Boolean} whether the given node structurally represents a directive
+ */
+ function looksLikeDirective(node) {
+ return node.type === "ExpressionStatement" &&
+ node.expression.type === "Literal" && typeof node.expression.value === "string";
+ }
+
+ /**
+ * @param {Function} predicate - ([a] -> Boolean) the function used to make the determination
+ * @param {a[]} list - the input list
+ * @returns {a[]} the leading sequence of members in the given list that pass the given predicate
+ */
+ function takeWhile(predicate, list) {
+ for (var i = 0, l = list.length; i < l; ++i) {
+ if (!predicate(list[i])) {
+ break;
+ }
+ }
+ return [].slice.call(list, 0, i);
+ }
+
+ /**
+ * @param {ASTNode} node - a Program or BlockStatement node
+ * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body
+ */
+ function directives(node) {
+ return takeWhile(looksLikeDirective, node.body);
+ }
+
+ /**
+ * @param {ASTNode} node - any node
+ * @param {ASTNode[]} ancestors - the given node's ancestors
+ * @returns {Boolean} whether the given node is considered a directive in its current position
+ */
+ function isDirective(node, ancestors) {
+ var parent = ancestors[ancestors.length - 1],
+ grandparent = ancestors[ancestors.length - 2];
+ return (parent.type === "Program" || parent.type === "BlockStatement" &&
+ (/Function/.test(grandparent.type))) &&
+ directives(parent).indexOf(node) >= 0;
+ }
+
+ return {
+ "ExpressionStatement": function(node) {
+
+ var type = node.expression.type,
+ ancestors = context.getAncestors();
+
+ if (
+ !/^(?:Assignment|Call|New|Update|Yield)Expression$/.test(type) &&
+ (type !== "UnaryExpression" || ["delete", "void"].indexOf(node.expression.operator) < 0) &&
+ !isDirective(node, ancestors)
+ ) {
+ context.report(node, "Expected an assignment or function call and instead saw an expression.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-unused-vars.js b/tools/eslint/lib/rules/no-unused-vars.js
new file mode 100644
index 0000000000..d6ec43bf1d
--- /dev/null
+++ b/tools/eslint/lib/rules/no-unused-vars.js
@@ -0,0 +1,180 @@
+/**
+ * @fileoverview Rule to flag declared but unused variables
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var MESSAGE = "{{name}} is defined but never used";
+
+ var config = {
+ vars: "all",
+ args: "after-used"
+ };
+
+ if (context.options[0]) {
+ if (typeof context.options[0] === "string") {
+ config.vars = context.options[0];
+ } else {
+ config.vars = context.options[0].vars || config.vars;
+ config.args = context.options[0].args || config.args;
+ }
+ }
+
+ /**
+ * Determines if a given variable is being exported from a module.
+ * @param {Variable} variable EScope variable object.
+ * @returns {boolean} True if the variable is exported, false if not.
+ * @private
+ */
+ function isExported(variable) {
+
+ var definition = variable.defs[0];
+
+ if (definition) {
+
+ definition = definition.node;
+ if (definition.type === "VariableDeclarator") {
+ definition = definition.parent;
+ }
+
+ return definition.parent.type.indexOf("Export") === 0;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determines if a reference is a read operation.
+ * @param {Reference} ref - an escope Reference
+ * @returns {Boolean} whether the given reference represents a read operation
+ * @private
+ */
+ function isReadRef(ref) {
+ return ref.isRead();
+ }
+
+ /**
+ * Determine if an identifier is referencing the enclosing function name.
+ * @param {Reference} ref The reference to check.
+ * @returns {boolean} True if it's a self-reference, false if not.
+ * @private
+ */
+ function isSelfReference(ref) {
+
+ if (ref.from.type === "function" && ref.from.block.id) {
+ return ref.identifier.name === ref.from.block.id.name;
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines if a reference should be counted as a read. A reference should
+ * be counted only if it's a read and it's not a reference to the containing
+ * function declaration name.
+ * @param {Reference} ref The reference to check.
+ * @returns {boolean} True if it's a value read reference, false if not.
+ * @private
+ */
+ function isValidReadRef(ref) {
+ return isReadRef(ref) && !isSelfReference(ref);
+ }
+
+ /**
+ * Gets an array of local variables without read references.
+ * @param {Scope} scope - an escope Scope object
+ * @returns {Variable[]} most of the local variables with no read references
+ * @private
+ */
+ function getUnusedLocals(scope) {
+ var unused = [];
+ var variables = scope.variables;
+
+ if (scope.type !== "global" && scope.type !== "TDZ") {
+ for (var i = 0, l = variables.length; i < l; ++i) {
+
+ // skip function expression names
+ if (scope.functionExpressionScope || variables[i].eslintUsed) {
+ continue;
+ }
+ // skip implicit "arguments" variable
+ if (scope.type === "function" && variables[i].name === "arguments" && variables[i].identifiers.length === 0) {
+ continue;
+ }
+
+ var def = variables[i].defs[0],
+ type = def.type;
+
+ // skip catch variables
+ if (type === "CatchClause") {
+ continue;
+ }
+
+ // skip any setter argument
+ if (type === "Parameter" && def.node.parent.type === "Property" && def.node.parent.kind === "set") {
+ continue;
+ }
+
+ // if "args" option is "none", skip any parameter
+ if (config.args === "none" && type === "Parameter") {
+ continue;
+ }
+
+ // if "args" option is "after-used", skip all but the last parameter
+ if (config.args === "after-used" && type === "Parameter" && variables[i].defs[0].index < variables[i].defs[0].node.params.length - 1) {
+ continue;
+ }
+
+ if (variables[i].references.filter(isValidReadRef).length === 0 && !isExported(variables[i])) {
+ unused.push(variables[i]);
+ }
+ }
+ }
+
+ return [].concat.apply(unused, scope.childScopes.map(getUnusedLocals));
+ }
+
+ return {
+ "Program:exit": function(programNode) {
+ var globalScope = context.getScope();
+ var unused = getUnusedLocals(globalScope);
+ var i, l;
+
+ // determine unused globals
+ if (config.vars === "all") {
+ var unresolvedRefs = globalScope.through.filter(isValidReadRef).map(function(ref) {
+ return ref.identifier.name;
+ });
+
+ for (i = 0, l = globalScope.variables.length; i < l; ++i) {
+ if (unresolvedRefs.indexOf(globalScope.variables[i].name) < 0 &&
+ !globalScope.variables[i].eslintUsed && !isExported(globalScope.variables[i])) {
+ unused.push(globalScope.variables[i]);
+ }
+ }
+ }
+
+ for (i = 0, l = unused.length; i < l; ++i) {
+ if (unused[i].eslintExplicitGlobal) {
+ context.report(programNode, MESSAGE, unused[i]);
+ } else if (unused[i].defs.length > 0) {
+
+ // TODO: Remove when https://github.com/estools/escope/issues/49 is resolved
+ if (unused[i].defs[0].type === "ClassName") {
+ continue;
+ }
+
+ context.report(unused[i].identifiers[0], MESSAGE, unused[i]);
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-use-before-define.js b/tools/eslint/lib/rules/no-use-before-define.js
new file mode 100644
index 0000000000..3f175dc78b
--- /dev/null
+++ b/tools/eslint/lib/rules/no-use-before-define.js
@@ -0,0 +1,67 @@
+/**
+ * @fileoverview Rule to flag use of variables before they are defined
+ * @author Ilya Volodin
+ * @copyright 2013 Ilya Volodin. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Constants
+//------------------------------------------------------------------------------
+
+var NO_FUNC = "nofunc";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ function findDeclaration(name, scope) {
+ // try searching in the current scope first
+ for (var i = 0, l = scope.variables.length; i < l; i++) {
+ if (scope.variables[i].name === name) {
+ return scope.variables[i];
+ }
+ }
+ // check if there's upper scope and call recursivly till we find the variable
+ if (scope.upper) {
+ return findDeclaration(name, scope.upper);
+ }
+ }
+
+ function findVariables() {
+ var scope = context.getScope();
+ var typeOption = context.options[0];
+
+ function checkLocationAndReport(reference, declaration) {
+ if (typeOption !== NO_FUNC || declaration.defs[0].type !== "FunctionName") {
+ if (declaration.identifiers[0].range[1] > reference.identifier.range[1]) {
+ context.report(reference.identifier, "{{a}} was used before it was defined", {a: reference.identifier.name});
+ }
+ }
+ }
+
+ scope.references.forEach(function(reference) {
+ // if the reference is resolved check for declaration location
+ // if not, it could be function invocation, try to find manually
+ if (reference.resolved && reference.resolved.identifiers.length > 0) {
+ checkLocationAndReport(reference, reference.resolved);
+ } else {
+ var declaration = findDeclaration(reference.identifier.name, scope);
+ // if there're no identifiers, this is a global environment variable
+ if (declaration && declaration.identifiers.length !== 0) {
+ checkLocationAndReport(reference, declaration);
+ }
+ }
+ });
+ }
+
+ return {
+ "Program": findVariables,
+ "FunctionExpression": findVariables,
+ "FunctionDeclaration": findVariables,
+ "ArrowFunctionExpression": findVariables
+ };
+};
diff --git a/tools/eslint/lib/rules/no-var.js b/tools/eslint/lib/rules/no-var.js
new file mode 100644
index 0000000000..6b47b3dab0
--- /dev/null
+++ b/tools/eslint/lib/rules/no-var.js
@@ -0,0 +1,24 @@
+/**
+ * @fileoverview Rule to check for the usage of var.
+ * @author Jamund Ferguson
+ * @copyright 2014 Jamund Ferguson. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ "VariableDeclaration": function (node) {
+ if (node.kind === "var") {
+ context.report(node, "Unexpected var, use let or const instead.");
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-void.js b/tools/eslint/lib/rules/no-void.js
new file mode 100644
index 0000000000..11c54af7fc
--- /dev/null
+++ b/tools/eslint/lib/rules/no-void.js
@@ -0,0 +1,26 @@
+/**
+ * @fileoverview Rule to disallow use of void operator.
+ * @author Mike Sidorov
+ * @copyright 2014 Mike Sidorov. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "UnaryExpression": function(node) {
+ if (node.operator === "void") {
+ context.report(node, "Expected 'undefined' and instead saw 'void'.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-warning-comments.js b/tools/eslint/lib/rules/no-warning-comments.js
new file mode 100644
index 0000000000..58b6764ff1
--- /dev/null
+++ b/tools/eslint/lib/rules/no-warning-comments.js
@@ -0,0 +1,84 @@
+/**
+ * @fileoverview Rule that warns about used warning comments
+ * @author Alexander Schmidt <https://github.com/lxanders>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function (context) {
+
+ var configuration = context.options[0] || {},
+ warningTerms = configuration.terms || ["todo", "fixme", "xxx"],
+ location = configuration.location || "start",
+ warningRegExps;
+
+ /**
+ * Convert a warning term into a RegExp which will match a comment containing that whole word in the specified
+ * location ("start" or "anywhere"). If the term starts or ends with non word characters, then the match will not
+ * require word boundaries on that side.
+ *
+ * @param {String} term A term to convert to a RegExp
+ * @returns {RegExp} The term converted to a RegExp
+ */
+ function convertToRegExp(term) {
+ var escaped = term.replace(/[-\/\\$\^*+?.()|\[\]{}]/g, "\\$&"),
+ // If the term ends in a word character (a-z0-9_), ensure a word boundary at the end, so that substrings do
+ // not get falsely matched. eg "todo" in a string such as "mastodon".
+ // If the term ends in a non-word character, then \b won't match on the boundary to the next non-word
+ // character, which would likely be a space. For example `/\bFIX!\b/.test('FIX! blah') === false`.
+ // In these cases, use no bounding match. Same applies for the prefix, handled below.
+ suffix = /\w$/.test(term) ? "\\b" : "",
+ prefix;
+
+ if (location === "start") {
+ // When matching at the start, ignore leading whitespace, and there's no need to worry about word boundaries
+ prefix = "^\\s*";
+ } else if (/^\w/.test(term)) {
+ prefix = "\\b";
+ } else {
+ prefix = "";
+ }
+
+ return new RegExp(prefix + escaped + suffix, "i");
+ }
+
+ /**
+ * Checks the specified comment for matches of the configured warning terms and returns the matches.
+ * @param {String} comment The comment which is checked.
+ * @returns {Array} All matched warning terms for this comment.
+ */
+ function commentContainsWarningTerm(comment) {
+ var matches = [];
+
+ warningRegExps.forEach(function (regex, index) {
+ if (regex.test(comment)) {
+ matches.push(warningTerms[index]);
+ }
+ });
+
+ return matches;
+ }
+
+ /**
+ * Checks the specified node for matching warning comments and reports them.
+ * @param {ASTNode} node The AST node being checked.
+ * @returns {void} undefined.
+ */
+ function checkComment(node) {
+ var matches = commentContainsWarningTerm(node.value);
+
+ matches.forEach(function (matchedTerm) {
+ context.report(node, "Unexpected " + matchedTerm + " comment.");
+ });
+ }
+
+ warningRegExps = warningTerms.map(convertToRegExp);
+ return {
+ "BlockComment": checkComment,
+ "LineComment": checkComment
+ };
+};
diff --git a/tools/eslint/lib/rules/no-with.js b/tools/eslint/lib/rules/no-with.js
new file mode 100644
index 0000000000..1b889dc5f4
--- /dev/null
+++ b/tools/eslint/lib/rules/no-with.js
@@ -0,0 +1,20 @@
+/**
+ * @fileoverview Rule to flag use of with statement
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ "WithStatement": function(node) {
+ context.report(node, "Unexpected use of 'with' statement.");
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/no-wrap-func.js b/tools/eslint/lib/rules/no-wrap-func.js
new file mode 100644
index 0000000000..a99c09ee38
--- /dev/null
+++ b/tools/eslint/lib/rules/no-wrap-func.js
@@ -0,0 +1,44 @@
+/**
+ * @fileoverview Rule to flag wrapping non-iife in parens
+ * @author Ilya Volodin
+ * @copyright 2013 Ilya Volodin. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Checks a function expression to see if its surrounded by parens.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ * @private
+ */
+ function checkFunction(node) {
+ var previousToken, nextToken;
+
+ if (node.type === "ArrowFunctionExpression" &&
+ /(?:Call|New|Logical|Binary|Conditional|Update)Expression/.test(node.parent.type)
+ ) {
+ return;
+ }
+
+ if (!/CallExpression|NewExpression/.test(node.parent.type)) {
+ previousToken = context.getTokenBefore(node);
+ nextToken = context.getTokenAfter(node);
+ if (previousToken.value === "(" && nextToken.value === ")") {
+ context.report(node, "Wrapping non-IIFE function literals in parens is unnecessary.");
+ }
+ }
+ }
+
+ return {
+ "ArrowFunctionExpression": checkFunction,
+ "FunctionExpression": checkFunction
+ };
+
+};
diff --git a/tools/eslint/lib/rules/object-shorthand.js b/tools/eslint/lib/rules/object-shorthand.js
new file mode 100644
index 0000000000..548c9245d6
--- /dev/null
+++ b/tools/eslint/lib/rules/object-shorthand.js
@@ -0,0 +1,63 @@
+/**
+ * @fileoverview Rule to enforce concise object methods and properties.
+ * @author Jamund Ferguson
+ * @copyright 2015 Jamund Ferguson. All rights reserved.
+ */
+
+"use strict";
+
+var OPTIONS = {
+ always: "always",
+ never: "never",
+ methods: "methods",
+ properties: "properties"
+};
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var APPLY = context.options[0] || OPTIONS.always;
+ var APPLY_TO_METHODS = APPLY === OPTIONS.methods || APPLY === OPTIONS.always;
+ var APPLY_TO_PROPS = APPLY === OPTIONS.properties || APPLY === OPTIONS.always;
+ var APPLY_NEVER = APPLY === OPTIONS.never;
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "Property": function(node) {
+ var isConciseProperty = node.method || node.shorthand,
+ type;
+
+ // if we're "never" and concise we should warn now
+ if (APPLY_NEVER && isConciseProperty) {
+ type = node.method ? "method" : "property";
+ context.report(node, "Expected longform " + type + " syntax.");
+ }
+
+ // at this point if we're concise or if we're "never" we can leave
+ if (APPLY_NEVER || isConciseProperty) {
+ return;
+ }
+
+ if (node.value.type === "ArrowFunctionExpression" && APPLY_TO_METHODS) {
+
+ // {x: ()=>{}} should be written as {x() {}}
+ context.report(node, "Expected method shorthand.");
+ } else if (node.value.type === "FunctionExpression" && APPLY_TO_METHODS) {
+
+ // {x: function(){}} should be written as {x() {}}
+ context.report(node, "Expected method shorthand.");
+ } else if (node.key.name === node.value.name && APPLY_TO_PROPS) {
+
+ // {x: x} should be written as {x}
+ context.report(node, "Expected property shorthand.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/one-var.js b/tools/eslint/lib/rules/one-var.js
new file mode 100644
index 0000000000..fd28522f0f
--- /dev/null
+++ b/tools/eslint/lib/rules/one-var.js
@@ -0,0 +1,175 @@
+/**
+ * @fileoverview A rule to ensure the use of a single variable declaration.
+ * @author Ian Christian Myers
+ * @copyright 2015 Joey Baker. All rights reserved.
+ * @copyright 2015 Danny Fritz. All rights reserved.
+ * @copyright 2013 Ian Christian Myers. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var MODE = context.options[0] || "always";
+ var options = {};
+
+ // simple options configuration with just a string or no option
+ if (typeof context.options[0] === "string" || context.options[0] == null) {
+ options.var = MODE;
+ options.let = MODE;
+ options.const = MODE;
+ } else {
+ options = context.options[0];
+ }
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ var functionStack = [];
+ var blockStack = [];
+
+ /**
+ * Increments the blockStack counter.
+ * @returns {void}
+ * @private
+ */
+ function startBlock() {
+ blockStack.push({let: false, const: false});
+ }
+
+ /**
+ * Increments the functionStack counter.
+ * @returns {void}
+ * @private
+ */
+ function startFunction() {
+ functionStack.push(false);
+ startBlock();
+ }
+
+ /**
+ * Decrements the blockStack counter.
+ * @returns {void}
+ * @private
+ */
+ function endBlock() {
+ blockStack.pop();
+ }
+
+ /**
+ * Decrements the functionStack counter.
+ * @returns {void}
+ * @private
+ */
+ function endFunction() {
+ functionStack.pop();
+ endBlock();
+ }
+
+ /**
+ * Determines if there is more than one var statement in the current scope.
+ * @returns {boolean} Returns true if it is the first var declaration, false if not.
+ * @private
+ */
+ function hasOnlyOneVar() {
+ if (functionStack[functionStack.length - 1]) {
+ return true;
+ } else {
+ functionStack[functionStack.length - 1] = true;
+ return false;
+ }
+ }
+
+ /**
+ * Determines if there is more than one let statement in the current scope.
+ * @returns {boolean} Returns true if it is the first let declaration, false if not.
+ * @private
+ */
+ function hasOnlyOneLet() {
+ if (blockStack[blockStack.length - 1].let) {
+ return true;
+ } else {
+ blockStack[blockStack.length - 1].let = true;
+ return false;
+ }
+ }
+
+ /**
+ * Determines if there is more than one const statement in the current scope.
+ * @returns {boolean} Returns true if it is the first const declaration, false if not.
+ * @private
+ */
+ function hasOnlyOneConst() {
+ if (blockStack[blockStack.length - 1].const) {
+ return true;
+ } else {
+ blockStack[blockStack.length - 1].const = true;
+ return false;
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+ "Program": startFunction,
+ "FunctionDeclaration": startFunction,
+ "FunctionExpression": startFunction,
+ "ArrowFunctionExpression": startFunction,
+ "BlockStatement": startBlock,
+ "ForStatement": startBlock,
+ "SwitchStatement": startBlock,
+
+ "VariableDeclaration": function(node) {
+ var declarationCount = node.declarations.length;
+
+ if (node.kind === "var") {
+ if (options.var === "never") {
+ if (declarationCount > 1) {
+ context.report(node, "Split 'var' declaration into multiple statements.");
+ }
+ } else {
+ if (hasOnlyOneVar()) {
+ context.report(node, "Combine this with the previous 'var' statement.");
+ }
+ }
+ } else if (node.kind === "let") {
+ if (options.let === "never") {
+ if (declarationCount > 1) {
+ context.report(node, "Split 'let' declaration into multiple statements.");
+ }
+ } else {
+ if (hasOnlyOneLet()) {
+ context.report(node, "Combine this with the previous 'let' statement.");
+ }
+ }
+ } else if (node.kind === "const") {
+ if (options.const === "never") {
+ if (declarationCount > 1) {
+ context.report(node, "Split 'const' declaration into multiple statements.");
+ }
+ } else {
+ if (hasOnlyOneConst()) {
+ context.report(node, "Combine this with the previous 'const' statement.");
+ }
+ }
+
+ }
+ },
+
+ "ForStatement:exit": endBlock,
+ "SwitchStatement:exit": endBlock,
+ "BlockStatement:exit": endBlock,
+ "Program:exit": endFunction,
+ "FunctionDeclaration:exit": endFunction,
+ "FunctionExpression:exit": endFunction,
+ "ArrowFunctionExpression:exit": endFunction
+ };
+
+};
diff --git a/tools/eslint/lib/rules/operator-assignment.js b/tools/eslint/lib/rules/operator-assignment.js
new file mode 100644
index 0000000000..7ebb74d2ba
--- /dev/null
+++ b/tools/eslint/lib/rules/operator-assignment.js
@@ -0,0 +1,112 @@
+/**
+ * @fileoverview Rule to replace assignment expressions with operator assignment
+ * @author Brandon Mills
+ * @copyright 2014 Brandon Mills. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether an operator is commutative and has an operator assignment
+ * shorthand form.
+ * @param {string} operator Operator to check.
+ * @returns {boolean} True if the operator is commutative and has a
+ * shorthand form.
+ */
+function isCommutativeOperatorWithShorthand(operator) {
+ return ["*", "&", "^", "|"].indexOf(operator) >= 0;
+}
+
+/**
+ * Checks whether an operator is not commuatative and has an operator assignment
+ * shorthand form.
+ * @param {string} operator Operator to check.
+ * @returns {boolean} True if the operator is not commuatative and has
+ * a shorthand form.
+ */
+function isNonCommutativeOperatorWithShorthand(operator) {
+ return ["+", "-", "/", "%", "<<", ">>", ">>>"].indexOf(operator) >= 0;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether two expressions reference the same value. For example:
+ * a = a
+ * a.b = a.b
+ * a[0] = a[0]
+ * a['b'] = a['b']
+ * @param {ASTNode} a Left side of the comparison.
+ * @param {ASTNode} b Right side of the comparison.
+ * @returns {boolean} True if both sides match and reference the same value.
+ */
+function same(a, b) {
+ if (a.type !== b.type) {
+ return false;
+ }
+
+ switch (a.type) {
+ case "Identifier":
+ return a.name === b.name;
+ case "Literal":
+ return a.value === b.value;
+ case "MemberExpression":
+ // x[0] = x[0]
+ // x[y] = x[y]
+ // x.y = x.y
+ return same(a.object, b.object) && same(a.property, b.property);
+ default:
+ return false;
+ }
+}
+
+module.exports = function(context) {
+
+ /**
+ * Ensures that an assignment uses the shorthand form where possible.
+ * @param {ASTNode} node An AssignmentExpression node.
+ * @returns {void}
+ */
+ function verify(node) {
+ var expr, left, operator;
+
+ if (node.operator !== "=" || node.right.type !== "BinaryExpression") {
+ return;
+ }
+
+ left = node.left;
+ expr = node.right;
+ operator = expr.operator;
+
+ if (isCommutativeOperatorWithShorthand(operator)) {
+ if (same(left, expr.left) || same(left, expr.right)) {
+ context.report(node, "Assignment can be replaced with operator assignment.");
+ }
+ } else if (isNonCommutativeOperatorWithShorthand(operator)) {
+ if (same(left, expr.left)) {
+ context.report(node, "Assignment can be replaced with operator assignment.");
+ }
+ }
+ }
+
+ /**
+ * Warns if an assignment expression uses operator assignment shorthand.
+ * @param {ASTNode} node An AssignmentExpression node.
+ * @returns {void}
+ */
+ function prohibit(node) {
+ if (node.operator !== "=") {
+ context.report(node, "Unexpected operator assignment shorthand.");
+ }
+ }
+
+ return {
+ "AssignmentExpression": context.options[0] !== "never" ? verify : prohibit
+ };
+
+};
diff --git a/tools/eslint/lib/rules/operator-linebreak.js b/tools/eslint/lib/rules/operator-linebreak.js
new file mode 100644
index 0000000000..020c61544c
--- /dev/null
+++ b/tools/eslint/lib/rules/operator-linebreak.js
@@ -0,0 +1,100 @@
+/**
+ * @fileoverview Operator linebreak - enforces operator linebreak style of two types: after and before
+ * @author Benoît Zugmeyer
+ * @copyright 2015 Benoît Zugmeyer. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var style = context.options[0] || "after";
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Checks whether two tokens are on the same line.
+ * @param {ASTNode} left The leftmost token.
+ * @param {ASTNode} right The rightmost token.
+ * @returns {boolean} True if the tokens are on the same line, false if not.
+ * @private
+ */
+ function isSameLine(left, right) {
+ return left.loc.end.line === right.loc.start.line;
+ }
+
+ /**
+ * Checks the operator placement
+ * @param {ASTNode} node The binary operator node to check
+ * @private
+ * @returns {void}
+ */
+ function validateBinaryExpression(node) {
+ var leftToken = context.getLastToken(node.left || node.id);
+ var operatorToken = context.getTokenAfter(leftToken);
+
+ // When the left part of a binary expression is a single expression wrapped in
+ // parentheses (ex: `(a) + b`), leftToken will be the last token of the expression
+ // and operatorToken will be the closing parenthesis.
+ // The leftToken should be the last closing parenthesis, and the operatorToken
+ // should be the token right after that.
+ while (operatorToken.value === ")") {
+ leftToken = operatorToken;
+ operatorToken = context.getTokenAfter(operatorToken);
+ }
+
+ var rightToken = context.getTokenAfter(operatorToken);
+ var operator = operatorToken.value;
+
+ // if single line
+ if (isSameLine(leftToken, operatorToken) &&
+ isSameLine(operatorToken, rightToken)) {
+
+ return;
+
+ } else if (!isSameLine(leftToken, operatorToken) &&
+ !isSameLine(operatorToken, rightToken)) {
+
+ // lone operator
+ context.report(node, {
+ line: operatorToken.loc.end.line,
+ column: operatorToken.loc.end.column
+ }, "Bad line breaking before and after '" + operator + "'.");
+
+ } else if (style === "before" && isSameLine(leftToken, operatorToken)) {
+
+ context.report(node, {
+ line: operatorToken.loc.end.line,
+ column: operatorToken.loc.end.column
+ }, "'" + operator + "' should be placed at the beginning of the line.");
+
+ } else if (style === "after" && isSameLine(operatorToken, rightToken)) {
+
+ context.report(node, {
+ line: operatorToken.loc.end.line,
+ column: operatorToken.loc.end.column
+ }, "'" + operator + "' should be placed at the end of the line.");
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "BinaryExpression": validateBinaryExpression,
+ "LogicalExpression": validateBinaryExpression,
+ "AssignmentExpression": validateBinaryExpression,
+ "VariableDeclarator": function (node) {
+ if (node.init) {
+ validateBinaryExpression(node);
+ }
+ }
+ };
+};
diff --git a/tools/eslint/lib/rules/padded-blocks.js b/tools/eslint/lib/rules/padded-blocks.js
new file mode 100644
index 0000000000..9a5be4df0e
--- /dev/null
+++ b/tools/eslint/lib/rules/padded-blocks.js
@@ -0,0 +1,92 @@
+/**
+ * @fileoverview A rule to ensure blank lines within blocks.
+ * @author Mathias Schreck <https://github.com/lo1tuma>
+ * @copyright 2014 Mathias Schreck. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function (context) {
+ var requirePadding = context.options[0] !== "never";
+
+ var ALWAYS_MESSAGE = "Block must be padded by blank lines.",
+ NEVER_MESSAGE = "Block must not be padded by blank lines.";
+
+ /**
+ * Checks if the given non empty block node has a blank line before its first child node.
+ * @param {ASTNode} node The AST node of a BlockStatement.
+ * @returns {boolean} Whether or not the block starts with a blank line.
+ */
+ function isBlockTopPadded(node) {
+ var blockStart = node.loc.start.line,
+ first = node.body[0],
+ firstLine = first.loc.start.line,
+ expectedFirstLine = blockStart + 2,
+ leadingComments = context.getComments(first).leading;
+
+ if (leadingComments.length > 0) {
+ firstLine = leadingComments[0].loc.start.line;
+ }
+
+ return expectedFirstLine <= firstLine;
+ }
+
+ /**
+ * Checks if the given non empty block node has a blank line after its last child node.
+ * @param {ASTNode} node The AST node of a BlockStatement.
+ * @returns {boolean} Whether or not the block ends with a blank line.
+ */
+ function isBlockBottomPadded(node) {
+ var blockEnd = node.loc.end.line,
+ last = node.body[node.body.length - 1],
+ lastLine = context.getLastToken(last).loc.end.line,
+ expectedLastLine = blockEnd - 2,
+ trailingComments = context.getComments(last).trailing;
+
+ if (trailingComments.length > 0) {
+ lastLine = trailingComments[trailingComments.length - 1].loc.end.line;
+ }
+
+ return lastLine <= expectedLastLine;
+ }
+
+ /**
+ * Checks the given BlockStatement node to be padded if the block is not empty.
+ * @param {ASTNode} node The AST node of a BlockStatement.
+ * @returns {void} undefined.
+ */
+ function checkPadding(node) {
+ if (node.body.length > 0) {
+
+ var blockHasTopPadding = isBlockTopPadded(node),
+ blockHasBottomPadding = isBlockBottomPadded(node);
+
+ if (requirePadding) {
+ if (!blockHasTopPadding) {
+ context.report(node, ALWAYS_MESSAGE);
+ }
+
+ if (!blockHasBottomPadding) {
+ context.report(node, node.loc.end, ALWAYS_MESSAGE);
+ }
+ } else {
+ if (blockHasTopPadding) {
+ context.report(node, NEVER_MESSAGE);
+ }
+
+ if (blockHasBottomPadding) {
+ context.report(node, node.loc.end, NEVER_MESSAGE);
+ }
+ }
+ }
+ }
+
+ return {
+ "BlockStatement": checkPadding
+ };
+
+};
diff --git a/tools/eslint/lib/rules/quote-props.js b/tools/eslint/lib/rules/quote-props.js
new file mode 100644
index 0000000000..ccac947853
--- /dev/null
+++ b/tools/eslint/lib/rules/quote-props.js
@@ -0,0 +1,66 @@
+/**
+ * @fileoverview Rule to flag non-quoted property names in object literals.
+ * @author Mathias Bynens <http://mathiasbynens.be/>
+ * @copyright 2014 Brandon Mills. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+var espree = require("espree");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var MODE = context.options[0];
+
+ /**
+ * Ensures that a property's key is quoted only when necessary
+ * @param {ASTNode} node Property AST node
+ * @returns {void}
+ */
+ function asNeeded(node) {
+ var key = node.key,
+ tokens;
+
+ if (key.type === "Literal" && typeof key.value === "string") {
+ try {
+ tokens = espree.tokenize(key.value);
+ } catch (e) {
+ return;
+ }
+
+ if (tokens.length === 1 &&
+ (["Identifier", "Null", "Boolean"].indexOf(tokens[0].type) >= 0 ||
+ (tokens[0].type === "Numeric" && "" + +tokens[0].value === tokens[0].value))
+ ) {
+ context.report(node, "Unnecessarily quoted property `{{value}}` found.", key);
+ }
+ }
+ }
+
+ /**
+ * Ensures that a property's key is quoted
+ * @param {ASTNode} node Property AST node
+ * @returns {void}
+ */
+ function always(node) {
+ var key = node.key;
+
+ if (!node.method && !(key.type === "Literal" && typeof key.value === "string")) {
+ context.report(node, "Unquoted property `{{key}}` found.", {
+ key: key.name || key.value
+ });
+ }
+ }
+
+ return {
+ "Property": MODE === "as-needed" ? asNeeded : always
+ };
+
+};
diff --git a/tools/eslint/lib/rules/quotes.js b/tools/eslint/lib/rules/quotes.js
new file mode 100644
index 0000000000..2bb10ea5e4
--- /dev/null
+++ b/tools/eslint/lib/rules/quotes.js
@@ -0,0 +1,83 @@
+/**
+ * @fileoverview A rule to choose between single and double quote marks
+ * @author Matt DuVall <http://www.mattduvall.com/>, Brandon Payton
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Constants
+//------------------------------------------------------------------------------
+
+var QUOTE_SETTINGS = {
+ "double": {
+ quote: "\"",
+ alternateQuote: "'",
+ description: "doublequote"
+ },
+ "single": {
+ quote: "'",
+ alternateQuote: "\"",
+ description: "singlequote"
+ },
+ "backtick": {
+ quote: "`",
+ alternateQuote: "\"",
+ description: "backtick"
+ }
+};
+
+var AVOID_ESCAPE = "avoid-escape";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ /**
+ * Validate that a string passed in is surrounded by the specified character
+ * @param {string} val The text to check.
+ * @param {string} character The character to see if it's surrounded by.
+ * @returns {boolean} True if the text is surrounded by the character, false if not.
+ * @private
+ */
+ function isSurroundedBy(val, character) {
+ return val[0] === character && val[val.length - 1] === character;
+ }
+
+ /**
+ * Determines if a given node is part of JSX syntax.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} True if the node is a JSX node, false if not.
+ * @private
+ */
+ function isJSXElement(node) {
+ return node.type.indexOf("JSX") === 0;
+ }
+
+ return {
+
+ "Literal": function(node) {
+ var val = node.value,
+ rawVal = node.raw,
+ quoteOption = context.options[0],
+ settings = QUOTE_SETTINGS[quoteOption],
+ avoidEscape = context.options[1] === AVOID_ESCAPE,
+ isValid;
+
+ if (settings && typeof val === "string") {
+ isValid = isJSXElement(node.parent) || isSurroundedBy(rawVal, settings.quote);
+
+ if (!isValid && avoidEscape) {
+ isValid = isSurroundedBy(rawVal, settings.alternateQuote) && rawVal.indexOf(settings.quote) >= 0;
+ }
+
+ if (!isValid) {
+ context.report(node, "Strings must use " + settings.description + ".");
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/radix.js b/tools/eslint/lib/rules/radix.js
new file mode 100644
index 0000000000..a0c7acfb2e
--- /dev/null
+++ b/tools/eslint/lib/rules/radix.js
@@ -0,0 +1,39 @@
+/**
+ * @fileoverview Rule to flag use of parseInt without a radix argument
+ * @author James Allardice
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+ "CallExpression": function(node) {
+
+ var radix;
+
+ if (node.callee.name === "parseInt") {
+
+ if (node.arguments.length < 2) {
+ context.report(node, "Missing radix parameter.");
+ } else {
+
+ radix = node.arguments[1];
+
+ // don't allow non-numeric literals or undefined
+ if ((radix.type === "Literal" && typeof radix.value !== "number") ||
+ (radix.type === "Identifier" && radix.name === "undefined")
+ ) {
+ context.report(node, "Invalid radix parameter.");
+ }
+ }
+
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/semi-spacing.js b/tools/eslint/lib/rules/semi-spacing.js
new file mode 100644
index 0000000000..05ced44593
--- /dev/null
+++ b/tools/eslint/lib/rules/semi-spacing.js
@@ -0,0 +1,152 @@
+/**
+ * @fileoverview Validates spacing before and after semicolon
+ * @author Mathias Schreck
+ * @copyright 2015 Mathias Schreck
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function (context) {
+
+ var config = context.options[0],
+ requireSpaceBefore = false,
+ requireSpaceAfter = true;
+
+ if (typeof config === "object") {
+ if (config.hasOwnProperty("before")) {
+ requireSpaceBefore = config.before;
+ }
+ if (config.hasOwnProperty("after")) {
+ requireSpaceAfter = config.after;
+ }
+ }
+
+ /**
+ * Determines whether two adjacent tokens are have whitespace between them.
+ * @param {Object} left - The left token object.
+ * @param {Object} right - The right token object.
+ * @returns {boolean} Whether or not there is space between the tokens.
+ */
+ function isSpaced(left, right) {
+ return left.range[1] < right.range[0];
+ }
+
+ /**
+ * Checks whether two tokens are on the same line.
+ * @param {Object} left The leftmost token.
+ * @param {Object} right The rightmost token.
+ * @returns {boolean} True if the tokens are on the same line, false if not.
+ * @private
+ */
+ function isSameLine(left, right) {
+ return left.loc.end.line === right.loc.start.line;
+ }
+
+ /**
+ * Checks if a given token has leading whitespace.
+ * @param {Object} token The token to check.
+ * @returns {boolean} True if the given token has leading space, false if not.
+ */
+ function hasLeadingSpace(token) {
+ var tokenBefore = context.getTokenBefore(token);
+ return tokenBefore && isSameLine(tokenBefore, token) && isSpaced(tokenBefore, token);
+ }
+
+ /**
+ * Checks if a given token has trailing whitespace.
+ * @param {Object} token The token to check.
+ * @returns {boolean} True if the given token has trailing space, false if not.
+ */
+ function hasTrailingSpace(token) {
+ var tokenAfter = context.getTokenAfter(token);
+ return tokenAfter && isSameLine(token, tokenAfter) && isSpaced(token, tokenAfter);
+ }
+
+ /**
+ * Checks if the given token is the last token in its line.
+ * @param {Token} token The token to check.
+ * @returns {boolean} Whether or not the token is the last in its line.
+ */
+ function isLastTokenInCurrentLine(token) {
+ var tokenAfter = context.getTokenAfter(token);
+ return !(tokenAfter && isSameLine(token, tokenAfter));
+ }
+
+ /**
+ * Checks if the given token is a semicolon.
+ * @param {Token} token The token to check.
+ * @returns {boolean} Whether or not the given token is a semicolon.
+ */
+ function isSemicolon(token) {
+ return token.type === "Punctuator" && token.value === ";";
+ }
+
+ /**
+ * Reports if the given token has invalid spacing.
+ * @param {Token} token The semicolon token to check.
+ * @param {ASTNode} node The corresponding node of the token.
+ * @returns {void}
+ */
+ function checkSemicolonSpacing(token, node) {
+ var location;
+
+ if (isSemicolon(token)) {
+ location = token.loc.start;
+
+ if (hasLeadingSpace(token)) {
+ if (!requireSpaceBefore) {
+ context.report(node, location, "Unexpected whitespace before semicolon.");
+ }
+ } else {
+ if (requireSpaceBefore) {
+ context.report(node, location, "Missing whitespace before semicolon.");
+ }
+ }
+
+ if (!isLastTokenInCurrentLine(token)) {
+ if (hasTrailingSpace(token)) {
+ if (!requireSpaceAfter) {
+ context.report(node, location, "Unexpected whitespace after semicolon.");
+ }
+ } else {
+ if (requireSpaceAfter) {
+ context.report(node, location, "Missing whitespace after semicolon.");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks the spacing of the semicolon with the assumption that the last token is the semicolon.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ */
+ function checkNode(node) {
+ var token = context.getLastToken(node);
+ checkSemicolonSpacing(token, node);
+ }
+
+ return {
+ "VariableDeclaration": checkNode,
+ "ExpressionStatement": checkNode,
+ "BreakStatement": checkNode,
+ "ContinueStatement": checkNode,
+ "DebuggerStatement": checkNode,
+ "ReturnStatement": checkNode,
+ "ThrowStatement": checkNode,
+ "ForStatement": function (node) {
+ if (node.init) {
+ checkSemicolonSpacing(context.getTokenAfter(node.init), node);
+ }
+
+ if (node.test) {
+ checkSemicolonSpacing(context.getTokenAfter(node.test), node);
+ }
+ }
+ };
+};
diff --git a/tools/eslint/lib/rules/semi.js b/tools/eslint/lib/rules/semi.js
new file mode 100644
index 0000000000..a9f841cd73
--- /dev/null
+++ b/tools/eslint/lib/rules/semi.js
@@ -0,0 +1,130 @@
+/**
+ * @fileoverview Rule to flag missing semicolons.
+ * @author Nicholas C. Zakas
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+module.exports = function(context) {
+
+ var OPT_OUT_PATTERN = /[\[\(\/\+\-]/; // One of [(/+-
+
+ var always = context.options[0] !== "never";
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Reports a semicolon error with appropriate location and message.
+ * @param {ASTNode} node The node with an extra or missing semicolon.
+ * @returns {void}
+ */
+ function report(node) {
+ var message = always ? "Missing semicolon." : "Extra semicolon.";
+ context.report(node, context.getLastToken(node).loc.end, message);
+ }
+
+ /**
+ * Checks whether a token is a semicolon punctuator.
+ * @param {Token} token The token.
+ * @returns {boolean} True if token is a semicolon punctuator.
+ */
+ function isSemicolon(token) {
+ return (token.type === "Punctuator" && token.value === ";");
+ }
+
+ /**
+ * Check if a semicolon is unnecessary, only true if:
+ * - next token is on a new line and is not one of the opt-out tokens
+ * - next token is a valid statement divider
+ * @param {Token} lastToken last token of current node.
+ * @returns {boolean} whether the semicolon is unnecessary.
+ */
+ function isUnnecessarySemicolon(lastToken) {
+ var isDivider, isOptOutToken, lastTokenLine, nextToken, nextTokenLine;
+
+ if (!isSemicolon(lastToken)) {
+ return false;
+ }
+
+ nextToken = context.getTokenAfter(lastToken);
+
+ if (!nextToken) {
+ return true;
+ }
+
+ lastTokenLine = lastToken.loc.end.line;
+ nextTokenLine = nextToken.loc.start.line;
+ isOptOutToken = OPT_OUT_PATTERN.test(nextToken.value);
+ isDivider = (nextToken.value === "}" || nextToken.value === ";");
+
+ return (lastTokenLine !== nextTokenLine && !isOptOutToken) || isDivider;
+ }
+
+ /**
+ * Checks a node to see if it's followed by a semicolon.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ */
+ function checkForSemicolon(node) {
+ var lastToken = context.getLastToken(node);
+
+ if (always) {
+ if (!isSemicolon(lastToken)) {
+ report(node);
+ }
+ } else {
+ if (isUnnecessarySemicolon(lastToken)) {
+ report(node);
+ }
+ }
+ }
+
+ /**
+ * Checks to see if there's a semicolon after a variable declaration.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ */
+ function checkForSemicolonForVariableDeclaration(node) {
+ var ancestors = context.getAncestors(),
+ parentIndex = ancestors.length - 1,
+ parent = ancestors[parentIndex];
+
+ if ((parent.type !== "ForStatement" || parent.init !== node) &&
+ (!/^For(?:In|Of)Statement/.test(parent.type) || parent.left !== node)
+ ) {
+ checkForSemicolon(node);
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "VariableDeclaration": checkForSemicolonForVariableDeclaration,
+ "ExpressionStatement": checkForSemicolon,
+ "ReturnStatement": checkForSemicolon,
+ "ThrowStatement": checkForSemicolon,
+ "DebuggerStatement": checkForSemicolon,
+ "BreakStatement": checkForSemicolon,
+ "ContinueStatement": checkForSemicolon,
+ "ImportDeclaration": checkForSemicolon,
+ "ExportAllDeclaration": checkForSemicolon,
+ "ExportNamedDeclaration": function (node) {
+ if (!node.declaration) {
+ checkForSemicolon(node);
+ }
+ },
+ "ExportDefaultDeclaration": function (node) {
+ if (!/(?:Class|Function)Declaration/.test(node.declaration.type)) {
+ checkForSemicolon(node);
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/sort-vars.js b/tools/eslint/lib/rules/sort-vars.js
new file mode 100644
index 0000000000..8d4b6a8980
--- /dev/null
+++ b/tools/eslint/lib/rules/sort-vars.js
@@ -0,0 +1,37 @@
+/**
+ * @fileoverview Rule to require sorting of variables within a single Variable Declaration block
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var configuration = context.options[0] || {},
+ ignoreCase = configuration.ignoreCase || false;
+
+ return {
+ "VariableDeclaration": function(node) {
+ node.declarations.reduce(function(memo, decl) {
+ var lastVariableName = memo.id.name,
+ currenVariableName = decl.id.name;
+
+ if (ignoreCase) {
+ lastVariableName = lastVariableName.toLowerCase();
+ currenVariableName = currenVariableName.toLowerCase();
+ }
+
+ if (currenVariableName < lastVariableName) {
+ context.report(decl, "Variables within the same declaration block should be sorted alphabetically");
+ return memo;
+ } else {
+ return decl;
+ }
+ }, node.declarations[0]);
+ }
+ };
+};
diff --git a/tools/eslint/lib/rules/space-after-function-name.js b/tools/eslint/lib/rules/space-after-function-name.js
new file mode 100644
index 0000000000..c477768c46
--- /dev/null
+++ b/tools/eslint/lib/rules/space-after-function-name.js
@@ -0,0 +1,43 @@
+/**
+ * @fileoverview Rule to enforce consistent spacing after function names
+ * @author Roberto Vidal
+ * @copyright 2014 Roberto Vidal. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var requiresSpace = context.options[0] === "always";
+
+ /**
+ * Reports if the give named function node has the correct spacing after its name
+ *
+ * @param {ASTNode} node The node to which the potential problem belongs.
+ * @returns {void}
+ */
+ function check(node) {
+ var tokens = context.getFirstTokens(node, 3),
+ hasSpace = tokens[1].range[1] < tokens[2].range[0];
+
+ if (hasSpace !== requiresSpace) {
+ context.report(node, "Function name \"{{name}}\" must {{not}}be followed by whitespace.", {
+ name: node.id.name,
+ not: requiresSpace ? "" : "not "
+ });
+ }
+ }
+
+ return {
+ "FunctionDeclaration": check,
+ "FunctionExpression": function (node) {
+ if (node.id) {
+ check(node);
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/space-after-keywords.js b/tools/eslint/lib/rules/space-after-keywords.js
new file mode 100644
index 0000000000..3ece8bb065
--- /dev/null
+++ b/tools/eslint/lib/rules/space-after-keywords.js
@@ -0,0 +1,76 @@
+/**
+ * @fileoverview Rule to enforce the number of spaces after certain keywords
+ * @author Nick Fisher
+ * @copyright 2014 Nick Fisher. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ // unless the first option is `"never"`, then a space is required
+ var requiresSpace = context.options[0] !== "never";
+
+ /**
+ * Check if the separation of two adjacent tokens meets the spacing rules, and report a problem if not.
+ *
+ * @param {ASTNode} node The node to which the potential problem belongs.
+ * @param {Token} left The first token.
+ * @param {Token} right The second token
+ * @returns {void}
+ */
+ function checkTokens(node, left, right) {
+ var hasSpace = left.range[1] < right.range[0],
+ value = left.value;
+
+ if (hasSpace !== requiresSpace) {
+ context.report(node, "Keyword \"{{value}}\" must {{not}}be followed by whitespace.", {
+ value: value,
+ not: requiresSpace ? "" : "not "
+ });
+ }
+ }
+
+ /**
+ * Check if the given node (`if`, `for`, `while`, etc), has the correct spacing after it.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ */
+ function check(node) {
+ var tokens = context.getFirstTokens(node, 2);
+ checkTokens(node, tokens[0], tokens[1]);
+ }
+
+ return {
+ "IfStatement": function (node) {
+ check(node);
+ // check the `else`
+ if (node.alternate && node.alternate.type !== "IfStatement") {
+ checkTokens(node.alternate, context.getTokenBefore(node.alternate), context.getFirstToken(node.alternate));
+ }
+ },
+ "ForStatement": check,
+ "ForOfStatement": check,
+ "ForInStatement": check,
+ "WhileStatement": check,
+ "DoWhileStatement": function (node) {
+ check(node);
+ // check the `while`
+ var whileTokens = context.getTokensBefore(node.test, 2);
+ checkTokens(node, whileTokens[0], whileTokens[1]);
+ },
+ "SwitchStatement": check,
+ "TryStatement": function (node) {
+ check(node);
+ // check the `finally`
+ if (node.finalizer) {
+ checkTokens(node.finalizer, context.getTokenBefore(node.finalizer), context.getFirstToken(node.finalizer));
+ }
+ },
+ "CatchStatement": check,
+ "WithStatement": check
+ };
+};
diff --git a/tools/eslint/lib/rules/space-before-blocks.js b/tools/eslint/lib/rules/space-before-blocks.js
new file mode 100644
index 0000000000..963ff42598
--- /dev/null
+++ b/tools/eslint/lib/rules/space-before-blocks.js
@@ -0,0 +1,85 @@
+/**
+ * @fileoverview A rule to ensure whitespace before blocks.
+ * @author Mathias Schreck <https://github.com/lo1tuma>
+ * @copyright 2014 Mathias Schreck. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function (context) {
+ var requireSpace = context.options[0] !== "never";
+
+ /**
+ * Determines whether two adjacent tokens are have whitespace between them.
+ * @param {Object} left - The left token object.
+ * @param {Object} right - The right token object.
+ * @returns {boolean} Whether or not there is space between the tokens.
+ */
+ function isSpaced(left, right) {
+ return left.range[1] < right.range[0];
+ }
+
+ /**
+ * Determines whether two adjacent tokens are on the same line.
+ * @param {Object} left - The left token object.
+ * @param {Object} right - The right token object.
+ * @returns {boolean} Whether or not the tokens are on the same line.
+ */
+ function isSameLine(left, right) {
+ return left.loc.start.line === right.loc.start.line;
+ }
+
+ /**
+ * Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line.
+ * @param {ASTNode|Token} node The AST node of a BlockStatement.
+ * @returns {void} undefined.
+ */
+ function checkPrecedingSpace(node) {
+ var precedingToken = context.getTokenBefore(node),
+ hasSpace;
+
+ if (precedingToken && isSameLine(precedingToken, node)) {
+ hasSpace = isSpaced(precedingToken, node);
+
+ if (requireSpace) {
+ if (!hasSpace) {
+ context.report(node, "Missing space before opening brace.");
+ }
+ } else {
+ if (hasSpace) {
+ context.report(node, "Unexpected space before opening brace.");
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if the CaseBlock of an given SwitchStatement node has a preceding space.
+ * @param {ASTNode} node The node of a SwitchStatement.
+ * @returns {void} undefined.
+ */
+ function checkSpaceBeforeCaseBlock(node) {
+ var cases = node.cases,
+ firstCase,
+ openingBrace;
+
+ if (cases.length > 0) {
+ firstCase = cases[0];
+ openingBrace = context.getTokenBefore(firstCase);
+ } else {
+ openingBrace = context.getLastToken(node, 1);
+ }
+
+ checkPrecedingSpace(openingBrace);
+ }
+
+ return {
+ "BlockStatement": checkPrecedingSpace,
+ "SwitchStatement": checkSpaceBeforeCaseBlock
+ };
+
+};
diff --git a/tools/eslint/lib/rules/space-before-function-paren.js b/tools/eslint/lib/rules/space-before-function-paren.js
new file mode 100644
index 0000000000..b56409ada0
--- /dev/null
+++ b/tools/eslint/lib/rules/space-before-function-paren.js
@@ -0,0 +1,117 @@
+/**
+ * @fileoverview Rule to validate spacing before function paren.
+ * @author Mathias Schreck <https://github.com/lo1tuma>
+ * @copyright 2015 Mathias Schreck
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var configuration = context.options[0],
+ requireAnonymousFunctionSpacing = true,
+ requireNamedFunctionSpacing = true;
+
+ if (typeof configuration === "object") {
+ requireAnonymousFunctionSpacing = configuration.anonymous !== "never";
+ requireNamedFunctionSpacing = configuration.named !== "never";
+ } else if (configuration === "never") {
+ requireAnonymousFunctionSpacing = false;
+ requireNamedFunctionSpacing = false;
+ }
+
+ /**
+ * Determines whether two adjacent tokens are have whitespace between them.
+ * @param {Object} left - The left token object.
+ * @param {Object} right - The right token object.
+ * @returns {boolean} Whether or not there is space between the tokens.
+ */
+ function isSpaced(left, right) {
+ return left.range[1] < right.range[0];
+ }
+
+ /**
+ * Determines whether a function has a name.
+ * @param {ASTNode} node The function node.
+ * @returns {boolean} Whether the function has a name.
+ */
+ function isNamedFunction(node) {
+ var parent;
+
+ if (node.id) {
+ return true;
+ }
+
+ parent = context.getAncestors().pop();
+ return parent.type === "MethodDefinition" ||
+ (parent.type === "Property" &&
+ (
+ parent.kind === "get" ||
+ parent.kind === "set" ||
+ parent.method
+ )
+ );
+ }
+
+ /**
+ * Validates the spacing before function parentheses.
+ * @param {ASTNode} node The node to be validated.
+ * @returns {void}
+ */
+ function validateSpacingBeforeParentheses(node) {
+ var isNamed = isNamedFunction(node),
+ tokens,
+ leftToken,
+ rightToken,
+ location;
+
+ if (node.generator && !isNamed) {
+ return;
+ }
+
+ tokens = context.getTokens(node);
+
+ if (node.generator) {
+ if (node.id) {
+ leftToken = tokens[2];
+ rightToken = tokens[3];
+ } else {
+ // Object methods are named but don't have an id
+ leftToken = context.getTokenBefore(node);
+ rightToken = tokens[0];
+ }
+ } else if (isNamed) {
+ if (node.id) {
+ leftToken = tokens[1];
+ rightToken = tokens[2];
+ } else {
+ // Object methods are named but don't have an id
+ leftToken = context.getTokenBefore(node);
+ rightToken = tokens[0];
+ }
+ } else {
+ leftToken = tokens[0];
+ rightToken = tokens[1];
+ }
+
+ location = leftToken.loc.end;
+
+ if (isSpaced(leftToken, rightToken)) {
+ if ((isNamed && !requireNamedFunctionSpacing) || (!isNamed && !requireAnonymousFunctionSpacing)) {
+ context.report(node, location, "Unexpected space before function parentheses.");
+ }
+ } else {
+ if ((isNamed && requireNamedFunctionSpacing) || (!isNamed && requireAnonymousFunctionSpacing)) {
+ context.report(node, location, "Missing space before function parentheses.");
+ }
+ }
+ }
+
+ return {
+ "FunctionDeclaration": validateSpacingBeforeParentheses,
+ "FunctionExpression": validateSpacingBeforeParentheses
+ };
+};
diff --git a/tools/eslint/lib/rules/space-before-function-parentheses.js b/tools/eslint/lib/rules/space-before-function-parentheses.js
new file mode 100644
index 0000000000..b40351d0b9
--- /dev/null
+++ b/tools/eslint/lib/rules/space-before-function-parentheses.js
@@ -0,0 +1,117 @@
+/**
+ * @fileoverview Rule to validate spacing before function parentheses.
+ * @author Mathias Schreck <https://github.com/lo1tuma>
+ * @copyright 2015 Mathias Schreck
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var configuration = context.options[0],
+ requireAnonymousFunctionSpacing = true,
+ requireNamedFunctionSpacing = true;
+
+ if (typeof configuration === "object") {
+ requireAnonymousFunctionSpacing = configuration.anonymous !== "never";
+ requireNamedFunctionSpacing = configuration.named !== "never";
+ } else if (configuration === "never") {
+ requireAnonymousFunctionSpacing = false;
+ requireNamedFunctionSpacing = false;
+ }
+
+ /**
+ * Determines whether two adjacent tokens are have whitespace between them.
+ * @param {Object} left - The left token object.
+ * @param {Object} right - The right token object.
+ * @returns {boolean} Whether or not there is space between the tokens.
+ */
+ function isSpaced(left, right) {
+ return left.range[1] < right.range[0];
+ }
+
+ /**
+ * Determines whether a function has a name.
+ * @param {ASTNode} node The function node.
+ * @returns {boolean} Whether the function has a name.
+ */
+ function isNamedFunction(node) {
+ var parent;
+
+ if (node.id) {
+ return true;
+ }
+
+ parent = context.getAncestors().pop();
+ return parent.type === "MethodDefinition" ||
+ (parent.type === "Property" &&
+ (
+ parent.kind === "get" ||
+ parent.kind === "set" ||
+ parent.method
+ )
+ );
+ }
+
+ /**
+ * Validates the spacing before function parentheses.
+ * @param {ASTNode} node The node to be validated.
+ * @returns {void}
+ */
+ function validateSpacingBeforeParentheses(node) {
+ var isNamed = isNamedFunction(node),
+ tokens,
+ leftToken,
+ rightToken,
+ location;
+
+ if (node.generator && !isNamed) {
+ return;
+ }
+
+ tokens = context.getTokens(node);
+
+ if (node.generator) {
+ if (node.id) {
+ leftToken = tokens[2];
+ rightToken = tokens[3];
+ } else {
+ // Object methods are named but don't have an id
+ leftToken = context.getTokenBefore(node);
+ rightToken = tokens[0];
+ }
+ } else if (isNamed) {
+ if (node.id) {
+ leftToken = tokens[1];
+ rightToken = tokens[2];
+ } else {
+ // Object methods are named but don't have an id
+ leftToken = context.getTokenBefore(node);
+ rightToken = tokens[0];
+ }
+ } else {
+ leftToken = tokens[0];
+ rightToken = tokens[1];
+ }
+
+ location = leftToken.loc.end;
+
+ if (isSpaced(leftToken, rightToken)) {
+ if ((isNamed && !requireNamedFunctionSpacing) || (!isNamed && !requireAnonymousFunctionSpacing)) {
+ context.report(node, location, "Unexpected space before function parentheses.");
+ }
+ } else {
+ if ((isNamed && requireNamedFunctionSpacing) || (!isNamed && requireAnonymousFunctionSpacing)) {
+ context.report(node, location, "Missing space before function parentheses.");
+ }
+ }
+ }
+
+ return {
+ "FunctionDeclaration": validateSpacingBeforeParentheses,
+ "FunctionExpression": validateSpacingBeforeParentheses
+ };
+};
diff --git a/tools/eslint/lib/rules/space-in-brackets.js b/tools/eslint/lib/rules/space-in-brackets.js
new file mode 100644
index 0000000000..1b5fcec466
--- /dev/null
+++ b/tools/eslint/lib/rules/space-in-brackets.js
@@ -0,0 +1,272 @@
+/**
+ * @fileoverview Disallows or enforces spaces inside of brackets.
+ * @author Ian Christian Myers
+ * @copyright 2014 Brandyn Bennett. All rights reserved.
+ * @copyright 2014 Michael Ficarra. No rights reserved.
+ * @copyright 2014 Vignesh Anand. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+ var spaced = context.options[0] === "always";
+
+ /**
+ * Determines whether an option is set, relative to the spacing option.
+ * If spaced is "always", then check whether option is set to false.
+ * If spaced is "never", then check whether option is set to true.
+ * @param {Object} option - The option to exclude.
+ * @returns {boolean} Whether or not the property is excluded.
+ */
+ function isOptionSet(option) {
+ return context.options[1] != null ? context.options[1][option] === !spaced : false;
+ }
+
+ var options = {
+ spaced: spaced,
+ singleElementException: isOptionSet("singleValue"),
+ objectsInArraysException: isOptionSet("objectsInArrays"),
+ arraysInArraysException: isOptionSet("arraysInArrays"),
+ arraysInObjectsException: isOptionSet("arraysInObjects"),
+ objectsInObjectsException: isOptionSet("objectsInObjects"),
+ propertyNameException: isOptionSet("propertyName")
+ };
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Determines whether two adjacent tokens are have whitespace between them.
+ * @param {Object} left - The left token object.
+ * @param {Object} right - The right token object.
+ * @returns {boolean} Whether or not there is space between the tokens.
+ */
+ function isSpaced(left, right) {
+ return left.range[1] < right.range[0];
+ }
+
+ /**
+ * Determines whether two adjacent tokens are on the same line.
+ * @param {Object} left - The left token object.
+ * @param {Object} right - The right token object.
+ * @returns {boolean} Whether or not the tokens are on the same line.
+ */
+ function isSameLine(left, right) {
+ return left.loc.start.line === right.loc.start.line;
+ }
+
+ /**
+ * Reports that there shouldn't be a space after the first token
+ * @param {ASTNode} node - The node to report in the event of an error.
+ * @param {Token} token - The token to use for the report.
+ * @returns {void}
+ */
+ function reportNoBeginningSpace(node, token) {
+ context.report(node, token.loc.start,
+ "There should be no space after '" + token.value + "'");
+ }
+
+ /**
+ * Reports that there shouldn't be a space before the last token
+ * @param {ASTNode} node - The node to report in the event of an error.
+ * @param {Token} token - The token to use for the report.
+ * @returns {void}
+ */
+ function reportNoEndingSpace(node, token) {
+ context.report(node, token.loc.start,
+ "There should be no space before '" + token.value + "'");
+ }
+
+ /**
+ * Reports that there should be a space after the first token
+ * @param {ASTNode} node - The node to report in the event of an error.
+ * @param {Token} token - The token to use for the report.
+ * @returns {void}
+ */
+ function reportRequiredBeginningSpace(node, token) {
+ context.report(node, token.loc.start,
+ "A space is required after '" + token.value + "'");
+ }
+
+ /**
+ * Reports that there should be a space before the last token
+ * @param {ASTNode} node - The node to report in the event of an error.
+ * @param {Token} token - The token to use for the report.
+ * @returns {void}
+ */
+ function reportRequiredEndingSpace(node, token) {
+ context.report(node, token.loc.start,
+ "A space is required before '" + token.value + "'");
+ }
+
+
+ /**
+ * Determines if spacing in curly braces is valid.
+ * @param {ASTNode} node The AST node to check.
+ * @param {Token} first The first token to check (should be the opening brace)
+ * @param {Token} second The second token to check (should be first after the opening brace)
+ * @param {Token} penultimate The penultimate token to check (should be last before closing brace)
+ * @param {Token} last The last token to check (should be closing brace)
+ * @returns {void}
+ */
+ function validateBraceSpacing(node, first, second, penultimate, last) {
+ var closingCurlyBraceMustBeSpaced =
+ options.arraysInObjectsException && penultimate.value === "]" ||
+ options.objectsInObjectsException && penultimate.value === "}"
+ ? !options.spaced : options.spaced;
+
+ if (isSameLine(first, second)) {
+ if (options.spaced && !isSpaced(first, second)) {
+ reportRequiredBeginningSpace(node, first);
+ }
+ if (!options.spaced && isSpaced(first, second)) {
+ reportNoBeginningSpace(node, first);
+ }
+ }
+
+ if (isSameLine(penultimate, last)) {
+ if (closingCurlyBraceMustBeSpaced && !isSpaced(penultimate, last)) {
+ reportRequiredEndingSpace(node, last);
+ }
+ if (!closingCurlyBraceMustBeSpaced && isSpaced(penultimate, last)) {
+ reportNoEndingSpace(node, last);
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ MemberExpression: function(node) {
+ if (!node.computed) {
+ return;
+ }
+
+ var property = node.property,
+ before = context.getTokenBefore(property),
+ first = context.getFirstToken(property),
+ last = context.getLastToken(property),
+ after = context.getTokenAfter(property);
+
+ var propertyNameMustBeSpaced = options.propertyNameException ?
+ !options.spaced : options.spaced;
+
+ if (isSameLine(before, first)) {
+ if (propertyNameMustBeSpaced) {
+ if (!isSpaced(before, first) && isSameLine(before, first)) {
+ reportRequiredBeginningSpace(node, before);
+ }
+ } else {
+ if (isSpaced(before, first)) {
+ reportNoBeginningSpace(node, before);
+ }
+ }
+ }
+
+ if (isSameLine(last, after)) {
+ if (propertyNameMustBeSpaced) {
+ if (!isSpaced(last, after) && isSameLine(last, after)) {
+ reportRequiredEndingSpace(node, after);
+ }
+ } else {
+ if (isSpaced(last, after)) {
+ reportNoEndingSpace(node, after);
+ }
+ }
+ }
+ },
+
+ ArrayExpression: function(node) {
+ if (node.elements.length === 0) {
+ return;
+ }
+
+ var first = context.getFirstToken(node),
+ second = context.getFirstToken(node, 1),
+ penultimate = context.getLastToken(node, 1),
+ last = context.getLastToken(node);
+
+ var openingBracketMustBeSpaced =
+ options.objectsInArraysException && second.value === "{" ||
+ options.arraysInArraysException && second.value === "[" ||
+ options.singleElementException && node.elements.length === 1
+ ? !options.spaced : options.spaced;
+
+ var closingBracketMustBeSpaced =
+ options.objectsInArraysException && penultimate.value === "}" ||
+ options.arraysInArraysException && penultimate.value === "]" ||
+ options.singleElementException && node.elements.length === 1
+ ? !options.spaced : options.spaced;
+
+ if (isSameLine(first, second)) {
+ if (openingBracketMustBeSpaced && !isSpaced(first, second)) {
+ reportRequiredBeginningSpace(node, first);
+ }
+ if (!openingBracketMustBeSpaced && isSpaced(first, second)) {
+ reportNoBeginningSpace(node, first);
+ }
+ }
+
+ if (isSameLine(penultimate, last)) {
+ if (closingBracketMustBeSpaced && !isSpaced(penultimate, last)) {
+ reportRequiredEndingSpace(node, last);
+ }
+ if (!closingBracketMustBeSpaced && isSpaced(penultimate, last)) {
+ reportNoEndingSpace(node, last);
+ }
+ }
+ },
+
+ ImportDeclaration: function(node) {
+
+ var firstSpecifier = node.specifiers[0],
+ lastSpecifier = node.specifiers[node.specifiers.length - 1];
+
+ // don't do anything for namespace or default imports
+ if (firstSpecifier.type === "ImportSpecifier" && lastSpecifier.type === "ImportSpecifier") {
+ var first = context.getTokenBefore(firstSpecifier),
+ second = context.getFirstToken(firstSpecifier),
+ penultimate = context.getLastToken(lastSpecifier),
+ last = context.getTokenAfter(lastSpecifier);
+
+ validateBraceSpacing(node, first, second, penultimate, last);
+ }
+
+ },
+
+ ExportNamedDeclaration: function(node) {
+
+ var firstSpecifier = node.specifiers[0],
+ lastSpecifier = node.specifiers[node.specifiers.length - 1],
+ first = context.getTokenBefore(firstSpecifier),
+ second = context.getFirstToken(firstSpecifier),
+ penultimate = context.getLastToken(lastSpecifier),
+ last = context.getTokenAfter(lastSpecifier);
+
+ validateBraceSpacing(node, first, second, penultimate, last);
+
+ },
+
+ ObjectExpression: function(node) {
+ if (node.properties.length === 0) {
+ return;
+ }
+
+ var first = context.getFirstToken(node),
+ second = context.getFirstToken(node, 1),
+ penultimate = context.getLastToken(node, 1),
+ last = context.getLastToken(node);
+
+ validateBraceSpacing(node, first, second, penultimate, last);
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/space-in-parens.js b/tools/eslint/lib/rules/space-in-parens.js
new file mode 100644
index 0000000000..ff124c1298
--- /dev/null
+++ b/tools/eslint/lib/rules/space-in-parens.js
@@ -0,0 +1,262 @@
+/**
+ * @fileoverview Disallows or enforces spaces inside of parentheses.
+ * @author Jonathan Rajavuori
+ * @copyright 2014 David Clark. All rights reserved.
+ * @copyright 2014 Jonathan Rajavuori. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var MISSING_SPACE_MESSAGE = "There must be a space inside this paren.",
+ REJECTED_SPACE_MESSAGE = "There should be no spaces inside this paren.",
+ exceptionsArray = (context.options.length === 2) ? context.options[1].exceptions : [],
+ options = {},
+ rejectedSpaceRegExp,
+ missingSpaceRegExp,
+ spaceChecks;
+
+ if (exceptionsArray && exceptionsArray.length) {
+ options.braceException = exceptionsArray.indexOf("{}") !== -1 || false;
+ options.bracketException = exceptionsArray.indexOf("[]") !== -1 || false;
+ options.parenException = exceptionsArray.indexOf("()") !== -1 || false;
+ options.empty = exceptionsArray.indexOf("empty") !== -1 || false;
+ }
+
+ /**
+ * Used with the `never` option to produce, given the exception options,
+ * two regular expressions to check for missing and rejected spaces.
+ * @param {Object} opts The exception options
+ * @returns {Object} `missingSpace` and `rejectedSpace` regular expressions
+ * @private
+ */
+ function getNeverChecks(opts) {
+ var missingSpaceOpeners = [],
+ missingSpaceClosers = [],
+ rejectedSpaceOpeners = ["\\s"],
+ rejectedSpaceClosers = ["\\s"],
+ missingSpaceCheck,
+ rejectedSpaceCheck;
+
+ // Populate openers and closers
+ if (opts.braceException) {
+ missingSpaceOpeners.push("\\{");
+ missingSpaceClosers.push("\\}");
+ rejectedSpaceOpeners.push("\\{");
+ rejectedSpaceClosers.push("\\}");
+ }
+ if (opts.bracketException) {
+ missingSpaceOpeners.push("\\[");
+ missingSpaceClosers.push("\\]");
+ rejectedSpaceOpeners.push("\\[");
+ rejectedSpaceClosers.push("\\]");
+ }
+ if (opts.parenException) {
+ missingSpaceOpeners.push("\\(");
+ missingSpaceClosers.push("\\)");
+ rejectedSpaceOpeners.push("\\(");
+ rejectedSpaceClosers.push("\\)");
+ }
+ if (opts.empty) {
+ missingSpaceOpeners.push("\\)");
+ missingSpaceClosers.push("\\(");
+ rejectedSpaceOpeners.push("\\)");
+ rejectedSpaceClosers.push("\\(");
+ }
+
+ if (missingSpaceOpeners.length) {
+ missingSpaceCheck = "\\((" + missingSpaceOpeners.join("|") + ")";
+ if (missingSpaceClosers.length) {
+ missingSpaceCheck += "|";
+ }
+ }
+ if (missingSpaceClosers.length) {
+ missingSpaceCheck += "(" + missingSpaceClosers.join("|") + ")\\)";
+ }
+
+ // compose the rejected regexp
+ rejectedSpaceCheck = "\\( +[^" + rejectedSpaceOpeners.join("") + "]";
+ rejectedSpaceCheck += "|[^" + rejectedSpaceClosers.join("") + "] +\\)";
+
+ return {
+ // e.g. \((\{)|(\})\) --- where {} is an exception
+ missingSpace: missingSpaceCheck || ".^",
+ // e.g. \( +[^ \n\r\{]|[^ \n\r\}] +\) --- where {} is an exception
+ rejectedSpace: rejectedSpaceCheck
+ };
+ }
+
+ /**
+ * Used with the `always` option to produce, given the exception options,
+ * two regular expressions to check for missing and rejected spaces.
+ * @param {Object} opts The exception options
+ * @returns {Object} `missingSpace` and `rejectedSpace` regular expressions
+ * @private
+ */
+ function getAlwaysChecks(opts) {
+ var missingSpaceOpeners = ["\\s", "\\)"],
+ missingSpaceClosers = ["\\s", "\\("],
+ rejectedSpaceOpeners = [],
+ rejectedSpaceClosers = [],
+ missingSpaceCheck,
+ rejectedSpaceCheck;
+
+ // Populate openers and closers
+ if (opts.braceException) {
+ missingSpaceOpeners.push("\\{");
+ missingSpaceClosers.push("\\}");
+ rejectedSpaceOpeners.push(" \\{");
+ rejectedSpaceClosers.push("\\} ");
+ }
+ if (opts.bracketException) {
+ missingSpaceOpeners.push("\\[");
+ missingSpaceClosers.push("\\]");
+ rejectedSpaceOpeners.push(" \\[");
+ rejectedSpaceClosers.push("\\] ");
+ }
+ if (opts.parenException) {
+ missingSpaceOpeners.push("\\(");
+ missingSpaceClosers.push("\\)");
+ rejectedSpaceOpeners.push(" \\(");
+ rejectedSpaceClosers.push("\\) ");
+ }
+ if (opts.empty) {
+ rejectedSpaceOpeners.push(" \\)");
+ rejectedSpaceClosers.push("\\( ");
+ }
+
+ // compose the allowed regexp
+ missingSpaceCheck = "\\([^" + missingSpaceOpeners.join("") + "]";
+ missingSpaceCheck += "|[^" + missingSpaceClosers.join("") + "]\\)";
+
+ // compose the rejected regexp
+ if (rejectedSpaceOpeners.length) {
+ rejectedSpaceCheck = "\\((" + rejectedSpaceOpeners.join("|") + ")";
+ if (rejectedSpaceClosers.length) {
+ rejectedSpaceCheck += "|";
+ }
+ }
+ if (rejectedSpaceClosers.length) {
+ rejectedSpaceCheck += "(" + rejectedSpaceClosers.join("|") + ")\\)";
+ }
+
+ return {
+ // e.g. \([^ \)\r\n\{]|[^ \(\r\n\}]\) --- where {} is an exception
+ missingSpace: missingSpaceCheck,
+ // e.g. \(( \{})|(\} )\) --- where {} is an excpetion
+ rejectedSpace: rejectedSpaceCheck || ".^"
+ };
+ }
+
+ spaceChecks = (context.options[0] === "always") ? getAlwaysChecks(options) : getNeverChecks(options);
+ missingSpaceRegExp = new RegExp(spaceChecks.missingSpace, "mg");
+ rejectedSpaceRegExp = new RegExp(spaceChecks.rejectedSpace, "mg");
+
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ var skipRanges = [];
+
+ /**
+ * Adds the range of a node to the set to be skipped when checking parens
+ * @param {ASTNode} node The node to skip
+ * @returns {void}
+ * @private
+ */
+ function addSkipRange(node) {
+ skipRanges.push(node.range);
+ }
+
+ /**
+ * Sorts the skipRanges array. Must be called before shouldSkip
+ * @returns {void}
+ * @private
+ */
+ function sortSkipRanges() {
+ skipRanges.sort(function (a, b) {
+ return a[0] - b[0];
+ });
+ }
+
+ /**
+ * Checks if a certain position in the source should be skipped
+ * @param {Number} pos The 0-based index in the source
+ * @returns {boolean} whether the position should be skipped
+ * @private
+ */
+ function shouldSkip(pos) {
+ var i, len, range;
+ for (i = 0, len = skipRanges.length; i < len; i += 1) {
+ range = skipRanges[i];
+ if (pos < range[0]) {
+ break;
+ } else if (pos < range[1]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "Program:exit": function checkParenSpaces(node) {
+
+ var nextMatch,
+ nextLine,
+ column,
+ line = 1,
+ source = context.getSource(),
+ pos = 0;
+
+ function checkMatch(match, message) {
+ if (source.charAt(match.index) !== "(") {
+ // Matched a closing paren pattern
+ match.index += 1;
+ }
+
+ if (!shouldSkip(match.index)) {
+ while ((nextLine = source.indexOf("\n", pos)) !== -1 && nextLine < match.index) {
+ pos = nextLine + 1;
+ line += 1;
+ }
+ column = match.index - pos;
+
+ context.report(node, { line: line, column: column }, message);
+ }
+ }
+
+ sortSkipRanges();
+
+ while ((nextMatch = rejectedSpaceRegExp.exec(source)) !== null) {
+ checkMatch(nextMatch, REJECTED_SPACE_MESSAGE);
+ }
+
+ while ((nextMatch = missingSpaceRegExp.exec(source)) !== null) {
+ checkMatch(nextMatch, MISSING_SPACE_MESSAGE);
+ }
+
+ },
+
+
+ // These nodes can contain parentheses that this rule doesn't care about
+
+ LineComment: addSkipRange,
+
+ BlockComment: addSkipRange,
+
+ Literal: addSkipRange
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/space-infix-ops.js b/tools/eslint/lib/rules/space-infix-ops.js
new file mode 100644
index 0000000000..a3f3c940ac
--- /dev/null
+++ b/tools/eslint/lib/rules/space-infix-ops.js
@@ -0,0 +1,94 @@
+/**
+ * @fileoverview Require spaces around infix operators
+ * @author Michael Ficarra
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+ var int32Hint = context.options[0] ? context.options[0].int32Hint === true : false;
+
+ var OPERATORS = [
+ "*", "/", "%", "+", "-", "<<", ">>", ">>>", "<", "<=", ">", ">=", "in",
+ "instanceof", "==", "!=", "===", "!==", "&", "^", "|", "&&", "||", "=",
+ "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "^=", "|=",
+ "?", ":", ","
+ ];
+
+ /**
+ * Returns the first token which violates the rule
+ * @param {ASTNode} left - The left node of the main node
+ * @param {ASTNode} right - The right node of the main node
+ * @returns {object} The violator token or null
+ * @private
+ */
+ function getFirstNonSpacedToken(left, right) {
+ var op, tokens = context.getTokensBetween(left, right, 1);
+ for (var i = 1, l = tokens.length - 1; i < l; ++i) {
+ op = tokens[i];
+ if (
+ op.type === "Punctuator" &&
+ OPERATORS.indexOf(op.value) >= 0 &&
+ (tokens[i - 1].range[1] >= op.range[0] || op.range[1] >= tokens[i + 1].range[0])
+ ) {
+ return op;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Reports an AST node as a rule violation
+ * @param {ASTNode} mainNode - The node to report
+ * @param {object} culpritToken - The token which has a problem
+ * @returns {void}
+ * @private
+ */
+ function report(mainNode, culpritToken) {
+ context.report(mainNode, culpritToken.loc.start, "Infix operators must be spaced.");
+ }
+
+ function checkBinary(node) {
+ var nonSpacedNode = getFirstNonSpacedToken(node.left, node.right);
+
+ if (nonSpacedNode) {
+ if (!(int32Hint && context.getSource(node).substr(-2) === "|0")) {
+ report(node, nonSpacedNode);
+ }
+ }
+ }
+
+ function checkConditional(node) {
+ var nonSpacedConsequesntNode = getFirstNonSpacedToken(node.test, node.consequent);
+ var nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate);
+
+ if (nonSpacedConsequesntNode) {
+ report(node, nonSpacedConsequesntNode);
+ } else if (nonSpacedAlternateNode) {
+ report(node, nonSpacedAlternateNode);
+ }
+ }
+
+ function checkVar(node) {
+ var nonSpacedNode;
+
+ if (node.init) {
+ nonSpacedNode = getFirstNonSpacedToken(node.id, node.init);
+ if (nonSpacedNode) {
+ report(node, nonSpacedNode);
+ }
+ }
+ }
+
+ return {
+ "AssignmentExpression": checkBinary,
+ "BinaryExpression": checkBinary,
+ "LogicalExpression": checkBinary,
+ "ConditionalExpression": checkConditional,
+ "VariableDeclarator": checkVar
+ };
+
+};
diff --git a/tools/eslint/lib/rules/space-return-throw-case.js b/tools/eslint/lib/rules/space-return-throw-case.js
new file mode 100644
index 0000000000..27928adec7
--- /dev/null
+++ b/tools/eslint/lib/rules/space-return-throw-case.js
@@ -0,0 +1,36 @@
+/**
+ * @fileoverview Require spaces following return, throw, and case
+ * @author Michael Ficarra
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ function check(node) {
+ var tokens = context.getFirstTokens(node, 2),
+ value = tokens[0].value;
+
+ if (tokens[0].range[1] >= tokens[1].range[0]) {
+ context.report(node, "Keyword \"" + value + "\" must be followed by whitespace.");
+ }
+ }
+
+ return {
+ "ReturnStatement": function(node) {
+ if (node.argument) {
+ check(node);
+ }
+ },
+ "SwitchCase": function(node) {
+ if (node.test) {
+ check(node);
+ }
+ },
+ "ThrowStatement": check
+ };
+
+};
diff --git a/tools/eslint/lib/rules/space-unary-ops.js b/tools/eslint/lib/rules/space-unary-ops.js
new file mode 100644
index 0000000000..2fe8a3f406
--- /dev/null
+++ b/tools/eslint/lib/rules/space-unary-ops.js
@@ -0,0 +1,118 @@
+/**
+ * @fileoverview This rule shoud require or disallow spaces before or after unary operations.
+ * @author Marcin Kumorek
+ * @copyright 2014 Marcin Kumorek. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+ var options = context.options && Array.isArray(context.options) && context.options[0] || { words: true, nonwords: false };
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Check if the parent unary operator is "!" in order to know if it's "!!" convert to Boolean or just "!" negation
+ * @param {ASTnode} node AST node
+ * @returns {boolean} Whether or not the parent is unary "!" operator
+ */
+ function isParentUnaryBangExpression(node) {
+ return node && node.parent && node.parent.type === "UnaryExpression" && node.parent.operator === "!";
+ }
+
+ /**
+ * Checks if the type is a unary word expression
+ * @param {string} type value of AST token
+ * @returns {boolean} Whether the word is in the list of known words
+ */
+ function isWordExpression(type) {
+ return ["delete", "new", "typeof", "void"].indexOf(type) !== -1;
+ }
+
+ /**
+ * Check if the node's child argument is an "ObjectExpression"
+ * @param {ASTnode} node AST node
+ * @returns {boolean} Whether or not the argument's type is "ObjectExpression"
+ */
+ function isArgumentObjectExpression(node) {
+ return node.argument && node.argument.type && node.argument.type === "ObjectExpression";
+ }
+
+ /**
+ * Check Unary Word Operators for spaces after the word operator
+ * @param {ASTnode} node AST node
+ * @param {object} firstToken first token from the AST node
+ * @param {object} secondToken second token from the AST node
+ * @returns {void}
+ */
+ function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken) {
+ if (options.words) {
+ if (secondToken.range[0] === firstToken.range[1]) {
+ context.report(node, "Unary word operator \"" + firstToken.value + "\" must be followed by whitespace.");
+ }
+ }
+
+ if (!options.words && isArgumentObjectExpression(node)) {
+ if (secondToken.range[0] > firstToken.range[1]) {
+ context.report(node, "Unexpected space after unary word operator \"" + firstToken.value + "\".");
+ }
+ }
+ }
+
+ /**
+ * Checks UnaryExpression, UpdateExpression and NewExpression for spaces before and after the operator
+ * @param {ASTnode} node AST node
+ * @returns {void}
+ */
+ function checkForSpaces(node) {
+ var tokens = context.getFirstTokens(node, 2),
+ firstToken = tokens[0],
+ secondToken = tokens[1];
+
+ if (isWordExpression(firstToken.value)) {
+ checkUnaryWordOperatorForSpaces(node, firstToken, secondToken);
+ return void 0;
+ }
+
+ if (options.nonwords) {
+ if (node.prefix) {
+ if (isParentUnaryBangExpression(node)) {
+ return void 0;
+ }
+ if (firstToken.range[1] === secondToken.range[0]) {
+ context.report(node, "Unary operator \"" + firstToken.value + "\" must be followed by whitespace.");
+ }
+ } else {
+ if (firstToken.range[1] === secondToken.range[0]) {
+ context.report(node, "Space is required before unary expressions \"" + secondToken.value + "\".");
+ }
+ }
+ } else {
+ if (node.prefix) {
+ if (secondToken.range[0] > firstToken.range[1]) {
+ context.report(node, "Unexpected space after unary operator \"" + firstToken.value + "\".");
+ }
+ } else {
+ if (secondToken.range[0] > firstToken.range[1]) {
+ context.report(node, "Unexpected space before unary operator \"" + secondToken.value + "\".");
+ }
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "UnaryExpression": checkForSpaces,
+ "UpdateExpression": checkForSpaces,
+ "NewExpression": checkForSpaces
+ };
+
+};
diff --git a/tools/eslint/lib/rules/spaced-line-comment.js b/tools/eslint/lib/rules/spaced-line-comment.js
new file mode 100644
index 0000000000..af5ba8cdb6
--- /dev/null
+++ b/tools/eslint/lib/rules/spaced-line-comment.js
@@ -0,0 +1,71 @@
+/**
+ * @fileoverview Enforces or disallows a space beginning a single-line comment.
+ * @author Greg Cochard
+ * @copyright 2014 Greg Cochard. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ // Unless the first option is never, require a space
+ var requireSpace = context.options[0] !== "never";
+
+ // Default to match anything, so all will fail if there are no exceptions
+ var exceptionMatcher = new RegExp(" ");
+
+ // Grab the exceptions array and build a RegExp matcher for it
+ var hasExceptions = context.options.length === 2;
+ var unescapedExceptions = hasExceptions ? context.options[1].exceptions : [];
+ var exceptions;
+
+ if (unescapedExceptions.length) {
+ exceptions = unescapedExceptions.map(function(s) {
+ return s.replace(/([.*+?${}()|\^\[\]\/\\])/g, "\\$1");
+ });
+ exceptionMatcher = new RegExp("(^(" + exceptions.join(")+$)|(^(") + ")+$)");
+ }
+
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "LineComment": function checkCommentForSpace(node) {
+
+ if (requireSpace) {
+
+ // If length is zero, ignore it
+ if (node.value.length === 0) {
+ return;
+ }
+
+ // Space expected and not found
+ if (node.value.indexOf(" ") !== 0 && node.value.indexOf("\t") !== 0) {
+
+ /*
+ * Do two tests; one for space starting the line,
+ * and one for a comment comprised only of exceptions
+ */
+ if (hasExceptions && !exceptionMatcher.test(node.value)) {
+ context.report(node, "Expected exception block, space or tab after // in comment.");
+ } else if (!hasExceptions) {
+ context.report(node, "Expected space or tab after // in comment.");
+ }
+ }
+
+ } else {
+
+ if (node.value.indexOf(" ") === 0 || node.value.indexOf("\t") === 0) {
+ context.report(node, "Unexpected space or tab after // in comment.");
+ }
+ }
+ }
+
+ };
+};
diff --git a/tools/eslint/lib/rules/strict.js b/tools/eslint/lib/rules/strict.js
new file mode 100644
index 0000000000..16d8612891
--- /dev/null
+++ b/tools/eslint/lib/rules/strict.js
@@ -0,0 +1,236 @@
+/**
+ * @fileoverview Rule to control usage of strict mode directives.
+ * @author Brandon Mills
+ * @copyright 2015 Brandon Mills. All rights reserved.
+ * @copyright 2013-2014 Nicholas C. Zakas. All rights reserved.
+ * @copyright 2013 Ian Christian Myers. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+var messages = {
+ function: "Use the function form of \"use strict\".",
+ global: "Use the global form of \"use strict\".",
+ multiple: "Multiple \"use strict\" directives.",
+ never: "Strict mode is not permitted.",
+ unnecessary: "Unnecessary \"use strict\" directive."
+};
+
+/**
+ * Gets all of the Use Strict Directives in the Directive Prologue of a group of
+ * statements.
+ * @param {ASTNode[]} statements Statements in the program or function body.
+ * @returns {ASTNode[]} All of the Use Strict Directives.
+ */
+function getUseStrictDirectives(statements) {
+ var directives = [],
+ i, statement;
+
+ for (i = 0; i < statements.length; i++) {
+ statement = statements[i];
+
+ if (
+ statement.type === "ExpressionStatement" &&
+ statement.expression.type === "Literal" &&
+ statement.expression.value === "use strict"
+ ) {
+ directives[i] = statement;
+ } else {
+ break;
+ }
+ }
+
+ return directives;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var mode = context.options[0],
+ isModule = context.ecmaFeatures.modules,
+ modes = {},
+ scopes = [];
+
+ /**
+ * Report a node or array of nodes with a given message.
+ * @param {(ASTNode|ASTNode[])} nodes Node or nodes to report.
+ * @param {string} message Message to display.
+ * @returns {void}
+ */
+ function report(nodes, message) {
+ var i;
+
+ if (Array.isArray(nodes)) {
+ for (i = 0; i < nodes.length; i++) {
+ context.report(nodes[i], message);
+ }
+ } else {
+ context.report(nodes, message);
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // "deprecated" mode (default)
+ //--------------------------------------------------------------------------
+
+ /**
+ * Determines if a given node is "use strict".
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} True if the node is a strict pragma, false if not.
+ * @void
+ */
+ function isStrictPragma(node) {
+ return (node && node.type === "ExpressionStatement" &&
+ node.expression.value === "use strict");
+ }
+
+ /**
+ * When you enter a scope, push the strict value from the previous scope
+ * onto the stack.
+ * @param {ASTNode} node The AST node being checked.
+ * @returns {void}
+ * @private
+ */
+ function enterScope(node) {
+
+ var isStrict = false,
+ isProgram = (node.type === "Program"),
+ isParentGlobal = scopes.length === 1,
+ isParentStrict = scopes.length ? scopes[scopes.length - 1] : false;
+
+ // look for the "use strict" pragma
+ if (isModule) {
+ isStrict = true;
+ } else if (isProgram) {
+ isStrict = isStrictPragma(node.body[0]) || isParentStrict;
+ } else {
+ isStrict = node.body.body && isStrictPragma(node.body.body[0]) || isParentStrict;
+ }
+
+ scopes.push(isStrict);
+
+ // never warn if the parent is strict or the function is strict
+ if (!isParentStrict && !isStrict && isParentGlobal) {
+ context.report(node, "Missing \"use strict\" statement.");
+ }
+ }
+
+ /**
+ * When you exit a scope, pop off the top scope and see if it's true or
+ * false.
+ * @returns {void}
+ * @private
+ */
+ function exitScope() {
+ scopes.pop();
+ }
+
+ modes.deprecated = {
+ "Program": enterScope,
+ "FunctionDeclaration": enterScope,
+ "FunctionExpression": enterScope,
+ "ArrowFunctionExpression": enterScope,
+
+ "Program:exit": exitScope,
+ "FunctionDeclaration:exit": exitScope,
+ "FunctionExpression:exit": exitScope,
+ "ArrowFunctionExpression:exit": exitScope
+ };
+
+ //--------------------------------------------------------------------------
+ // "never" mode
+ //--------------------------------------------------------------------------
+
+ modes.never = {
+ "Program": function(node) {
+ report(getUseStrictDirectives(node.body), messages.never);
+ },
+ "FunctionDeclaration": function(node) {
+ report(getUseStrictDirectives(node.body.body), messages.never);
+ },
+ "FunctionExpression": function(node) {
+ report(getUseStrictDirectives(node.body.body), messages.never);
+ }
+ };
+
+ //--------------------------------------------------------------------------
+ // "global" mode
+ //--------------------------------------------------------------------------
+
+ modes.global = {
+ "Program": function(node) {
+ var useStrictDirectives = getUseStrictDirectives(node.body);
+
+ if (!isModule && node.body.length && useStrictDirectives.length < 1) {
+ report(node, messages.global);
+ } else if (isModule) {
+ report(useStrictDirectives, messages.unnecessary);
+ } else {
+ report(useStrictDirectives.slice(1), messages.multiple);
+ }
+ },
+ "FunctionDeclaration": function(node) {
+ report(getUseStrictDirectives(node.body.body), messages.global);
+ },
+ "FunctionExpression": function(node) {
+ report(getUseStrictDirectives(node.body.body), messages.global);
+ }
+ };
+
+ //--------------------------------------------------------------------------
+ // "function" mode
+ //--------------------------------------------------------------------------
+
+ /**
+ * Entering a function pushes a new nested scope onto the stack. The new
+ * scope is true if the nested function is strict mode code.
+ * @param {ASTNode} node The function declaration or expression.
+ * @returns {void}
+ */
+ function enterFunction(node) {
+ var useStrictDirectives = getUseStrictDirectives(node.body.body),
+ isParentGlobal = scopes.length === 0,
+ isParentStrict = isModule || (scopes.length && scopes[scopes.length - 1]),
+ isStrict = useStrictDirectives.length > 0 || isModule;
+
+ if (isStrict) {
+ if (isParentStrict && useStrictDirectives.length) {
+ report(useStrictDirectives[0], messages.unnecessary);
+ }
+
+ report(useStrictDirectives.slice(1), messages.multiple);
+ } else if (isParentGlobal && !isModule) {
+ report(node, messages.function);
+ }
+
+ scopes.push(isParentStrict || isStrict);
+ }
+
+ /**
+ * Exiting a function pops its scope off the stack.
+ * @returns {void}
+ */
+ function exitFunction() {
+ scopes.pop();
+ }
+
+ modes.function = {
+ "Program": function(node) {
+ report(getUseStrictDirectives(node.body), messages.function);
+ },
+ "FunctionDeclaration": enterFunction,
+ "FunctionExpression": enterFunction,
+ "FunctionDeclaration:exit": exitFunction,
+ "FunctionExpression:exit": exitFunction
+ };
+
+ return modes[mode || "deprecated"];
+
+};
diff --git a/tools/eslint/lib/rules/use-isnan.js b/tools/eslint/lib/rules/use-isnan.js
new file mode 100644
index 0000000000..d7a3f53768
--- /dev/null
+++ b/tools/eslint/lib/rules/use-isnan.js
@@ -0,0 +1,24 @@
+/**
+ * @fileoverview Rule to flag comparisons to the value NaN
+ * @author James Allardice
+ * @copyright 2014 Jordan Harband. All rights reserved.
+ * @copyright 2013 James Allardice. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function (context) {
+
+ return {
+ "BinaryExpression": function (node) {
+ if (/^(?:[<>]|[!=]=)=?$/.test(node.operator) && (node.left.name === "NaN" || node.right.name === "NaN")) {
+ context.report(node, "Use the isNaN function to compare with NaN.");
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/valid-jsdoc.js b/tools/eslint/lib/rules/valid-jsdoc.js
new file mode 100644
index 0000000000..a79bf7fea4
--- /dev/null
+++ b/tools/eslint/lib/rules/valid-jsdoc.js
@@ -0,0 +1,191 @@
+/**
+ * @fileoverview Validates JSDoc comments are syntactically correct
+ * @author Nicholas C. Zakas
+ * @copyright 2014 Nicholas C. Zakas. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+var doctrine = require("doctrine");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var options = context.options[0] || {},
+ prefer = options.prefer || {},
+
+ // these both default to true, so you have to explicitly make them false
+ requireReturn = options.requireReturn === false ? false : true,
+ requireParamDescription = options.requireParamDescription !== false,
+ requireReturnDescription = options.requireReturnDescription !== false;
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ // Using a stack to store if a function returns or not (handling nested functions)
+ var fns = [];
+
+ /**
+ * When parsing a new function, store it in our function stack.
+ * @returns {void}
+ * @private
+ */
+ function startFunction() {
+ fns.push({returnPresent: false});
+ }
+
+ /**
+ * Indicate that return has been found in the current function.
+ * @param {ASTNode} node The return node.
+ * @returns {void}
+ * @private
+ */
+ function addReturn(node) {
+ var functionState = fns[fns.length - 1];
+
+ if (functionState && node.argument !== null) {
+ functionState.returnPresent = true;
+ }
+ }
+
+ /**
+ * Validate the JSDoc node and output warnings if anything is wrong.
+ * @param {ASTNode} node The AST node to check.
+ * @returns {void}
+ * @private
+ */
+ function checkJSDoc(node) {
+ var jsdocNode = context.getJSDocComment(node),
+ functionData = fns.pop(),
+ hasReturns = false,
+ hasConstructor = false,
+ params = Object.create(null),
+ jsdoc;
+
+ // make sure only to validate JSDoc comments
+ if (jsdocNode) {
+
+ try {
+ jsdoc = doctrine.parse(jsdocNode.value, {
+ strict: true,
+ unwrap: true,
+ sloppy: true
+ });
+ } catch (ex) {
+
+ if (/braces/i.test(ex.message)) {
+ context.report(jsdocNode, "JSDoc type missing brace.");
+ } else {
+ context.report(jsdocNode, "JSDoc syntax error.");
+ }
+
+ return;
+ }
+
+ jsdoc.tags.forEach(function(tag) {
+
+ switch (tag.title) {
+
+ case "param":
+ if (!tag.type) {
+ context.report(jsdocNode, "Missing JSDoc parameter type for '{{name}}'.", { name: tag.name });
+ }
+
+ if (!tag.description && requireParamDescription) {
+ context.report(jsdocNode, "Missing JSDoc parameter description for '{{name}}'.", { name: tag.name });
+ }
+
+ if (params[tag.name]) {
+ context.report(jsdocNode, "Duplicate JSDoc parameter '{{name}}'.", { name: tag.name });
+ } else if (tag.name.indexOf(".") === -1) {
+ params[tag.name] = 1;
+ }
+ break;
+
+ case "return":
+ case "returns":
+ hasReturns = true;
+
+ if (!requireReturn && !functionData.returnPresent && tag.type.name !== "void" && tag.type.name !== "undefined") {
+ context.report(jsdocNode, "Unexpected @" + tag.title + " tag; function has no return statement.");
+ } else {
+ if (!tag.type) {
+ context.report(jsdocNode, "Missing JSDoc return type.");
+ }
+
+ if (tag.type.name !== "void" && !tag.description && requireReturnDescription) {
+ context.report(jsdocNode, "Missing JSDoc return description.");
+ }
+ }
+
+ break;
+
+ case "constructor":
+ case "class":
+ hasConstructor = true;
+ break;
+
+ // no default
+ }
+
+ // check tag preferences
+ if (prefer.hasOwnProperty(tag.title)) {
+ context.report(jsdocNode, "Use @{{name}} instead.", { name: prefer[tag.title] });
+ }
+
+ });
+
+ // check for functions missing @returns
+ if (!hasReturns && !hasConstructor) {
+ if (requireReturn || functionData.returnPresent) {
+ context.report(jsdocNode, "Missing JSDoc @returns for function.");
+ }
+ }
+
+ // check the parameters
+ var jsdocParams = Object.keys(params);
+
+ node.params.forEach(function(param, i) {
+ var name = param.name;
+
+ // TODO(nzakas): Figure out logical things to do with destructured, default, rest params
+ if (param.type === "Identifier") {
+ if (jsdocParams[i] && (name !== jsdocParams[i])) {
+ context.report(jsdocNode, "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.", {
+ name: name,
+ jsdocName: jsdocParams[i]
+ });
+ } else if (!params[name]) {
+ context.report(jsdocNode, "Missing JSDoc for parameter '{{name}}'.", {
+ name: name
+ });
+ }
+ }
+ });
+
+ }
+
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "ArrowFunctionExpression": startFunction,
+ "FunctionExpression": startFunction,
+ "FunctionDeclaration": startFunction,
+ "ArrowFunctionExpression:exit": checkJSDoc,
+ "FunctionExpression:exit": checkJSDoc,
+ "FunctionDeclaration:exit": checkJSDoc,
+ "ReturnStatement": addReturn
+ };
+
+};
diff --git a/tools/eslint/lib/rules/valid-typeof.js b/tools/eslint/lib/rules/valid-typeof.js
new file mode 100644
index 0000000000..a108ab360f
--- /dev/null
+++ b/tools/eslint/lib/rules/valid-typeof.js
@@ -0,0 +1,40 @@
+/**
+ * @fileoverview Ensures that the results of typeof are compared against a valid string
+ * @author Ian Christian Myers
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var VALID_TYPES = ["symbol", "undefined", "object", "boolean", "number", "string", "function"],
+ OPERATORS = ["==", "===", "!=", "!=="];
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+
+ "UnaryExpression": function (node) {
+ var parent, sibling;
+
+ if (node.operator === "typeof") {
+ parent = context.getAncestors().pop();
+
+ if (parent.type === "BinaryExpression" && OPERATORS.indexOf(parent.operator) !== -1) {
+ sibling = parent.left === node ? parent.right : parent.left;
+
+ if (sibling.type === "Literal" && VALID_TYPES.indexOf(sibling.value) === -1) {
+ context.report(sibling, "Invalid typeof comparison value");
+ }
+ }
+ }
+ }
+
+ };
+
+};
diff --git a/tools/eslint/lib/rules/vars-on-top.js b/tools/eslint/lib/rules/vars-on-top.js
new file mode 100644
index 0000000000..4af70a8803
--- /dev/null
+++ b/tools/eslint/lib/rules/vars-on-top.js
@@ -0,0 +1,113 @@
+/**
+ * @fileoverview Rule to enforce var declarations are only at the top of a function.
+ * @author Danny Fritz
+ * @author Gyandeep Singh
+ * @copyright 2014 Danny Fritz. All rights reserved.
+ * @copyright 2014 Gyandeep Singh. All rights reserved.
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function (context) {
+ var errorMessage = "All \"var\" declarations must be at the top of the function scope.";
+
+ //--------------------------------------------------------------------------
+ // Helpers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @param {ASTNode} node - any node
+ * @returns {Boolean} whether the given node structurally represents a directive
+ */
+ function looksLikeDirective(node) {
+ return node.type === "ExpressionStatement" &&
+ node.expression.type === "Literal" && typeof node.expression.value === "string";
+ }
+
+ /**
+ * Check to see if its a ES6 import declaration
+ * @param {ASTNode} node - any node
+ * @returns {Boolean} whether the given node represents a import declaration
+ */
+ function looksLikeImport(node) {
+ return node.type === "ImportDeclaration" || node.type === "ImportSpecifier" ||
+ node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier";
+ }
+
+ /**
+ * Checks whether this variable is on top of the block body
+ * @param {ASTNode} node - The node to check
+ * @param {ASTNode[]} statements - collection of ASTNodes for the parent node block
+ * @returns {Boolean} True if var is on top otherwise false
+ */
+ function isVarOnTop(node, statements) {
+ var i = 0, l = statements.length;
+
+ // skip over directives
+ for (; i < l; ++i) {
+ if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) {
+ break;
+ }
+ }
+
+ for (; i < l; ++i) {
+ if (statements[i].type !== "VariableDeclaration") {
+ return false;
+ }
+ if (statements[i] === node) {
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Checks whether variable is on top at the global level
+ * @param {ASTNode} node - The node to check
+ * @param {ASTNode} parent - Parent of the node
+ * @returns {void}
+ */
+ function globalVarCheck(node, parent) {
+ if (!isVarOnTop(node, parent.body)) {
+ context.report(node, errorMessage);
+ }
+ }
+
+ /**
+ * Checks whether variable is on top at functional block scope level
+ * @param {ASTNode} node - The node to check
+ * @param {ASTNode} parent - Parent of the node
+ * @param {ASTNode} grandParent - Parent of the node's parent
+ * @returns {void}
+ */
+ function blockScopeVarCheck(node, parent, grandParent) {
+ if (!(/Function/.test(grandParent.type) &&
+ parent.type === "BlockStatement" &&
+ isVarOnTop(node, parent.body))) {
+ context.report(node, errorMessage);
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Public API
+ //--------------------------------------------------------------------------
+
+ return {
+ "VariableDeclaration": function (node) {
+ var ancestors = context.getAncestors();
+ var parent = ancestors.pop();
+ var grandParent = ancestors.pop();
+
+ if (node.kind === "var") {// check variable is `var` type and not `let` or `const`
+ if (parent.type === "Program") {// That means its a global variable
+ globalVarCheck(node, parent);
+ } else {
+ blockScopeVarCheck(node, parent, grandParent);
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/wrap-iife.js b/tools/eslint/lib/rules/wrap-iife.js
new file mode 100644
index 0000000000..eb3c5d1dc3
--- /dev/null
+++ b/tools/eslint/lib/rules/wrap-iife.js
@@ -0,0 +1,42 @@
+/**
+ * @fileoverview Rule to flag when IIFE is not wrapped in parens
+ * @author Ilya Volodin
+ * @copyright 2013 Ilya Volodin. All rights reserved.
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ var style = context.options[0] || "outside";
+
+ function wrapped(node) {
+ var previousToken = context.getTokenBefore(node),
+ nextToken = context.getTokenAfter(node);
+ return previousToken && previousToken.value === "(" &&
+ nextToken && nextToken.value === ")";
+ }
+
+ return {
+
+ "CallExpression": function(node) {
+ if (node.callee.type === "FunctionExpression") {
+ var callExpressionWrapped = wrapped(node),
+ functionExpressionWrapped = wrapped(node.callee);
+
+ if (!callExpressionWrapped && !functionExpressionWrapped) {
+ context.report(node, "Wrap an immediate function invocation in parentheses.");
+ } else if (style === "inside" && !functionExpressionWrapped) {
+ context.report(node, "Wrap only the function expression in parens.");
+ } else if (style === "outside" && !callExpressionWrapped) {
+ context.report(node, "Move the invocation into the parens that contain the function.");
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/wrap-regex.js b/tools/eslint/lib/rules/wrap-regex.js
new file mode 100644
index 0000000000..e2cc7d2253
--- /dev/null
+++ b/tools/eslint/lib/rules/wrap-regex.js
@@ -0,0 +1,36 @@
+/**
+ * @fileoverview Rule to flag when regex literals are not wrapped in parens
+ * @author Matt DuVall <http://www.mattduvall.com>
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function(context) {
+
+ return {
+
+ "Literal": function(node) {
+ var token = context.getFirstToken(node),
+ nodeType = token.type,
+ source,
+ grandparent,
+ ancestors;
+
+ if (nodeType === "RegularExpression") {
+ source = context.getTokenBefore(node);
+ ancestors = context.getAncestors();
+ grandparent = ancestors[ancestors.length - 1];
+
+ if (grandparent.type === "MemberExpression" && grandparent.object === node &&
+ (!source || source.value !== "(")) {
+ context.report(node, "Wrap the regexp literal in parens to disambiguate the slash.");
+ }
+ }
+ }
+ };
+
+};
diff --git a/tools/eslint/lib/rules/yoda.js b/tools/eslint/lib/rules/yoda.js
new file mode 100644
index 0000000000..cdb5bdad08
--- /dev/null
+++ b/tools/eslint/lib/rules/yoda.js
@@ -0,0 +1,210 @@
+/**
+ * @fileoverview Rule to require or disallow yoda comparisons
+ * @author Nicholas C. Zakas
+ * @copyright 2014 Nicholas C. Zakas. All rights reserved.
+ * @copyright 2014 Brandon Mills. All rights reserved.
+ */
+"use strict";
+
+//--------------------------------------------------------------------------
+// Helpers
+//--------------------------------------------------------------------------
+
+/**
+ * Determines whether an operator is a comparison operator.
+ * @param {String} operator The operator to check.
+ * @returns {boolean} Whether or not it is a comparison operator.
+ */
+function isComparisonOperator(operator) {
+ return (/^(==|===|!=|!==|<|>|<=|>=)$/).test(operator);
+}
+
+/**
+ * Determines whether an operator is one used in a range test.
+ * Allowed operators are `<` and `<=`.
+ * @param {String} operator The operator to check.
+ * @returns {boolean} Whether the operator is used in range tests.
+ */
+function isRangeTestOperator(operator) {
+ return ["<", "<="].indexOf(operator) >= 0;
+}
+
+/**
+ * Determines whether a non-Literal node is a negative number that should be
+ * treated as if it were a single Literal node.
+ * @param {ASTNode} node Node to test.
+ * @returns {boolean} True if the node is a negative number that looks like a
+ * real literal and should be treated as such.
+ */
+function looksLikeLiteral(node) {
+ return (node.type === "UnaryExpression" &&
+ node.operator === "-" &&
+ node.prefix &&
+ node.argument.type === "Literal" &&
+ typeof node.argument.value === "number");
+}
+
+/**
+ * Attempts to derive a Literal node from nodes that are treated like literals.
+ * @param {ASTNode} node Node to normalize.
+ * @returns {ASTNode} The original node if the node is already a Literal, or a
+ * normalized Literal node with the negative number as the
+ * value if the node represents a negative number literal,
+ * otherwise null if the node cannot be converted to a
+ * normalized literal.
+ */
+function getNormalizedLiteral(node) {
+ if (node.type === "Literal") {
+ return node;
+ }
+
+ if (looksLikeLiteral(node)) {
+ return {
+ type: "Literal",
+ value: -node.argument.value,
+ raw: "-" + node.argument.value
+ };
+ }
+
+ return null;
+}
+
+/**
+ * Checks whether two expressions reference the same value. For example:
+ * a = a
+ * a.b = a.b
+ * a[0] = a[0]
+ * a['b'] = a['b']
+ * @param {ASTNode} a Left side of the comparison.
+ * @param {ASTNode} b Right side of the comparison.
+ * @returns {boolean} True if both sides match and reference the same value.
+ */
+function same(a, b) {
+ if (a.type !== b.type) {
+ return false;
+ }
+
+ switch (a.type) {
+ case "Identifier":
+ return a.name === b.name;
+ case "Literal":
+ return a.value === b.value;
+ case "MemberExpression":
+ // x[0] = x[0]
+ // x[y] = x[y]
+ // x.y = x.y
+ return same(a.object, b.object) && same(a.property, b.property);
+ case "ThisExpression":
+ return true;
+ default:
+ return false;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = function (context) {
+
+ // Default to "never" (!always) if no option
+ var always = (context.options[0] === "always");
+ var exceptRange = (context.options[1] && context.options[1].exceptRange);
+
+ /**
+ * Determines whether node represents a range test.
+ * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside"
+ * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and
+ * both operators must be `<` or `<=`. Finally, the literal on the left side
+ * must be less than or equal to the literal on the right side so that the
+ * test makes any sense.
+ * @param {ASTNode} node LogicalExpression node to test.
+ * @returns {Boolean} Whether node is a range test.
+ */
+ function isRangeTest(node) {
+ var left = node.left,
+ right = node.right;
+
+ /**
+ * Determines whether node is of the form `0 <= x && x < 1`.
+ * @returns {Boolean} Whether node is a "between" range test.
+ */
+ function isBetweenTest() {
+ var leftLiteral, rightLiteral;
+
+ return (node.operator === "&&" &&
+ (leftLiteral = getNormalizedLiteral(left.left)) &&
+ (rightLiteral = getNormalizedLiteral(right.right)) &&
+ leftLiteral.value <= rightLiteral.value &&
+ same(left.right, right.left));
+ }
+
+ /**
+ * Determines whether node is of the form `x < 0 || 1 <= x`.
+ * @returns {Boolean} Whether node is an "outside" range test.
+ */
+ function isOutsideTest() {
+ var leftLiteral, rightLiteral;
+
+ return (node.operator === "||" &&
+ (leftLiteral = getNormalizedLiteral(left.right)) &&
+ (rightLiteral = getNormalizedLiteral(right.left)) &&
+ leftLiteral.value <= rightLiteral.value &&
+ same(left.left, right.right));
+ }
+
+ /**
+ * Determines whether node is wrapped in parentheses.
+ * @returns {Boolean} Whether node is preceded immediately by an open
+ * paren token and followed immediately by a close
+ * paren token.
+ */
+ function isParenWrapped() {
+ var tokenBefore, tokenAfter;
+
+ return ((tokenBefore = context.getTokenBefore(node)) &&
+ tokenBefore.value === "(" &&
+ (tokenAfter = context.getTokenAfter(node)) &&
+ tokenAfter.value === ")");
+ }
+
+ return (node.type === "LogicalExpression" &&
+ left.type === "BinaryExpression" &&
+ right.type === "BinaryExpression" &&
+ isRangeTestOperator(left.operator) &&
+ isRangeTestOperator(right.operator) &&
+ (isBetweenTest() || isOutsideTest()) &&
+ isParenWrapped());
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ return {
+ "BinaryExpression": always ? function(node) {
+
+ // Comparisons must always be yoda-style: if ("blue" === color)
+ if (
+ (node.right.type === "Literal" || looksLikeLiteral(node.right)) &&
+ isComparisonOperator(node.operator) &&
+ !(exceptRange && isRangeTest(context.getAncestors().pop()))
+ ) {
+ context.report(node, "Expected literal to be on the left side of " + node.operator + ".");
+ }
+
+ } : function(node) {
+
+ // Comparisons must never be yoda-style (default)
+ if (
+ (node.left.type === "Literal" || looksLikeLiteral(node.left)) &&
+ isComparisonOperator(node.operator) &&
+ !(exceptRange && isRangeTest(context.getAncestors().pop()))
+ ) {
+ context.report(node, "Expected literal to be on the right side of " + node.operator + ".");
+ }
+
+ }
+ };
+
+};