From 220713ed00d59df57f7145c1b369ab64bb613e70 Mon Sep 17 00:00:00 2001 From: Vitor Balocco Date: Tue, 19 Apr 2016 18:03:17 +0200 Subject: [PATCH] Chore: Add metadata to existing rules - Batch 4 (refs #5417) Chore: Add metadata to existing rules - Batch 4 (refs #5417) --- lib/rules/no-extend-native.js | 146 ++-- lib/rules/no-extra-bind.js | 250 +++---- lib/rules/no-extra-boolean-cast.js | 128 ++-- lib/rules/no-extra-label.js | 200 +++--- lib/rules/no-extra-parens.js | 982 +++++++++++++------------- lib/rules/no-extra-semi.js | 131 ++-- lib/rules/no-fallthrough.js | 136 ++-- lib/rules/no-floating-decimal.js | 36 +- lib/rules/no-func-assign.js | 86 ++- lib/rules/no-implicit-coercion.js | 190 ++--- lib/rules/no-implicit-globals.js | 62 +- lib/rules/no-implied-eval.js | 260 +++---- lib/rules/no-inline-comments.js | 68 +- lib/rules/no-inner-declarations.js | 120 ++-- lib/rules/no-invalid-regexp.js | 124 ++-- lib/rules/no-invalid-this.js | 178 ++--- lib/rules/no-irregular-whitespace.js | 318 +++++---- lib/rules/no-iterator.js | 32 +- lib/rules/no-label-var.js | 72 +- lib/rules/no-labels.js | 214 +++--- lib/rules/no-lone-blocks.js | 152 ++-- lib/rules/no-lonely-if.js | 40 +- lib/rules/no-loop-func.js | 72 +- lib/rules/no-magic-numbers.js | 226 +++--- lib/rules/no-mixed-requires.js | 338 ++++----- lib/rules/no-mixed-spaces-and-tabs.js | 212 +++--- lib/rules/no-multi-spaces.js | 228 +++--- lib/rules/no-multi-str.js | 56 +- lib/rules/no-multiple-empty-lines.js | 254 +++---- lib/rules/no-native-reassign.js | 114 +-- lib/rules/no-negated-condition.js | 118 ++-- lib/rules/no-negated-in-lhs.js | 28 +- lib/rules/no-nested-ternary.js | 30 +- lib/rules/no-new-func.js | 54 +- lib/rules/no-new-object.js | 28 +- lib/rules/no-new-require.js | 28 +- lib/rules/no-new-symbol.js | 48 +- lib/rules/no-new-wrappers.js | 30 +- lib/rules/no-new.js | 28 +- lib/rules/no-obj-calls.js | 32 +- 40 files changed, 3126 insertions(+), 2723 deletions(-) diff --git a/lib/rules/no-extend-native.js b/lib/rules/no-extend-native.js index cf2cf33f3a0..6ed08e90108 100644 --- a/lib/rules/no-extend-native.js +++ b/lib/rules/no-extend-native.js @@ -15,87 +15,97 @@ var globals = require("globals"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var config = context.options[0] || {}; - var exceptions = config.exceptions || []; - var modifiedBuiltins = Object.keys(globals.builtin).filter(function(builtin) { - return builtin[0].toUpperCase() === builtin[0]; - }); - - if (exceptions.length) { - modifiedBuiltins = modifiedBuiltins.filter(function(builtIn) { - return exceptions.indexOf(builtIn) === -1; - }); - } - - return { - - // handle the Array.prototype.extra style case - "AssignmentExpression": function(node) { - var lhs = node.left, - affectsProto; +module.exports = { + meta: { + docs: { + description: "disallow extending native types", + category: "Best Practices", + recommended: false + }, - if (lhs.type !== "MemberExpression" || lhs.object.type !== "MemberExpression") { - return; + schema: [ + { + "type": "object", + "properties": { + "exceptions": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + } + }, + "additionalProperties": false } + ] + }, - affectsProto = lhs.object.computed ? - lhs.object.property.type === "Literal" && lhs.object.property.value === "prototype" : - lhs.object.property.name === "prototype"; + create: function(context) { - if (!affectsProto) { - return; - } + var config = context.options[0] || {}; + var exceptions = config.exceptions || []; + var modifiedBuiltins = Object.keys(globals.builtin).filter(function(builtin) { + return builtin[0].toUpperCase() === builtin[0]; + }); - modifiedBuiltins.forEach(function(builtin) { - if (lhs.object.object.name === builtin) { - context.report(node, builtin + " prototype is read only, properties should not be added."); - } + if (exceptions.length) { + modifiedBuiltins = modifiedBuiltins.filter(function(builtIn) { + return exceptions.indexOf(builtIn) === -1; }); - }, + } - // handle the Object.definePropert[y|ies](Array.prototype) case - "CallExpression": function(node) { + return { - var callee = node.callee, - subject, - object; + // handle the Array.prototype.extra style case + "AssignmentExpression": function(node) { + var lhs = node.left, + affectsProto; - // only worry about Object.definePropert[y|ies] - if (callee.type === "MemberExpression" && - callee.object.name === "Object" && - (callee.property.name === "defineProperty" || callee.property.name === "defineProperties")) { + if (lhs.type !== "MemberExpression" || lhs.object.type !== "MemberExpression") { + return; + } - // verify the object being added to is a native prototype - subject = node.arguments[0]; - object = subject && subject.object; - if (object && - object.type === "Identifier" && - (modifiedBuiltins.indexOf(object.name) > -1) && - subject.property.name === "prototype") { + affectsProto = lhs.object.computed ? + lhs.object.property.type === "Literal" && lhs.object.property.value === "prototype" : + lhs.object.property.name === "prototype"; - context.report(node, object.name + " prototype is read only, properties should not be added."); + if (!affectsProto) { + return; } - } - } - }; - -}; + modifiedBuiltins.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.definePropert[y|ies](Array.prototype) case + "CallExpression": function(node) { + + var callee = node.callee, + subject, + object; + + // only worry about Object.definePropert[y|ies] + if (callee.type === "MemberExpression" && + callee.object.name === "Object" && + (callee.property.name === "defineProperty" || callee.property.name === "defineProperties")) { + + // verify the object being added to is a native prototype + subject = node.arguments[0]; + object = subject && subject.object; + if (object && + object.type === "Identifier" && + (modifiedBuiltins.indexOf(object.name) > -1) && + subject.property.name === "prototype") { + + context.report(node, object.name + " prototype is read only, properties should not be added."); + } + } -module.exports.schema = [ - { - "type": "object", - "properties": { - "exceptions": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true } - }, - "additionalProperties": false + }; + } -]; +}; diff --git a/lib/rules/no-extra-bind.js b/lib/rules/no-extra-bind.js index cd800eabd77..67eda7e73bf 100644 --- a/lib/rules/no-extra-bind.js +++ b/lib/rules/no-extra-bind.js @@ -11,139 +11,149 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var scopeInfo = null; +module.exports = { + meta: { + docs: { + description: "disallow unnecessary calls to `.bind()`", + category: "Best Practices", + recommended: false + }, - /** - * Reports a given function node. - * - * @param {ASTNode} node - A node to report. This is a FunctionExpression or - * an ArrowFunctionExpression. - * @returns {void} - */ - function report(node) { - context.report({ - node: node.parent.parent, - message: "The function binding is unnecessary.", - loc: node.parent.property.loc.start - }); - } + schema: [] + }, - /** - * Gets the property name of a given node. - * If the property name is dynamic, this returns an empty string. - * - * @param {ASTNode} node - A node to check. This is a MemberExpression. - * @returns {string} The property name of the node. - */ - function getPropertyName(node) { - if (node.computed) { - switch (node.property.type) { - case "Literal": - return String(node.property.value); - case "TemplateLiteral": - if (node.property.expressions.length === 0) { - return node.property.quasis[0].value.cooked; - } + create: function(context) { + var scopeInfo = null; - // fallthrough - default: - return false; - } + /** + * Reports a given function node. + * + * @param {ASTNode} node - A node to report. This is a FunctionExpression or + * an ArrowFunctionExpression. + * @returns {void} + */ + function report(node) { + context.report({ + node: node.parent.parent, + message: "The function binding is unnecessary.", + loc: node.parent.property.loc.start + }); } - return node.property.name; - } - /** - * Checks whether or not a given function node is the callee of `.bind()` - * method. - * - * e.g. `(function() {}.bind(foo))` - * - * @param {ASTNode} node - A node to report. This is a FunctionExpression or - * an ArrowFunctionExpression. - * @returns {boolean} `true` if the node is the callee of `.bind()` method. - */ - function isCalleeOfBindMethod(node) { - var parent = node.parent; - var grandparent = parent.parent; + /** + * Gets the property name of a given node. + * If the property name is dynamic, this returns an empty string. + * + * @param {ASTNode} node - A node to check. This is a MemberExpression. + * @returns {string} The property name of the node. + */ + function getPropertyName(node) { + if (node.computed) { + switch (node.property.type) { + case "Literal": + return String(node.property.value); + case "TemplateLiteral": + if (node.property.expressions.length === 0) { + return node.property.quasis[0].value.cooked; + } - return ( - grandparent && - grandparent.type === "CallExpression" && - grandparent.callee === parent && - grandparent.arguments.length === 1 && - parent.type === "MemberExpression" && - parent.object === node && - getPropertyName(parent) === "bind" - ); - } + // fallthrough + default: + return false; + } + } + return node.property.name; + } - /** - * Adds a scope information object to the stack. - * - * @param {ASTNode} node - A node to add. This node is a FunctionExpression - * or a FunctionDeclaration node. - * @returns {void} - */ - function enterFunction(node) { - scopeInfo = { - isBound: isCalleeOfBindMethod(node), - thisFound: false, - upper: scopeInfo - }; - } + /** + * Checks whether or not a given function node is the callee of `.bind()` + * method. + * + * e.g. `(function() {}.bind(foo))` + * + * @param {ASTNode} node - A node to report. This is a FunctionExpression or + * an ArrowFunctionExpression. + * @returns {boolean} `true` if the node is the callee of `.bind()` method. + */ + function isCalleeOfBindMethod(node) { + var parent = node.parent; + var grandparent = parent.parent; - /** - * Removes the scope information object from the top of the stack. - * At the same time, this reports the function node if the function has - * `.bind()` and the `this` keywords found. - * - * @param {ASTNode} node - A node to remove. This node is a - * FunctionExpression or a FunctionDeclaration node. - * @returns {void} - */ - function exitFunction(node) { - if (scopeInfo.isBound && !scopeInfo.thisFound) { - report(node); + return ( + grandparent && + grandparent.type === "CallExpression" && + grandparent.callee === parent && + grandparent.arguments.length === 1 && + parent.type === "MemberExpression" && + parent.object === node && + getPropertyName(parent) === "bind" + ); } - scopeInfo = scopeInfo.upper; - } + /** + * Adds a scope information object to the stack. + * + * @param {ASTNode} node - A node to add. This node is a FunctionExpression + * or a FunctionDeclaration node. + * @returns {void} + */ + function enterFunction(node) { + scopeInfo = { + isBound: isCalleeOfBindMethod(node), + thisFound: false, + upper: scopeInfo + }; + } - /** - * Reports a given arrow function if the function is callee of `.bind()` - * method. - * - * @param {ASTNode} node - A node to report. This node is an - * ArrowFunctionExpression. - * @returns {void} - */ - function exitArrowFunction(node) { - if (isCalleeOfBindMethod(node)) { - report(node); + /** + * Removes the scope information object from the top of the stack. + * At the same time, this reports the function node if the function has + * `.bind()` and the `this` keywords found. + * + * @param {ASTNode} node - A node to remove. This node is a + * FunctionExpression or a FunctionDeclaration node. + * @returns {void} + */ + function exitFunction(node) { + if (scopeInfo.isBound && !scopeInfo.thisFound) { + report(node); + } + + scopeInfo = scopeInfo.upper; } - } - /** - * Set the mark as the `this` keyword was found in this scope. - * - * @returns {void} - */ - function markAsThisFound() { - if (scopeInfo) { - scopeInfo.thisFound = true; + /** + * Reports a given arrow function if the function is callee of `.bind()` + * method. + * + * @param {ASTNode} node - A node to report. This node is an + * ArrowFunctionExpression. + * @returns {void} + */ + function exitArrowFunction(node) { + if (isCalleeOfBindMethod(node)) { + report(node); + } } - } - return { - "ArrowFunctionExpression:exit": exitArrowFunction, - "FunctionDeclaration": enterFunction, - "FunctionDeclaration:exit": exitFunction, - "FunctionExpression": enterFunction, - "FunctionExpression:exit": exitFunction, - "ThisExpression": markAsThisFound - }; -}; + /** + * Set the mark as the `this` keyword was found in this scope. + * + * @returns {void} + */ + function markAsThisFound() { + if (scopeInfo) { + scopeInfo.thisFound = true; + } + } -module.exports.schema = []; + return { + "ArrowFunctionExpression:exit": exitArrowFunction, + "FunctionDeclaration": enterFunction, + "FunctionDeclaration:exit": exitFunction, + "FunctionExpression": enterFunction, + "FunctionExpression:exit": exitFunction, + "ThisExpression": markAsThisFound + }; + } +}; diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index c7ee1adc6b8..56e7c2b2a9d 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -9,72 +9,82 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - // Node types which have a test which will coerce values to booleans. - var BOOLEAN_NODE_TYPES = [ - "IfStatement", - "DoWhileStatement", - "WhileStatement", - "ConditionalExpression", - "ForStatement" - ]; - - /** - * Check if a node is in a context where its value would be coerced to a boolean at runtime. - * - * @param {Object} node The node - * @param {Object} parent Its parent - * @returns {Boolean} If it is in a boolean context - */ - function isInBooleanContext(node, parent) { - return ( - (BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 && - node === parent.test) || - - // ! - (parent.type === "UnaryExpression" && - parent.operator === "!") - ); - } +module.exports = { + meta: { + docs: { + description: "disallow unnecessary boolean casts", + category: "Possible Errors", + recommended: true + }, + schema: [] + }, - return { - "UnaryExpression": function(node) { - var ancestors = context.getAncestors(), - parent = ancestors.pop(), - grandparent = ancestors.pop(); + create: function(context) { - // Exit early if it's guaranteed not to match - if (node.operator !== "!" || - parent.type !== "UnaryExpression" || - parent.operator !== "!") { - return; - } + // Node types which have a test which will coerce values to booleans. + var BOOLEAN_NODE_TYPES = [ + "IfStatement", + "DoWhileStatement", + "WhileStatement", + "ConditionalExpression", + "ForStatement" + ]; - if (isInBooleanContext(parent, grandparent) || + /** + * Check if a node is in a context where its value would be coerced to a boolean at runtime. + * + * @param {Object} node The node + * @param {Object} parent Its parent + * @returns {Boolean} If it is in a boolean context + */ + function isInBooleanContext(node, parent) { + return ( + (BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 && + node === parent.test) || - // Boolean() and new Boolean() - ((grandparent.type === "CallExpression" || grandparent.type === "NewExpression") && - grandparent.callee.type === "Identifier" && - grandparent.callee.name === "Boolean") - ) { - context.report(node, "Redundant double negation."); - } - }, - "CallExpression": function(node) { - var parent = node.parent; + // ! + (parent.type === "UnaryExpression" && + parent.operator === "!") + ); + } - if (node.callee.type !== "Identifier" || node.callee.name !== "Boolean") { - return; - } - if (isInBooleanContext(node, parent)) { - context.report(node, "Redundant Boolean call."); + 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 (isInBooleanContext(parent, grandparent) || + + // Boolean() and new Boolean() + ((grandparent.type === "CallExpression" || grandparent.type === "NewExpression") && + grandparent.callee.type === "Identifier" && + grandparent.callee.name === "Boolean") + ) { + context.report(node, "Redundant double negation."); + } + }, + "CallExpression": function(node) { + var parent = node.parent; + + if (node.callee.type !== "Identifier" || node.callee.name !== "Boolean") { + return; + } + + if (isInBooleanContext(node, parent)) { + context.report(node, "Redundant Boolean call."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-extra-label.js b/lib/rules/no-extra-label.js index c11a7035764..66aaab1586d 100644 --- a/lib/rules/no-extra-label.js +++ b/lib/rules/no-extra-label.js @@ -17,116 +17,126 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var scopeInfo = null; - - /** - * Creates a new scope with a breakable statement. - * - * @param {ASTNode} node - A node to create. This is a BreakableStatement. - * @returns {void} - */ - function enterBreakableStatement(node) { - scopeInfo = { - label: astUtils.getLabel(node), - breakable: true, - upper: scopeInfo - }; - } +module.exports = { + meta: { + docs: { + description: "disallow unnecessary labels", + category: "Best Practices", + recommended: false + }, - /** - * Removes the top scope of the stack. - * - * @returns {void} - */ - function exitBreakableStatement() { - scopeInfo = scopeInfo.upper; - } + schema: [] + }, - /** - * Creates a new scope with a labeled statement. - * - * This ignores it if the body is a breakable statement. - * In this case it's handled in the `enterBreakableStatement` function. - * - * @param {ASTNode} node - A node to create. This is a LabeledStatement. - * @returns {void} - */ - function enterLabeledStatement(node) { - if (!astUtils.isBreakableStatement(node.body)) { + create: function(context) { + var scopeInfo = null; + + /** + * Creates a new scope with a breakable statement. + * + * @param {ASTNode} node - A node to create. This is a BreakableStatement. + * @returns {void} + */ + function enterBreakableStatement(node) { scopeInfo = { - label: node.label.name, - breakable: false, + label: astUtils.getLabel(node), + breakable: true, upper: scopeInfo }; } - } - /** - * Removes the top scope of the stack. - * - * This ignores it if the body is a breakable statement. - * In this case it's handled in the `exitBreakableStatement` function. - * - * @param {ASTNode} node - A node. This is a LabeledStatement. - * @returns {void} - */ - function exitLabeledStatement(node) { - if (!astUtils.isBreakableStatement(node.body)) { + /** + * Removes the top scope of the stack. + * + * @returns {void} + */ + function exitBreakableStatement() { scopeInfo = scopeInfo.upper; } - } - /** - * Reports a given control node if it's unnecessary. - * - * @param {ASTNode} node - A node. This is a BreakStatement or a - * ContinueStatement. - * @returns {void} - */ - function reportIfUnnecessary(node) { - if (!node.label) { - return; + /** + * Creates a new scope with a labeled statement. + * + * This ignores it if the body is a breakable statement. + * In this case it's handled in the `enterBreakableStatement` function. + * + * @param {ASTNode} node - A node to create. This is a LabeledStatement. + * @returns {void} + */ + function enterLabeledStatement(node) { + if (!astUtils.isBreakableStatement(node.body)) { + scopeInfo = { + label: node.label.name, + breakable: false, + upper: scopeInfo + }; + } } - var labelNode = node.label; - var label = labelNode.name; - var info = scopeInfo; - - while (info) { - if (info.breakable || info.label === label) { - if (info.breakable && info.label === label) { - context.report({ - node: labelNode, - message: "This label '{{name}}' is unnecessary.", - data: labelNode - }); - } + /** + * Removes the top scope of the stack. + * + * This ignores it if the body is a breakable statement. + * In this case it's handled in the `exitBreakableStatement` function. + * + * @param {ASTNode} node - A node. This is a LabeledStatement. + * @returns {void} + */ + function exitLabeledStatement(node) { + if (!astUtils.isBreakableStatement(node.body)) { + scopeInfo = scopeInfo.upper; + } + } + + /** + * Reports a given control node if it's unnecessary. + * + * @param {ASTNode} node - A node. This is a BreakStatement or a + * ContinueStatement. + * @returns {void} + */ + function reportIfUnnecessary(node) { + if (!node.label) { return; } - info = info.upper; + var labelNode = node.label; + var label = labelNode.name; + var info = scopeInfo; + + while (info) { + if (info.breakable || info.label === label) { + if (info.breakable && info.label === label) { + context.report({ + node: labelNode, + message: "This label '{{name}}' is unnecessary.", + data: labelNode + }); + } + return; + } + + info = info.upper; + } } - } - return { - "WhileStatement": enterBreakableStatement, - "WhileStatement:exit": exitBreakableStatement, - "DoWhileStatement": enterBreakableStatement, - "DoWhileStatement:exit": exitBreakableStatement, - "ForStatement": enterBreakableStatement, - "ForStatement:exit": exitBreakableStatement, - "ForInStatement": enterBreakableStatement, - "ForInStatement:exit": exitBreakableStatement, - "ForOfStatement": enterBreakableStatement, - "ForOfStatement:exit": exitBreakableStatement, - "SwitchStatement": enterBreakableStatement, - "SwitchStatement:exit": exitBreakableStatement, - "LabeledStatement": enterLabeledStatement, - "LabeledStatement:exit": exitLabeledStatement, - "BreakStatement": reportIfUnnecessary, - "ContinueStatement": reportIfUnnecessary - }; + return { + "WhileStatement": enterBreakableStatement, + "WhileStatement:exit": exitBreakableStatement, + "DoWhileStatement": enterBreakableStatement, + "DoWhileStatement:exit": exitBreakableStatement, + "ForStatement": enterBreakableStatement, + "ForStatement:exit": exitBreakableStatement, + "ForInStatement": enterBreakableStatement, + "ForInStatement:exit": exitBreakableStatement, + "ForOfStatement": enterBreakableStatement, + "ForOfStatement:exit": exitBreakableStatement, + "SwitchStatement": enterBreakableStatement, + "SwitchStatement:exit": exitBreakableStatement, + "LabeledStatement": enterLabeledStatement, + "LabeledStatement:exit": exitLabeledStatement, + "BreakStatement": reportIfUnnecessary, + "ContinueStatement": reportIfUnnecessary + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js index 17d8589e9bc..34e0519a054 100644 --- a/lib/rules/no-extra-parens.js +++ b/lib/rules/no-extra-parens.js @@ -12,573 +12,583 @@ var astUtils = require("../ast-utils.js"); -module.exports = function(context) { - var isParenthesised = astUtils.isParenthesised.bind(astUtils, context); - var ALL_NODES = context.options[0] !== "functions"; - var EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false; - var NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false; - var sourceCode = context.getSourceCode(); - - /** - * Determines if this rule should be enforced for a node given the current configuration. - * @param {ASTNode} node - The node to be checked. - * @returns {boolean} True if the rule should be enforced for this node. - * @private - */ - function ruleApplies(node) { - return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression"; - } +module.exports = { + meta: { + docs: { + description: "disallow unnecessary parentheses", + category: "Possible Errors", + recommended: false + }, - /** - * Determines if a node is surrounded by parentheses twice. - * @param {ASTNode} node - The node to be checked. - * @returns {boolean} True if the node is doubly parenthesised. - * @private - */ - 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]; - } + schema: { + "anyOf": [ + { + "type": "array", + "items": [ + { + "enum": ["functions"] + } + ], + "minItems": 0, + "maxItems": 1 + }, + { + "type": "array", + "items": [ + { + "enum": ["all"] + }, + { + "type": "object", + "properties": { + "conditionalAssign": {"type": "boolean"}, + "nestedBinaryExpressions": {"type": "boolean"} + }, + "additionalProperties": false + } + ], + "minItems": 0, + "maxItems": 2 + } + ] + } + }, + + create: function(context) { + var isParenthesised = astUtils.isParenthesised.bind(astUtils, context); + var ALL_NODES = context.options[0] !== "functions"; + var EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false; + var NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false; + var sourceCode = context.getSourceCode(); + + /** + * Determines if this rule should be enforced for a node given the current configuration. + * @param {ASTNode} node - The node to be checked. + * @returns {boolean} True if the rule should be enforced for this node. + * @private + */ + function ruleApplies(node) { + return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression"; + } - /** - * Determines if a node is surrounded by (potentially) invalid parentheses. - * @param {ASTNode} node - The node to be checked. - * @returns {boolean} True if the node is incorrectly parenthesised. - * @private - */ - function hasExcessParens(node) { - return ruleApplies(node) && isParenthesised(node); - } + /** + * Determines if a node is surrounded by parentheses twice. + * @param {ASTNode} node - The node to be checked. + * @returns {boolean} True if the node is doubly parenthesised. + * @private + */ + 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]; + } - /** - * Determines if a node that is expected to be parenthesised is surrounded by - * (potentially) invalid extra parentheses. - * @param {ASTNode} node - The node to be checked. - * @returns {boolean} True if the node is has an unexpected extra pair of parentheses. - * @private - */ - function hasDoubleExcessParens(node) { - return ruleApplies(node) && isParenthesisedTwice(node); - } + /** + * Determines if a node is surrounded by (potentially) invalid parentheses. + * @param {ASTNode} node - The node to be checked. + * @returns {boolean} True if the node is incorrectly parenthesised. + * @private + */ + function hasExcessParens(node) { + return ruleApplies(node) && isParenthesised(node); + } - /** - * Determines if a node test expression is allowed to have a parenthesised assignment - * @param {ASTNode} node - The node to be checked. - * @returns {boolean} True if the assignment can be parenthesised. - * @private - */ - function isCondAssignException(node) { - return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression"; - } + /** + * Determines if a node that is expected to be parenthesised is surrounded by + * (potentially) invalid extra parentheses. + * @param {ASTNode} node - The node to be checked. + * @returns {boolean} True if the node is has an unexpected extra pair of parentheses. + * @private + */ + function hasDoubleExcessParens(node) { + return ruleApplies(node) && isParenthesisedTwice(node); + } - /** - * Determines if a node following a [no LineTerminator here] restriction is - * surrounded by (potentially) invalid extra parentheses. - * @param {Token} token - The token preceding the [no LineTerminator here] restriction. - * @param {ASTNode} node - The node to be checked. - * @returns {boolean} True if the node is incorrectly parenthesised. - * @private - */ - function hasExcessParensNoLineTerminator(token, node) { - if (token.loc.end.line === node.loc.start.line) { - return hasExcessParens(node); + /** + * Determines if a node test expression is allowed to have a parenthesised assignment + * @param {ASTNode} node - The node to be checked. + * @returns {boolean} True if the assignment can be parenthesised. + * @private + */ + function isCondAssignException(node) { + return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression"; } - return hasDoubleExcessParens(node); - } + /** + * Determines if a node following a [no LineTerminator here] restriction is + * surrounded by (potentially) invalid extra parentheses. + * @param {Token} token - The token preceding the [no LineTerminator here] restriction. + * @param {ASTNode} node - The node to be checked. + * @returns {boolean} True if the node is incorrectly parenthesised. + * @private + */ + function hasExcessParensNoLineTerminator(token, node) { + if (token.loc.end.line === node.loc.start.line) { + return hasExcessParens(node); + } - /** - * Checks whether or not a given node is located at the head of ExpressionStatement. - * @param {ASTNode} node - A node to check. - * @returns {boolean} `true` if the node is located at the head of ExpressionStatement. - */ - function isHeadOfExpressionStatement(node) { - var parent = node.parent; + return hasDoubleExcessParens(node); + } - while (parent) { - switch (parent.type) { - case "SequenceExpression": - if (parent.expressions[0] !== node || isParenthesised(node)) { + /** + * Checks whether or not a given node is located at the head of ExpressionStatement. + * @param {ASTNode} node - A node to check. + * @returns {boolean} `true` if the node is located at the head of ExpressionStatement. + */ + function isHeadOfExpressionStatement(node) { + var parent = node.parent; + + while (parent) { + switch (parent.type) { + case "SequenceExpression": + if (parent.expressions[0] !== node || isParenthesised(node)) { + return false; + } + break; + + case "UnaryExpression": + case "UpdateExpression": + if (parent.prefix || isParenthesised(node)) { + return false; + } + break; + + case "BinaryExpression": + case "LogicalExpression": + if (parent.left !== node || isParenthesised(node)) { + return false; + } + break; + + case "ConditionalExpression": + if (parent.test !== node || isParenthesised(node)) { + return false; + } + break; + + case "CallExpression": + if (parent.callee !== node || isParenthesised(node)) { + return false; + } + break; + + case "MemberExpression": + if (parent.object !== node || isParenthesised(node)) { + return false; + } + break; + + case "ExpressionStatement": + return true; + + default: return false; - } - break; + } - case "UnaryExpression": - case "UpdateExpression": - if (parent.prefix || isParenthesised(node)) { - return false; - } - break; + node = parent; + parent = parent.parent; + } - case "BinaryExpression": - case "LogicalExpression": - if (parent.left !== node || isParenthesised(node)) { - return false; - } - break; + /* istanbul ignore next */ + throw new Error("unreachable"); + } - case "ConditionalExpression": - if (parent.test !== node || isParenthesised(node)) { - return false; - } - break; + /** + * Get the precedence level based on the node type + * @param {ASTNode} node node to evaluate + * @returns {int} precedence level + * @private + */ + function precedence(node) { - case "CallExpression": - if (parent.callee !== node || isParenthesised(node)) { - return false; - } - break; + switch (node.type) { + case "SequenceExpression": + return 0; - case "MemberExpression": - if (parent.object !== node || isParenthesised(node)) { - return false; - } - break; + case "AssignmentExpression": + case "ArrowFunctionExpression": + case "YieldExpression": + return 1; - case "ExpressionStatement": - return true; + case "ConditionalExpression": + return 3; - default: - return false; - } + case "LogicalExpression": + switch (node.operator) { + case "||": + return 4; + case "&&": + return 5; - node = parent; - parent = parent.parent; - } + // no default + } - /* istanbul ignore next */ - throw new Error("unreachable"); - } + /* falls through */ - /** - * Get the precedence level based on the node type - * @param {ASTNode} node node to evaluate - * @returns {int} precedence level - * @private - */ - 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 - } + case "BinaryExpression": - /* 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 - } + 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 */ + /* falls through */ - case "UnaryExpression": - return 14; + case "UnaryExpression": + return 14; - case "UpdateExpression": - return 15; + case "UpdateExpression": + return 15; - case "CallExpression": + case "CallExpression": - // IIFE is allowed to have parens in any position (#655) - if (node.callee.type === "FunctionExpression") { - return -1; - } - return 16; + // IIFE is allowed to have parens in any position (#655) + if (node.callee.type === "FunctionExpression") { + return -1; + } + return 16; - case "NewExpression": - return 17; + case "NewExpression": + return 17; - // no default + // no default + } + return 18; } - return 18; - } - /** - * Report the node - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function report(node) { - var previousToken = context.getTokenBefore(node); - - context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression."); - } + /** + * Report the node + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function report(node) { + var previousToken = context.getTokenBefore(node); - /** - * Evaluate Unary update - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function dryUnaryUpdate(node) { - if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) { - report(node.argument); + context.report(node, previousToken.loc.start, "Gratuitous parentheses around expression."); } - } - /** - * Evaluate a new call - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function dryCallNew(node) { - if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !( - node.type === "CallExpression" && - node.callee.type === "FunctionExpression" && - - // One set of parentheses are allowed for a function expression - !hasDoubleExcessParens(node.callee) - )) { - report(node.callee); + /** + * Evaluate Unary update + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function dryUnaryUpdate(node) { + if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) { + report(node.argument); + } } - if (node.arguments.length === 1) { - if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({type: "AssignmentExpression"})) { - report(node.arguments[0]); + + /** + * Evaluate a new call + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function dryCallNew(node) { + if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !( + node.type === "CallExpression" && + node.callee.type === "FunctionExpression" && + + // One set of parentheses are allowed for a function expression + !hasDoubleExcessParens(node.callee) + )) { + report(node.callee); } - } else { - [].forEach.call(node.arguments, function(arg) { - if (hasExcessParens(arg) && precedence(arg) >= precedence({type: "AssignmentExpression"})) { - report(arg); + if (node.arguments.length === 1) { + if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({type: "AssignmentExpression"})) { + report(node.arguments[0]); } - }); + } else { + [].forEach.call(node.arguments, function(arg) { + if (hasExcessParens(arg) && precedence(arg) >= precedence({type: "AssignmentExpression"})) { + report(arg); + } + }); + } } - } - /** - * Evaluate binary logicals - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function dryBinaryLogical(node) { - if (!NESTED_BINARY) { - var prec = precedence(node); - - if (hasExcessParens(node.left) && precedence(node.left) >= prec) { - report(node.left); - } - if (hasExcessParens(node.right) && precedence(node.right) > prec) { - report(node.right); + /** + * Evaluate binary logicals + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function dryBinaryLogical(node) { + if (!NESTED_BINARY) { + var prec = precedence(node); + + if (hasExcessParens(node.left) && precedence(node.left) >= prec) { + report(node.left); + } + if (hasExcessParens(node.right) && precedence(node.right) > prec) { + report(node.right); + } } } - } - return { - "ArrayExpression": function(node) { - [].forEach.call(node.elements, function(e) { - if (e && hasExcessParens(e) && precedence(e) >= precedence({type: "AssignmentExpression"})) { - report(e); + return { + "ArrayExpression": function(node) { + [].forEach.call(node.elements, function(e) { + if (e && hasExcessParens(e) && precedence(e) >= precedence({type: "AssignmentExpression"})) { + report(e); + } + }); + }, + + "ArrowFunctionExpression": function(node) { + if (node.body.type !== "BlockStatement") { + if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) { + report(node.body); + return; + } + + // Object literals *must* be parenthesised + if (node.body.type === "ObjectExpression" && hasDoubleExcessParens(node.body)) { + report(node.body); + return; + } } - }); - }, + }, - "ArrowFunctionExpression": function(node) { - if (node.body.type !== "BlockStatement") { - if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) { - report(node.body); - return; + "AssignmentExpression": function(node) { + if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) { + report(node.right); } + }, - // Object literals *must* be parenthesised - if (node.body.type === "ObjectExpression" && hasDoubleExcessParens(node.body)) { - report(node.body); - return; + "BinaryExpression": dryBinaryLogical, + "CallExpression": dryCallNew, + + "ConditionalExpression": function(node) { + if (hasExcessParens(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) { + report(node.test); } - } - }, + if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) { + report(node.consequent); + } + if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) { + report(node.alternate); + } + }, - "AssignmentExpression": function(node) { - if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) { - report(node.right); - } - }, + "DoWhileStatement": function(node) { + if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { + report(node.test); + } + }, + + "ExpressionStatement": function(node) { + var firstToken, secondToken, firstTokens; + + if (hasExcessParens(node.expression)) { + firstTokens = context.getFirstTokens(node.expression, 2); + firstToken = firstTokens[0]; + secondToken = firstTokens[1]; + + if ( + !firstToken || + firstToken.value !== "{" && + firstToken.value !== "function" && + firstToken.value !== "class" && + ( + firstToken.value !== "let" || + !secondToken || + secondToken.value !== "[" + ) + ) { + report(node.expression); + } + } + }, - "BinaryExpression": dryBinaryLogical, - "CallExpression": dryCallNew, + "ForInStatement": function(node) { + if (hasExcessParens(node.right)) { + report(node.right); + } + }, - "ConditionalExpression": function(node) { - if (hasExcessParens(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) { - report(node.test); - } - if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) { - report(node.consequent); - } - if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) { - report(node.alternate); - } - }, + "ForOfStatement": function(node) { + if (hasExcessParens(node.right)) { + report(node.right); + } + }, - "DoWhileStatement": function(node) { - if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { - report(node.test); - } - }, + "ForStatement": function(node) { + if (node.init && hasExcessParens(node.init)) { + report(node.init); + } - "ExpressionStatement": function(node) { - var firstToken, secondToken, firstTokens; + if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) { + report(node.test); + } - if (hasExcessParens(node.expression)) { - firstTokens = context.getFirstTokens(node.expression, 2); - firstToken = firstTokens[0]; - secondToken = firstTokens[1]; + if (node.update && hasExcessParens(node.update)) { + report(node.update); + } + }, + "IfStatement": function(node) { + if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { + report(node.test); + } + }, + + "LogicalExpression": dryBinaryLogical, + + "MemberExpression": function(node) { if ( - !firstToken || - firstToken.value !== "{" && - firstToken.value !== "function" && - firstToken.value !== "class" && + hasExcessParens(node.object) && + precedence(node.object) >= precedence(node) && ( - firstToken.value !== "let" || - !secondToken || - secondToken.value !== "[" + 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) + ) + ) && + !( + (node.object.type === "FunctionExpression" || node.object.type === "ClassExpression") && + isHeadOfExpressionStatement(node) && + !hasDoubleExcessParens(node.object) ) ) { - report(node.expression); + report(node.object); } - } - }, - - "ForInStatement": function(node) { - if (hasExcessParens(node.right)) { - report(node.right); - } - }, - - "ForOfStatement": function(node) { - if (hasExcessParens(node.right)) { - report(node.right); - } - }, + if (node.computed && hasExcessParens(node.property)) { + report(node.property); + } + }, - "ForStatement": function(node) { - if (node.init && hasExcessParens(node.init)) { - report(node.init); - } + "NewExpression": dryCallNew, - if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) { - report(node.test); - } + "ObjectExpression": function(node) { + [].forEach.call(node.properties, function(e) { + var v = e.value; - if (node.update && hasExcessParens(node.update)) { - report(node.update); - } - }, + if (v && hasExcessParens(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) { + report(v); + } + }); + }, - "IfStatement": function(node) { - if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { - report(node.test); - } - }, + "ReturnStatement": function(node) { + var returnToken = sourceCode.getFirstToken(node); - "LogicalExpression": dryBinaryLogical, - - "MemberExpression": function(node) { - if ( - hasExcessParens(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)) - || + if (node.argument && + hasExcessParensNoLineTerminator(returnToken, node.argument) && // RegExp literal is allowed to have parens (#1589) - (node.object.type === "Literal" && node.object.regex) - ) - ) && - !( - (node.object.type === "FunctionExpression" || node.object.type === "ClassExpression") && - isHeadOfExpressionStatement(node) && - !hasDoubleExcessParens(node.object) - ) - ) { - report(node.object); - } - if (node.computed && hasExcessParens(node.property)) { - report(node.property); - } - }, - - "NewExpression": dryCallNew, - - "ObjectExpression": function(node) { - [].forEach.call(node.properties, function(e) { - var v = e.value; - - if (v && hasExcessParens(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) { - report(v); + !(node.argument.type === "Literal" && node.argument.regex)) { + report(node.argument); } - }); - }, + }, - "ReturnStatement": function(node) { - var returnToken = sourceCode.getFirstToken(node); - - if (node.argument && - hasExcessParensNoLineTerminator(returnToken, 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 (hasExcessParens(e) && precedence(e) >= precedence(node)) { + report(e); + } + }); + }, - "SequenceExpression": function(node) { - [].forEach.call(node.expressions, function(e) { - if (hasExcessParens(e) && precedence(e) >= precedence(node)) { - report(e); + "SwitchCase": function(node) { + if (node.test && hasExcessParens(node.test)) { + report(node.test); } - }); - }, + }, - "SwitchCase": function(node) { - if (node.test && hasExcessParens(node.test)) { - report(node.test); - } - }, - - "SwitchStatement": function(node) { - if (hasDoubleExcessParens(node.discriminant)) { - report(node.discriminant); - } - }, + "SwitchStatement": function(node) { + if (hasDoubleExcessParens(node.discriminant)) { + report(node.discriminant); + } + }, - "ThrowStatement": function(node) { - var throwToken = sourceCode.getFirstToken(node); + "ThrowStatement": function(node) { + var throwToken = sourceCode.getFirstToken(node); - if (hasExcessParensNoLineTerminator(throwToken, node.argument)) { - report(node.argument); - } - }, + if (hasExcessParensNoLineTerminator(throwToken, node.argument)) { + report(node.argument); + } + }, - "UnaryExpression": dryUnaryUpdate, - "UpdateExpression": dryUnaryUpdate, + "UnaryExpression": dryUnaryUpdate, + "UpdateExpression": dryUnaryUpdate, - "VariableDeclarator": function(node) { - if (node.init && hasExcessParens(node.init) && - precedence(node.init) >= precedence({type: "AssignmentExpression"}) && + "VariableDeclarator": function(node) { + if (node.init && hasExcessParens(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); - } - }, + // RegExp literal is allowed to have parens (#1589) + !(node.init.type === "Literal" && node.init.regex)) { + report(node.init); + } + }, - "WhileStatement": function(node) { - if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { - report(node.test); - } - }, + "WhileStatement": function(node) { + if (hasDoubleExcessParens(node.test) && !isCondAssignException(node)) { + report(node.test); + } + }, - "WithStatement": function(node) { - if (hasDoubleExcessParens(node.object)) { - report(node.object); - } - }, + "WithStatement": function(node) { + if (hasDoubleExcessParens(node.object)) { + report(node.object); + } + }, - "YieldExpression": function(node) { - var yieldToken; + "YieldExpression": function(node) { + var yieldToken; - if (node.argument) { - yieldToken = sourceCode.getFirstToken(node); + if (node.argument) { + yieldToken = sourceCode.getFirstToken(node); - if ((precedence(node.argument) >= precedence(node) && - hasExcessParensNoLineTerminator(yieldToken, node.argument)) || - hasDoubleExcessParens(node.argument)) { - report(node.argument); + if ((precedence(node.argument) >= precedence(node) && + hasExcessParensNoLineTerminator(yieldToken, node.argument)) || + hasDoubleExcessParens(node.argument)) { + report(node.argument); + } } } - } - }; + }; -}; - -module.exports.schema = { - "anyOf": [ - { - "type": "array", - "items": [ - { - "enum": ["functions"] - } - ], - "minItems": 0, - "maxItems": 1 - }, - { - "type": "array", - "items": [ - { - "enum": ["all"] - }, - { - "type": "object", - "properties": { - "conditionalAssign": {"type": "boolean"}, - "nestedBinaryExpressions": {"type": "boolean"} - }, - "additionalProperties": false - } - ], - "minItems": 0, - "maxItems": 2 - } - ] + } }; diff --git a/lib/rules/no-extra-semi.js b/lib/rules/no-extra-semi.js index 9639ae178ea..e844b2c17e7 100644 --- a/lib/rules/no-extra-semi.js +++ b/lib/rules/no-extra-semi.js @@ -9,76 +9,87 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Reports an unnecessary semicolon error. - * @param {Node|Token} nodeOrToken - A node or a token to be reported. - * @returns {void} - */ - function report(nodeOrToken) { - context.report({ - node: nodeOrToken, - message: "Unnecessary semicolon.", - fix: function(fixer) { - return fixer.remove(nodeOrToken); - } - }); - } +module.exports = { + meta: { + docs: { + description: "disallow unnecessary semicolons", + category: "Possible Errors", + recommended: true + }, - /** - * Checks for a part of a class body. - * This checks tokens from a specified token to a next MethodDefinition or the end of class body. - * - * @param {Token} firstToken - The first token to check. - * @returns {void} - */ - function checkForPartOfClassBody(firstToken) { - for (var token = firstToken; - token.type === "Punctuator" && token.value !== "}"; - token = context.getTokenAfter(token) - ) { - if (token.value === ";") { - report(token); - } - } - } + fixable: "code", + schema: [] + }, - return { + create: function(context) { /** - * Reports this empty statement, except if the parent node is a loop. - * @param {Node} node - A EmptyStatement node to be reported. + * Reports an unnecessary semicolon error. + * @param {Node|Token} nodeOrToken - A node or a token to be reported. * @returns {void} */ - "EmptyStatement": function(node) { - var parent = node.parent, - allowedParentTypes = ["ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement"]; - - if (allowedParentTypes.indexOf(parent.type) === -1) { - report(node); - } - }, - - /** - * Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body. - * @param {Node} node - A ClassBody node to check. - * @returns {void} - */ - "ClassBody": function(node) { - checkForPartOfClassBody(context.getFirstToken(node, 1)); // 0 is `{`. - }, + function report(nodeOrToken) { + context.report({ + node: nodeOrToken, + message: "Unnecessary semicolon.", + fix: function(fixer) { + return fixer.remove(nodeOrToken); + } + }); + } /** - * Checks tokens from this MethodDefinition to the next MethodDefinition or the end of this class body. - * @param {Node} node - A MethodDefinition node of the start point. + * Checks for a part of a class body. + * This checks tokens from a specified token to a next MethodDefinition or the end of class body. + * + * @param {Token} firstToken - The first token to check. * @returns {void} */ - "MethodDefinition": function(node) { - checkForPartOfClassBody(context.getTokenAfter(node)); + function checkForPartOfClassBody(firstToken) { + for (var token = firstToken; + token.type === "Punctuator" && token.value !== "}"; + token = context.getTokenAfter(token) + ) { + if (token.value === ";") { + report(token); + } + } } - }; -}; + return { + + /** + * Reports this empty statement, except if the parent node is a loop. + * @param {Node} node - A EmptyStatement node to be reported. + * @returns {void} + */ + "EmptyStatement": function(node) { + var parent = node.parent, + allowedParentTypes = ["ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement"]; -module.exports.schema = []; + if (allowedParentTypes.indexOf(parent.type) === -1) { + report(node); + } + }, + + /** + * Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body. + * @param {Node} node - A ClassBody node to check. + * @returns {void} + */ + "ClassBody": function(node) { + checkForPartOfClassBody(context.getFirstToken(node, 1)); // 0 is `{`. + }, + + /** + * Checks tokens from this MethodDefinition to the next MethodDefinition or the end of this class body. + * @param {Node} node - A MethodDefinition node of the start point. + * @returns {void} + */ + "MethodDefinition": function(node) { + checkForPartOfClassBody(context.getTokenAfter(node)); + } + }; + + } +}; diff --git a/lib/rules/no-fallthrough.js b/lib/rules/no-fallthrough.js index f8c41820545..8e703255fcd 100644 --- a/lib/rules/no-fallthrough.js +++ b/lib/rules/no-fallthrough.js @@ -53,73 +53,83 @@ function hasBlankLinesBetween(node, token) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0] || {}; - var currentCodePath = null; - var sourceCode = context.getSourceCode(); - - /* - * We need to use leading comments of the next SwitchCase node because - * trailing comments is wrong if semicolons are omitted. - */ - var fallthroughCase = null; - var fallthroughCommentPattern = null; - - if (options.commentPattern) { - fallthroughCommentPattern = new RegExp(options.commentPattern); - } else { - fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT; - } - - return { - "onCodePathStart": function(codePath) { - currentCodePath = codePath; - }, - "onCodePathEnd": function() { - currentCodePath = currentCodePath.upper; +module.exports = { + meta: { + docs: { + description: "disallow fallthrough of `case` statements", + category: "Best Practices", + recommended: true }, - "SwitchCase": function(node) { - - /* - * Checks whether or not there is a fallthrough comment. - * And reports the previous fallthrough node if that does not exist. - */ - if (fallthroughCase && !hasFallthroughComment(node, context, fallthroughCommentPattern)) { - context.report({ - message: "Expected a 'break' statement before '{{type}}'.", - data: {type: node.test ? "case" : "default"}, - node: node - }); - } - fallthroughCase = null; - }, - - "SwitchCase:exit": function(node) { - var nextToken = sourceCode.getTokenAfter(node); - - /* - * `reachable` meant fall through because statements preceded by - * `break`, `return`, or `throw` are unreachable. - * And allows empty cases and the last case. - */ - if (currentCodePath.currentSegments.some(isReachable) && - (node.consequent.length > 0 || hasBlankLinesBetween(node, nextToken)) && - lodash.last(node.parent.cases) !== node) { - fallthroughCase = node; + schema: [ + { + "type": "object", + "properties": { + "commentPattern": { + "type": "string" + } + }, + "additionalProperties": false } + ] + }, + + create: function(context) { + var options = context.options[0] || {}; + var currentCodePath = null; + var sourceCode = context.getSourceCode(); + + /* + * We need to use leading comments of the next SwitchCase node because + * trailing comments is wrong if semicolons are omitted. + */ + var fallthroughCase = null; + var fallthroughCommentPattern = null; + + if (options.commentPattern) { + fallthroughCommentPattern = new RegExp(options.commentPattern); + } else { + fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT; } - }; -}; -module.exports.schema = [ - { - "type": "object", - "properties": { - "commentPattern": { - "type": "string" + return { + "onCodePathStart": function(codePath) { + currentCodePath = codePath; + }, + "onCodePathEnd": function() { + currentCodePath = currentCodePath.upper; + }, + + "SwitchCase": function(node) { + + /* + * Checks whether or not there is a fallthrough comment. + * And reports the previous fallthrough node if that does not exist. + */ + if (fallthroughCase && !hasFallthroughComment(node, context, fallthroughCommentPattern)) { + context.report({ + message: "Expected a 'break' statement before '{{type}}'.", + data: {type: node.test ? "case" : "default"}, + node: node + }); + } + fallthroughCase = null; + }, + + "SwitchCase:exit": function(node) { + var nextToken = sourceCode.getTokenAfter(node); + + /* + * `reachable` meant fall through because statements preceded by + * `break`, `return`, or `throw` are unreachable. + * And allows empty cases and the last case. + */ + if (currentCodePath.currentSegments.some(isReachable) && + (node.consequent.length > 0 || hasBlankLinesBetween(node, nextToken)) && + lodash.last(node.parent.cases) !== node) { + fallthroughCase = node; + } } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/lib/rules/no-floating-decimal.js b/lib/rules/no-floating-decimal.js index 61a84f5e46e..dc3166e3665 100644 --- a/lib/rules/no-floating-decimal.js +++ b/lib/rules/no-floating-decimal.js @@ -9,22 +9,32 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow leading or trailing decimal points in numeric literals", + category: "Best Practices", + recommended: false + }, - return { - "Literal": function(node) { + schema: [] + }, - 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."); + create: 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."); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-func-assign.js b/lib/rules/no-func-assign.js index f72c8c01507..287803bb230 100644 --- a/lib/rules/no-func-assign.js +++ b/lib/rules/no-func-assign.js @@ -12,46 +12,56 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Reports a reference if is non initializer and writable. - * @param {References} references - Collection of reference to check. - * @returns {void} - */ - function checkReference(references) { - astUtils.getModifyingReferences(references).forEach(function(reference) { - context.report( - reference.identifier, - "'{{name}}' is a function.", - {name: reference.identifier.name}); - }); - } +module.exports = { + meta: { + docs: { + description: "disallow reassigning `function` declarations", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable - A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - if (variable.defs[0].type === "FunctionName") { - checkReference(variable.references); + /** + * Reports a reference if is non initializer and writable. + * @param {References} references - Collection of reference to check. + * @returns {void} + */ + function checkReference(references) { + astUtils.getModifyingReferences(references).forEach(function(reference) { + context.report( + reference.identifier, + "'{{name}}' is a function.", + {name: reference.identifier.name}); + }); } - } - /** - * Checks parameters of a given function node. - * @param {ASTNode} node - A function node to check. - * @returns {void} - */ - function checkForFunction(node) { - context.getDeclaredVariables(node).forEach(checkVariable); - } + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable - A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if (variable.defs[0].type === "FunctionName") { + checkReference(variable.references); + } + } - return { - "FunctionDeclaration": checkForFunction, - "FunctionExpression": checkForFunction - }; -}; + /** + * Checks parameters of a given function node. + * @param {ASTNode} node - A function node to check. + * @returns {void} + */ + function checkForFunction(node) { + context.getDeclaredVariables(node).forEach(checkVariable); + } -module.exports.schema = []; + return { + "FunctionDeclaration": checkForFunction, + "FunctionExpression": checkForFunction + }; + } +}; diff --git a/lib/rules/no-implicit-coercion.js b/lib/rules/no-implicit-coercion.js index 2bc96485c30..430169f06a5 100644 --- a/lib/rules/no-implicit-coercion.js +++ b/lib/rules/no-implicit-coercion.js @@ -144,104 +144,114 @@ function getOtherOperand(node, value) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = parseOptions(context.options[0]), - operatorAllowed = false; +module.exports = { + meta: { + docs: { + description: "disallow shorthand type conversions", + category: "Best Practices", + recommended: false + }, - return { - "UnaryExpression": function(node) { - - // !!foo - operatorAllowed = options.allow.indexOf("!!") >= 0; - if (!operatorAllowed && options.boolean && isDoubleLogicalNegating(node)) { - context.report( - node, - "use `Boolean({{code}})` instead.", { - code: context.getSource(node.argument.argument) - }); - } + schema: [{ + "type": "object", + "properties": { + "boolean": { + "type": "boolean" + }, + "number": { + "type": "boolean" + }, + "string": { + "type": "boolean" + }, + "allow": { + "type": "array", + "items": { + "enum": ALLOWABLE_OPERATORS + }, + "uniqueItems": true + } + }, + "additionalProperties": false + }] + }, - // ~foo.indexOf(bar) - operatorAllowed = options.allow.indexOf("~") >= 0; - if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) { - context.report( - node, - "use `{{code}} !== -1` instead.", { - code: context.getSource(node.argument) - }); - } + create: function(context) { + var options = parseOptions(context.options[0]), + operatorAllowed = false; - // +foo - operatorAllowed = options.allow.indexOf("+") >= 0; - if (!operatorAllowed && options.number && node.operator === "+" && !isNumeric(node.argument)) { - context.report( - node, - "use `Number({{code}})` instead.", { - code: context.getSource(node.argument) - }); - } - }, + return { + "UnaryExpression": function(node) { - // Use `:exit` to prevent double reporting - "BinaryExpression:exit": function(node) { + // !!foo + operatorAllowed = options.allow.indexOf("!!") >= 0; + if (!operatorAllowed && options.boolean && isDoubleLogicalNegating(node)) { + context.report( + node, + "use `Boolean({{code}})` instead.", { + code: context.getSource(node.argument.argument) + }); + } - // 1 * foo - operatorAllowed = options.allow.indexOf("*") >= 0; - var nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && getNonNumericOperand(node); + // ~foo.indexOf(bar) + operatorAllowed = options.allow.indexOf("~") >= 0; + if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) { + context.report( + node, + "use `{{code}} !== -1` instead.", { + code: context.getSource(node.argument) + }); + } - if (nonNumericOperand) { - context.report( - node, - "use `Number({{code}})` instead.", { - code: context.getSource(nonNumericOperand) - }); - } + // +foo + operatorAllowed = options.allow.indexOf("+") >= 0; + if (!operatorAllowed && options.number && node.operator === "+" && !isNumeric(node.argument)) { + context.report( + node, + "use `Number({{code}})` instead.", { + code: context.getSource(node.argument) + }); + } + }, - // "" + foo - operatorAllowed = options.allow.indexOf("+") >= 0; - if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) { - context.report( - node, - "use `String({{code}})` instead.", { - code: context.getSource(getOtherOperand(node, "")) - }); - } - }, + // Use `:exit` to prevent double reporting + "BinaryExpression:exit": function(node) { - "AssignmentExpression": function(node) { + // 1 * foo + operatorAllowed = options.allow.indexOf("*") >= 0; + var nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && getNonNumericOperand(node); - // foo += "" - operatorAllowed = options.allow.indexOf("+") >= 0; - if (options.string && isAppendEmptyString(node)) { - context.report( - node, - "use `{{code}} = String({{code}})` instead.", { - code: context.getSource(getOtherOperand(node, "")) - }); - } - } - }; -}; + if (nonNumericOperand) { + context.report( + node, + "use `Number({{code}})` instead.", { + code: context.getSource(nonNumericOperand) + }); + } -module.exports.schema = [{ - "type": "object", - "properties": { - "boolean": { - "type": "boolean" - }, - "number": { - "type": "boolean" - }, - "string": { - "type": "boolean" - }, - "allow": { - "type": "array", - "items": { - "enum": ALLOWABLE_OPERATORS + // "" + foo + operatorAllowed = options.allow.indexOf("+") >= 0; + if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) { + context.report( + node, + "use `String({{code}})` instead.", { + code: context.getSource(getOtherOperand(node, "")) + }); + } }, - "uniqueItems": true - } - }, - "additionalProperties": false -}]; + + "AssignmentExpression": function(node) { + + // foo += "" + operatorAllowed = options.allow.indexOf("+") >= 0; + if (options.string && isAppendEmptyString(node)) { + context.report( + node, + "use `{{code}} = String({{code}})` instead.", { + code: context.getSource(getOtherOperand(node, "")) + }); + } + } + }; + } +}; diff --git a/lib/rules/no-implicit-globals.js b/lib/rules/no-implicit-globals.js index 045ebffed20..a33510f97d7 100644 --- a/lib/rules/no-implicit-globals.js +++ b/lib/rules/no-implicit-globals.js @@ -11,37 +11,47 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - return { - "Program": function() { - var scope = context.getScope(); - - scope.variables.forEach(function(variable) { - if (variable.writeable) { - return; - } - - variable.defs.forEach(function(def) { - if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) { - context.report(def.node, "Implicit global variable, assign as global property instead."); +module.exports = { + meta: { + docs: { + description: "disallow `var` and named `function` declarations in the global scope", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + return { + "Program": function() { + var scope = context.getScope(); + + scope.variables.forEach(function(variable) { + if (variable.writeable) { + return; } + + variable.defs.forEach(function(def) { + if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) { + context.report(def.node, "Implicit global variable, assign as global property instead."); + } + }); }); - }); - scope.implicit.variables.forEach(function(variable) { - var scopeVariable = scope.set.get(variable.name); + scope.implicit.variables.forEach(function(variable) { + var scopeVariable = scope.set.get(variable.name); - if (scopeVariable && scopeVariable.writeable) { - return; - } + if (scopeVariable && scopeVariable.writeable) { + return; + } - variable.defs.forEach(function(def) { - context.report(def.node, "Implicit global variable, assign as global property instead."); + variable.defs.forEach(function(def) { + context.report(def.node, "Implicit global variable, assign as global property instead."); + }); }); - }); - } - }; + } + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-implied-eval.js b/lib/rules/no-implied-eval.js index eea0f7fe418..2b285d4dc11 100644 --- a/lib/rules/no-implied-eval.js +++ b/lib/rules/no-implied-eval.js @@ -11,142 +11,152 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var CALLEE_RE = /set(?:Timeout|Interval)|execScript/; - - /* - * Figures out if we should inspect a given binary expression. Is a stack - * of stacks, where the first element in each substack is a CallExpression. - */ - var impliedEvalAncestorsStack = []; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Get the last element of an array, without modifying arr, like pop(), but non-destructive. - * @param {array} arr What to inspect - * @returns {*} The last element of arr - * @private - */ - function last(arr) { - return arr ? arr[arr.length - 1] : null; - } - - /** - * Checks if the given MemberExpression node is a potentially implied eval identifier on window. - * @param {ASTNode} node The MemberExpression node to check. - * @returns {boolean} Whether or not the given node is potentially an implied eval. - * @private - */ - function isImpliedEvalMemberExpression(node) { - var object = node.object, - property = node.property, - hasImpliedEvalName = CALLEE_RE.test(property.name) || CALLEE_RE.test(property.value); - - return object.name === "window" && hasImpliedEvalName; - } - - /** - * Determines if a node represents a call to a potentially implied eval. - * - * This checks the callee name and that there's an argument, but not the type of the argument. - * - * @param {ASTNode} node The CallExpression to check. - * @returns {boolean} True if the node matches, false if not. - * @private - */ - function isImpliedEvalCallExpression(node) { - var isMemberExpression = (node.callee.type === "MemberExpression"), - isIdentifier = (node.callee.type === "Identifier"), - isImpliedEvalCallee = - (isIdentifier && CALLEE_RE.test(node.callee.name)) || - (isMemberExpression && isImpliedEvalMemberExpression(node.callee)); - - return isImpliedEvalCallee && node.arguments.length; - } - - /** - * Checks that the parent is a direct descendent of an potential implied eval CallExpression, and if the parent is a CallExpression, that we're the first argument. - * @param {ASTNode} node The node to inspect the parent of. - * @returns {boolean} Was the parent a direct descendent, and is the child therefore potentially part of a dangerous argument? - * @private - */ - function hasImpliedEvalParent(node) { - - // make sure our parent is marked - return node.parent === last(last(impliedEvalAncestorsStack)) && - - // if our parent is a CallExpression, make sure we're the first argument - (node.parent.type !== "CallExpression" || node === node.parent.arguments[0]); - } +module.exports = { + meta: { + docs: { + description: "disallow the use of `eval()`-like methods", + category: "Best Practices", + recommended: false + }, - /** - * Checks if our parent is marked as part of an implied eval argument. If - * so, collapses the top of impliedEvalAncestorsStack and reports on the - * original CallExpression. - * @param {ASTNode} node The CallExpression to check. - * @returns {boolean} True if the node matches, false if not. - * @private - */ - function checkString(node) { - if (hasImpliedEvalParent(node)) { - - // remove the entire substack, to avoid duplicate reports - var substack = impliedEvalAncestorsStack.pop(); - - context.report(substack[0], "Implied eval. Consider passing a function instead of a string."); + schema: [] + }, + + create: function(context) { + var CALLEE_RE = /set(?:Timeout|Interval)|execScript/; + + /* + * Figures out if we should inspect a given binary expression. Is a stack + * of stacks, where the first element in each substack is a CallExpression. + */ + var impliedEvalAncestorsStack = []; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Get the last element of an array, without modifying arr, like pop(), but non-destructive. + * @param {array} arr What to inspect + * @returns {*} The last element of arr + * @private + */ + function last(arr) { + return arr ? arr[arr.length - 1] : null; } - } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "CallExpression": function(node) { - if (isImpliedEvalCallExpression(node)) { + /** + * Checks if the given MemberExpression node is a potentially implied eval identifier on window. + * @param {ASTNode} node The MemberExpression node to check. + * @returns {boolean} Whether or not the given node is potentially an implied eval. + * @private + */ + function isImpliedEvalMemberExpression(node) { + var object = node.object, + property = node.property, + hasImpliedEvalName = CALLEE_RE.test(property.name) || CALLEE_RE.test(property.value); + + return object.name === "window" && hasImpliedEvalName; + } - // call expressions create a new substack - impliedEvalAncestorsStack.push([node]); - } - }, + /** + * Determines if a node represents a call to a potentially implied eval. + * + * This checks the callee name and that there's an argument, but not the type of the argument. + * + * @param {ASTNode} node The CallExpression to check. + * @returns {boolean} True if the node matches, false if not. + * @private + */ + function isImpliedEvalCallExpression(node) { + var isMemberExpression = (node.callee.type === "MemberExpression"), + isIdentifier = (node.callee.type === "Identifier"), + isImpliedEvalCallee = + (isIdentifier && CALLEE_RE.test(node.callee.name)) || + (isMemberExpression && isImpliedEvalMemberExpression(node.callee)); + + return isImpliedEvalCallee && node.arguments.length; + } - "CallExpression:exit": function(node) { - if (node === last(last(impliedEvalAncestorsStack))) { + /** + * Checks that the parent is a direct descendent of an potential implied eval CallExpression, and if the parent is a CallExpression, that we're the first argument. + * @param {ASTNode} node The node to inspect the parent of. + * @returns {boolean} Was the parent a direct descendent, and is the child therefore potentially part of a dangerous argument? + * @private + */ + function hasImpliedEvalParent(node) { - /* Destroys the entire sub-stack, rather than just using - * last(impliedEvalAncestorsStack).pop(), as a CallExpression is - * always the bottom of a impliedEvalAncestorsStack substack. - */ - impliedEvalAncestorsStack.pop(); - } - }, + // make sure our parent is marked + return node.parent === last(last(impliedEvalAncestorsStack)) && - "BinaryExpression": function(node) { - if (node.operator === "+" && hasImpliedEvalParent(node)) { - last(impliedEvalAncestorsStack).push(node); - } - }, + // if our parent is a CallExpression, make sure we're the first argument + (node.parent.type !== "CallExpression" || node === node.parent.arguments[0]); + } - "BinaryExpression:exit": function(node) { - if (node === last(last(impliedEvalAncestorsStack))) { - last(impliedEvalAncestorsStack).pop(); + /** + * Checks if our parent is marked as part of an implied eval argument. If + * so, collapses the top of impliedEvalAncestorsStack and reports on the + * original CallExpression. + * @param {ASTNode} node The CallExpression to check. + * @returns {boolean} True if the node matches, false if not. + * @private + */ + function checkString(node) { + if (hasImpliedEvalParent(node)) { + + // remove the entire substack, to avoid duplicate reports + var substack = impliedEvalAncestorsStack.pop(); + + context.report(substack[0], "Implied eval. Consider passing a function instead of a string."); } - }, + } - "Literal": function(node) { - if (typeof node.value === "string") { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + "CallExpression": function(node) { + if (isImpliedEvalCallExpression(node)) { + + // call expressions create a new substack + impliedEvalAncestorsStack.push([node]); + } + }, + + "CallExpression:exit": function(node) { + if (node === last(last(impliedEvalAncestorsStack))) { + + /* Destroys the entire sub-stack, rather than just using + * last(impliedEvalAncestorsStack).pop(), as a CallExpression is + * always the bottom of a impliedEvalAncestorsStack substack. + */ + impliedEvalAncestorsStack.pop(); + } + }, + + "BinaryExpression": function(node) { + if (node.operator === "+" && hasImpliedEvalParent(node)) { + last(impliedEvalAncestorsStack).push(node); + } + }, + + "BinaryExpression:exit": function(node) { + if (node === last(last(impliedEvalAncestorsStack))) { + last(impliedEvalAncestorsStack).pop(); + } + }, + + "Literal": function(node) { + if (typeof node.value === "string") { + checkString(node); + } + }, + + "TemplateLiteral": function(node) { checkString(node); } - }, - - "TemplateLiteral": function(node) { - checkString(node); - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-inline-comments.js b/lib/rules/no-inline-comments.js index 4048802bc8c..c8fbe8bbc77 100644 --- a/lib/rules/no-inline-comments.js +++ b/lib/rules/no-inline-comments.js @@ -11,44 +11,54 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow inline comments after code", + category: "Stylistic Issues", + recommended: false + }, - /** - * 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) { + schema: [] + }, - // 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]); + create: function(context) { - var preamble = startLine.slice(0, node.loc.start.column).trim(); + /** + * 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) { - // Also check after the comment - var postamble = endLine.slice(node.loc.end.column).trim(); + // 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]); - // Check that this comment isn't an ESLint directive - var isDirective = astUtils.isDirectiveComment(node); + var preamble = startLine.slice(0, node.loc.start.column).trim(); - // Should be empty if there was only whitespace around the comment - if (!isDirective && (preamble || postamble)) { - context.report(node, "Unexpected comment inline with code."); + // Also check after the comment + var postamble = endLine.slice(node.loc.end.column).trim(); + + // Check that this comment isn't an ESLint directive + var isDirective = astUtils.isDirectiveComment(node); + + // Should be empty if there was only whitespace around the comment + if (!isDirective && (preamble || postamble)) { + context.report(node, "Unexpected comment inline with code."); + } } - } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return { + return { - "LineComment": testCodeAroundComment, - "BlockComment": testCodeAroundComment + "LineComment": testCodeAroundComment, + "BlockComment": testCodeAroundComment - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-inner-declarations.js b/lib/rules/no-inner-declarations.js index 8dcd3e827c9..ca2834cfbc3 100644 --- a/lib/rules/no-inner-declarations.js +++ b/lib/rules/no-inner-declarations.js @@ -10,71 +10,81 @@ // 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(); - } +module.exports = { + meta: { + docs: { + description: "disallow `function` or `var` declarations in nested blocks", + category: "Possible Errors", + recommended: true + }, - return { + schema: [ + { + "enum": ["functions", "both"] + } + ] + }, - // Type of containing ancestor - type: ancestor.type, + create: function(context) { - // Separation between ancestor and node - distance: generation - }; - } + /** + * 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; - /** - * 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") - } - ); + 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 + }; } - } - return { + /** + * 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); - "FunctionDeclaration": check, - "VariableDeclaration": function(node) { - if (context.options[0] === "both" && node.kind === "var") { - check(node); + 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); + } + } + + }; -module.exports.schema = [ - { - "enum": ["functions", "both"] } -]; +}; diff --git a/lib/rules/no-invalid-regexp.js b/lib/rules/no-invalid-regexp.js index 014e7058387..9e504a2a5bb 100644 --- a/lib/rules/no-invalid-regexp.js +++ b/lib/rules/no-invalid-regexp.js @@ -15,73 +15,83 @@ var espree = require("espree"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var options = context.options[0], - allowedFlags = ""; +module.exports = { + meta: { + docs: { + description: "disallow invalid regular expression strings in `RegExp` constructors", + category: "Possible Errors", + recommended: true + }, + + schema: [{ + "type": "object", + "properties": { + "allowConstructorFlags": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }] + }, - if (options && options.allowConstructorFlags) { - allowedFlags = options.allowConstructorFlags.join(""); - } + create: function(context) { - /** - * Check if node is a string - * @param {ASTNode} node node to evaluate - * @returns {boolean} True if its a string - * @private - */ - function isString(node) { - return node && node.type === "Literal" && typeof node.value === "string"; - } + var options = context.options[0], + allowedFlags = ""; - /** - * Validate strings passed to the RegExp constructor - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - 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 : ""; - - if (allowedFlags) { - flags = flags.replace(new RegExp("[" + allowedFlags + "]", "gi"), ""); - } + if (options && options.allowConstructorFlags) { + allowedFlags = options.allowConstructorFlags.join(""); + } - try { - void new RegExp(node.arguments[0].value); - } catch (e) { - context.report(node, e.message); - } + /** + * Check if node is a string + * @param {ASTNode} node node to evaluate + * @returns {boolean} True if its a string + * @private + */ + function isString(node) { + return node && node.type === "Literal" && typeof node.value === "string"; + } - if (flags) { + /** + * Validate strings passed to the RegExp constructor + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + 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 : ""; + + if (allowedFlags) { + flags = flags.replace(new RegExp("[" + allowedFlags + "]", "gi"), ""); + } try { - espree.parse("/./" + flags, context.parserOptions); - } catch (ex) { - context.report(node, "Invalid flags supplied to RegExp constructor '" + flags + "'"); + void new RegExp(node.arguments[0].value); + } catch (e) { + context.report(node, e.message); } - } - } - } - - return { - "CallExpression": check, - "NewExpression": check - }; + if (flags) { -}; + try { + espree.parse("/./" + flags, context.parserOptions); + } catch (ex) { + context.report(node, "Invalid flags supplied to RegExp constructor '" + flags + "'"); + } + } -module.exports.schema = [{ - "type": "object", - "properties": { - "allowConstructorFlags": { - "type": "array", - "items": { - "type": "string" } } - }, - "additionalProperties": false -}]; + + return { + "CallExpression": check, + "NewExpression": check + }; + + } +}; diff --git a/lib/rules/no-invalid-this.js b/lib/rules/no-invalid-this.js index 0a9e535542c..773090edc40 100644 --- a/lib/rules/no-invalid-this.js +++ b/lib/rules/no-invalid-this.js @@ -17,98 +17,108 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var stack = [], - sourceCode = context.getSourceCode(); - - /** - * Gets the current checking context. - * - * The return value has a flag that whether or not `this` keyword is valid. - * The flag is initialized when got at the first time. - * - * @returns {{valid: boolean}} - * an object which has a flag that whether or not `this` keyword is valid. - */ - stack.getCurrent = function() { - var current = this[this.length - 1]; - - if (!current.init) { - current.init = true; - current.valid = !astUtils.isDefaultThisBinding( - current.node, - sourceCode); - } - return current; - }; - - /** - * Pushs new checking context into the stack. - * - * The checking context is not initialized yet. - * Because most functions don't have `this` keyword. - * When `this` keyword was found, the checking context is initialized. - * - * @param {ASTNode} node - A function node that was entered. - * @returns {void} - */ - function enterFunction(node) { - - // `this` can be invalid only under strict mode. - stack.push({ - init: !context.getScope().isStrict, - node: node, - valid: true - }); - } - - /** - * Pops the current checking context from the stack. - * @returns {void} - */ - function exitFunction() { - stack.pop(); - } - - return { +module.exports = { + meta: { + docs: { + description: "disallow `this` keywords outside of classes or class-like objects", + category: "Best Practices", + recommended: false + }, - /* - * `this` is invalid only under strict mode. - * Modules is always strict mode. + schema: [] + }, + + create: function(context) { + var stack = [], + sourceCode = context.getSourceCode(); + + /** + * Gets the current checking context. + * + * The return value has a flag that whether or not `this` keyword is valid. + * The flag is initialized when got at the first time. + * + * @returns {{valid: boolean}} + * an object which has a flag that whether or not `this` keyword is valid. + */ + stack.getCurrent = function() { + var current = this[this.length - 1]; + + if (!current.init) { + current.init = true; + current.valid = !astUtils.isDefaultThisBinding( + current.node, + sourceCode); + } + return current; + }; + + /** + * Pushs new checking context into the stack. + * + * The checking context is not initialized yet. + * Because most functions don't have `this` keyword. + * When `this` keyword was found, the checking context is initialized. + * + * @param {ASTNode} node - A function node that was entered. + * @returns {void} */ - "Program": function(node) { - var scope = context.getScope(), - features = context.parserOptions.ecmaFeatures || {}; + function enterFunction(node) { + // `this` can be invalid only under strict mode. stack.push({ - init: true, + init: !context.getScope().isStrict, node: node, - valid: !( - scope.isStrict || - node.sourceType === "module" || - (features.globalReturn && scope.childScopes[0].isStrict) - ) + valid: true }); - }, + } - "Program:exit": function() { + /** + * Pops the current checking context from the stack. + * @returns {void} + */ + function exitFunction() { stack.pop(); - }, - - "FunctionDeclaration": enterFunction, - "FunctionDeclaration:exit": exitFunction, - "FunctionExpression": enterFunction, - "FunctionExpression:exit": exitFunction, - - // Reports if `this` of the current context is invalid. - "ThisExpression": function(node) { - var current = stack.getCurrent(); + } - if (current && !current.valid) { - context.report(node, "Unexpected 'this'."); + return { + + /* + * `this` is invalid only under strict mode. + * Modules is always strict mode. + */ + "Program": function(node) { + var scope = context.getScope(), + features = context.parserOptions.ecmaFeatures || {}; + + stack.push({ + init: true, + node: node, + valid: !( + scope.isStrict || + node.sourceType === "module" || + (features.globalReturn && scope.childScopes[0].isStrict) + ) + }); + }, + + "Program:exit": function() { + stack.pop(); + }, + + "FunctionDeclaration": enterFunction, + "FunctionDeclaration:exit": exitFunction, + "FunctionExpression": enterFunction, + "FunctionExpression:exit": exitFunction, + + // Reports if `this` of the current context is invalid. + "ThisExpression": function(node) { + var current = stack.getCurrent(); + + if (current && !current.valid) { + context.report(node, "Unexpected 'this'."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-irregular-whitespace.js b/lib/rules/no-irregular-whitespace.js index a849f060bd9..1199a495aaf 100644 --- a/lib/rules/no-irregular-whitespace.js +++ b/lib/rules/no-irregular-whitespace.js @@ -11,187 +11,197 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow irregular whitespace outside of strings and comments", + category: "Possible Errors", + recommended: true + }, + + schema: [ + { + "type": "object", + "properties": { + "skipComments": { + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + }, + + create: function(context) { - var irregularWhitespace = /[\u0085\u00A0\ufeff\f\v\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg, - irregularLineTerminators = /[\u2028\u2029]/mg; + var irregularWhitespace = /[\u0085\u00A0\ufeff\f\v\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg, + irregularLineTerminators = /[\u2028\u2029]/mg; - // Module store of errors that we have found - var errors = []; + // Module store of errors that we have found + var errors = []; - // Comment nodes. We accumulate these as we go, so we can be sure to trigger them after the whole `Program` entity is parsed, even for top-of-file comments. - var commentNodes = []; + // Comment nodes. We accumulate these as we go, so we can be sure to trigger them after the whole `Program` entity is parsed, even for top-of-file comments. + var commentNodes = []; - // Lookup the `skipComments` option, which defaults to `false`. - var options = context.options[0] || {}; - var skipComments = !!options.skipComments; + // Lookup the `skipComments` option, which defaults to `false`. + var options = context.options[0] || {}; + var skipComments = !!options.skipComments; - /** - * Removes errors that occur inside a string node - * @param {ASTNode} node to check for matching errors. - * @returns {void} - * @private - */ - function removeWhitespaceError(node) { - var locStart = node.loc.start; - var locEnd = node.loc.end; + /** + * Removes errors that occur inside a string node + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeWhitespaceError(node) { + var locStart = node.loc.start; + var locEnd = node.loc.end; - errors = errors.filter(function(error) { - var errorLoc = error[1]; + 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 || errorLoc.line < locEnd.line)) { + return false; + } + } + return true; + }); + } - if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) { - if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) { - return false; + /** + * Checks identifier or literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeInvalidNodeErrorsInIdentifierOrLiteral(node) { + if (typeof node.value === "string") { + + // If we have irregular characters remove them from the errors list + if (node.raw.match(irregularWhitespace) || node.raw.match(irregularLineTerminators)) { + removeWhitespaceError(node); } } - return true; - }); - } + } - /** - * Checks identifier or literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors - * @param {ASTNode} node to check for matching errors. - * @returns {void} - * @private - */ - function removeInvalidNodeErrorsInIdentifierOrLiteral(node) { - if (typeof node.value === "string") { - - // If we have irregular characters remove them from the errors list - if (node.raw.match(irregularWhitespace) || node.raw.match(irregularLineTerminators)) { + /** + * Checks comment nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeInvalidNodeErrorsInComment(node) { + if (node.value.match(irregularWhitespace) || node.value.match(irregularLineTerminators)) { removeWhitespaceError(node); } } - } - /** - * Checks comment nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors - * @param {ASTNode} node to check for matching errors. - * @returns {void} - * @private - */ - function removeInvalidNodeErrorsInComment(node) { - if (node.value.match(irregularWhitespace) || node.value.match(irregularLineTerminators)) { - removeWhitespaceError(node); + /** + * Checks the program source for irregular whitespace + * @param {ASTNode} node The program node + * @returns {void} + * @private + */ + function checkForIrregularWhitespace(node) { + var sourceLines = context.getSourceLines(); + + sourceLines.forEach(function(sourceLine, lineIndex) { + var lineNumber = lineIndex + 1, + location, + match; + + while ((match = irregularWhitespace.exec(sourceLine)) !== null) { + location = { + line: lineNumber, + column: match.index + }; + + errors.push([node, location, "Irregular whitespace not allowed"]); + } + }); } - } - /** - * Checks the program source for irregular whitespace - * @param {ASTNode} node The program node - * @returns {void} - * @private - */ - function checkForIrregularWhitespace(node) { - var sourceLines = context.getSourceLines(); - - sourceLines.forEach(function(sourceLine, lineIndex) { - var lineNumber = lineIndex + 1, + /** + * Checks the program source for irregular line terminators + * @param {ASTNode} node The program node + * @returns {void} + * @private + */ + function checkForIrregularLineTerminators(node) { + var source = context.getSource(), + sourceLines = context.getSourceLines(), + linebreaks = source.match(/\r\n|\r|\n|\u2028|\u2029/g), + lastLineIndex = -1, + lineIndex, location, match; - while ((match = irregularWhitespace.exec(sourceLine)) !== null) { + while ((match = irregularLineTerminators.exec(source)) !== null) { + lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0; + location = { - line: lineNumber, - column: match.index + line: lineIndex + 1, + column: sourceLines[lineIndex].length }; errors.push([node, location, "Irregular whitespace not allowed"]); + lastLineIndex = lineIndex; } - }); - } - - /** - * Checks the program source for irregular line terminators - * @param {ASTNode} node The program node - * @returns {void} - * @private - */ - function checkForIrregularLineTerminators(node) { - var source = context.getSource(), - sourceLines = context.getSourceLines(), - linebreaks = source.match(/\r\n|\r|\n|\u2028|\u2029/g), - lastLineIndex = -1, - lineIndex, - location, - match; - - while ((match = irregularLineTerminators.exec(source)) !== null) { - lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0; - - location = { - line: lineIndex + 1, - column: sourceLines[lineIndex].length - }; - - errors.push([node, location, "Irregular whitespace not allowed"]); - lastLineIndex = lineIndex; } - } - - /** - * Stores a comment node (`LineComment` or `BlockComment`) for later stripping of errors within; a necessary deferring of processing to deal with top-of-file comments. - * @param {ASTNode} node The comment node - * @returns {void} - * @private - */ - function rememberCommentNode(node) { - commentNodes.push(node); - } - - /** - * A no-op function to act as placeholder for comment accumulation when the `skipComments` option is `false`. - * @returns {void} - * @private - */ - function noop() {} - - 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. - */ - - checkForIrregularWhitespace(node); - checkForIrregularLineTerminators(node); - }, - - "Identifier": removeInvalidNodeErrorsInIdentifierOrLiteral, - "Literal": removeInvalidNodeErrorsInIdentifierOrLiteral, - "LineComment": skipComments ? rememberCommentNode : noop, - "BlockComment": skipComments ? rememberCommentNode : noop, - "Program:exit": function() { - - if (skipComments) { - // First strip errors occurring in comment nodes. We have to do this post-`Program` to deal with top-of-file comments. - commentNodes.forEach(removeInvalidNodeErrorsInComment); - } - - // If we have any errors remaining report on them - errors.forEach(function(error) { - context.report.apply(context, error); - }); + /** + * Stores a comment node (`LineComment` or `BlockComment`) for later stripping of errors within; a necessary deferring of processing to deal with top-of-file comments. + * @param {ASTNode} node The comment node + * @returns {void} + * @private + */ + function rememberCommentNode(node) { + commentNodes.push(node); } - }; -}; -module.exports.schema = [ - { - "type": "object", - "properties": { - "skipComments": { - "type": "boolean" + /** + * A no-op function to act as placeholder for comment accumulation when the `skipComments` option is `false`. + * @returns {void} + * @private + */ + function noop() {} + + 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. + */ + + checkForIrregularWhitespace(node); + checkForIrregularLineTerminators(node); + }, + + "Identifier": removeInvalidNodeErrorsInIdentifierOrLiteral, + "Literal": removeInvalidNodeErrorsInIdentifierOrLiteral, + "LineComment": skipComments ? rememberCommentNode : noop, + "BlockComment": skipComments ? rememberCommentNode : noop, + "Program:exit": function() { + + if (skipComments) { + + // First strip errors occurring in comment nodes. We have to do this post-`Program` to deal with top-of-file comments. + commentNodes.forEach(removeInvalidNodeErrorsInComment); + } + + // If we have any errors remaining report on them + errors.forEach(function(error) { + context.report.apply(context, error); + }); } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/lib/rules/no-iterator.js b/lib/rules/no-iterator.js index 817980c99f6..52609a1f4d7 100644 --- a/lib/rules/no-iterator.js +++ b/lib/rules/no-iterator.js @@ -9,20 +9,30 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of the `__iterator__` property", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "MemberExpression": function(node) { + create: function(context) { - 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__'."); + 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__'."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-label-var.js b/lib/rules/no-label-var.js index 20fbfc182df..4e8943dc962 100644 --- a/lib/rules/no-label-var.js +++ b/lib/rules/no-label-var.js @@ -15,43 +15,53 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Check if the identifier is present inside current scope - * @param {object} scope current scope - * @param {string} name To evaluate - * @returns {boolean} True if its present - * @private - */ - function findIdentifier(scope, name) { - return astUtils.getVariableByName(scope, name) !== null; - } +module.exports = { + meta: { + docs: { + description: "disallow labels that share a name with a variable", + category: "Variables", + recommended: false + }, + + schema: [] + }, + + create: function(context) { - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Check if the identifier is present inside current scope + * @param {object} scope current scope + * @param {string} name To evaluate + * @returns {boolean} True if its present + * @private + */ + function findIdentifier(scope, name) { + return astUtils.getVariableByName(scope, name) !== null; + } - return { + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - "LabeledStatement": function(node) { + return { - // Fetch the innermost scope. - var scope = context.getScope(); + "LabeledStatement": function(node) { - // 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."); + // 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."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-labels.js b/lib/rules/no-labels.js index fe42c3d886d..dc592e6dd85 100644 --- a/lib/rules/no-labels.js +++ b/lib/rules/no-labels.js @@ -16,120 +16,130 @@ var LOOP_TYPES = /^(?:While|DoWhile|For|ForIn|ForOf)Statement$/; // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0]; - var allowLoop = Boolean(options && options.allowLoop); - var allowSwitch = Boolean(options && options.allowSwitch); - var scopeInfo = null; - - /** - * Gets the kind of a given node. - * - * @param {ASTNode} node - A node to get. - * @returns {string} The kind of the node. - */ - function getBodyKind(node) { - var type = node.type; - - if (LOOP_TYPES.test(type)) { - return "loop"; - } - if (type === "SwitchStatement") { - return "switch"; - } - return "other"; - } +module.exports = { + meta: { + docs: { + description: "disallow labeled statements", + category: "Best Practices", + recommended: false + }, - /** - * Checks whether the label of a given kind is allowed or not. - * - * @param {string} kind - A kind to check. - * @returns {boolean} `true` if the kind is allowed. - */ - function isAllowed(kind) { - switch (kind) { - case "loop": return allowLoop; - case "switch": return allowSwitch; - default: return false; + schema: [ + { + type: "object", + properties: { + allowLoop: { + type: "boolean" + }, + allowSwitch: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var options = context.options[0]; + var allowLoop = Boolean(options && options.allowLoop); + var allowSwitch = Boolean(options && options.allowSwitch); + var scopeInfo = null; + + /** + * Gets the kind of a given node. + * + * @param {ASTNode} node - A node to get. + * @returns {string} The kind of the node. + */ + function getBodyKind(node) { + var type = node.type; + + if (LOOP_TYPES.test(type)) { + return "loop"; + } + if (type === "SwitchStatement") { + return "switch"; + } + return "other"; } - } - /** - * Checks whether a given name is a label of a loop or not. - * - * @param {string} label - A name of a label to check. - * @returns {boolean} `true` if the name is a label of a loop. - */ - function getKind(label) { - var info = scopeInfo; - - while (info) { - if (info.label === label) { - return info.kind; + /** + * Checks whether the label of a given kind is allowed or not. + * + * @param {string} kind - A kind to check. + * @returns {boolean} `true` if the kind is allowed. + */ + function isAllowed(kind) { + switch (kind) { + case "loop": return allowLoop; + case "switch": return allowSwitch; + default: return false; } - info = info.upper; } - /* istanbul ignore next: syntax error */ - return "other"; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "LabeledStatement": function(node) { - scopeInfo = { - label: node.label.name, - kind: getBodyKind(node.body), - upper: scopeInfo - }; - }, - - "LabeledStatement:exit": function(node) { - if (!isAllowed(scopeInfo.kind)) { - context.report({ - node: node, - message: "Unexpected labeled statement." - }); + /** + * Checks whether a given name is a label of a loop or not. + * + * @param {string} label - A name of a label to check. + * @returns {boolean} `true` if the name is a label of a loop. + */ + function getKind(label) { + var info = scopeInfo; + + while (info) { + if (info.label === label) { + return info.kind; + } + info = info.upper; } - scopeInfo = scopeInfo.upper; - }, + /* istanbul ignore next: syntax error */ + return "other"; + } - "BreakStatement": function(node) { - if (node.label && !isAllowed(getKind(node.label.name))) { - context.report({ - node: node, - message: "Unexpected label in break statement." - }); - } - }, + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + "LabeledStatement": function(node) { + scopeInfo = { + label: node.label.name, + kind: getBodyKind(node.body), + upper: scopeInfo + }; + }, - "ContinueStatement": function(node) { - if (node.label && !isAllowed(getKind(node.label.name))) { - context.report({ - node: node, - message: "Unexpected label in continue statement." - }); - } - } - }; + "LabeledStatement:exit": function(node) { + if (!isAllowed(scopeInfo.kind)) { + context.report({ + node: node, + message: "Unexpected labeled statement." + }); + } -}; + scopeInfo = scopeInfo.upper; + }, -module.exports.schema = [ - { - type: "object", - properties: { - allowLoop: { - type: "boolean" + "BreakStatement": function(node) { + if (node.label && !isAllowed(getKind(node.label.name))) { + context.report({ + node: node, + message: "Unexpected label in break statement." + }); + } }, - allowSwitch: { - type: "boolean" + + "ContinueStatement": function(node) { + if (node.label && !isAllowed(getKind(node.label.name))) { + context.report({ + node: node, + message: "Unexpected label in continue statement." + }); + } } - }, - additionalProperties: false + }; + } -]; +}; diff --git a/lib/rules/no-lone-blocks.js b/lib/rules/no-lone-blocks.js index 976a817b5e3..2f99b1212b6 100644 --- a/lib/rules/no-lone-blocks.js +++ b/lib/rules/no-lone-blocks.js @@ -11,94 +11,104 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - // A stack of lone blocks to be checked for block-level bindings - var loneBlocks = [], - ruleDef; - - /** - * Reports a node as invalid. - * @param {ASTNode} node - The node to be reported. - * @returns {void} - */ - function report(node) { - var parent = context.getAncestors().pop(); - - context.report(node, parent.type === "Program" ? - "Block is redundant." : - "Nested block is redundant." - ); - } +module.exports = { + meta: { + docs: { + description: "disallow unnecessary nested blocks", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + // A stack of lone blocks to be checked for block-level bindings + var loneBlocks = [], + ruleDef; + + /** + * Reports a node as invalid. + * @param {ASTNode} node - The node to be reported. + * @returns {void} + */ + function report(node) { + var parent = context.getAncestors().pop(); + + context.report(node, parent.type === "Program" ? + "Block is redundant." : + "Nested block is redundant." + ); + } - /** - * Checks for any ocurrence of BlockStatement > BlockStatement or Program > BlockStatement - * @returns {boolean} True if the current node is a lone block. - */ - function isLoneBlock() { - var parent = context.getAncestors().pop(); + /** + * Checks for any ocurrence of BlockStatement > BlockStatement or Program > BlockStatement + * @returns {boolean} True if the current node is a lone block. + */ + function isLoneBlock() { + var parent = context.getAncestors().pop(); - return parent.type === "BlockStatement" || parent.type === "Program"; - } - - /** - * Checks the enclosing block of the current node for block-level bindings, - * and "marks it" as valid if any. - * @returns {void} - */ - function markLoneBlock() { - if (loneBlocks.length === 0) { - return; + return parent.type === "BlockStatement" || parent.type === "Program"; } - var block = context.getAncestors().pop(); + /** + * Checks the enclosing block of the current node for block-level bindings, + * and "marks it" as valid if any. + * @returns {void} + */ + function markLoneBlock() { + if (loneBlocks.length === 0) { + return; + } - if (loneBlocks[loneBlocks.length - 1] === block) { - loneBlocks.pop(); - } - } + var block = context.getAncestors().pop(); - // Default rule definition: report all lone blocks - ruleDef = { - BlockStatement: function(node) { - if (isLoneBlock(node)) { - report(node); + if (loneBlocks[loneBlocks.length - 1] === block) { + loneBlocks.pop(); } } - }; - // ES6: report blocks without block-level bindings - if (context.parserOptions.ecmaVersion >= 6) { + // Default rule definition: report all lone blocks ruleDef = { - "BlockStatement": function(node) { + BlockStatement: function(node) { if (isLoneBlock(node)) { - loneBlocks.push(node); - } - }, - "BlockStatement:exit": function(node) { - if (loneBlocks.length > 0 && loneBlocks[loneBlocks.length - 1] === node) { - loneBlocks.pop(); report(node); } } }; - ruleDef.VariableDeclaration = function(node) { - if (node.kind === "let" || node.kind === "const") { - markLoneBlock(node); - } - }; + // ES6: report blocks without block-level bindings + if (context.parserOptions.ecmaVersion >= 6) { + ruleDef = { + "BlockStatement": function(node) { + if (isLoneBlock(node)) { + loneBlocks.push(node); + } + }, + "BlockStatement:exit": function(node) { + if (loneBlocks.length > 0 && loneBlocks[loneBlocks.length - 1] === node) { + loneBlocks.pop(); + report(node); + } + } + }; - ruleDef.FunctionDeclaration = function(node) { - if (context.getScope().isStrict) { - markLoneBlock(node); - } - }; + ruleDef.VariableDeclaration = function(node) { + if (node.kind === "let" || node.kind === "const") { + markLoneBlock(node); + } + }; - ruleDef.ClassDeclaration = markLoneBlock; - } + ruleDef.FunctionDeclaration = function(node) { + if (context.getScope().isStrict) { + markLoneBlock(node); + } + }; - return ruleDef; -}; + ruleDef.ClassDeclaration = markLoneBlock; + } -module.exports.schema = []; + return ruleDef; + } +}; diff --git a/lib/rules/no-lonely-if.js b/lib/rules/no-lonely-if.js index 0d8ab9124a6..d93efb5f294 100644 --- a/lib/rules/no-lonely-if.js +++ b/lib/rules/no-lonely-if.js @@ -8,23 +8,33 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `if` statements as the only statement in `else` blocks", + category: "Stylistic Issues", + recommended: false + }, - return { - "IfStatement": function(node) { - var ancestors = context.getAncestors(), - parent = ancestors.pop(), - grandparent = ancestors.pop(); + schema: [] + }, - 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."); + create: 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."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-loop-func.js b/lib/rules/no-loop-func.js index 0ab2fb853ac..e107cec47da 100644 --- a/lib/rules/no-loop-func.js +++ b/lib/rules/no-loop-func.js @@ -152,38 +152,48 @@ function isSafe(funcNode, loopNode, reference) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Reports functions which match the following condition: - * - * - has a loop node in ancestors. - * - has any references which refers to an unsafe variable. - * - * @param {ASTNode} node The AST node to check. - * @returns {boolean} Whether or not the node is within a loop. - */ - function checkForLoops(node) { - var loopNode = getContainingLoopNode(node); - - if (!loopNode) { - return; +module.exports = { + meta: { + docs: { + description: "disallow `function` declarations and expressions inside loop statements", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + /** + * Reports functions which match the following condition: + * + * - has a loop node in ancestors. + * - has any references which refers to an unsafe variable. + * + * @param {ASTNode} node The AST node to check. + * @returns {boolean} Whether or not the node is within a loop. + */ + function checkForLoops(node) { + var loopNode = getContainingLoopNode(node); + + if (!loopNode) { + return; + } + + var references = context.getScope().through; + + if (references.length > 0 && + !references.every(isSafe.bind(null, node, loopNode)) + ) { + context.report(node, "Don't make functions within a loop"); + } } - var references = context.getScope().through; - - if (references.length > 0 && - !references.every(isSafe.bind(null, node, loopNode)) - ) { - context.report(node, "Don't make functions within a loop"); - } + return { + "ArrowFunctionExpression": checkForLoops, + "FunctionExpression": checkForLoops, + "FunctionDeclaration": checkForLoops + }; } - - return { - "ArrowFunctionExpression": checkForLoops, - "FunctionExpression": checkForLoops, - "FunctionDeclaration": checkForLoops - }; }; - -module.exports.schema = []; diff --git a/lib/rules/no-magic-numbers.js b/lib/rules/no-magic-numbers.js index c015f9ed93f..b63a525716d 100644 --- a/lib/rules/no-magic-numbers.js +++ b/lib/rules/no-magic-numbers.js @@ -35,126 +35,136 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var config = context.options[0] || {}, - detectObjects = !!config.detectObjects, - enforceConst = !!config.enforceConst, - ignore = config.ignore || [], - ignoreArrayIndexes = !!config.ignoreArrayIndexes; - - /** - * Returns whether the node is number literal - * @param {Node} node - the node literal being evaluated - * @returns {boolean} true if the node is a number literal - */ - function isNumber(node) { - return typeof node.value === "number"; - } +module.exports = { + meta: { + docs: { + description: "disallow magic numbers", + category: "Best Practices", + recommended: false + }, - /** - * Returns whether the number should be ignored - * @param {number} num - the number - * @returns {boolean} true if the number should be ignored - */ - function shouldIgnoreNumber(num) { - return ignore.indexOf(num) !== -1; - } + schema: [{ + "type": "object", + "properties": { + "detectObjects": { + "type": "boolean" + }, + "enforceConst": { + "type": "boolean" + }, + "ignore": { + "type": "array", + "items": { + "type": "number" + }, + "uniqueItems": true + }, + "ignoreArrayIndexes": { + "type": "boolean" + } + }, + "additionalProperties": false + }] + }, - /** - * Returns whether the number should be ignored when used as a radix within parseInt() or Number.parseInt() - * @param {ASTNode} parent - the non-"UnaryExpression" parent - * @param {ASTNode} node - the node literal being evaluated - * @returns {boolean} true if the number should be ignored - */ - function shouldIgnoreParseInt(parent, node) { - return parent.type === "CallExpression" && node === parent.arguments[1] && - (parent.callee.name === "parseInt" || - parent.callee.type === "MemberExpression" && - parent.callee.object.name === "Number" && - parent.callee.property.name === "parseInt"); - } + create: function(context) { + var config = context.options[0] || {}, + detectObjects = !!config.detectObjects, + enforceConst = !!config.enforceConst, + ignore = config.ignore || [], + ignoreArrayIndexes = !!config.ignoreArrayIndexes; + + /** + * Returns whether the node is number literal + * @param {Node} node - the node literal being evaluated + * @returns {boolean} true if the node is a number literal + */ + function isNumber(node) { + return typeof node.value === "number"; + } - /** - * Returns whether the number should be ignored when used to define a JSX prop - * @param {ASTNode} parent - the non-"UnaryExpression" parent - * @returns {boolean} true if the number should be ignored - */ - function shouldIgnoreJSXNumbers(parent) { - return parent.type.indexOf("JSX") === 0; - } + /** + * Returns whether the number should be ignored + * @param {number} num - the number + * @returns {boolean} true if the number should be ignored + */ + function shouldIgnoreNumber(num) { + return ignore.indexOf(num) !== -1; + } - /** - * Returns whether the number should be ignored when used as an array index with enabled 'ignoreArrayIndexes' option. - * @param {ASTNode} parent - the non-"UnaryExpression" parent. - * @returns {boolean} true if the number should be ignored - */ - function shouldIgnoreArrayIndexes(parent) { - return parent.type === "MemberExpression" && ignoreArrayIndexes; - } + /** + * Returns whether the number should be ignored when used as a radix within parseInt() or Number.parseInt() + * @param {ASTNode} parent - the non-"UnaryExpression" parent + * @param {ASTNode} node - the node literal being evaluated + * @returns {boolean} true if the number should be ignored + */ + function shouldIgnoreParseInt(parent, node) { + return parent.type === "CallExpression" && node === parent.arguments[1] && + (parent.callee.name === "parseInt" || + parent.callee.type === "MemberExpression" && + parent.callee.object.name === "Number" && + parent.callee.property.name === "parseInt"); + } - return { - "Literal": function(node) { - var parent = node.parent, - value = node.value, - raw = node.raw, - okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"]; + /** + * Returns whether the number should be ignored when used to define a JSX prop + * @param {ASTNode} parent - the non-"UnaryExpression" parent + * @returns {boolean} true if the number should be ignored + */ + function shouldIgnoreJSXNumbers(parent) { + return parent.type.indexOf("JSX") === 0; + } - if (!isNumber(node)) { - return; - } + /** + * Returns whether the number should be ignored when used as an array index with enabled 'ignoreArrayIndexes' option. + * @param {ASTNode} parent - the non-"UnaryExpression" parent. + * @returns {boolean} true if the number should be ignored + */ + function shouldIgnoreArrayIndexes(parent) { + return parent.type === "MemberExpression" && ignoreArrayIndexes; + } - // For negative magic numbers: update the value and parent node - if (parent.type === "UnaryExpression" && parent.operator === "-") { - node = parent; - parent = node.parent; - value = -value; - raw = "-" + raw; - } + return { + "Literal": function(node) { + var parent = node.parent, + value = node.value, + raw = node.raw, + okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"]; - if (shouldIgnoreNumber(value) || - shouldIgnoreParseInt(parent, node) || - shouldIgnoreArrayIndexes(parent) || - shouldIgnoreJSXNumbers(parent)) { - return; - } + if (!isNumber(node)) { + return; + } + + // For negative magic numbers: update the value and parent node + if (parent.type === "UnaryExpression" && parent.operator === "-") { + node = parent; + parent = node.parent; + value = -value; + raw = "-" + raw; + } - if (parent.type === "VariableDeclarator") { - if (enforceConst && parent.parent.kind !== "const") { + if (shouldIgnoreNumber(value) || + shouldIgnoreParseInt(parent, node) || + shouldIgnoreArrayIndexes(parent) || + shouldIgnoreJSXNumbers(parent)) { + return; + } + + if (parent.type === "VariableDeclarator") { + if (enforceConst && parent.parent.kind !== "const") { + context.report({ + node: node, + message: "Number constants declarations must use 'const'" + }); + } + } else if (okTypes.indexOf(parent.type) === -1 || + (parent.type === "AssignmentExpression" && parent.operator !== "=")) { context.report({ node: node, - message: "Number constants declarations must use 'const'" + message: "No magic number: " + raw }); } - } else if (okTypes.indexOf(parent.type) === -1 || - (parent.type === "AssignmentExpression" && parent.operator !== "=")) { - context.report({ - node: node, - message: "No magic number: " + raw - }); } - } - }; + }; + } }; - -module.exports.schema = [{ - "type": "object", - "properties": { - "detectObjects": { - "type": "boolean" - }, - "enforceConst": { - "type": "boolean" - }, - "ignore": { - "type": "array", - "items": { - "type": "number" - }, - "uniqueItems": true - }, - "ignoreArrayIndexes": { - "type": "boolean" - } - }, - "additionalProperties": false -}]; diff --git a/lib/rules/no-mixed-requires.js b/lib/rules/no-mixed-requires.js index 63494d95256..3f3c60fe4a8 100644 --- a/lib/rules/no-mixed-requires.js +++ b/lib/rules/no-mixed-requires.js @@ -9,204 +9,214 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `require` calls to be mixed with regular `var` declarations", + category: "Node.js and CommonJS", + recommended: false + }, + + schema: [ + { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "grouping": { + "type": "boolean" + }, + "allowCall": { + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + } + ] + }, - var grouping = false, - allowCall = false, - options = context.options[0]; + create: function(context) { - if (typeof options === "object") { - grouping = options.grouping; - allowCall = options.allowCall; - } else { - grouping = !!options; - } + var grouping = false, + allowCall = false, + options = context.options[0]; + + if (typeof options === "object") { + grouping = options.grouping; + allowCall = options.allowCall; + } else { + grouping = !!options; + } - /** - * 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 + /** + * Returns the list of built-in modules. + * + * @returns {string[]} An array of built-in Node.js modules. */ - 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" - ]; - } + 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 BUILTIN_MODULES = getBuiltinModules(); - var DECL_REQUIRE = "require", - DECL_UNINITIALIZED = "uninitialized", - DECL_OTHER = "other"; + var DECL_REQUIRE = "require", + DECL_UNINITIALIZED = "uninitialized", + DECL_OTHER = "other"; - var REQ_CORE = "core", - REQ_FILE = "file", - REQ_MODULE = "module", - REQ_COMPUTED = "computed"; + 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) { + /** + * 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; - } + // "var x;" + return DECL_UNINITIALIZED; + } - if (initExpression.type === "CallExpression" && - initExpression.callee.type === "Identifier" && - initExpression.callee.name === "require" - ) { + if (initExpression.type === "CallExpression" && + initExpression.callee.type === "Identifier" && + initExpression.callee.name === "require" + ) { - // "var x = require('util');" - return DECL_REQUIRE; - } else if (allowCall && - initExpression.type === "CallExpression" && - initExpression.callee.type === "CallExpression" - ) { + // "var x = require('util');" + return DECL_REQUIRE; + } else if (allowCall && + initExpression.type === "CallExpression" && + initExpression.callee.type === "CallExpression" + ) { - // "var x = require('diagnose')('sub-module');" - return getDeclarationType(initExpression.callee); - } else if (initExpression.type === "MemberExpression") { + // "var x = require('diagnose')('sub-module');" + return getDeclarationType(initExpression.callee); + } else if (initExpression.type === "MemberExpression") { - // "var x = require('glob').Glob;" - return getDeclarationType(initExpression.object); - } + // "var x = require('glob').Glob;" + return getDeclarationType(initExpression.object); + } - // "var x = 42;" - return DECL_OTHER; - } + // "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") { + /** + * 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('glob').Glob;" + return inferModuleType(initExpression.object); + } else if (initExpression.arguments.length === 0) { - // "var x = require();" - return REQ_COMPUTED; - } + // "var x = require();" + return REQ_COMPUTED; + } - var arg = initExpression.arguments[0]; + var arg = initExpression.arguments[0]; - if (arg.type !== "Literal" || typeof arg.value !== "string") { + if (arg.type !== "Literal" || typeof arg.value !== "string") { - // "var x = require(42);" - return REQ_COMPUTED; - } + // "var x = require(42);" + return REQ_COMPUTED; + } - if (BUILTIN_MODULES.indexOf(arg.value) !== -1) { + if (BUILTIN_MODULES.indexOf(arg.value) !== -1) { - // "var fs = require('fs');" - return REQ_CORE; - } else if (/^\.{0,2}\//.test(arg.value)) { + // "var fs = require('fs');" + return REQ_CORE; + } else if (/^\.{0,2}\//.test(arg.value)) { - // "var utils = require('./utils');" - return REQ_FILE; - } else { + // "var utils = require('./utils');" + return REQ_FILE; + } else { - // "var async = require('async');" - return REQ_MODULE; + // "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 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 = {}; - /** - * 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; - } - }); + declarations.forEach(function(declaration) { + var type = getDeclarationType(declaration.init); - return Object.keys(found).length <= 1; - } + contains[type] = true; + }); + return !!( + contains[DECL_REQUIRE] && + (contains[DECL_UNINITIALIZED] || contains[DECL_OTHER]) + ); + } - return { + /** + * 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 = {}; - "VariableDeclaration": function(node) { + declarations.forEach(function(declaration) { + if (getDeclarationType(declaration.init) === DECL_REQUIRE) { + found[inferModuleType(declaration.init)] = true; + } + }); - 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." - ); - } + return Object.keys(found).length <= 1; } - }; -}; -module.exports.schema = [ - { - "oneOf": [ - { - "type": "boolean" - }, - { - "type": "object", - "properties": { - "grouping": { - "type": "boolean" - }, - "allowCall": { - "type": "boolean" - } - }, - "additionalProperties": false + return { + + "VariableDeclaration": function(node) { + + 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/lib/rules/no-mixed-spaces-and-tabs.js b/lib/rules/no-mixed-spaces-and-tabs.js index b7b191cf02b..dde09a83111 100644 --- a/lib/rules/no-mixed-spaces-and-tabs.js +++ b/lib/rules/no-mixed-spaces-and-tabs.js @@ -11,126 +11,136 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var smartTabs, - ignoredLocs = []; - - switch (context.options[0]) { - case true: // Support old syntax, maybe add deprecation warning here - case "smart-tabs": - smartTabs = true; - break; - default: - smartTabs = false; - } +module.exports = { + meta: { + docs: { + description: "disallow mixed spaces and tabs for indentation", + category: "Stylistic Issues", + recommended: true + }, - /** - * Determines if a given line and column are before a location. - * @param {Location} loc The location object from an AST node. - * @param {int} line The line to check. - * @param {int} column The column to check. - * @returns {boolean} True if the line and column are before the location, false if not. - * @private - */ - function beforeLoc(loc, line, column) { - if (line < loc.start.line) { - return true; - } - return line === loc.start.line && column < loc.start.column; - } + schema: [ + { + "enum": ["smart-tabs", true, false] + } + ] + }, - /** - * Determines if a given line and column are after a location. - * @param {Location} loc The location object from an AST node. - * @param {int} line The line to check. - * @param {int} column The column to check. - * @returns {boolean} True if the line and column are after the location, false if not. - * @private - */ - function afterLoc(loc, line, column) { - if (line > loc.end.line) { - return true; - } - return line === loc.end.line && column > loc.end.column; - } + create: function(context) { - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + var smartTabs, + ignoredLocs = []; - return { + switch (context.options[0]) { + case true: // Support old syntax, maybe add deprecation warning here + case "smart-tabs": + smartTabs = true; + break; + default: + smartTabs = false; + } - "TemplateElement": function(node) { - ignoredLocs.push(node.loc); - }, + /** + * Determines if a given line and column are before a location. + * @param {Location} loc The location object from an AST node. + * @param {int} line The line to check. + * @param {int} column The column to check. + * @returns {boolean} True if the line and column are before the location, false if not. + * @private + */ + function beforeLoc(loc, line, column) { + if (line < loc.start.line) { + return true; + } + return line === loc.start.line && column < loc.start.column; + } - "Program:exit": 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(), - comments = context.getAllComments(); - - comments.forEach(function(comment) { - ignoredLocs.push(comment.loc); - }); - - ignoredLocs.sort(function(first, second) { - if (beforeLoc(first, second.start.line, second.start.column)) { - return 1; - } + /** + * Determines if a given line and column are after a location. + * @param {Location} loc The location object from an AST node. + * @param {int} line The line to check. + * @param {int} column The column to check. + * @returns {boolean} True if the line and column are after the location, false if not. + * @private + */ + function afterLoc(loc, line, column) { + if (line > loc.end.line) { + return true; + } + return line === loc.end.line && column > loc.end.column; + } - if (beforeLoc(second, first.start.line, second.start.column)) { - return -1; - } + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return 0; - }); + return { - if (smartTabs) { + "TemplateElement": function(node) { + ignoredLocs.push(node.loc); + }, + + "Program:exit": function(node) { /* * At least one space followed by a tab - * before non-tab/-space characters begin. + * or the reverse before non-tab/-space + * characters begin. */ - regex = /^(?=[\t ]* \t)/; - } - - lines.forEach(function(line, i) { - match = regex.exec(line); + var regex = /^(?=[\t ]*(\t | \t))/, + match, + lines = context.getSourceLines(), + comments = context.getAllComments(); + + comments.forEach(function(comment) { + ignoredLocs.push(comment.loc); + }); + + ignoredLocs.sort(function(first, second) { + if (beforeLoc(first, second.start.line, second.start.column)) { + return 1; + } - if (match) { - var lineNumber = i + 1, - column = match.index + 1; + if (beforeLoc(second, first.start.line, second.start.column)) { + return -1; + } - for (var j = 0; j < ignoredLocs.length; j++) { - if (beforeLoc(ignoredLocs[j], lineNumber, column)) { - continue; - } - if (afterLoc(ignoredLocs[j], lineNumber, column)) { - continue; - } + return 0; + }); - return; - } + if (smartTabs) { - context.report(node, { line: lineNumber, column: column }, "Mixed spaces and tabs."); + /* + * 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) { + var lineNumber = i + 1, + column = match.index + 1; + + for (var j = 0; j < ignoredLocs.length; j++) { + if (beforeLoc(ignoredLocs[j], lineNumber, column)) { + continue; + } + if (afterLoc(ignoredLocs[j], lineNumber, column)) { + continue; + } + + return; + } + + context.report(node, { line: lineNumber, column: column }, "Mixed spaces and tabs."); + } + }); + } + + }; -module.exports.schema = [ - { - "enum": ["smart-tabs", true, false] } -]; +}; diff --git a/lib/rules/no-multi-spaces.js b/lib/rules/no-multi-spaces.js index e57bdd3910d..674d8c4279d 100644 --- a/lib/rules/no-multi-spaces.js +++ b/lib/rules/no-multi-spaces.js @@ -11,129 +11,141 @@ // 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]; +module.exports = { + meta: { + docs: { + description: "disallow multiple spaces", + category: "Best Practices", + recommended: false + }, + + fixable: "whitespace", + + schema: [ + { + "type": "object", + "properties": { + "exceptions": { + "type": "object", + "patternProperties": { + "^([A-Z][a-z]*)+$": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } - }); - hasExceptions = Object.keys(exceptions).length > 0; - } + ] + }, + + create: 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; + } - /** - * 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; } - return false; - } + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + "Program": function() { + + var source = context.getSource(), + allComments = context.getAllComments(), + pattern = /[^\n\r\u2028\u2029\t ].? {2,}/g, // note: repeating space + token, + previousToken, + parent; + + + /** + * Creates a fix function that removes the multiple spaces between the two tokens + * @param {RuleFixer} leftToken left token + * @param {RuleFixer} rightToken right token + * @returns {function} fix function + * @private + */ + function createFix(leftToken, rightToken) { + return function(fixer) { + return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " "); + }; + } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "Program": function() { - - var source = context.getSource(), - allComments = context.getAllComments(), - pattern = /[^\n\r\u2028\u2029\t ].? {2,}/g, // note: repeating space - token, - previousToken, - parent; - - - /** - * Creates a fix function that removes the multiple spaces between the two tokens - * @param {RuleFixer} leftToken left token - * @param {RuleFixer} rightToken right token - * @returns {function} fix function - * @private - */ - function createFix(leftToken, rightToken) { - return function(fixer) { - return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " "); - }; - } + while (pattern.test(source)) { - while (pattern.test(source)) { + // do not flag anything inside of comments + if (!isIndexInComment(pattern.lastIndex, allComments)) { - // do not flag anything inside of comments - if (!isIndexInComment(pattern.lastIndex, allComments)) { + token = context.getTokenByRangeStart(pattern.lastIndex); + if (token) { + previousToken = context.getTokenBefore(token); - token = context.getTokenByRangeStart(pattern.lastIndex); - if (token) { - previousToken = context.getTokenBefore(token); + if (hasExceptions) { + parent = context.getNodeByRangeIndex(pattern.lastIndex - 1); + } - if (hasExceptions) { - parent = context.getNodeByRangeIndex(pattern.lastIndex - 1); + if (!parent || !exceptions[parent.type]) { + context.report({ + node: token, + loc: token.loc.start, + message: "Multiple spaces found before '{{value}}'.", + data: { value: token.value }, + fix: createFix(previousToken, token) + }); + } } - if (!parent || !exceptions[parent.type]) { - context.report({ - node: token, - loc: token.loc.start, - message: "Multiple spaces found before '{{value}}'.", - data: { value: token.value }, - fix: createFix(previousToken, token) - }); - } } - } } - } - }; + }; -}; - -module.exports.schema = [ - { - "type": "object", - "properties": { - "exceptions": { - "type": "object", - "patternProperties": { - "^([A-Z][a-z]*)+$": { - "type": "boolean" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false } -]; +}; diff --git a/lib/rules/no-multi-str.js b/lib/rules/no-multi-str.js index 470c65871ea..ae1dcca0670 100644 --- a/lib/rules/no-multi-str.js +++ b/lib/rules/no-multi-str.js @@ -11,33 +11,43 @@ // 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; - } +module.exports = { + meta: { + docs: { + description: "disallow multiline strings", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: 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 - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - return { + return { - "Literal": function(node) { - var lineBreak = /\n/; + "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."); + if (lineBreak.test(node.raw) && !isJSXElement(node.parent)) { + context.report(node, "Multiline support is limited to browsers supporting ES5 only."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-multiple-empty-lines.js b/lib/rules/no-multiple-empty-lines.js index 34b9b8eff93..9a83abb69a8 100644 --- a/lib/rules/no-multiple-empty-lines.js +++ b/lib/rules/no-multiple-empty-lines.js @@ -10,152 +10,162 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow multiple empty lines", + category: "Stylistic Issues", + recommended: false + }, - // Use options.max or 2 as default - var max = 2, - maxEOF, - maxBOF; + schema: [ + { + "type": "object", + "properties": { + "max": { + "type": "integer", + "minimum": 0 + }, + "maxEOF": { + "type": "integer", + "minimum": 0 + }, + "maxBOF": { + "type": "integer", + "minimum": 0 + } + }, + "required": ["max"], + "additionalProperties": false + } + ] + }, - // store lines that appear empty but really aren't - var notEmpty = []; + create: function(context) { - if (context.options.length) { - max = context.options[0].max; - maxEOF = context.options[0].maxEOF; - maxBOF = context.options[0].maxBOF; - } + // Use options.max or 2 as default + var max = 2, + maxEOF, + maxBOF; - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + // store lines that appear empty but really aren't + var notEmpty = []; - return { + if (context.options.length) { + max = context.options[0].max; + maxEOF = context.options[0].maxEOF; + maxBOF = context.options[0].maxBOF; + } - "TemplateLiteral": function(node) { - var start = node.loc.start.line; - var end = node.loc.end.line; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - while (start <= end) { - notEmpty.push(start); - start++; - } - }, + return { + + "TemplateLiteral": function(node) { + var start = node.loc.start.line; + var end = node.loc.end.line; - "Program:exit": function checkBlankLines(node) { - var lines = context.getSourceLines(), - currentLocation = -1, - lastLocation, - blankCounter = 0, - location, - firstOfEndingBlankLines, - firstNonBlankLine = -1, - trimmedLines = []; - - lines.forEach(function(str, i) { - var trimmed = str.trim(); - - if ((firstNonBlankLine === -1) && (trimmed !== "")) { - firstNonBlankLine = i; + while (start <= end) { + notEmpty.push(start); + start++; } + }, + + "Program:exit": function checkBlankLines(node) { + var lines = context.getSourceLines(), + currentLocation = -1, + lastLocation, + blankCounter = 0, + location, + firstOfEndingBlankLines, + firstNonBlankLine = -1, + trimmedLines = []; + + lines.forEach(function(str, i) { + var trimmed = str.trim(); + + if ((firstNonBlankLine === -1) && (trimmed !== "")) { + firstNonBlankLine = i; + } - trimmedLines.push(trimmed); - }); + trimmedLines.push(trimmed); + }); - // add the notEmpty lines in there with a placeholder - notEmpty.forEach(function(x, i) { - trimmedLines[i] = x; - }); + // add the notEmpty lines in there with a placeholder + notEmpty.forEach(function(x, i) { + trimmedLines[i] = x; + }); - if (typeof maxEOF === "undefined") { + if (typeof maxEOF === "undefined") { - /* - * Swallow the final newline, as some editors add it - * automatically and we don't want it to cause an issue - */ - if (trimmedLines[trimmedLines.length - 1] === "") { - trimmedLines = trimmedLines.slice(0, -1); - } + /* + * Swallow the final newline, as some editors add it + * automatically and we don't want it to cause an issue + */ + if (trimmedLines[trimmedLines.length - 1] === "") { + trimmedLines = trimmedLines.slice(0, -1); + } - firstOfEndingBlankLines = trimmedLines.length; - } else { + firstOfEndingBlankLines = trimmedLines.length; + } else { - // save the number of the first of the last blank lines - firstOfEndingBlankLines = trimmedLines.length; - while (trimmedLines[firstOfEndingBlankLines - 1] === "" - && firstOfEndingBlankLines > 0) { - firstOfEndingBlankLines--; + // save the number of the first of the last blank lines + firstOfEndingBlankLines = trimmedLines.length; + while (trimmedLines[firstOfEndingBlankLines - 1] === "" + && firstOfEndingBlankLines > 0) { + firstOfEndingBlankLines--; + } } - } - // Aggregate and count blank lines - if (firstNonBlankLine > maxBOF) { - context.report(node, 0, - "Too many blank lines at the beginning of file. Max of " + maxBOF + " allowed."); - } + // Aggregate and count blank lines + if (firstNonBlankLine > maxBOF) { + context.report(node, 0, + "Too many blank lines at the beginning of file. Max of " + maxBOF + " allowed."); + } - lastLocation = currentLocation; - currentLocation = trimmedLines.indexOf("", currentLocation + 1); - while (currentLocation !== -1) { lastLocation = currentLocation; currentLocation = trimmedLines.indexOf("", currentLocation + 1); - if (lastLocation === currentLocation - 1) { - blankCounter++; - } else { - location = { - line: lastLocation + 1, - column: 1 - }; - - if (lastLocation < firstOfEndingBlankLines) { - - // within the file, not at the end - if (blankCounter >= max) { - context.report({ - node: node, - loc: location, - message: "More than " + max + " blank " + (max === 1 ? "line" : "lines") + " not allowed." - }); - } + while (currentLocation !== -1) { + lastLocation = currentLocation; + currentLocation = trimmedLines.indexOf("", currentLocation + 1); + if (lastLocation === currentLocation - 1) { + blankCounter++; } else { - - // inside the last blank lines - if (blankCounter > maxEOF) { - context.report({ - node: node, - loc: location, - message: "Too many blank lines at the end of file. Max of " + maxEOF + " allowed." - }); + location = { + line: lastLocation + 1, + column: 1 + }; + + if (lastLocation < firstOfEndingBlankLines) { + + // within the file, not at the end + if (blankCounter >= max) { + context.report({ + node: node, + loc: location, + message: "More than " + max + " blank " + (max === 1 ? "line" : "lines") + " not allowed." + }); + } + } else { + + // inside the last blank lines + if (blankCounter > maxEOF) { + context.report({ + node: node, + loc: location, + message: "Too many blank lines at the end of file. Max of " + maxEOF + " allowed." + }); + } } - } - // Finally, reset the blank counter - blankCounter = 0; + // Finally, reset the blank counter + blankCounter = 0; + } } } - } - }; + }; -}; - -module.exports.schema = [ - { - "type": "object", - "properties": { - "max": { - "type": "integer", - "minimum": 0 - }, - "maxEOF": { - "type": "integer", - "minimum": 0 - }, - "maxBOF": { - "type": "integer", - "minimum": 0 - } - }, - "required": ["max"], - "additionalProperties": false } -]; +}; diff --git a/lib/rules/no-native-reassign.js b/lib/rules/no-native-reassign.js index b4323369e42..0e1787c8d33 100644 --- a/lib/rules/no-native-reassign.js +++ b/lib/rules/no-native-reassign.js @@ -9,65 +9,75 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var config = context.options[0]; - var exceptions = (config && config.exceptions) || []; +module.exports = { + meta: { + docs: { + description: "disallow reassigning native objects", + category: "Best Practices", + recommended: false + }, - /** - * Reports write references. - * @param {Reference} reference - A reference to check. - * @param {int} index - The index of the reference in the references. - * @param {Reference[]} references - The array that the reference belongs to. - * @returns {void} - */ - function checkReference(reference, index, references) { - var identifier = reference.identifier; + schema: [ + { + "type": "object", + "properties": { + "exceptions": { + "type": "array", + "items": {"type": "string"}, + "uniqueItems": true + } + }, + "additionalProperties": false + } + ] + }, - if (reference.init === false && - reference.isWrite() && + create: function(context) { + var config = context.options[0]; + var exceptions = (config && config.exceptions) || []; - // Destructuring assignments can have multiple default value, - // so possibly there are multiple writeable references for the same identifier. - (index === 0 || references[index - 1].identifier !== identifier) - ) { - context.report({ - node: identifier, - message: "{{name}} is a read-only native object.", - data: identifier - }); - } - } + /** + * Reports write references. + * @param {Reference} reference - A reference to check. + * @param {int} index - The index of the reference in the references. + * @param {Reference[]} references - The array that the reference belongs to. + * @returns {void} + */ + function checkReference(reference, index, references) { + var identifier = reference.identifier; - /** - * Reports write references if a given variable is readonly builtin. - * @param {Variable} variable - A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - if (variable.writeable === false && exceptions.indexOf(variable.name) === -1) { - variable.references.forEach(checkReference); - } - } + if (reference.init === false && + reference.isWrite() && - return { - "Program": function() { - var globalScope = context.getScope(); + // Destructuring assignments can have multiple default value, + // so possibly there are multiple writeable references for the same identifier. + (index === 0 || references[index - 1].identifier !== identifier) + ) { + context.report({ + node: identifier, + message: "{{name}} is a read-only native object.", + data: identifier + }); + } + } - globalScope.variables.forEach(checkVariable); + /** + * Reports write references if a given variable is readonly builtin. + * @param {Variable} variable - A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if (variable.writeable === false && exceptions.indexOf(variable.name) === -1) { + variable.references.forEach(checkReference); + } } - }; -}; -module.exports.schema = [ - { - "type": "object", - "properties": { - "exceptions": { - "type": "array", - "items": {"type": "string"}, - "uniqueItems": true + return { + "Program": function() { + var globalScope = context.getScope(); + + globalScope.variables.forEach(checkVariable); } - }, - "additionalProperties": false + }; } -]; +}; diff --git a/lib/rules/no-negated-condition.js b/lib/rules/no-negated-condition.js index 0d5b283e54a..1aad1d63661 100644 --- a/lib/rules/no-negated-condition.js +++ b/lib/rules/no-negated-condition.js @@ -10,65 +10,75 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow negated conditions", + category: "Stylistic Issues", + recommended: false + }, - /** - * Determines if a given node is an if-else without a condition on the else - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node has an else without an if. - * @private - */ - function hasElseWithoutCondition(node) { - return node.alternate && node.alternate.type !== "IfStatement"; - } + schema: [] + }, - /** - * Determines if a given node is a negated unary expression - * @param {Object} test The test object to check. - * @returns {boolean} True if the node is a negated unary expression. - * @private - */ - function isNegatedUnaryExpression(test) { - return test.type === "UnaryExpression" && test.operator === "!"; - } + create: function(context) { - /** - * Determines if a given node is a negated binary expression - * @param {Test} test The test to check. - * @returns {boolean} True if the node is a negated binary expression. - * @private - */ - function isNegatedBinaryExpression(test) { - return test.type === "BinaryExpression" && - (test.operator === "!=" || test.operator === "!=="); - } + /** + * Determines if a given node is an if-else without a condition on the else + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node has an else without an if. + * @private + */ + function hasElseWithoutCondition(node) { + return node.alternate && node.alternate.type !== "IfStatement"; + } - /** - * Determines if a given node has a negated if expression - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node has a negated if expression. - * @private - */ - function isNegatedIf(node) { - return isNegatedUnaryExpression(node.test) || isNegatedBinaryExpression(node.test); - } + /** + * Determines if a given node is a negated unary expression + * @param {Object} test The test object to check. + * @returns {boolean} True if the node is a negated unary expression. + * @private + */ + function isNegatedUnaryExpression(test) { + return test.type === "UnaryExpression" && test.operator === "!"; + } - return { - "IfStatement": function(node) { - if (!hasElseWithoutCondition(node)) { - return; - } + /** + * Determines if a given node is a negated binary expression + * @param {Test} test The test to check. + * @returns {boolean} True if the node is a negated binary expression. + * @private + */ + function isNegatedBinaryExpression(test) { + return test.type === "BinaryExpression" && + (test.operator === "!=" || test.operator === "!=="); + } - if (isNegatedIf(node)) { - context.report(node, "Unexpected negated condition."); - } - }, - "ConditionalExpression": function(node) { - if (isNegatedIf(node)) { - context.report(node, "Unexpected negated condition."); - } + /** + * Determines if a given node has a negated if expression + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node has a negated if expression. + * @private + */ + function isNegatedIf(node) { + return isNegatedUnaryExpression(node.test) || isNegatedBinaryExpression(node.test); } - }; -}; -module.exports.schema = []; + return { + "IfStatement": function(node) { + if (!hasElseWithoutCondition(node)) { + return; + } + + if (isNegatedIf(node)) { + context.report(node, "Unexpected negated condition."); + } + }, + "ConditionalExpression": function(node) { + if (isNegatedIf(node)) { + context.report(node, "Unexpected negated condition."); + } + } + }; + } +}; diff --git a/lib/rules/no-negated-in-lhs.js b/lib/rules/no-negated-in-lhs.js index 67be9bb06d3..57241bcfc63 100644 --- a/lib/rules/no-negated-in-lhs.js +++ b/lib/rules/no-negated-in-lhs.js @@ -9,17 +9,27 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow negating the left operand in `in` expressions", + category: "Possible Errors", + recommended: true + }, - return { + schema: [] + }, - "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"); + create: 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"); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-nested-ternary.js b/lib/rules/no-nested-ternary.js index 2686ebd9814..f8d745f0a4a 100644 --- a/lib/rules/no-nested-ternary.js +++ b/lib/rules/no-nested-ternary.js @@ -9,16 +9,26 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow nested ternary expressions", + category: "Stylistic Issues", + recommended: false + }, - return { - "ConditionalExpression": function(node) { - if (node.alternate.type === "ConditionalExpression" || - node.consequent.type === "ConditionalExpression") { - context.report(node, "Do not nest ternary expressions"); + schema: [] + }, + + create: function(context) { + + return { + "ConditionalExpression": function(node) { + if (node.alternate.type === "ConditionalExpression" || + node.consequent.type === "ConditionalExpression") { + context.report(node, "Do not nest ternary expressions"); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-new-func.js b/lib/rules/no-new-func.js index cbc0248fb11..4f2b25a1af5 100644 --- a/lib/rules/no-new-func.js +++ b/lib/rules/no-new-func.js @@ -9,29 +9,39 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Checks if the callee is the Function constructor, and if so, reports an issue. - * @param {ASTNode} node The node to check and report on - * @returns {void} - * @private - */ - function validateCallee(node) { - if (node.callee.name === "Function") { - context.report(node, "The Function constructor is eval."); +module.exports = { + meta: { + docs: { + description: "disallow `new` operators with the `Function` object", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Checks if the callee is the Function constructor, and if so, reports an issue. + * @param {ASTNode} node The node to check and report on + * @returns {void} + * @private + */ + function validateCallee(node) { + if (node.callee.name === "Function") { + context.report(node, "The Function constructor is eval."); + } } - } - return { - "NewExpression": validateCallee, - "CallExpression": validateCallee - }; + return { + "NewExpression": validateCallee, + "CallExpression": validateCallee + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-new-object.js b/lib/rules/no-new-object.js index dd1cd10d758..d59b1324e52 100644 --- a/lib/rules/no-new-object.js +++ b/lib/rules/no-new-object.js @@ -9,17 +9,27 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `Object` constructors", + category: "Stylistic Issues", + recommended: false + }, - return { + schema: [] + }, - "NewExpression": function(node) { - if (node.callee.name === "Object") { - context.report(node, "The object literal notation {} is preferrable."); + create: function(context) { + + return { + + "NewExpression": function(node) { + if (node.callee.name === "Object") { + context.report(node, "The object literal notation {} is preferrable."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-new-require.js b/lib/rules/no-new-require.js index cd2eec562fc..f1cbccd733a 100644 --- a/lib/rules/no-new-require.js +++ b/lib/rules/no-new-require.js @@ -9,17 +9,27 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `new` operators with calls to `require`", + category: "Node.js and CommonJS", + recommended: false + }, - return { + schema: [] + }, - "NewExpression": function(node) { - if (node.callee.type === "Identifier" && node.callee.name === "require") { - context.report(node, "Unexpected use of new with require."); + create: function(context) { + + return { + + "NewExpression": function(node) { + if (node.callee.type === "Identifier" && node.callee.name === "require") { + context.report(node, "Unexpected use of new with require."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-new-symbol.js b/lib/rules/no-new-symbol.js index 143deec0a3c..5f28d5c3fd8 100644 --- a/lib/rules/no-new-symbol.js +++ b/lib/rules/no-new-symbol.js @@ -11,25 +11,35 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - return { - "Program:exit": function() { - var globalScope = context.getScope(); - var variable = globalScope.set.get("Symbol"); - - if (variable && variable.defs.length === 0) { - variable.references.forEach(function(ref) { - var node = ref.identifier; - - if (node.parent && node.parent.type === "NewExpression") { - context.report(node, "`Symbol` cannot be called as a constructor."); - } - }); +module.exports = { + meta: { + docs: { + description: "disallow `new` operators with the `Symbol` object", + category: "ECMAScript 6", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + return { + "Program:exit": function() { + var globalScope = context.getScope(); + var variable = globalScope.set.get("Symbol"); + + if (variable && variable.defs.length === 0) { + variable.references.forEach(function(ref) { + var node = ref.identifier; + + if (node.parent && node.parent.type === "NewExpression") { + context.report(node, "`Symbol` cannot be called as a constructor."); + } + }); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-new-wrappers.js b/lib/rules/no-new-wrappers.js index 88c22c13c4b..75b56462ed0 100644 --- a/lib/rules/no-new-wrappers.js +++ b/lib/rules/no-new-wrappers.js @@ -9,19 +9,29 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `new` operators with the `String`, `Number`, and `Boolean` objects", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "NewExpression": function(node) { - var wrapperObjects = ["String", "Number", "Boolean", "Math", "JSON"]; + create: function(context) { - if (wrapperObjects.indexOf(node.callee.name) > -1) { - context.report(node, "Do not use {{fn}} as a constructor.", { fn: node.callee.name }); + 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 }); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-new.js b/lib/rules/no-new.js index e431d4fb78e..c8fbe9aeca2 100644 --- a/lib/rules/no-new.js +++ b/lib/rules/no-new.js @@ -10,18 +10,28 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `new` operators outside of assignments or comparisons", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "ExpressionStatement": function(node) { + create: function(context) { - if (node.expression.type === "NewExpression") { - context.report(node, "Do not use 'new' for side effects."); + return { + + "ExpressionStatement": function(node) { + + if (node.expression.type === "NewExpression") { + context.report(node, "Do not use 'new' for side effects."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-obj-calls.js b/lib/rules/no-obj-calls.js index e8f1c946112..717005a8e96 100644 --- a/lib/rules/no-obj-calls.js +++ b/lib/rules/no-obj-calls.js @@ -9,21 +9,31 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow calling global object properties as functions", + category: "Possible Errors", + recommended: true + }, - return { - "CallExpression": function(node) { + schema: [] + }, - if (node.callee.type === "Identifier") { - var name = node.callee.name; + create: function(context) { - if (name === "Math" || name === "JSON") { - context.report(node, "'{{name}}' is not a function.", { name: name }); + 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 }); + } } } - } - }; + }; + } }; - -module.exports.schema = [];