From f827f7287ce5221cc317db27088668adafc41944 Mon Sep 17 00:00:00 2001 From: Vitor Balocco Date: Sat, 23 Apr 2016 20:45:41 +0200 Subject: [PATCH] Chore: Add metadata to existing rules - Batch 5 (refs #5417) Chore: Add metadata to existing rules - Batch 5 (refs #5417) --- lib/rules/no-octal-escape.js | 46 +- lib/rules/no-octal.js | 28 +- lib/rules/no-param-reassign.js | 232 +++++----- lib/rules/no-path-concat.js | 46 +- lib/rules/no-plusplus.js | 58 ++- lib/rules/no-process-env.js | 34 +- lib/rules/no-process-exit.js | 40 +- lib/rules/no-proto.js | 32 +- lib/rules/no-redeclare.js | 156 ++++--- lib/rules/no-regex-spaces.js | 50 +- lib/rules/no-restricted-globals.js | 110 +++-- lib/rules/no-restricted-imports.js | 58 ++- lib/rules/no-restricted-modules.js | 122 ++--- lib/rules/no-restricted-syntax.js | 62 +-- lib/rules/no-return-assign.js | 72 +-- lib/rules/no-script-url.js | 34 +- lib/rules/no-self-assign.js | 56 ++- lib/rules/no-self-compare.js | 34 +- lib/rules/no-sequences.js | 160 ++++--- lib/rules/no-shadow-restricted-names.js | 82 ++-- lib/rules/no-shadow.js | 300 ++++++------ lib/rules/no-spaced-func.js | 99 ++-- lib/rules/no-sparse-arrays.js | 36 +- lib/rules/no-sync.js | 32 +- lib/rules/no-ternary.js | 26 +- lib/rules/no-this-before-super.js | 446 +++++++++--------- lib/rules/no-throw-literal.js | 40 +- lib/rules/no-trailing-spaces.js | 216 ++++----- lib/rules/no-undef-init.js | 32 +- lib/rules/no-undef.js | 68 +-- lib/rules/no-undefined.js | 32 +- lib/rules/no-underscore-dangle.js | 242 +++++----- lib/rules/no-unexpected-multiline.js | 104 +++-- lib/rules/no-unmodified-loop-condition.js | 198 ++++---- lib/rules/no-unneeded-ternary.js | 94 ++-- lib/rules/no-unreachable.js | 112 ++--- lib/rules/no-unused-expressions.js | 172 +++---- lib/rules/no-unused-labels.js | 124 ++--- lib/rules/no-unused-vars.js | 526 +++++++++++----------- lib/rules/no-use-before-define.js | 204 +++++---- 40 files changed, 2509 insertions(+), 2106 deletions(-) diff --git a/lib/rules/no-octal-escape.js b/lib/rules/no-octal-escape.js index 65be7e397e5..3ca01324b5e 100644 --- a/lib/rules/no-octal-escape.js +++ b/lib/rules/no-octal-escape.js @@ -9,31 +9,41 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow octal escape sequences in string literals", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - Literal: function(node) { - if (typeof node.value !== "string") { - return; - } + create: function(context) { + + return { - var match = node.raw.match(/^([^\\]|\\[^0-7])*\\([0-3][0-7]{1,2}|[4-7][0-7]|[0-7])/), - octalDigit; + Literal: function(node) { + if (typeof node.value !== "string") { + return; + } - if (match) { - octalDigit = match[2]; + var match = node.raw.match(/^([^\\]|\\[^0-7])*\\([0-3][0-7]{1,2}|[4-7][0-7]|[0-7])/), + octalDigit; - // \0 is actually not considered an octal - if (match[2] !== "0" || typeof match[3] !== "undefined") { - context.report(node, "Don't use octal: '\\{{octalDigit}}'. Use '\\u....' instead.", - { octalDigit: octalDigit }); + if (match) { + octalDigit = match[2]; + + // \0 is actually not considered an octal + if (match[2] !== "0" || typeof match[3] !== "undefined") { + context.report(node, "Don't use octal: '\\{{octalDigit}}'. Use '\\u....' instead.", + { octalDigit: octalDigit }); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-octal.js b/lib/rules/no-octal.js index 34b7b151d88..1332dde5e82 100644 --- a/lib/rules/no-octal.js +++ b/lib/rules/no-octal.js @@ -9,17 +9,27 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow octal literals", + category: "Best Practices", + recommended: true + }, - return { + schema: [] + }, - Literal: function(node) { - if (typeof node.value === "number" && /^0[0-7]/.test(node.raw)) { - context.report(node, "Octal literals should not be used."); + create: function(context) { + + return { + + Literal: function(node) { + if (typeof node.value === "number" && /^0[0-7]/.test(node.raw)) { + context.report(node, "Octal literals should not be used."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-param-reassign.js b/lib/rules/no-param-reassign.js index 8b4711bea0a..fad61fc0c1e 100644 --- a/lib/rules/no-param-reassign.js +++ b/lib/rules/no-param-reassign.js @@ -10,128 +10,138 @@ var stopNodePattern = /(?:Statement|Declaration|Function(?:Expression)?|Program)$/; -module.exports = function(context) { - var props = context.options[0] && Boolean(context.options[0].props); - - /** - * Checks whether or not the reference modifies properties of its variable. - * @param {Reference} reference - A reference to check. - * @returns {boolean} Whether or not the reference modifies properties of its variable. - */ - function isModifyingProp(reference) { - var node = reference.identifier; - var parent = node.parent; - - while (parent && !stopNodePattern.test(parent.type)) { - switch (parent.type) { - - // e.g. foo.a = 0; - case "AssignmentExpression": - return parent.left === node; - - // e.g. ++foo.a; - case "UpdateExpression": - return true; - - // e.g. delete foo.a; - case "UnaryExpression": - if (parent.operator === "delete") { +module.exports = { + meta: { + docs: { + description: "disallow reassigning `function` parameters", + category: "Best Practices", + recommended: false + }, + + schema: [ + { + type: "object", + properties: { + props: {type: "boolean"} + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var props = context.options[0] && Boolean(context.options[0].props); + + /** + * Checks whether or not the reference modifies properties of its variable. + * @param {Reference} reference - A reference to check. + * @returns {boolean} Whether or not the reference modifies properties of its variable. + */ + function isModifyingProp(reference) { + var node = reference.identifier; + var parent = node.parent; + + while (parent && !stopNodePattern.test(parent.type)) { + switch (parent.type) { + + // e.g. foo.a = 0; + case "AssignmentExpression": + return parent.left === node; + + // e.g. ++foo.a; + case "UpdateExpression": return true; - } - break; - - // EXCLUDES: e.g. cache.get(foo.a).b = 0; - case "CallExpression": - if (parent.callee !== node) { - return false; - } - break; - - // EXCLUDES: e.g. cache[foo.a] = 0; - case "MemberExpression": - if (parent.property === node) { - return false; - } - break; - - default: - break; + + // e.g. delete foo.a; + case "UnaryExpression": + if (parent.operator === "delete") { + return true; + } + break; + + // EXCLUDES: e.g. cache.get(foo.a).b = 0; + case "CallExpression": + if (parent.callee !== node) { + return false; + } + break; + + // EXCLUDES: e.g. cache[foo.a] = 0; + case "MemberExpression": + if (parent.property === node) { + return false; + } + break; + + default: + break; + } + + node = parent; + parent = node.parent; } - node = parent; - parent = node.parent; + return false; } - return false; - } - - /** - * Reports a reference if is non initializer and writable. - * @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; - - if (identifier && - !reference.init && - - // 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) - ) { - if (reference.isWrite()) { - context.report( - identifier, - "Assignment to function parameter '{{name}}'.", - {name: identifier.name}); - } else if (props && isModifyingProp(reference)) { - context.report( - identifier, - "Assignment to property of function parameter '{{name}}'.", - {name: identifier.name}); + /** + * Reports a reference if is non initializer and writable. + * @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; + + if (identifier && + !reference.init && + + // 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) + ) { + if (reference.isWrite()) { + context.report( + identifier, + "Assignment to function parameter '{{name}}'.", + {name: identifier.name}); + } else if (props && isModifyingProp(reference)) { + context.report( + identifier, + "Assignment to property of function parameter '{{name}}'.", + {name: identifier.name}); + } } } - } - /** - * 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 === "Parameter") { - variable.references.forEach(checkReference); + /** + * 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 === "Parameter") { + variable.references.forEach(checkReference); + } } - } - /** - * 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); - } + /** + * 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); + } - return { + return { - // `:exit` is needed for the `node.parent` property of identifier nodes. - "FunctionDeclaration:exit": checkForFunction, - "FunctionExpression:exit": checkForFunction, - "ArrowFunctionExpression:exit": checkForFunction - }; + // `:exit` is needed for the `node.parent` property of identifier nodes. + "FunctionDeclaration:exit": checkForFunction, + "FunctionExpression:exit": checkForFunction, + "ArrowFunctionExpression:exit": checkForFunction + }; -}; - -module.exports.schema = [ - { - type: "object", - properties: { - props: {type: "boolean"} - }, - additionalProperties: false } -]; +}; diff --git a/lib/rules/no-path-concat.js b/lib/rules/no-path-concat.js index 7b346ecbc20..1412c6c32e3 100644 --- a/lib/rules/no-path-concat.js +++ b/lib/rules/no-path-concat.js @@ -8,32 +8,42 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow string concatenation with `__dirname` and `__filename`", + category: "Node.js and CommonJS", + recommended: false + }, - var MATCHER = /^__(?:dir|file)name$/; + schema: [] + }, - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + create: function(context) { - return { + var MATCHER = /^__(?:dir|file)name$/; - BinaryExpression: function(node) { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - var left = node.left, - right = node.right; + return { - if (node.operator === "+" && - ((left.type === "Identifier" && MATCHER.test(left.name)) || - (right.type === "Identifier" && MATCHER.test(right.name))) - ) { + BinaryExpression: function(node) { - context.report(node, "Use path.join() or path.resolve() instead of + to create paths."); + var left = node.left, + right = node.right; + + if (node.operator === "+" && + ((left.type === "Identifier" && MATCHER.test(left.name)) || + (right.type === "Identifier" && MATCHER.test(right.name))) + ) { + + context.report(node, "Use path.join() or path.resolve() instead of + to create paths."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-plusplus.js b/lib/rules/no-plusplus.js index 802121581f6..159a42be2c1 100644 --- a/lib/rules/no-plusplus.js +++ b/lib/rules/no-plusplus.js @@ -10,36 +10,46 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the unary operators `++` and `--`", + category: "Stylistic Issues", + recommended: false + }, - var config = context.options[0], - allowInForAfterthought = false; + schema: [ + { + type: "object", + properties: { + allowForLoopAfterthoughts: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - if (typeof config === "object") { - allowInForAfterthought = config.allowForLoopAfterthoughts === true; - } + create: function(context) { - return { + var config = context.options[0], + allowInForAfterthought = false; - UpdateExpression: function(node) { - if (allowInForAfterthought && node.parent.type === "ForStatement") { - return; - } - context.report(node, "Unary operator '" + node.operator + "' used."); + if (typeof config === "object") { + allowInForAfterthought = config.allowForLoopAfterthoughts === true; } - }; - -}; + return { -module.exports.schema = [ - { - type: "object", - properties: { - allowForLoopAfterthoughts: { - type: "boolean" + UpdateExpression: function(node) { + if (allowInForAfterthought && node.parent.type === "ForStatement") { + return; + } + context.report(node, "Unary operator '" + node.operator + "' used."); } - }, - additionalProperties: false + + }; + } -]; +}; diff --git a/lib/rules/no-process-env.js b/lib/rules/no-process-env.js index 10b5a7d1d03..af48c780298 100644 --- a/lib/rules/no-process-env.js +++ b/lib/rules/no-process-env.js @@ -8,22 +8,32 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of `process.env`", + category: "Node.js and CommonJS", + recommended: false + }, - return { + schema: [] + }, - MemberExpression: function(node) { - var objectName = node.object.name, - propertyName = node.property.name; + create: function(context) { - if (objectName === "process" && !node.computed && propertyName && propertyName === "env") { - context.report(node, "Unexpected use of process.env."); - } + return { - } + MemberExpression: function(node) { + var objectName = node.object.name, + propertyName = node.property.name; - }; + if (objectName === "process" && !node.computed && propertyName && propertyName === "env") { + context.report(node, "Unexpected use of process.env."); + } -}; + } -module.exports.schema = []; + }; + + } +}; diff --git a/lib/rules/no-process-exit.js b/lib/rules/no-process-exit.js index 2bfa0495540..6d8674418bb 100644 --- a/lib/rules/no-process-exit.js +++ b/lib/rules/no-process-exit.js @@ -8,26 +8,36 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of `process.exit()`", + category: "Node.js and CommonJS", + recommended: false + }, - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + schema: [] + }, - return { + create: function(context) { - CallExpression: function(node) { - var callee = node.callee; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - if (callee.type === "MemberExpression" && callee.object.name === "process" && - callee.property.name === "exit" - ) { - context.report(node, "Don't use process.exit(); throw an error instead."); + return { + + CallExpression: function(node) { + var callee = node.callee; + + if (callee.type === "MemberExpression" && callee.object.name === "process" && + callee.property.name === "exit" + ) { + context.report(node, "Don't use process.exit(); throw an error instead."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-proto.js b/lib/rules/no-proto.js index bbc77980830..325f3d1c019 100644 --- a/lib/rules/no-proto.js +++ b/lib/rules/no-proto.js @@ -9,20 +9,30 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of the `__proto__` property", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - MemberExpression: function(node) { + create: function(context) { - if (node.property && - (node.property.type === "Identifier" && node.property.name === "__proto__" && !node.computed) || - (node.property.type === "Literal" && node.property.value === "__proto__")) { - context.report(node, "The '__proto__' property is deprecated."); + return { + + MemberExpression: function(node) { + + if (node.property && + (node.property.type === "Identifier" && node.property.name === "__proto__" && !node.computed) || + (node.property.type === "Literal" && node.property.value === "__proto__")) { + context.report(node, "The '__proto__' property is deprecated."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-redeclare.js b/lib/rules/no-redeclare.js index 6a0e1f859ef..4253cc85334 100644 --- a/lib/rules/no-redeclare.js +++ b/lib/rules/no-redeclare.js @@ -9,88 +9,98 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = { - builtinGlobals: Boolean(context.options[0] && context.options[0].builtinGlobals) - }; +module.exports = { + meta: { + docs: { + description: "disallow `var` redeclaration", + category: "Best Practices", + recommended: true + }, + + schema: [ + { + type: "object", + properties: { + builtinGlobals: {type: "boolean"} + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var options = { + builtinGlobals: Boolean(context.options[0] && context.options[0].builtinGlobals) + }; - /** - * Find variables in a given scope and flag redeclared ones. - * @param {Scope} scope - An escope scope object. - * @returns {void} - * @private - */ - function findVariablesInScope(scope) { - scope.variables.forEach(function(variable) { - var hasBuiltin = options.builtinGlobals && "writeable" in variable; - var count = (hasBuiltin ? 1 : 0) + variable.identifiers.length; + /** + * Find variables in a given scope and flag redeclared ones. + * @param {Scope} scope - An escope scope object. + * @returns {void} + * @private + */ + function findVariablesInScope(scope) { + scope.variables.forEach(function(variable) { + var hasBuiltin = options.builtinGlobals && "writeable" in variable; + var count = (hasBuiltin ? 1 : 0) + variable.identifiers.length; - if (count >= 2) { - variable.identifiers.sort(function(a, b) { - return a.range[1] - b.range[1]; - }); + if (count >= 2) { + variable.identifiers.sort(function(a, b) { + return a.range[1] - b.range[1]; + }); - for (var i = (hasBuiltin ? 0 : 1), l = variable.identifiers.length; i < l; i++) { - context.report( - variable.identifiers[i], - "'{{a}}' is already defined", - {a: variable.name}); + for (var i = (hasBuiltin ? 0 : 1), l = variable.identifiers.length; i < l; i++) { + context.report( + variable.identifiers[i], + "'{{a}}' is already defined", + {a: variable.name}); + } } - } - }); + }); - } + } - /** - * Find variables in the current scope. - * @param {ASTNode} node - The Program node. - * @returns {void} - * @private - */ - function checkForGlobal(node) { - var scope = context.getScope(), - parserOptions = context.parserOptions, - ecmaFeatures = parserOptions.ecmaFeatures || {}; + /** + * Find variables in the current scope. + * @param {ASTNode} node - The Program node. + * @returns {void} + * @private + */ + function checkForGlobal(node) { + var scope = context.getScope(), + parserOptions = context.parserOptions, + ecmaFeatures = parserOptions.ecmaFeatures || {}; - // Nodejs env or modules has a special scope. - if (ecmaFeatures.globalReturn || node.sourceType === "module") { - findVariablesInScope(scope.childScopes[0]); - } else { - findVariablesInScope(scope); + // Nodejs env or modules has a special scope. + if (ecmaFeatures.globalReturn || node.sourceType === "module") { + findVariablesInScope(scope.childScopes[0]); + } else { + findVariablesInScope(scope); + } } - } - /** - * Find variables in the current scope. - * @returns {void} - * @private - */ - function checkForBlock() { - findVariablesInScope(context.getScope()); - } + /** + * Find variables in the current scope. + * @returns {void} + * @private + */ + function checkForBlock() { + findVariablesInScope(context.getScope()); + } - if (context.parserOptions.ecmaVersion >= 6) { - return { - Program: checkForGlobal, - BlockStatement: checkForBlock, - SwitchStatement: checkForBlock - }; - } else { - return { - Program: checkForGlobal, - FunctionDeclaration: checkForBlock, - FunctionExpression: checkForBlock, - ArrowFunctionExpression: checkForBlock - }; + if (context.parserOptions.ecmaVersion >= 6) { + return { + Program: checkForGlobal, + BlockStatement: checkForBlock, + SwitchStatement: checkForBlock + }; + } else { + return { + Program: checkForGlobal, + FunctionDeclaration: checkForBlock, + FunctionExpression: checkForBlock, + ArrowFunctionExpression: checkForBlock + }; + } } }; - -module.exports.schema = [ - { - type: "object", - properties: { - builtinGlobals: {type: "boolean"} - }, - additionalProperties: false - } -]; diff --git a/lib/rules/no-regex-spaces.js b/lib/rules/no-regex-spaces.js index afbe86289c6..05841319243 100644 --- a/lib/rules/no-regex-spaces.js +++ b/lib/rules/no-regex-spaces.js @@ -9,27 +9,37 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - return { - - Literal: function(node) { - var token = context.getFirstToken(node), - nodeType = token.type, - nodeValue = token.value, - multipleSpacesRegex = /( {2,})+?/, - regexResults; - - if (nodeType === "RegularExpression") { - regexResults = multipleSpacesRegex.exec(nodeValue); - - if (regexResults !== null) { - context.report(node, "Spaces are hard to count. Use {" + regexResults[0].length + "}."); +module.exports = { + meta: { + docs: { + description: "disallow multiple spaces in regular expression literals", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + return { + + Literal: function(node) { + var token = context.getFirstToken(node), + nodeType = token.type, + nodeValue = token.value, + multipleSpacesRegex = /( {2,})+?/, + regexResults; + + if (nodeType === "RegularExpression") { + regexResults = multipleSpacesRegex.exec(nodeValue); + + if (regexResults !== null) { + context.report(node, "Spaces are hard to count. Use {" + regexResults[0].length + "}."); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-restricted-globals.js b/lib/rules/no-restricted-globals.js index 939dae93a24..3292705b13b 100644 --- a/lib/rules/no-restricted-globals.js +++ b/lib/rules/no-restricted-globals.js @@ -8,63 +8,73 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var restrictedGlobals = context.options; +module.exports = { + meta: { + docs: { + description: "disallow specified global variables", + category: "Variables", + recommended: false + }, - // if no globals are restricted we don't need to check - if (restrictedGlobals.length === 0) { - return {}; - } - - /** - * Report a variable to be used as a restricted global. - * @param {Reference} reference the variable reference - * @returns {void} - * @private - */ - function reportReference(reference) { - context.report(reference.identifier, "Unexpected use of '{{name}}'", { - name: reference.identifier.name - }); - } - - /** - * Check if the given name is a restricted global name. - * @param {string} name name of a variable - * @returns {boolean} whether the variable is a restricted global or not - * @private - */ - function isRestricted(name) { - return restrictedGlobals.indexOf(name) >= 0; - } + schema: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } + }, - return { - Program: function() { - var scope = context.getScope(); + create: function(context) { + var restrictedGlobals = context.options; - // Report variables declared elsewhere (ex: variables defined as "global" by eslint) - scope.variables.forEach(function(variable) { - if (!variable.defs.length && isRestricted(variable.name)) { - variable.references.forEach(reportReference); - } - }); + // if no globals are restricted we don't need to check + if (restrictedGlobals.length === 0) { + return {}; + } - // Report variables not declared at all - scope.through.forEach(function(reference) { - if (isRestricted(reference.identifier.name)) { - reportReference(reference); - } + /** + * Report a variable to be used as a restricted global. + * @param {Reference} reference the variable reference + * @returns {void} + * @private + */ + function reportReference(reference) { + context.report(reference.identifier, "Unexpected use of '{{name}}'", { + name: reference.identifier.name }); + } + /** + * Check if the given name is a restricted global name. + * @param {string} name name of a variable + * @returns {boolean} whether the variable is a restricted global or not + * @private + */ + function isRestricted(name) { + return restrictedGlobals.indexOf(name) >= 0; } - }; -}; -module.exports.schema = { - type: "array", - items: { - type: "string" - }, - uniqueItems: true + return { + Program: function() { + var scope = context.getScope(); + + // Report variables declared elsewhere (ex: variables defined as "global" by eslint) + scope.variables.forEach(function(variable) { + if (!variable.defs.length && isRestricted(variable.name)) { + variable.references.forEach(reportReference); + } + }); + + // Report variables not declared at all + scope.through.forEach(function(reference) { + if (isRestricted(reference.identifier.name)) { + reportReference(reference); + } + }); + + } + }; + } }; diff --git a/lib/rules/no-restricted-imports.js b/lib/rules/no-restricted-imports.js index 40ab28afbd6..3129ce72784 100644 --- a/lib/rules/no-restricted-imports.js +++ b/lib/rules/no-restricted-imports.js @@ -8,34 +8,44 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var restrictedImports = context.options; +module.exports = { + meta: { + docs: { + description: "disallow specified modules when loaded by `import`", + category: "ECMAScript 6", + recommended: false + }, - // if no imports are restricted we don"t need to check - if (restrictedImports.length === 0) { - return {}; - } + schema: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } + }, + + create: function(context) { + var restrictedImports = context.options; + + // if no imports are restricted we don"t need to check + if (restrictedImports.length === 0) { + return {}; + } - return { - ImportDeclaration: function(node) { - if (node && node.source && node.source.value) { + return { + ImportDeclaration: function(node) { + if (node && node.source && node.source.value) { - var value = node.source.value.trim(); + var value = node.source.value.trim(); - if (restrictedImports.indexOf(value) !== -1) { - context.report(node, "'{{importName}}' import is restricted from being used.", { - importName: value - }); + if (restrictedImports.indexOf(value) !== -1) { + context.report(node, "'{{importName}}' import is restricted from being used.", { + importName: value + }); + } } } - } - }; -}; - -module.exports.schema = { - type: "array", - items: { - type: "string" - }, - uniqueItems: true + }; + } }; diff --git a/lib/rules/no-restricted-modules.js b/lib/rules/no-restricted-modules.js index ff1ca9aa3f8..43e53915628 100644 --- a/lib/rules/no-restricted-modules.js +++ b/lib/rules/no-restricted-modules.js @@ -8,74 +8,84 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow specified modules when loaded by `require`", + category: "Node.js and CommonJS", + recommended: false + }, - // trim restricted module names - var restrictedModules = context.options; + schema: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true + } + }, - // if no modules are restricted we don't need to check the CallExpressions - if (restrictedModules.length === 0) { - return {}; - } + create: function(context) { - /** - * Function to check if a node is a string literal. - * @param {ASTNode} node The node to check. - * @returns {boolean} If the node is a string literal. - */ - function isString(node) { - return node && node.type === "Literal" && typeof node.value === "string"; - } + // trim restricted module names + var restrictedModules = context.options; - /** - * Function to check if a node is a require call. - * @param {ASTNode} node The node to check. - * @returns {boolean} If the node is a require call. - */ - function isRequireCall(node) { - return node.callee.type === "Identifier" && node.callee.name === "require"; - } - - /** - * Function to check if a node has an argument that is an restricted module and return its name. - * @param {ASTNode} node The node to check - * @returns {undefined|String} restricted module name or undefined if node argument isn't restricted. - */ - function getRestrictedModuleName(node) { - var moduleName; + // if no modules are restricted we don't need to check the CallExpressions + if (restrictedModules.length === 0) { + return {}; + } - // node has arguments and first argument is string - if (node.arguments.length && isString(node.arguments[0])) { - var argumentValue = node.arguments[0].value.trim(); + /** + * Function to check if a node is a string literal. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a string literal. + */ + function isString(node) { + return node && node.type === "Literal" && typeof node.value === "string"; + } - // check if argument value is in restricted modules array - if (restrictedModules.indexOf(argumentValue) !== -1) { - moduleName = argumentValue; - } + /** + * Function to check if a node is a require call. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a require call. + */ + function isRequireCall(node) { + return node.callee.type === "Identifier" && node.callee.name === "require"; } - return moduleName; - } + /** + * Function to check if a node has an argument that is an restricted module and return its name. + * @param {ASTNode} node The node to check + * @returns {undefined|String} restricted module name or undefined if node argument isn't restricted. + */ + function getRestrictedModuleName(node) { + var moduleName; - return { - CallExpression: function(node) { - if (isRequireCall(node)) { - var restrictedModuleName = getRestrictedModuleName(node); + // node has arguments and first argument is string + if (node.arguments.length && isString(node.arguments[0])) { + var argumentValue = node.arguments[0].value.trim(); - if (restrictedModuleName) { - context.report(node, "'{{moduleName}}' module is restricted from being used.", { - moduleName: restrictedModuleName - }); + // check if argument value is in restricted modules array + if (restrictedModules.indexOf(argumentValue) !== -1) { + moduleName = argumentValue; } } + + return moduleName; } - }; -}; -module.exports.schema = { - type: "array", - items: { - type: "string" - }, - uniqueItems: true + return { + CallExpression: function(node) { + if (isRequireCall(node)) { + var restrictedModuleName = getRestrictedModuleName(node); + + if (restrictedModuleName) { + context.report(node, "'{{moduleName}}' module is restricted from being used.", { + moduleName: restrictedModuleName + }); + } + } + } + }; + } }; diff --git a/lib/rules/no-restricted-syntax.js b/lib/rules/no-restricted-syntax.js index 3c0d7273954..cd9eb603f98 100644 --- a/lib/rules/no-restricted-syntax.js +++ b/lib/rules/no-restricted-syntax.js @@ -10,34 +10,44 @@ var nodeTypes = require("espree").Syntax; -module.exports = function(context) { - - /** - * Generates a warning from the provided node, saying that node type is not allowed. - * @param {ASTNode} node The node to warn on - * @returns {void} - */ - function warn(node) { - context.report(node, "Using '{{type}}' is not allowed.", node); - } +module.exports = { + meta: { + docs: { + description: "disallow specified syntax", + category: "Stylistic Issues", + recommended: false + }, + + schema: { + type: "array", + items: [ + { + enum: Object.keys(nodeTypes).map(function(k) { + return nodeTypes[k]; + }) + } + ], + uniqueItems: true, + minItems: 0 + } + }, - return context.options.reduce(function(result, nodeType) { - result[nodeType] = warn; + create: function(context) { - return result; - }, {}); + /** + * Generates a warning from the provided node, saying that node type is not allowed. + * @param {ASTNode} node The node to warn on + * @returns {void} + */ + function warn(node) { + context.report(node, "Using '{{type}}' is not allowed.", node); + } -}; + return context.options.reduce(function(result, nodeType) { + result[nodeType] = warn; -module.exports.schema = { - type: "array", - items: [ - { - enum: Object.keys(nodeTypes).map(function(k) { - return nodeTypes[k]; - }) - } - ], - uniqueItems: true, - minItems: 0 + return result; + }, {}); + + } }; diff --git a/lib/rules/no-return-assign.js b/lib/rules/no-return-assign.js index 5819dbd2e34..0d9e0b4b07b 100644 --- a/lib/rules/no-return-assign.js +++ b/lib/rules/no-return-assign.js @@ -34,41 +34,51 @@ function isEnclosedInParens(node, context) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var always = (context.options[0] || "except-parens") !== "except-parens"; - - /** - * Check whether return statement contains assignment - * @param {ASTNode} nodeToCheck node to check - * @param {ASTNode} nodeToReport node to report - * @param {string} message message to report - * @returns {void} - * @private - */ - function checkForAssignInReturn(nodeToCheck, nodeToReport, message) { - if (isAssignment(nodeToCheck) && (always || !isEnclosedInParens(nodeToCheck, context))) { - context.report(nodeToReport, message); - } - } +module.exports = { + meta: { + docs: { + description: "disallow assignment operators in `return` statements", + category: "Best Practices", + recommended: false + }, - return { - ReturnStatement: function(node) { - var message = "Return statement should not contain assignment."; + schema: [ + { + enum: ["except-parens", "always"] + } + ] + }, - checkForAssignInReturn(node.argument, node, message); - }, - ArrowFunctionExpression: function(node) { - if (node.body.type !== "BlockStatement") { - var message = "Arrow function should not return assignment."; + create: function(context) { + var always = (context.options[0] || "except-parens") !== "except-parens"; - checkForAssignInReturn(node.body, node, message); + /** + * Check whether return statement contains assignment + * @param {ASTNode} nodeToCheck node to check + * @param {ASTNode} nodeToReport node to report + * @param {string} message message to report + * @returns {void} + * @private + */ + function checkForAssignInReturn(nodeToCheck, nodeToReport, message) { + if (isAssignment(nodeToCheck) && (always || !isEnclosedInParens(nodeToCheck, context))) { + context.report(nodeToReport, message); } } - }; -}; -module.exports.schema = [ - { - enum: ["except-parens", "always"] + return { + ReturnStatement: function(node) { + var message = "Return statement should not contain assignment."; + + checkForAssignInReturn(node.argument, node, message); + }, + ArrowFunctionExpression: function(node) { + if (node.body.type !== "BlockStatement") { + var message = "Arrow function should not return assignment."; + + checkForAssignInReturn(node.body, node, message); + } + } + }; } -]; +}; diff --git a/lib/rules/no-script-url.js b/lib/rules/no-script-url.js index 4d6032e1298..0605cd86429 100644 --- a/lib/rules/no-script-url.js +++ b/lib/rules/no-script-url.js @@ -11,24 +11,34 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `javascript", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - Literal: function(node) { + create: function(context) { - var value; + return { - if (node.value && typeof node.value === "string") { - value = node.value.toLowerCase(); + Literal: function(node) { - if (value.indexOf("javascript:") === 0) { - context.report(node, "Script URL is a form of eval."); + var value; + + if (node.value && typeof node.value === "string") { + value = node.value.toLowerCase(); + + if (value.indexOf("javascript:") === 0) { + context.report(node, "Script URL is a form of eval."); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-self-assign.js b/lib/rules/no-self-assign.js index 4b4a7967d0a..bcccc3d8380 100644 --- a/lib/rules/no-self-assign.js +++ b/lib/rules/no-self-assign.js @@ -95,29 +95,39 @@ function eachSelfAssignment(left, right, report) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Reports a given node as self assignments. - * - * @param {ASTNode} node - A node to report. This is an Identifier node. - * @returns {void} - */ - function report(node) { - context.report({ - node: node, - message: "'{{name}}' is assigned to itself.", - data: node - }); - } +module.exports = { + meta: { + docs: { + description: "disallow assignments where both sides are exactly the same", + category: "Best Practices", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + /** + * Reports a given node as self assignments. + * + * @param {ASTNode} node - A node to report. This is an Identifier node. + * @returns {void} + */ + function report(node) { + context.report({ + node: node, + message: "'{{name}}' is assigned to itself.", + data: node + }); + } - return { - AssignmentExpression: function(node) { - if (node.operator === "=") { - eachSelfAssignment(node.left, node.right, report); + return { + AssignmentExpression: function(node) { + if (node.operator === "=") { + eachSelfAssignment(node.left, node.right, report); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-self-compare.js b/lib/rules/no-self-compare.js index 3313432b0c4..eef05080b75 100644 --- a/lib/rules/no-self-compare.js +++ b/lib/rules/no-self-compare.js @@ -10,21 +10,31 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow comparisons where both sides are exactly the same", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - BinaryExpression: function(node) { - var operators = ["===", "==", "!==", "!=", ">", "<", ">=", "<="]; + create: function(context) { - if (operators.indexOf(node.operator) > -1 && - (node.left.type === "Identifier" && node.right.type === "Identifier" && node.left.name === node.right.name || - node.left.type === "Literal" && node.right.type === "Literal" && node.left.value === node.right.value)) { - context.report(node, "Comparing to itself is potentially pointless."); + return { + + BinaryExpression: function(node) { + var operators = ["===", "==", "!==", "!=", ">", "<", ">=", "<="]; + + if (operators.indexOf(node.operator) > -1 && + (node.left.type === "Identifier" && node.right.type === "Identifier" && node.left.name === node.right.name || + node.left.type === "Literal" && node.right.type === "Literal" && node.left.value === node.right.value)) { + context.report(node, "Comparing to itself is potentially pointless."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-sequences.js b/lib/rules/no-sequences.js index e6581c077a2..ea20a4b955d 100644 --- a/lib/rules/no-sequences.js +++ b/lib/rules/no-sequences.js @@ -9,89 +9,99 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Parts of the grammar that are required to have parens. - */ - var parenthesized = { - DoWhileStatement: "test", - IfStatement: "test", - SwitchStatement: "discriminant", - WhileStatement: "test", - WithStatement: "object" - - // Omitting CallExpression - commas are parsed as argument separators - // Omitting NewExpression - commas are parsed as argument separators - // Omitting ForInStatement - parts aren't individually parenthesised - // Omitting ForStatement - parts aren't individually parenthesised - }; - - /** - * Determines whether a node is required by the grammar to be wrapped in - * parens, e.g. the test of an if statement. - * @param {ASTNode} node - The AST node - * @returns {boolean} True if parens around node belong to parent node. - */ - function requiresExtraParens(node) { - return node.parent && parenthesized[node.parent.type] && - node === node.parent[parenthesized[node.parent.type]]; - } - - /** - * Check if a node is wrapped in parens. - * @param {ASTNode} node - The AST node - * @returns {boolean} True if the node has a paren on each side. - */ - function isParenthesised(node) { - var previousToken = context.getTokenBefore(node), - nextToken = context.getTokenAfter(node); - - return previousToken && nextToken && - previousToken.value === "(" && previousToken.range[1] <= node.range[0] && - nextToken.value === ")" && nextToken.range[0] >= node.range[1]; - } +module.exports = { + meta: { + docs: { + description: "disallow comma operators", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + /** + * Parts of the grammar that are required to have parens. + */ + var parenthesized = { + DoWhileStatement: "test", + IfStatement: "test", + SwitchStatement: "discriminant", + WhileStatement: "test", + WithStatement: "object" + + // Omitting CallExpression - commas are parsed as argument separators + // Omitting NewExpression - commas are parsed as argument separators + // Omitting ForInStatement - parts aren't individually parenthesised + // Omitting ForStatement - parts aren't individually parenthesised + }; + + /** + * Determines whether a node is required by the grammar to be wrapped in + * parens, e.g. the test of an if statement. + * @param {ASTNode} node - The AST node + * @returns {boolean} True if parens around node belong to parent node. + */ + function requiresExtraParens(node) { + return node.parent && parenthesized[node.parent.type] && + node === node.parent[parenthesized[node.parent.type]]; + } - /** - * Check if a node is wrapped in two levels of parens. - * @param {ASTNode} node - The AST node - * @returns {boolean} True if two parens surround the node on each side. - */ - function isParenthesisedTwice(node) { - var previousToken = context.getTokenBefore(node, 1), - nextToken = context.getTokenAfter(node, 1); - - return isParenthesised(node) && previousToken && nextToken && - previousToken.value === "(" && previousToken.range[1] <= node.range[0] && - nextToken.value === ")" && nextToken.range[0] >= node.range[1]; - } + /** + * Check if a node is wrapped in parens. + * @param {ASTNode} node - The AST node + * @returns {boolean} True if the node has a paren on each side. + */ + function isParenthesised(node) { + var previousToken = context.getTokenBefore(node), + nextToken = context.getTokenAfter(node); + + return previousToken && nextToken && + previousToken.value === "(" && previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && nextToken.range[0] >= node.range[1]; + } - return { - SequenceExpression: function(node) { + /** + * Check if a node is wrapped in two levels of parens. + * @param {ASTNode} node - The AST node + * @returns {boolean} True if two parens surround the node on each side. + */ + function isParenthesisedTwice(node) { + var previousToken = context.getTokenBefore(node, 1), + nextToken = context.getTokenAfter(node, 1); + + return isParenthesised(node) && previousToken && nextToken && + previousToken.value === "(" && previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && nextToken.range[0] >= node.range[1]; + } - // Always allow sequences in for statement update - if (node.parent.type === "ForStatement" && - (node === node.parent.init || node === node.parent.update)) { - return; - } + return { + SequenceExpression: function(node) { - // Wrapping a sequence in extra parens indicates intent - if (requiresExtraParens(node)) { - if (isParenthesisedTwice(node)) { + // Always allow sequences in for statement update + if (node.parent.type === "ForStatement" && + (node === node.parent.init || node === node.parent.update)) { return; } - } else { - if (isParenthesised(node)) { - return; + + // Wrapping a sequence in extra parens indicates intent + if (requiresExtraParens(node)) { + if (isParenthesisedTwice(node)) { + return; + } + } else { + if (isParenthesised(node)) { + return; + } } - } - var child = context.getTokenAfter(node.expressions[0]); + var child = context.getTokenAfter(node.expressions[0]); - context.report(node, child.loc.start, "Unexpected use of comma operator."); - } - }; + context.report(node, child.loc.start, "Unexpected use of comma operator."); + } + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-shadow-restricted-names.js b/lib/rules/no-shadow-restricted-names.js index 023be9d4133..b7731d9d676 100644 --- a/lib/rules/no-shadow-restricted-names.js +++ b/lib/rules/no-shadow-restricted-names.js @@ -8,46 +8,56 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var RESTRICTED = ["undefined", "NaN", "Infinity", "arguments", "eval"]; - - /** - * Check if the node name is present inside the restricted list - * @param {ASTNode} id id to evaluate - * @returns {void} - * @private - */ - function checkForViolation(id) { - if (RESTRICTED.indexOf(id.name) > -1) { - context.report(id, "Shadowing of global property '" + id.name + "'."); - } - } - - return { - VariableDeclarator: function(node) { - checkForViolation(node.id); +module.exports = { + meta: { + docs: { + description: "disallow identifiers from shadowing restricted names", + category: "Variables", + recommended: false }, - ArrowFunctionExpression: function(node) { - [].map.call(node.params, checkForViolation); - }, - FunctionExpression: function(node) { - if (node.id) { - checkForViolation(node.id); + + schema: [] + }, + + create: function(context) { + + var RESTRICTED = ["undefined", "NaN", "Infinity", "arguments", "eval"]; + + /** + * Check if the node name is present inside the restricted list + * @param {ASTNode} id id to evaluate + * @returns {void} + * @private + */ + function checkForViolation(id) { + if (RESTRICTED.indexOf(id.name) > -1) { + context.report(id, "Shadowing of global property '" + id.name + "'."); } - [].map.call(node.params, checkForViolation); - }, - FunctionDeclaration: function(node) { - if (node.id) { + } + + return { + VariableDeclarator: function(node) { checkForViolation(node.id); + }, + ArrowFunctionExpression: function(node) { + [].map.call(node.params, checkForViolation); + }, + FunctionExpression: function(node) { + if (node.id) { + checkForViolation(node.id); + } [].map.call(node.params, checkForViolation); + }, + FunctionDeclaration: function(node) { + if (node.id) { + checkForViolation(node.id); + [].map.call(node.params, checkForViolation); + } + }, + CatchClause: function(node) { + checkForViolation(node.param); } - }, - CatchClause: function(node) { - checkForViolation(node.param); - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 11f982635f2..ee0dd69f3a5 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -15,164 +15,174 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var options = { - builtinGlobals: Boolean(context.options[0] && context.options[0].builtinGlobals), - hoist: (context.options[0] && context.options[0].hoist) || "functions", - allow: (context.options[0] && context.options[0].allow) || [] - }; - - /** - * Check if variable name is allowed. - * - * @param {ASTNode} variable The variable to check. - * @returns {boolean} Whether or not the variable name is allowed. - */ - function isAllowed(variable) { - return options.allow.indexOf(variable.name) !== -1; - } - - /** - * Checks if a variable of the class name in the class scope of ClassDeclaration. - * - * ClassDeclaration creates two variables of its name into its outer scope and its class scope. - * So we should ignore the variable in the class scope. - * - * @param {Object} variable The variable to check. - * @returns {boolean} Whether or not the variable of the class name in the class scope of ClassDeclaration. - */ - function isDuplicatedClassNameVariable(variable) { - var block = variable.scope.block; - - return block.type === "ClassDeclaration" && block.id === variable.identifiers[0]; - } +module.exports = { + meta: { + docs: { + description: "disallow `var` declarations from shadowing variables in the outer scope", + category: "Variables", + recommended: false + }, - /** - * Checks if a variable is inside the initializer of scopeVar. - * - * To avoid reporting at declarations such as `var a = function a() {};`. - * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`. - * - * @param {Object} variable The variable to check. - * @param {Object} scopeVar The scope variable to look for. - * @returns {boolean} Whether or not the variable is inside initializer of scopeVar. - */ - function isOnInitializer(variable, scopeVar) { - var outerScope = scopeVar.scope; - var outerDef = scopeVar.defs[0]; - var outer = outerDef && outerDef.parent && outerDef.parent.range; - var innerScope = variable.scope; - var innerDef = variable.defs[0]; - var inner = innerDef && innerDef.name.range; - - return ( - outer && - inner && - outer[0] < inner[0] && - inner[1] < outer[1] && - ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") && - outerScope === innerScope.upper - ); - } + schema: [ + { + type: "object", + properties: { + builtinGlobals: {type: "boolean"}, + hoist: {enum: ["all", "functions", "never"]}, + allow: { + type: "array", + items: { + type: "string" + } + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + + var options = { + builtinGlobals: Boolean(context.options[0] && context.options[0].builtinGlobals), + hoist: (context.options[0] && context.options[0].hoist) || "functions", + allow: (context.options[0] && context.options[0].allow) || [] + }; + + /** + * Check if variable name is allowed. + * + * @param {ASTNode} variable The variable to check. + * @returns {boolean} Whether or not the variable name is allowed. + */ + function isAllowed(variable) { + return options.allow.indexOf(variable.name) !== -1; + } - /** - * Get a range of a variable's identifier node. - * @param {Object} variable The variable to get. - * @returns {Array|undefined} The range of the variable's identifier node. - */ - function getNameRange(variable) { - var def = variable.defs[0]; + /** + * Checks if a variable of the class name in the class scope of ClassDeclaration. + * + * ClassDeclaration creates two variables of its name into its outer scope and its class scope. + * So we should ignore the variable in the class scope. + * + * @param {Object} variable The variable to check. + * @returns {boolean} Whether or not the variable of the class name in the class scope of ClassDeclaration. + */ + function isDuplicatedClassNameVariable(variable) { + var block = variable.scope.block; + + return block.type === "ClassDeclaration" && block.id === variable.identifiers[0]; + } - return def && def.name.range; - } + /** + * Checks if a variable is inside the initializer of scopeVar. + * + * To avoid reporting at declarations such as `var a = function a() {};`. + * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`. + * + * @param {Object} variable The variable to check. + * @param {Object} scopeVar The scope variable to look for. + * @returns {boolean} Whether or not the variable is inside initializer of scopeVar. + */ + function isOnInitializer(variable, scopeVar) { + var outerScope = scopeVar.scope; + var outerDef = scopeVar.defs[0]; + var outer = outerDef && outerDef.parent && outerDef.parent.range; + var innerScope = variable.scope; + var innerDef = variable.defs[0]; + var inner = innerDef && innerDef.name.range; + + return ( + outer && + inner && + outer[0] < inner[0] && + inner[1] < outer[1] && + ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") && + outerScope === innerScope.upper + ); + } - /** - * Checks if a variable is in TDZ of scopeVar. - * @param {Object} variable The variable to check. - * @param {Object} scopeVar The variable of TDZ. - * @returns {boolean} Whether or not the variable is in TDZ of scopeVar. - */ - function isInTdz(variable, scopeVar) { - var outerDef = scopeVar.defs[0]; - var inner = getNameRange(variable); - var outer = getNameRange(scopeVar); - - return ( - inner && - outer && - inner[1] < outer[0] && - - // Excepts FunctionDeclaration if is {"hoist":"function"}. - (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") - ); - } + /** + * Get a range of a variable's identifier node. + * @param {Object} variable The variable to get. + * @returns {Array|undefined} The range of the variable's identifier node. + */ + function getNameRange(variable) { + var def = variable.defs[0]; - /** - * Checks the current context for shadowed variables. - * @param {Scope} scope - Fixme - * @returns {void} - */ - function checkForShadows(scope) { - var variables = scope.variables; - - for (var i = 0; i < variables.length; ++i) { - var variable = variables[i]; - - // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration. - if (variable.identifiers.length === 0 || - isDuplicatedClassNameVariable(variable) || - isAllowed(variable) - ) { - continue; - } + return def && def.name.range; + } - // Gets shadowed variable. - var shadowed = astUtils.getVariableByName(scope.upper, variable.name); - - if (shadowed && - (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && - !isOnInitializer(variable, shadowed) && - !(options.hoist !== "all" && isInTdz(variable, shadowed)) - ) { - context.report({ - node: variable.identifiers[0], - message: "'{{name}}' is already declared in the upper scope.", - data: variable - }); - } + /** + * Checks if a variable is in TDZ of scopeVar. + * @param {Object} variable The variable to check. + * @param {Object} scopeVar The variable of TDZ. + * @returns {boolean} Whether or not the variable is in TDZ of scopeVar. + */ + function isInTdz(variable, scopeVar) { + var outerDef = scopeVar.defs[0]; + var inner = getNameRange(variable); + var outer = getNameRange(scopeVar); + + return ( + inner && + outer && + inner[1] < outer[0] && + + // Excepts FunctionDeclaration if is {"hoist":"function"}. + (options.hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") + ); } - } - return { - "Program:exit": function() { - var globalScope = context.getScope(); - var stack = globalScope.childScopes.slice(); - var scope; + /** + * Checks the current context for shadowed variables. + * @param {Scope} scope - Fixme + * @returns {void} + */ + function checkForShadows(scope) { + var variables = scope.variables; + + for (var i = 0; i < variables.length; ++i) { + var variable = variables[i]; + + // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration. + if (variable.identifiers.length === 0 || + isDuplicatedClassNameVariable(variable) || + isAllowed(variable) + ) { + continue; + } - while (stack.length) { - scope = stack.pop(); - stack.push.apply(stack, scope.childScopes); - checkForShadows(scope); + // Gets shadowed variable. + var shadowed = astUtils.getVariableByName(scope.upper, variable.name); + + if (shadowed && + (shadowed.identifiers.length > 0 || (options.builtinGlobals && "writeable" in shadowed)) && + !isOnInitializer(variable, shadowed) && + !(options.hoist !== "all" && isInTdz(variable, shadowed)) + ) { + context.report({ + node: variable.identifiers[0], + message: "'{{name}}' is already declared in the upper scope.", + data: variable + }); + } } } - }; -}; + return { + "Program:exit": function() { + var globalScope = context.getScope(); + var stack = globalScope.childScopes.slice(); + var scope; -module.exports.schema = [ - { - type: "object", - properties: { - builtinGlobals: {type: "boolean"}, - hoist: {enum: ["all", "functions", "never"]}, - allow: { - type: "array", - items: { - type: "string" + while (stack.length) { + scope = stack.pop(); + stack.push.apply(stack, scope.childScopes); + checkForShadows(scope); } } - }, - additionalProperties: false + }; + } -]; +}; diff --git a/lib/rules/no-spaced-func.js b/lib/rules/no-spaced-func.js index 1ae192beddf..f0a16121136 100644 --- a/lib/rules/no-spaced-func.js +++ b/lib/rules/no-spaced-func.js @@ -9,52 +9,63 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var sourceCode = context.getSourceCode(); - - /** - * Check if open space is present in a function name - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function detectOpenSpaces(node) { - var lastCalleeToken = sourceCode.getLastToken(node.callee), - prevToken = lastCalleeToken, - parenToken = sourceCode.getTokenAfter(lastCalleeToken); - - // advances to an open parenthesis. - while ( - parenToken && - parenToken.range[1] < node.range[1] && - parenToken.value !== "(" - ) { - prevToken = parenToken; - parenToken = sourceCode.getTokenAfter(parenToken); - } +module.exports = { + meta: { + docs: { + description: "disallow spacing between `function` identifiers and their applications", + category: "Stylistic Issues", + recommended: false + }, + + fixable: "whitespace", + schema: [] + }, + + create: function(context) { + + var sourceCode = context.getSourceCode(); + + /** + * Check if open space is present in a function name + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function detectOpenSpaces(node) { + var lastCalleeToken = sourceCode.getLastToken(node.callee), + prevToken = lastCalleeToken, + parenToken = sourceCode.getTokenAfter(lastCalleeToken); - // look for a space between the callee and the open paren - if (parenToken && - parenToken.range[1] < node.range[1] && - sourceCode.isSpaceBetweenTokens(prevToken, parenToken) - ) { - context.report({ - node: node, - loc: lastCalleeToken.loc.start, - message: "Unexpected space between function name and paren.", - fix: function(fixer) { - return fixer.removeRange([prevToken.range[1], parenToken.range[0]]); - } - }); + // advances to an open parenthesis. + while ( + parenToken && + parenToken.range[1] < node.range[1] && + parenToken.value !== "(" + ) { + prevToken = parenToken; + parenToken = sourceCode.getTokenAfter(parenToken); + } + + // look for a space between the callee and the open paren + if (parenToken && + parenToken.range[1] < node.range[1] && + sourceCode.isSpaceBetweenTokens(prevToken, parenToken) + ) { + context.report({ + node: node, + loc: lastCalleeToken.loc.start, + message: "Unexpected space between function name and paren.", + fix: function(fixer) { + return fixer.removeRange([prevToken.range[1], parenToken.range[0]]); + } + }); + } } - } - return { - CallExpression: detectOpenSpaces, - NewExpression: detectOpenSpaces - }; + return { + CallExpression: detectOpenSpaces, + NewExpression: detectOpenSpaces + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-sparse-arrays.js b/lib/rules/no-sparse-arrays.js index ba124e2c61f..b1ae0ba7403 100644 --- a/lib/rules/no-sparse-arrays.js +++ b/lib/rules/no-sparse-arrays.js @@ -8,26 +8,36 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow sparse arrays", + category: "Possible Errors", + recommended: true + }, + schema: [] + }, - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + create: function(context) { - return { - ArrayExpression: function(node) { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - var emptySpot = node.elements.indexOf(null) > -1; + return { - if (emptySpot) { - context.report(node, "Unexpected comma in middle of array."); + ArrayExpression: function(node) { + + var emptySpot = node.elements.indexOf(null) > -1; + + if (emptySpot) { + context.report(node, "Unexpected comma in middle of array."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-sync.js b/lib/rules/no-sync.js index b2da140b3d2..be6860e75af 100644 --- a/lib/rules/no-sync.js +++ b/lib/rules/no-sync.js @@ -11,20 +11,30 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow synchronous methods", + category: "Node.js and CommonJS", + recommended: false + }, - return { + schema: [] + }, - MemberExpression: function(node) { - var propertyName = node.property.name, - syncRegex = /.*Sync$/; + create: function(context) { - if (syncRegex.exec(propertyName) !== null) { - context.report(node, "Unexpected sync method: '" + propertyName + "'."); + return { + + MemberExpression: function(node) { + var propertyName = node.property.name, + syncRegex = /.*Sync$/; + + if (syncRegex.exec(propertyName) !== null) { + context.report(node, "Unexpected sync method: '" + propertyName + "'."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-ternary.js b/lib/rules/no-ternary.js index f302307192d..fb986152bbf 100644 --- a/lib/rules/no-ternary.js +++ b/lib/rules/no-ternary.js @@ -9,16 +9,26 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow ternary operators", + category: "Stylistic Issues", + recommended: false + }, - return { + schema: [] + }, - ConditionalExpression: function(node) { - context.report(node, "Ternary operator used."); - } + create: function(context) { - }; + return { -}; + ConditionalExpression: function(node) { + context.report(node, "Ternary operator used."); + } + + }; -module.exports.schema = []; + } +}; diff --git a/lib/rules/no-this-before-super.js b/lib/rules/no-this-before-super.js index d753781d084..fb172f47f3a 100644 --- a/lib/rules/no-this-before-super.js +++ b/lib/rules/no-this-before-super.js @@ -34,256 +34,266 @@ function isConstructorFunction(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /* - * Information for each constructor. - * - upper: Information of the upper constructor. - * - hasExtends: A flag which shows whether the owner class has a valid - * `extends` part. - * - scope: The scope of the owner class. - * - codePath: The code path of this constructor. - */ - var funcInfo = null; - - /* - * Information for each code path segment. - * Each key is the id of a code path segment. - * Each value is an object: - * - superCalled: The flag which shows `super()` called in all code paths. - * - invalidNodes: The array of invalid ThisExpression and Super nodes. - */ - var segInfoMap = Object.create(null); - - /** - * Gets whether or not `super()` is called in a given code path segment. - * @param {CodePathSegment} segment - A code path segment to get. - * @returns {boolean} `true` if `super()` is called. - */ - function isCalled(segment) { - return !segment.reachable || segInfoMap[segment.id].superCalled; - } - - /** - * Checks whether or not this is in a constructor. - * @returns {boolean} `true` if this is in a constructor. - */ - function isInConstructorOfDerivedClass() { - return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends); - } +module.exports = { + meta: { + docs: { + description: "disallow `this`/`super` before calling `super()` in constructors", + category: "ECMAScript 6", + recommended: true + }, - /** - * Checks whether or not this is before `super()` is called. - * @returns {boolean} `true` if this is before `super()` is called. - */ - function isBeforeCallOfSuper() { - return ( - isInConstructorOfDerivedClass(funcInfo) && - !funcInfo.codePath.currentSegments.every(isCalled) - ); - } + schema: [] + }, - /** - * Sets a given node as invalid. - * @param {ASTNode} node - A node to set as invalid. This is one of - * a ThisExpression and a Super. - * @returns {void} - */ - function setInvalid(node) { - var segments = funcInfo.codePath.currentSegments; + create: function(context) { - for (var i = 0; i < segments.length; ++i) { - var segment = segments[i]; + /* + * Information for each constructor. + * - upper: Information of the upper constructor. + * - hasExtends: A flag which shows whether the owner class has a valid + * `extends` part. + * - scope: The scope of the owner class. + * - codePath: The code path of this constructor. + */ + var funcInfo = null; + + /* + * Information for each code path segment. + * Each key is the id of a code path segment. + * Each value is an object: + * - superCalled: The flag which shows `super()` called in all code paths. + * - invalidNodes: The array of invalid ThisExpression and Super nodes. + */ + var segInfoMap = Object.create(null); - if (segment.reachable) { - segInfoMap[segment.id].invalidNodes.push(node); - } + /** + * Gets whether or not `super()` is called in a given code path segment. + * @param {CodePathSegment} segment - A code path segment to get. + * @returns {boolean} `true` if `super()` is called. + */ + function isCalled(segment) { + return !segment.reachable || segInfoMap[segment.id].superCalled; } - } - /** - * Sets the current segment as `super` was called. - * @returns {void} - */ - function setSuperCalled() { - var segments = funcInfo.codePath.currentSegments; - - for (var i = 0; i < segments.length; ++i) { - var segment = segments[i]; - - if (segment.reachable) { - segInfoMap[segment.id].superCalled = true; - } + /** + * Checks whether or not this is in a constructor. + * @returns {boolean} `true` if this is in a constructor. + */ + function isInConstructorOfDerivedClass() { + return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends); } - } - - return { /** - * Adds information of a constructor into the stack. - * @param {CodePath} codePath - A code path which was started. - * @param {ASTNode} node - The current node. - * @returns {void} + * Checks whether or not this is before `super()` is called. + * @returns {boolean} `true` if this is before `super()` is called. */ - onCodePathStart: function(codePath, node) { - if (isConstructorFunction(node)) { - - // Class > ClassBody > MethodDefinition > FunctionExpression - var classNode = node.parent.parent.parent; - - funcInfo = { - upper: funcInfo, - isConstructor: true, - hasExtends: Boolean( - classNode.superClass && - !astUtils.isNullOrUndefined(classNode.superClass) - ), - codePath: codePath - }; - } else { - funcInfo = { - upper: funcInfo, - isConstructor: false, - hasExtends: false, - codePath: codePath - }; - } - }, + function isBeforeCallOfSuper() { + return ( + isInConstructorOfDerivedClass(funcInfo) && + !funcInfo.codePath.currentSegments.every(isCalled) + ); + } /** - * Removes the top of stack item. - * - * And this treverses all segments of this code path then reports every - * invalid node. - * - * @param {CodePath} codePath - A code path which was ended. - * @param {ASTNode} node - The current node. + * Sets a given node as invalid. + * @param {ASTNode} node - A node to set as invalid. This is one of + * a ThisExpression and a Super. * @returns {void} */ - onCodePathEnd: function(codePath) { - var isDerivedClass = funcInfo.hasExtends; + function setInvalid(node) { + var segments = funcInfo.codePath.currentSegments; - funcInfo = funcInfo.upper; - if (!isDerivedClass) { - return; - } - - codePath.traverseSegments(function(segment, controller) { - var info = segInfoMap[segment.id]; - - for (var i = 0; i < info.invalidNodes.length; ++i) { - var invalidNode = info.invalidNodes[i]; - - context.report({ - message: "'{{kind}}' is not allowed before 'super()'.", - node: invalidNode, - data: { - kind: invalidNode.type === "Super" ? "super" : "this" - } - }); - } + for (var i = 0; i < segments.length; ++i) { + var segment = segments[i]; - if (info.superCalled) { - controller.skip(); + if (segment.reachable) { + segInfoMap[segment.id].invalidNodes.push(node); } - }); - }, + } + } /** - * Initialize information of a given code path segment. - * @param {CodePathSegment} segment - A code path segment to initialize. + * Sets the current segment as `super` was called. * @returns {void} */ - onCodePathSegmentStart: function(segment) { - if (!isInConstructorOfDerivedClass(funcInfo)) { - return; - } + function setSuperCalled() { + var segments = funcInfo.codePath.currentSegments; - // Initialize info. - segInfoMap[segment.id] = { - superCalled: ( - segment.prevSegments.length > 0 && - segment.prevSegments.every(isCalled) - ), - invalidNodes: [] - }; - }, + for (var i = 0; i < segments.length; ++i) { + var segment = segments[i]; - /** - * Update information of the code path segment when a code path was - * looped. - * @param {CodePathSegment} fromSegment - The code path segment of the - * end of a loop. - * @param {CodePathSegment} toSegment - A code path segment of the head - * of a loop. - * @returns {void} - */ - onCodePathSegmentLoop: function(fromSegment, toSegment) { - if (!isInConstructorOfDerivedClass(funcInfo)) { - return; + if (segment.reachable) { + segInfoMap[segment.id].superCalled = true; + } } + } - // Update information inside of the loop. - funcInfo.codePath.traverseSegments( - {first: toSegment, last: fromSegment}, - function(segment, controller) { + return { + + /** + * Adds information of a constructor into the stack. + * @param {CodePath} codePath - A code path which was started. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathStart: function(codePath, node) { + if (isConstructorFunction(node)) { + + // Class > ClassBody > MethodDefinition > FunctionExpression + var classNode = node.parent.parent.parent; + + funcInfo = { + upper: funcInfo, + isConstructor: true, + hasExtends: Boolean( + classNode.superClass && + !astUtils.isNullOrUndefined(classNode.superClass) + ), + codePath: codePath + }; + } else { + funcInfo = { + upper: funcInfo, + isConstructor: false, + hasExtends: false, + codePath: codePath + }; + } + }, + + /** + * Removes the top of stack item. + * + * And this treverses all segments of this code path then reports every + * invalid node. + * + * @param {CodePath} codePath - A code path which was ended. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathEnd: function(codePath) { + var isDerivedClass = funcInfo.hasExtends; + + funcInfo = funcInfo.upper; + if (!isDerivedClass) { + return; + } + + codePath.traverseSegments(function(segment, controller) { var info = segInfoMap[segment.id]; + for (var i = 0; i < info.invalidNodes.length; ++i) { + var invalidNode = info.invalidNodes[i]; + + context.report({ + message: "'{{kind}}' is not allowed before 'super()'.", + node: invalidNode, + data: { + kind: invalidNode.type === "Super" ? "super" : "this" + } + }); + } + if (info.superCalled) { - info.invalidNodes = []; controller.skip(); - } else if ( - segment.prevSegments.length > 0 && - segment.prevSegments.every(isCalled) - ) { - info.superCalled = true; - info.invalidNodes = []; } + }); + }, + + /** + * Initialize information of a given code path segment. + * @param {CodePathSegment} segment - A code path segment to initialize. + * @returns {void} + */ + onCodePathSegmentStart: function(segment) { + if (!isInConstructorOfDerivedClass(funcInfo)) { + return; } - ); - }, - /** - * Reports if this is before `super()`. - * @param {ASTNode} node - A target node. - * @returns {void} - */ - ThisExpression: function(node) { - if (isBeforeCallOfSuper()) { - setInvalid(node); - } - }, - - /** - * Reports if this is before `super()`. - * @param {ASTNode} node - A target node. - * @returns {void} - */ - Super: function(node) { - if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) { - setInvalid(node); - } - }, + // Initialize info. + segInfoMap[segment.id] = { + superCalled: ( + segment.prevSegments.length > 0 && + segment.prevSegments.every(isCalled) + ), + invalidNodes: [] + }; + }, + + /** + * Update information of the code path segment when a code path was + * looped. + * @param {CodePathSegment} fromSegment - The code path segment of the + * end of a loop. + * @param {CodePathSegment} toSegment - A code path segment of the head + * of a loop. + * @returns {void} + */ + onCodePathSegmentLoop: function(fromSegment, toSegment) { + if (!isInConstructorOfDerivedClass(funcInfo)) { + return; + } - /** - * Marks `super()` called. - * @param {ASTNode} node - A target node. - * @returns {void} - */ - "CallExpression:exit": function(node) { - if (node.callee.type === "Super" && isBeforeCallOfSuper()) { - setSuperCalled(); + // Update information inside of the loop. + funcInfo.codePath.traverseSegments( + {first: toSegment, last: fromSegment}, + function(segment, controller) { + var info = segInfoMap[segment.id]; + + if (info.superCalled) { + info.invalidNodes = []; + controller.skip(); + } else if ( + segment.prevSegments.length > 0 && + segment.prevSegments.every(isCalled) + ) { + info.superCalled = true; + info.invalidNodes = []; + } + } + ); + }, + + /** + * Reports if this is before `super()`. + * @param {ASTNode} node - A target node. + * @returns {void} + */ + ThisExpression: function(node) { + if (isBeforeCallOfSuper()) { + setInvalid(node); + } + }, + + /** + * Reports if this is before `super()`. + * @param {ASTNode} node - A target node. + * @returns {void} + */ + Super: function(node) { + if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) { + setInvalid(node); + } + }, + + /** + * Marks `super()` called. + * @param {ASTNode} node - A target node. + * @returns {void} + */ + "CallExpression:exit": function(node) { + if (node.callee.type === "Super" && isBeforeCallOfSuper()) { + setSuperCalled(); + } + }, + + /** + * Resets state. + * @returns {void} + */ + "Program:exit": function() { + segInfoMap = Object.create(null); } - }, - - /** - * Resets state. - * @returns {void} - */ - "Program:exit": function() { - segInfoMap = Object.create(null); - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-throw-literal.js b/lib/rules/no-throw-literal.js index c8951732248..bedf94379e8 100644 --- a/lib/rules/no-throw-literal.js +++ b/lib/rules/no-throw-literal.js @@ -47,23 +47,33 @@ function couldBeError(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - return { - - ThrowStatement: function(node) { - if (!couldBeError(node.argument)) { - context.report(node, "Expected an object to be thrown."); - } else if (node.argument.type === "Identifier") { - if (node.argument.name === "undefined") { - context.report(node, "Do not throw undefined."); +module.exports = { + meta: { + docs: { + description: "disallow throwing literals as exceptions", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + return { + + ThrowStatement: function(node) { + if (!couldBeError(node.argument)) { + context.report(node, "Expected an object to be thrown."); + } else if (node.argument.type === "Identifier") { + if (node.argument.name === "undefined") { + context.report(node, "Do not throw undefined."); + } } - } - } + } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-trailing-spaces.js b/lib/rules/no-trailing-spaces.js index 17bf214ecf4..a08907fc95f 100644 --- a/lib/rules/no-trailing-spaces.js +++ b/lib/rules/no-trailing-spaces.js @@ -8,116 +8,128 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var sourceCode = context.getSourceCode(); - - var BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u2028\u2029\u3000]", - SKIP_BLANK = "^" + BLANK_CLASS + "*$", - NONBLANK = BLANK_CLASS + "+$"; - - var options = context.options[0] || {}, - skipBlankLines = options.skipBlankLines || false; - - /** - * Report the error message - * @param {ASTNode} node node to report - * @param {int[]} location range information - * @param {int[]} fixRange Range based on the whole program - * @returns {void} - */ - function report(node, location, fixRange) { - - /* - * Passing node is a bit dirty, because message data will contain big - * text in `source`. But... who cares :) ? - * One more kludge will not make worse the bloody wizardry of this - * plugin. - */ - context.report({ - node: node, - loc: location, - message: "Trailing spaces not allowed.", - fix: function(fixer) { - return fixer.removeRange(fixRange); - } - }); - } +module.exports = { + meta: { + docs: { + description: "disallow trailing whitespace at the end of lines", + category: "Stylistic Issues", + recommended: false + }, + fixable: "whitespace", - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - - Program: function checkTrailingSpaces(node) { - - // Let's hack. Since Espree does not return whitespace nodes, - // fetch the source code and do matching via regexps. - - var re = new RegExp(NONBLANK), - skipMatch = new RegExp(SKIP_BLANK), - matches, - lines = sourceCode.lines, - linebreaks = sourceCode.getText().match(/\r\n|\r|\n|\u2028|\u2029/g), - location, - totalLength = 0, - rangeStart, - rangeEnd, - fixRange = [], - containingNode; - - for (var i = 0, ii = lines.length; i < ii; i++) { - matches = re.exec(lines[i]); - - // Always add linebreak length to line length to accommodate for line break (\n or \r\n) - // Because during the fix time they also reserve one spot in the array. - // Usually linebreak length is 2 for \r\n (CRLF) and 1 for \n (LF) - var linebreakLength = linebreaks && linebreaks[i] ? linebreaks[i].length : 1; - var lineLength = lines[i].length + linebreakLength; - - if (matches) { - location = { - line: i + 1, - column: matches.index - }; - - rangeStart = totalLength + location.column; - rangeEnd = totalLength + lineLength - linebreakLength; - containingNode = sourceCode.getNodeByRangeIndex(rangeStart); - - if (containingNode && containingNode.type === "TemplateElement" && - rangeStart > containingNode.parent.range[0] && - rangeEnd < containingNode.parent.range[1]) { - totalLength += lineLength; - continue; + schema: [ + { + type: "object", + properties: { + skipBlankLines: { + type: "boolean" } + }, + additionalProperties: false + } + ] + }, - // If the line has only whitespace, and skipBlankLines - // is true, don't report it - if (skipBlankLines && skipMatch.test(lines[i])) { - continue; - } + create: function(context) { + var sourceCode = context.getSourceCode(); - fixRange = [rangeStart, rangeEnd]; - report(node, location, fixRange); - } + var BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u2028\u2029\u3000]", + SKIP_BLANK = "^" + BLANK_CLASS + "*$", + NONBLANK = BLANK_CLASS + "+$"; - totalLength += lineLength; - } + var options = context.options[0] || {}, + skipBlankLines = options.skipBlankLines || false; + + /** + * Report the error message + * @param {ASTNode} node node to report + * @param {int[]} location range information + * @param {int[]} fixRange Range based on the whole program + * @returns {void} + */ + function report(node, location, fixRange) { + + /* + * Passing node is a bit dirty, because message data will contain big + * text in `source`. But... who cares :) ? + * One more kludge will not make worse the bloody wizardry of this + * plugin. + */ + context.report({ + node: node, + loc: location, + message: "Trailing spaces not allowed.", + fix: function(fixer) { + return fixer.removeRange(fixRange); + } + }); } - }; -}; -module.exports.schema = [ - { - type: "object", - properties: { - skipBlankLines: { - type: "boolean" + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + + Program: function checkTrailingSpaces(node) { + + // Let's hack. Since Espree does not return whitespace nodes, + // fetch the source code and do matching via regexps. + + var re = new RegExp(NONBLANK), + skipMatch = new RegExp(SKIP_BLANK), + matches, + lines = sourceCode.lines, + linebreaks = sourceCode.getText().match(/\r\n|\r|\n|\u2028|\u2029/g), + location, + totalLength = 0, + rangeStart, + rangeEnd, + fixRange = [], + containingNode; + + for (var i = 0, ii = lines.length; i < ii; i++) { + matches = re.exec(lines[i]); + + // Always add linebreak length to line length to accommodate for line break (\n or \r\n) + // Because during the fix time they also reserve one spot in the array. + // Usually linebreak length is 2 for \r\n (CRLF) and 1 for \n (LF) + var linebreakLength = linebreaks && linebreaks[i] ? linebreaks[i].length : 1; + var lineLength = lines[i].length + linebreakLength; + + if (matches) { + location = { + line: i + 1, + column: matches.index + }; + + rangeStart = totalLength + location.column; + rangeEnd = totalLength + lineLength - linebreakLength; + containingNode = sourceCode.getNodeByRangeIndex(rangeStart); + + if (containingNode && containingNode.type === "TemplateElement" && + rangeStart > containingNode.parent.range[0] && + rangeEnd < containingNode.parent.range[1]) { + totalLength += lineLength; + continue; + } + + // If the line has only whitespace, and skipBlankLines + // is true, don't report it + if (skipBlankLines && skipMatch.test(lines[i])) { + continue; + } + + fixRange = [rangeStart, rangeEnd]; + report(node, location, fixRange); + } + + totalLength += lineLength; + } } - }, - additionalProperties: false + + }; } -]; +}; diff --git a/lib/rules/no-undef-init.js b/lib/rules/no-undef-init.js index a6acd161c2f..8622e45701f 100644 --- a/lib/rules/no-undef-init.js +++ b/lib/rules/no-undef-init.js @@ -9,20 +9,30 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow initializing variables to `undefined`", + category: "Variables", + recommended: false + }, - return { + schema: [] + }, - VariableDeclarator: function(node) { - var name = node.id.name, - init = node.init && node.init.name; + create: function(context) { - if (init === "undefined" && node.parent.kind !== "const") { - context.report(node, "It's not necessary to initialize '{{name}}' to undefined.", { name: name }); + return { + + VariableDeclarator: function(node) { + var name = node.id.name, + init = node.init && node.init.name; + + if (init === "undefined" && node.parent.kind !== "const") { + context.report(node, "It's not necessary to initialize '{{name}}' to undefined.", { name: name }); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-undef.js b/lib/rules/no-undef.js index 4b8a40fbb15..b76ce4bb5bf 100644 --- a/lib/rules/no-undef.js +++ b/lib/rules/no-undef.js @@ -23,39 +23,49 @@ function hasTypeOfOperator(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0]; - var considerTypeOf = options && options.typeof === true || false; +module.exports = { + meta: { + docs: { + description: "disallow the use of undeclared variables unless mentioned in `/*global */` comments", + category: "Variables", + recommended: true + }, - return { - "Program:exit": function(/* node */) { - var globalScope = context.getScope(); + schema: [ + { + type: "object", + properties: { + typeof: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - globalScope.through.forEach(function(ref) { - var identifier = ref.identifier; + create: function(context) { + var options = context.options[0]; + var considerTypeOf = options && options.typeof === true || false; - if (!considerTypeOf && hasTypeOfOperator(identifier)) { - return; - } + return { + "Program:exit": function(/* node */) { + var globalScope = context.getScope(); - context.report({ - node: identifier, - message: "'{{name}}' is not defined.", - data: identifier - }); - }); - } - }; -}; + globalScope.through.forEach(function(ref) { + var identifier = ref.identifier; -module.exports.schema = [ - { - type: "object", - properties: { - typeof: { - type: "boolean" + if (!considerTypeOf && hasTypeOfOperator(identifier)) { + return; + } + + context.report({ + node: identifier, + message: "'{{name}}' is not defined.", + data: identifier + }); + }); } - }, - additionalProperties: false + }; } -]; +}; diff --git a/lib/rules/no-undefined.js b/lib/rules/no-undefined.js index 74f3ed2d426..3ad2128b4ef 100644 --- a/lib/rules/no-undefined.js +++ b/lib/rules/no-undefined.js @@ -8,21 +8,31 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of `undefined` as an identifier", + category: "Variables", + recommended: false + }, - return { + schema: [] + }, - Identifier: function(node) { - if (node.name === "undefined") { - var parent = context.getAncestors().pop(); + create: function(context) { - if (!parent || parent.type !== "MemberExpression" || node !== parent.property || parent.computed) { - context.report(node, "Unexpected use of undefined."); + return { + + Identifier: function(node) { + if (node.name === "undefined") { + var parent = context.getAncestors().pop(); + + if (!parent || parent.type !== "MemberExpression" || node !== parent.property || parent.computed) { + context.report(node, "Unexpected use of undefined."); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-underscore-dangle.js b/lib/rules/no-underscore-dangle.js index 6abfdd6ce72..4217f8adc53 100644 --- a/lib/rules/no-underscore-dangle.js +++ b/lib/rules/no-underscore-dangle.js @@ -9,136 +9,146 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var options = context.options[0] || {}; - var ALLOWED_VARIABLES = options.allow ? options.allow : []; - var allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false; - - //------------------------------------------------------------------------- - // Helpers - //------------------------------------------------------------------------- - - /** - * Check if identifier is present inside the allowed option - * @param {string} identifier name of the node - * @returns {boolean} true if its is present - * @private - */ - function isAllowed(identifier) { - return ALLOWED_VARIABLES.some(function(ident) { - return ident === identifier; - }); - } +module.exports = { + meta: { + docs: { + description: "disallow dangling underscores in identifiers", + category: "Stylistic Issues", + recommended: false + }, - /** - * Check if identifier has a underscore at the end - * @param {ASTNode} identifier node to evaluate - * @returns {boolean} true if its is present - * @private - */ - function hasTrailingUnderscore(identifier) { - var len = identifier.length; + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { + type: "string" + } + }, + allowAfterThis: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + + var options = context.options[0] || {}; + var ALLOWED_VARIABLES = options.allow ? options.allow : []; + var allowAfterThis = typeof options.allowAfterThis !== "undefined" ? options.allowAfterThis : false; + + //------------------------------------------------------------------------- + // Helpers + //------------------------------------------------------------------------- + + /** + * Check if identifier is present inside the allowed option + * @param {string} identifier name of the node + * @returns {boolean} true if its is present + * @private + */ + function isAllowed(identifier) { + return ALLOWED_VARIABLES.some(function(ident) { + return ident === identifier; + }); + } - return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_"); - } + /** + * Check if identifier has a underscore at the end + * @param {ASTNode} identifier node to evaluate + * @returns {boolean} true if its is present + * @private + */ + function hasTrailingUnderscore(identifier) { + var len = identifier.length; - /** - * Check if identifier is a special case member expression - * @param {ASTNode} identifier node to evaluate - * @returns {boolean} true if its is a special case - * @private - */ - function isSpecialCaseIdentifierForMemberExpression(identifier) { - return identifier === "__proto__"; - } + return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_"); + } - /** - * Check if identifier is a special case variable expression - * @param {ASTNode} identifier node to evaluate - * @returns {boolean} true if its is a special case - * @private - */ - function isSpecialCaseIdentifierInVariableExpression(identifier) { + /** + * Check if identifier is a special case member expression + * @param {ASTNode} identifier node to evaluate + * @returns {boolean} true if its is a special case + * @private + */ + function isSpecialCaseIdentifierForMemberExpression(identifier) { + return identifier === "__proto__"; + } - // Checks for the underscore library usage here - return identifier === "_"; - } + /** + * Check if identifier is a special case variable expression + * @param {ASTNode} identifier node to evaluate + * @returns {boolean} true if its is a special case + * @private + */ + function isSpecialCaseIdentifierInVariableExpression(identifier) { - /** - * Check if function has a underscore at the end - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkForTrailingUnderscoreInFunctionDeclaration(node) { - if (node.id) { - var identifier = node.id.name; + // Checks for the underscore library usage here + return identifier === "_"; + } - if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) { - context.report(node, "Unexpected dangling '_' in '" + identifier + "'."); + /** + * Check if function has a underscore at the end + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForTrailingUnderscoreInFunctionDeclaration(node) { + if (node.id) { + var identifier = node.id.name; + + if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) { + context.report(node, "Unexpected dangling '_' in '" + identifier + "'."); + } } } - } - /** - * Check if variable expression has a underscore at the end - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkForTrailingUnderscoreInVariableExpression(node) { - var identifier = node.id.name; - - if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && - !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) { - context.report(node, "Unexpected dangling '_' in '" + identifier + "'."); - } - } + /** + * Check if variable expression has a underscore at the end + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForTrailingUnderscoreInVariableExpression(node) { + var identifier = node.id.name; - /** - * Check if member expression has a underscore at the end - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkForTrailingUnderscoreInMemberExpression(node) { - var identifier = node.property.name, - isMemberOfThis = node.object.type === "ThisExpression"; - - if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && - !(isMemberOfThis && allowAfterThis) && - !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) { - context.report(node, "Unexpected dangling '_' in '" + identifier + "'."); + if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && + !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) { + context.report(node, "Unexpected dangling '_' in '" + identifier + "'."); + } } - } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + /** + * Check if member expression has a underscore at the end + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForTrailingUnderscoreInMemberExpression(node) { + var identifier = node.property.name, + isMemberOfThis = node.object.type === "ThisExpression"; + + if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && + !(isMemberOfThis && allowAfterThis) && + !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) { + context.report(node, "Unexpected dangling '_' in '" + identifier + "'."); + } + } - return { - FunctionDeclaration: checkForTrailingUnderscoreInFunctionDeclaration, - VariableDeclarator: checkForTrailingUnderscoreInVariableExpression, - MemberExpression: checkForTrailingUnderscoreInMemberExpression - }; + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- -}; + return { + FunctionDeclaration: checkForTrailingUnderscoreInFunctionDeclaration, + VariableDeclarator: checkForTrailingUnderscoreInVariableExpression, + MemberExpression: checkForTrailingUnderscoreInMemberExpression + }; -module.exports.schema = [ - { - type: "object", - properties: { - allow: { - type: "array", - items: { - type: "string" - } - }, - allowAfterThis: { - type: "boolean" - } - }, - additionalProperties: false } -]; +}; diff --git a/lib/rules/no-unexpected-multiline.js b/lib/rules/no-unexpected-multiline.js index 4f827e86f2a..c066673301a 100644 --- a/lib/rules/no-unexpected-multiline.js +++ b/lib/rules/no-unexpected-multiline.js @@ -7,63 +7,73 @@ //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow confusing multiline expressions", + category: "Possible Errors", + recommended: true + }, - var FUNCTION_MESSAGE = "Unexpected newline between function and ( of function call."; - var PROPERTY_MESSAGE = "Unexpected newline between object and [ of property access."; - var TAGGED_TEMPLATE_MESSAGE = "Unexpected newline between template tag and template literal."; + schema: [] + }, - /** - * Check to see if there is a newline between the node and the following open bracket - * line's expression - * @param {ASTNode} node The node to check. - * @param {string} msg The error message to use. - * @returns {void} - * @private - */ - function checkForBreakAfter(node, msg) { - var nodeExpressionEnd = node; - var openParen = context.getTokenAfter(node); + create: function(context) { - // Move along until the end of the wrapped expression - while (openParen.value === ")") { - nodeExpressionEnd = openParen; - openParen = context.getTokenAfter(nodeExpressionEnd); - } + var FUNCTION_MESSAGE = "Unexpected newline between function and ( of function call."; + var PROPERTY_MESSAGE = "Unexpected newline between object and [ of property access."; + var TAGGED_TEMPLATE_MESSAGE = "Unexpected newline between template tag and template literal."; - if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) { - context.report(node, openParen.loc.start, msg, { char: openParen.value }); + /** + * Check to see if there is a newline between the node and the following open bracket + * line's expression + * @param {ASTNode} node The node to check. + * @param {string} msg The error message to use. + * @returns {void} + * @private + */ + function checkForBreakAfter(node, msg) { + var nodeExpressionEnd = node; + var openParen = context.getTokenAfter(node); + + // Move along until the end of the wrapped expression + while (openParen.value === ")") { + nodeExpressionEnd = openParen; + openParen = context.getTokenAfter(nodeExpressionEnd); + } + + if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) { + context.report(node, openParen.loc.start, msg, { char: openParen.value }); + } } - } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - return { + return { - MemberExpression: function(node) { - if (!node.computed) { - return; - } - checkForBreakAfter(node.object, PROPERTY_MESSAGE); - }, + MemberExpression: function(node) { + if (!node.computed) { + return; + } + checkForBreakAfter(node.object, PROPERTY_MESSAGE); + }, - TaggedTemplateExpression: function(node) { - if (node.tag.loc.end.line === node.quasi.loc.start.line) { - return; - } - context.report(node, node.loc.start, TAGGED_TEMPLATE_MESSAGE); - }, + TaggedTemplateExpression: function(node) { + if (node.tag.loc.end.line === node.quasi.loc.start.line) { + return; + } + context.report(node, node.loc.start, TAGGED_TEMPLATE_MESSAGE); + }, - CallExpression: function(node) { - if (node.arguments.length === 0) { - return; + CallExpression: function(node) { + if (node.arguments.length === 0) { + return; + } + checkForBreakAfter(node.callee, FUNCTION_MESSAGE); } - checkForBreakAfter(node.callee, FUNCTION_MESSAGE); - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-unmodified-loop-condition.js b/lib/rules/no-unmodified-loop-condition.js index 6fbb7768a40..ed49b5996ef 100644 --- a/lib/rules/no-unmodified-loop-condition.js +++ b/lib/rules/no-unmodified-loop-condition.js @@ -243,115 +243,125 @@ function updateModifiedFlag(conditions, modifiers) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var groupMap = null; - - /** - * Reports a given condition info. - * - * @param {LoopConditionInfo} condition - A loop condition info to report. - * @returns {void} - */ - function report(condition) { - var node = condition.reference.identifier; - - context.report({ - node: node, - message: "'{{name}}' is not modified in this loop.", - data: node - }); - } +module.exports = { + meta: { + docs: { + description: "disallow unmodified loop conditions", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + var groupMap = null; + + /** + * Reports a given condition info. + * + * @param {LoopConditionInfo} condition - A loop condition info to report. + * @returns {void} + */ + function report(condition) { + var node = condition.reference.identifier; + + context.report({ + node: node, + message: "'{{name}}' is not modified in this loop.", + data: node + }); + } - /** - * Registers given conditions to the group the condition belongs to. - * - * @param {LoopConditionInfo[]} conditions - A loop condition info to - * register. - * @returns {void} - */ - function registerConditionsToGroup(conditions) { - for (var i = 0; i < conditions.length; ++i) { - var condition = conditions[i]; - - if (condition.group) { - var group = groupMap.get(condition.group); - - if (!group) { - group = []; - groupMap.set(condition.group, group); + /** + * Registers given conditions to the group the condition belongs to. + * + * @param {LoopConditionInfo[]} conditions - A loop condition info to + * register. + * @returns {void} + */ + function registerConditionsToGroup(conditions) { + for (var i = 0; i < conditions.length; ++i) { + var condition = conditions[i]; + + if (condition.group) { + var group = groupMap.get(condition.group); + + if (!group) { + group = []; + groupMap.set(condition.group, group); + } + group.push(condition); } - group.push(condition); } } - } - /** - * Reports references which are inside of unmodified groups. - * - * @param {LoopConditionInfo[]} conditions - A loop condition info to report. - * @returns {void} - */ - function checkConditionsInGroup(conditions) { - if (conditions.every(isUnmodified)) { - conditions.forEach(report); + /** + * Reports references which are inside of unmodified groups. + * + * @param {LoopConditionInfo[]} conditions - A loop condition info to report. + * @returns {void} + */ + function checkConditionsInGroup(conditions) { + if (conditions.every(isUnmodified)) { + conditions.forEach(report); + } } - } - /** - * Finds unmodified references which are inside of a loop condition. - * Then reports the references which are outside of groups. - * - * @param {escope.Variable} variable - A variable to report. - * @returns {void} - */ - function checkReferences(variable) { - - // Gets references that exist in loop conditions. - var conditions = variable - .references - .map(toLoopCondition) - .filter(Boolean); - - if (conditions.length === 0) { - return; - } + /** + * Finds unmodified references which are inside of a loop condition. + * Then reports the references which are outside of groups. + * + * @param {escope.Variable} variable - A variable to report. + * @returns {void} + */ + function checkReferences(variable) { + + // Gets references that exist in loop conditions. + var conditions = variable + .references + .map(toLoopCondition) + .filter(Boolean); + + if (conditions.length === 0) { + return; + } - // Registers the conditions to belonging groups. - registerConditionsToGroup(conditions); + // Registers the conditions to belonging groups. + registerConditionsToGroup(conditions); - // Check the conditions are modified. - var modifiers = variable.references.filter(isWriteReference); + // Check the conditions are modified. + var modifiers = variable.references.filter(isWriteReference); - if (modifiers.length > 0) { - updateModifiedFlag(conditions, modifiers); + if (modifiers.length > 0) { + updateModifiedFlag(conditions, modifiers); + } + + /* + * Reports the conditions which are not belonging to groups. + * Others will be reported after all variables are done. + */ + conditions + .filter(isUnmodifiedAndNotBelongToGroup) + .forEach(report); } - /* - * Reports the conditions which are not belonging to groups. - * Others will be reported after all variables are done. - */ - conditions - .filter(isUnmodifiedAndNotBelongToGroup) - .forEach(report); - } + return { + "Program:exit": function() { + var queue = [context.getScope()]; - return { - "Program:exit": function() { - var queue = [context.getScope()]; + groupMap = new Map(); - groupMap = new Map(); + var scope; - var scope; + while ((scope = queue.pop())) { + pushAll(queue, scope.childScopes); + scope.variables.forEach(checkReferences); + } - while ((scope = queue.pop())) { - pushAll(queue, scope.childScopes); - scope.variables.forEach(checkReferences); + groupMap.forEach(checkConditionsInGroup); + groupMap = null; } - - groupMap.forEach(checkConditionsInGroup); - groupMap = null; - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-unneeded-ternary.js b/lib/rules/no-unneeded-ternary.js index 16a30b01e0f..1c344a42e61 100644 --- a/lib/rules/no-unneeded-ternary.js +++ b/lib/rules/no-unneeded-ternary.js @@ -9,52 +9,62 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0] || {}; - var defaultAssignment = options.defaultAssignment !== false; - - /** - * Test if the node is a boolean literal - * @param {ASTNode} node - The node to report. - * @returns {boolean} True if the its a boolean literal - * @private - */ - function isBooleanLiteral(node) { - return node.type === "Literal" && typeof node.value === "boolean"; - } +module.exports = { + meta: { + docs: { + description: "disallow ternary operators when simpler alternatives exist", + category: "Stylistic Issues", + recommended: false + }, - /** - * Test if the node matches the pattern id ? id : expression - * @param {ASTNode} node - The ConditionalExpression to check. - * @returns {boolean} True if the pattern is matched, and false otherwise - * @private - */ - function matchesDefaultAssignment(node) { - return node.test.type === "Identifier" && - node.consequent.type === "Identifier" && - node.test.name === node.consequent.name; - } + schema: [ + { + type: "object", + properties: { + defaultAssignment: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, - return { + create: function(context) { + var options = context.options[0] || {}; + var defaultAssignment = options.defaultAssignment !== false; - ConditionalExpression: function(node) { - if (isBooleanLiteral(node.alternate) && isBooleanLiteral(node.consequent)) { - context.report(node, node.consequent.loc.start, "Unnecessary use of boolean literals in conditional expression"); - } else if (!defaultAssignment && matchesDefaultAssignment(node)) { - context.report(node, node.consequent.loc.start, "Unnecessary use of conditional expression for default assignment"); - } + /** + * Test if the node is a boolean literal + * @param {ASTNode} node - The node to report. + * @returns {boolean} True if the its a boolean literal + * @private + */ + function isBooleanLiteral(node) { + return node.type === "Literal" && typeof node.value === "boolean"; } - }; -}; -module.exports.schema = [ - { - type: "object", - properties: { - defaultAssignment: { - type: "boolean" + /** + * Test if the node matches the pattern id ? id : expression + * @param {ASTNode} node - The ConditionalExpression to check. + * @returns {boolean} True if the pattern is matched, and false otherwise + * @private + */ + function matchesDefaultAssignment(node) { + return node.test.type === "Identifier" && + node.consequent.type === "Identifier" && + node.test.name === node.consequent.name; + } + + return { + + ConditionalExpression: function(node) { + if (isBooleanLiteral(node.alternate) && isBooleanLiteral(node.consequent)) { + context.report(node, node.consequent.loc.start, "Unnecessary use of boolean literals in conditional expression"); + } else if (!defaultAssignment && matchesDefaultAssignment(node)) { + context.report(node, node.consequent.loc.start, "Unnecessary use of conditional expression for default assignment"); + } } - }, - additionalProperties: false + }; } -]; +}; diff --git a/lib/rules/no-unreachable.js b/lib/rules/no-unreachable.js index cbdb402dcb9..c28a6d2f2fa 100644 --- a/lib/rules/no-unreachable.js +++ b/lib/rules/no-unreachable.js @@ -30,63 +30,73 @@ function isUnreachable(segment) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var currentCodePath = null; +module.exports = { + meta: { + docs: { + description: "disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + var currentCodePath = null; - /** - * Reports a given node if it's unreachable. - * @param {ASTNode} node - A statement node to report. - * @returns {void} - */ - function reportIfUnreachable(node) { - if (currentCodePath.currentSegments.every(isUnreachable)) { - context.report({message: "Unreachable code.", node: node}); + /** + * Reports a given node if it's unreachable. + * @param {ASTNode} node - A statement node to report. + * @returns {void} + */ + function reportIfUnreachable(node) { + if (currentCodePath.currentSegments.every(isUnreachable)) { + context.report({message: "Unreachable code.", node: node}); + } } - } - return { + return { - // Manages the current code path. - onCodePathStart: function(codePath) { - currentCodePath = codePath; - }, + // Manages the current code path. + onCodePathStart: function(codePath) { + currentCodePath = codePath; + }, - onCodePathEnd: function() { - currentCodePath = currentCodePath.upper; - }, + onCodePathEnd: function() { + currentCodePath = currentCodePath.upper; + }, - // Registers for all statement nodes (excludes FunctionDeclaration). - BlockStatement: reportIfUnreachable, - BreakStatement: reportIfUnreachable, - ClassDeclaration: reportIfUnreachable, - ContinueStatement: reportIfUnreachable, - DebuggerStatement: reportIfUnreachable, - DoWhileStatement: reportIfUnreachable, - EmptyStatement: reportIfUnreachable, - ExpressionStatement: reportIfUnreachable, - ForInStatement: reportIfUnreachable, - ForOfStatement: reportIfUnreachable, - ForStatement: reportIfUnreachable, - IfStatement: reportIfUnreachable, - ImportDeclaration: reportIfUnreachable, - LabeledStatement: reportIfUnreachable, - ReturnStatement: reportIfUnreachable, - SwitchStatement: reportIfUnreachable, - ThrowStatement: reportIfUnreachable, - TryStatement: reportIfUnreachable, + // Registers for all statement nodes (excludes FunctionDeclaration). + BlockStatement: reportIfUnreachable, + BreakStatement: reportIfUnreachable, + ClassDeclaration: reportIfUnreachable, + ContinueStatement: reportIfUnreachable, + DebuggerStatement: reportIfUnreachable, + DoWhileStatement: reportIfUnreachable, + EmptyStatement: reportIfUnreachable, + ExpressionStatement: reportIfUnreachable, + ForInStatement: reportIfUnreachable, + ForOfStatement: reportIfUnreachable, + ForStatement: reportIfUnreachable, + IfStatement: reportIfUnreachable, + ImportDeclaration: reportIfUnreachable, + LabeledStatement: reportIfUnreachable, + ReturnStatement: reportIfUnreachable, + SwitchStatement: reportIfUnreachable, + ThrowStatement: reportIfUnreachable, + TryStatement: reportIfUnreachable, - VariableDeclaration: function(node) { - if (node.kind !== "var" || node.declarations.some(isInitialized)) { - reportIfUnreachable(node); - } - }, + VariableDeclaration: function(node) { + if (node.kind !== "var" || node.declarations.some(isInitialized)) { + reportIfUnreachable(node); + } + }, - WhileStatement: reportIfUnreachable, - WithStatement: reportIfUnreachable, - ExportNamedDeclaration: reportIfUnreachable, - ExportDefaultDeclaration: reportIfUnreachable, - ExportAllDeclaration: reportIfUnreachable - }; + WhileStatement: reportIfUnreachable, + WithStatement: reportIfUnreachable, + ExportNamedDeclaration: reportIfUnreachable, + ExportDefaultDeclaration: reportIfUnreachable, + ExportAllDeclaration: reportIfUnreachable + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-unused-expressions.js b/lib/rules/no-unused-expressions.js index 019166a2ff5..9438268ab24 100644 --- a/lib/rules/no-unused-expressions.js +++ b/lib/rules/no-unused-expressions.js @@ -8,100 +8,110 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var config = context.options[0] || {}, - allowShortCircuit = config.allowShortCircuit || false, - allowTernary = config.allowTernary || false; +module.exports = { + meta: { + docs: { + description: "disallow unused expressions", + category: "Best Practices", + recommended: false + }, - /** - * @param {ASTNode} node - any node - * @returns {boolean} whether the given node structurally represents a directive - */ - function looksLikeDirective(node) { - return node.type === "ExpressionStatement" && - node.expression.type === "Literal" && typeof node.expression.value === "string"; - } + schema: [ + { + type: "object", + properties: { + allowShortCircuit: { + type: "boolean" + }, + allowTernary: { + type: "boolean" + } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + var config = context.options[0] || {}, + allowShortCircuit = config.allowShortCircuit || false, + allowTernary = config.allowTernary || false; + + /** + * @param {ASTNode} node - any node + * @returns {boolean} whether the given node structurally represents a directive + */ + function looksLikeDirective(node) { + return node.type === "ExpressionStatement" && + node.expression.type === "Literal" && typeof node.expression.value === "string"; + } - /** - * @param {Function} predicate - ([a] -> Boolean) the function used to make the determination - * @param {a[]} list - the input list - * @returns {a[]} the leading sequence of members in the given list that pass the given predicate - */ - function takeWhile(predicate, list) { - for (var i = 0, l = list.length; i < l; ++i) { - if (!predicate(list[i])) { - break; + /** + * @param {Function} predicate - ([a] -> Boolean) the function used to make the determination + * @param {a[]} list - the input list + * @returns {a[]} the leading sequence of members in the given list that pass the given predicate + */ + function takeWhile(predicate, list) { + for (var i = 0, l = list.length; i < l; ++i) { + if (!predicate(list[i])) { + break; + } } + return [].slice.call(list, 0, i); } - return [].slice.call(list, 0, i); - } - /** - * @param {ASTNode} node - a Program or BlockStatement node - * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body - */ - function directives(node) { - return takeWhile(looksLikeDirective, node.body); - } + /** + * @param {ASTNode} node - a Program or BlockStatement node + * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body + */ + function directives(node) { + return takeWhile(looksLikeDirective, node.body); + } - /** - * @param {ASTNode} node - any node - * @param {ASTNode[]} ancestors - the given node's ancestors - * @returns {boolean} whether the given node is considered a directive in its current position - */ - function isDirective(node, ancestors) { - var parent = ancestors[ancestors.length - 1], - grandparent = ancestors[ancestors.length - 2]; + /** + * @param {ASTNode} node - any node + * @param {ASTNode[]} ancestors - the given node's ancestors + * @returns {boolean} whether the given node is considered a directive in its current position + */ + function isDirective(node, ancestors) { + var parent = ancestors[ancestors.length - 1], + grandparent = ancestors[ancestors.length - 2]; - return (parent.type === "Program" || parent.type === "BlockStatement" && - (/Function/.test(grandparent.type))) && - directives(parent).indexOf(node) >= 0; - } + return (parent.type === "Program" || parent.type === "BlockStatement" && + (/Function/.test(grandparent.type))) && + directives(parent).indexOf(node) >= 0; + } - /** - * Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags. - * @param {ASTNode} node - any node - * @returns {boolean} whether the given node is a valid expression - */ - function isValidExpression(node) { - if (allowTernary) { + /** + * Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags. + * @param {ASTNode} node - any node + * @returns {boolean} whether the given node is a valid expression + */ + function isValidExpression(node) { + if (allowTernary) { - // Recursive check for ternary and logical expressions - if (node.type === "ConditionalExpression") { - return isValidExpression(node.consequent) && isValidExpression(node.alternate); + // Recursive check for ternary and logical expressions + if (node.type === "ConditionalExpression") { + return isValidExpression(node.consequent) && isValidExpression(node.alternate); + } } - } - if (allowShortCircuit) { - if (node.type === "LogicalExpression") { - return isValidExpression(node.right); + if (allowShortCircuit) { + if (node.type === "LogicalExpression") { + return isValidExpression(node.right); + } } - } - return /^(?:Assignment|Call|New|Update|Yield)Expression$/.test(node.type) || - (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0); - } - - return { - ExpressionStatement: function(node) { - if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) { - context.report(node, "Expected an assignment or function call and instead saw an expression."); - } + return /^(?:Assignment|Call|New|Update|Yield)Expression$/.test(node.type) || + (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0); } - }; -}; - -module.exports.schema = [ - { - type: "object", - properties: { - allowShortCircuit: { - type: "boolean" - }, - allowTernary: { - type: "boolean" + return { + ExpressionStatement: function(node) { + if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) { + context.report(node, "Expected an assignment or function call and instead saw an expression."); + } } - }, - additionalProperties: false + }; + } -]; +}; diff --git a/lib/rules/no-unused-labels.js b/lib/rules/no-unused-labels.js index 55c8869d86e..77713fc408b 100644 --- a/lib/rules/no-unused-labels.js +++ b/lib/rules/no-unused-labels.js @@ -9,72 +9,82 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var scopeInfo = null; +module.exports = { + meta: { + docs: { + description: "disallow unused labels", + category: "Best Practices", + recommended: true + }, - /** - * Adds a scope info to the stack. - * - * @param {ASTNode} node - A node to add. This is a LabeledStatement. - * @returns {void} - */ - function enterLabeledScope(node) { - scopeInfo = { - label: node.label.name, - used: false, - upper: scopeInfo - }; - } + schema: [] + }, + + create: function(context) { + var scopeInfo = null; - /** - * Removes the top of the stack. - * At the same time, this reports the label if it's never used. - * - * @param {ASTNode} node - A node to report. This is a LabeledStatement. - * @returns {void} - */ - function exitLabeledScope(node) { - if (!scopeInfo.used) { - context.report({ - node: node.label, - message: "'{{name}}:' is defined but never used.", - data: node.label - }); + /** + * Adds a scope info to the stack. + * + * @param {ASTNode} node - A node to add. This is a LabeledStatement. + * @returns {void} + */ + function enterLabeledScope(node) { + scopeInfo = { + label: node.label.name, + used: false, + upper: scopeInfo + }; } - scopeInfo = scopeInfo.upper; - } + /** + * Removes the top of the stack. + * At the same time, this reports the label if it's never used. + * + * @param {ASTNode} node - A node to report. This is a LabeledStatement. + * @returns {void} + */ + function exitLabeledScope(node) { + if (!scopeInfo.used) { + context.report({ + node: node.label, + message: "'{{name}}:' is defined but never used.", + data: node.label + }); + } - /** - * Marks the label of a given node as used. - * - * @param {ASTNode} node - A node to mark. This is a BreakStatement or - * ContinueStatement. - * @returns {void} - */ - function markAsUsed(node) { - if (!node.label) { - return; + scopeInfo = scopeInfo.upper; } - var label = node.label.name; - var info = scopeInfo; + /** + * Marks the label of a given node as used. + * + * @param {ASTNode} node - A node to mark. This is a BreakStatement or + * ContinueStatement. + * @returns {void} + */ + function markAsUsed(node) { + if (!node.label) { + return; + } - while (info) { - if (info.label === label) { - info.used = true; - break; + var label = node.label.name; + var info = scopeInfo; + + while (info) { + if (info.label === label) { + info.used = true; + break; + } + info = info.upper; } - info = info.upper; } - } - return { - LabeledStatement: enterLabeledScope, - "LabeledStatement:exit": exitLabeledScope, - BreakStatement: markAsUsed, - ContinueStatement: markAsUsed - }; + return { + LabeledStatement: enterLabeledScope, + "LabeledStatement:exit": exitLabeledScope, + BreakStatement: markAsUsed, + ContinueStatement: markAsUsed + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js index 84db439ba67..89d43c7bfd6 100644 --- a/lib/rules/no-unused-vars.js +++ b/lib/rules/no-unused-vars.js @@ -15,318 +15,328 @@ var lodash = require("lodash"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow unused variables", + category: "Variables", + recommended: true + }, + + schema: [ + { + oneOf: [ + { + enum: ["all", "local"] + }, + { + type: "object", + properties: { + vars: { + enum: ["all", "local"] + }, + varsIgnorePattern: { + type: "string" + }, + args: { + enum: ["all", "after-used", "none"] + }, + argsIgnorePattern: { + type: "string" + }, + caughtErrors: { + enum: ["all", "none"] + }, + caughtErrorsIgnorePattern: { + type: "string" + } + } + } + ] + } + ] + }, - var MESSAGE = "'{{name}}' is defined but never used"; + create: function(context) { - var config = { - vars: "all", - args: "after-used", - caughtErrors: "none" - }; + var MESSAGE = "'{{name}}' is defined but never used"; - var firstOption = context.options[0]; + var config = { + vars: "all", + args: "after-used", + caughtErrors: "none" + }; - if (firstOption) { - if (typeof firstOption === "string") { - config.vars = firstOption; - } else { - config.vars = firstOption.vars || config.vars; - config.args = firstOption.args || config.args; - config.caughtErrors = firstOption.caughtErrors || config.caughtErrors; + var firstOption = context.options[0]; - if (firstOption.varsIgnorePattern) { - config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern); - } + if (firstOption) { + if (typeof firstOption === "string") { + config.vars = firstOption; + } else { + config.vars = firstOption.vars || config.vars; + config.args = firstOption.args || config.args; + config.caughtErrors = firstOption.caughtErrors || config.caughtErrors; - if (firstOption.argsIgnorePattern) { - config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern); - } + if (firstOption.varsIgnorePattern) { + config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern); + } - if (firstOption.caughtErrorsIgnorePattern) { - config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern); + if (firstOption.argsIgnorePattern) { + config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern); + } + + if (firstOption.caughtErrorsIgnorePattern) { + config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern); + } } } - } - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Determines if a given variable is being exported from a module. + * @param {Variable} variable - EScope variable object. + * @returns {boolean} True if the variable is exported, false if not. + * @private + */ + function isExported(variable) { - /** - * Determines if a given variable is being exported from a module. - * @param {Variable} variable - EScope variable object. - * @returns {boolean} True if the variable is exported, false if not. - * @private - */ - function isExported(variable) { + var definition = variable.defs[0]; - var definition = variable.defs[0]; + if (definition) { - if (definition) { + var node = definition.node; - var node = definition.node; + if (node.type === "VariableDeclarator") { + node = node.parent; + } else if (definition.type === "Parameter") { + return false; + } - if (node.type === "VariableDeclarator") { - node = node.parent; - } else if (definition.type === "Parameter") { + return node.parent.type.indexOf("Export") === 0; + } else { return false; } - - return node.parent.type.indexOf("Export") === 0; - } else { - return false; } - } - /** - * Determines if a reference is a read operation. - * @param {Reference} ref - An escope Reference - * @returns {Boolean} whether the given reference represents a read operation - * @private - */ - function isReadRef(ref) { - return ref.isRead(); - } - - /** - * Determine if an identifier is referencing an enclosing function name. - * @param {Reference} ref - The reference to check. - * @param {ASTNode[]} nodes - The candidate function nodes. - * @returns {boolean} True if it's a self-reference, false if not. - * @private - */ - function isSelfReference(ref, nodes) { - var scope = ref.from; - - while (scope) { - if (nodes.indexOf(scope.block) >= 0) { - return true; - } - - scope = scope.upper; + /** + * Determines if a reference is a read operation. + * @param {Reference} ref - An escope Reference + * @returns {Boolean} whether the given reference represents a read operation + * @private + */ + function isReadRef(ref) { + return ref.isRead(); } - return false; - } - - /** - * Determines if the variable is used. - * @param {Variable} variable - The variable to check. - * @param {Reference[]} references - The variable references to check. - * @returns {boolean} True if the variable is used - */ - function isUsedVariable(variable) { - var functionNodes = variable.defs.filter(function(def) { - return def.type === "FunctionName"; - }).map(function(def) { - return def.node; - }), - isFunctionDefinition = functionNodes.length > 0; - - return variable.references.some(function(ref) { - return isReadRef(ref) && !(isFunctionDefinition && isSelfReference(ref, functionNodes)); - }); - } - - /** - * Gets an array of variables without read references. - * @param {Scope} scope - an escope Scope object. - * @param {Variable[]} unusedVars - an array that saving result. - * @returns {Variable[]} unused variables of the scope and descendant scopes. - * @private - */ - function collectUnusedVariables(scope, unusedVars) { - var variables = scope.variables; - var childScopes = scope.childScopes; - var i, l; - - if (scope.type !== "TDZ" && (scope.type !== "global" || config.vars === "all")) { - for (i = 0, l = variables.length; i < l; ++i) { - var variable = variables[i]; - - // skip a variable of class itself name in the class scope - if (scope.type === "class" && scope.block.id === variable.identifiers[0]) { - continue; + /** + * Determine if an identifier is referencing an enclosing function name. + * @param {Reference} ref - The reference to check. + * @param {ASTNode[]} nodes - The candidate function nodes. + * @returns {boolean} True if it's a self-reference, false if not. + * @private + */ + function isSelfReference(ref, nodes) { + var scope = ref.from; + + while (scope) { + if (nodes.indexOf(scope.block) >= 0) { + return true; } - // skip function expression names and variables marked with markVariableAsUsed() - if (scope.functionExpressionScope || variable.eslintUsed) { - continue; - } + scope = scope.upper; + } - // skip implicit "arguments" variable - if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) { - continue; - } + return false; + } - // explicit global variables don't have definitions. - var def = variable.defs[0]; + /** + * Determines if the variable is used. + * @param {Variable} variable - The variable to check. + * @param {Reference[]} references - The variable references to check. + * @returns {boolean} True if the variable is used + */ + function isUsedVariable(variable) { + var functionNodes = variable.defs.filter(function(def) { + return def.type === "FunctionName"; + }).map(function(def) { + return def.node; + }), + isFunctionDefinition = functionNodes.length > 0; + + return variable.references.some(function(ref) { + return isReadRef(ref) && !(isFunctionDefinition && isSelfReference(ref, functionNodes)); + }); + } - if (def) { - var type = def.type; + /** + * Gets an array of variables without read references. + * @param {Scope} scope - an escope Scope object. + * @param {Variable[]} unusedVars - an array that saving result. + * @returns {Variable[]} unused variables of the scope and descendant scopes. + * @private + */ + function collectUnusedVariables(scope, unusedVars) { + var variables = scope.variables; + var childScopes = scope.childScopes; + var i, l; + + if (scope.type !== "TDZ" && (scope.type !== "global" || config.vars === "all")) { + for (i = 0, l = variables.length; i < l; ++i) { + var variable = variables[i]; + + // skip a variable of class itself name in the class scope + if (scope.type === "class" && scope.block.id === variable.identifiers[0]) { + continue; + } - // skip catch variables - if (type === "CatchClause") { - if (config.caughtErrors === "none") { - continue; - } + // skip function expression names and variables marked with markVariableAsUsed() + if (scope.functionExpressionScope || variable.eslintUsed) { + continue; + } - // skip ignored parameters - if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) { - continue; - } + // skip implicit "arguments" variable + if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) { + continue; } - if (type === "Parameter") { + // explicit global variables don't have definitions. + var def = variable.defs[0]; - // skip any setter argument - if (def.node.parent.type === "Property" && def.node.parent.kind === "set") { - continue; - } + if (def) { + var type = def.type; - // if "args" option is "none", skip any parameter - if (config.args === "none") { - continue; - } + // skip catch variables + if (type === "CatchClause") { + if (config.caughtErrors === "none") { + continue; + } - // skip ignored parameters - if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) { - continue; + // skip ignored parameters + if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) { + continue; + } } - // if "args" option is "after-used", skip all but the last parameter - if (config.args === "after-used" && def.index < def.node.params.length - 1) { - continue; + if (type === "Parameter") { + + // skip any setter argument + if (def.node.parent.type === "Property" && def.node.parent.kind === "set") { + continue; + } + + // if "args" option is "none", skip any parameter + if (config.args === "none") { + continue; + } + + // skip ignored parameters + if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) { + continue; + } + + // if "args" option is "after-used", skip all but the last parameter + if (config.args === "after-used" && def.index < def.node.params.length - 1) { + continue; + } + } else { + + // skip ignored variables + if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) { + continue; + } } - } else { + } - // skip ignored variables - if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) { - continue; - } + if (!isUsedVariable(variable) && !isExported(variable)) { + unusedVars.push(variable); } } + } - if (!isUsedVariable(variable) && !isExported(variable)) { - unusedVars.push(variable); - } + for (i = 0, l = childScopes.length; i < l; ++i) { + collectUnusedVariables(childScopes[i], unusedVars); } - } - for (i = 0, l = childScopes.length; i < l; ++i) { - collectUnusedVariables(childScopes[i], unusedVars); + return unusedVars; } - return unusedVars; - } - - /** - * Gets the index of a given variable name in a given comment. - * @param {escope.Variable} variable - A variable to get. - * @param {ASTNode} comment - A comment node which includes the variable name. - * @returns {number} The index of the variable name's location. - */ - function getColumnInComment(variable, comment) { - var namePattern = new RegExp("[\\s,]" + lodash.escapeRegExp(variable.name) + "(?:$|[\\s,:])", "g"); - - // To ignore the first text "global". - namePattern.lastIndex = comment.value.indexOf("global") + 6; + /** + * Gets the index of a given variable name in a given comment. + * @param {escope.Variable} variable - A variable to get. + * @param {ASTNode} comment - A comment node which includes the variable name. + * @returns {number} The index of the variable name's location. + */ + function getColumnInComment(variable, comment) { + var namePattern = new RegExp("[\\s,]" + lodash.escapeRegExp(variable.name) + "(?:$|[\\s,:])", "g"); - // Search a given variable name. - var match = namePattern.exec(comment.value); + // To ignore the first text "global". + namePattern.lastIndex = comment.value.indexOf("global") + 6; - return match ? match.index + 1 : 0; - } + // Search a given variable name. + var match = namePattern.exec(comment.value); - /** - * Creates the correct location of a given variables. - * The location is at its name string in a `/*global` comment. - * - * @param {escope.Variable} variable - A variable to get its location. - * @returns {{line: number, column: number}} The location object for the variable. - */ - function getLocation(variable) { - var comment = variable.eslintExplicitGlobalComment; - var baseLoc = comment.loc.start; - var column = getColumnInComment(variable, comment); - var prefix = comment.value.slice(0, column); - var lineInComment = (prefix.match(/\n/g) || []).length; - - if (lineInComment > 0) { - column -= 1 + prefix.lastIndexOf("\n"); - } else { - - // 2 is for `/*` - column += baseLoc.column + 2; + return match ? match.index + 1 : 0; } - return { - line: baseLoc.line + lineInComment, - column: column - }; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "Program:exit": function(programNode) { - var unusedVars = collectUnusedVariables(context.getScope(), []); - - for (var i = 0, l = unusedVars.length; i < l; ++i) { - var unusedVar = unusedVars[i]; - - if (unusedVar.eslintExplicitGlobal) { - context.report({ - node: programNode, - loc: getLocation(unusedVar), - message: MESSAGE, - data: unusedVar - }); - } else if (unusedVar.defs.length > 0) { - context.report({ - node: unusedVar.identifiers[0], - message: MESSAGE, - data: unusedVar - }); - } + /** + * Creates the correct location of a given variables. + * The location is at its name string in a `/*global` comment. + * + * @param {escope.Variable} variable - A variable to get its location. + * @returns {{line: number, column: number}} The location object for the variable. + */ + function getLocation(variable) { + var comment = variable.eslintExplicitGlobalComment; + var baseLoc = comment.loc.start; + var column = getColumnInComment(variable, comment); + var prefix = comment.value.slice(0, column); + var lineInComment = (prefix.match(/\n/g) || []).length; + + if (lineInComment > 0) { + column -= 1 + prefix.lastIndexOf("\n"); + } else { + + // 2 is for `/*` + column += baseLoc.column + 2; } + + return { + line: baseLoc.line + lineInComment, + column: column + }; } - }; -}; + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- -module.exports.schema = [ - { - oneOf: [ - { - enum: ["all", "local"] - }, - { - type: "object", - properties: { - vars: { - enum: ["all", "local"] - }, - varsIgnorePattern: { - type: "string" - }, - args: { - enum: ["all", "after-used", "none"] - }, - argsIgnorePattern: { - type: "string" - }, - caughtErrors: { - enum: ["all", "none"] - }, - caughtErrorsIgnorePattern: { - type: "string" + return { + "Program:exit": function(programNode) { + var unusedVars = collectUnusedVariables(context.getScope(), []); + + for (var i = 0, l = unusedVars.length; i < l; ++i) { + var unusedVar = unusedVars[i]; + + if (unusedVar.eslintExplicitGlobal) { + context.report({ + node: programNode, + loc: getLocation(unusedVar), + message: MESSAGE, + data: unusedVar + }); + } else if (unusedVar.defs.length > 0) { + context.report({ + node: unusedVar.identifiers[0], + message: MESSAGE, + data: unusedVar + }); } } } - ] + }; + } -]; +}; diff --git a/lib/rules/no-use-before-define.js b/lib/rules/no-use-before-define.js index a0ce34c2a98..889e7099482 100644 --- a/lib/rules/no-use-before-define.js +++ b/lib/rules/no-use-before-define.js @@ -123,114 +123,124 @@ function isInInitializer(variable, reference) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = parseOptions(context.options[0]); - - // Defines a function which checks whether or not a reference is allowed according to the option. - var isAllowed; - - if (options.functions && options.classes) { - isAllowed = alwaysFalse; - } else if (options.functions) { - isAllowed = isOuterClass; - } else if (options.classes) { - isAllowed = isFunction; - } else { - isAllowed = isFunctionOrOuterClass; - } - - /** - * Finds and validates all variables in a given scope. - * @param {Scope} scope The scope object. - * @returns {void} - * @private - */ - function findVariablesInScope(scope) { - scope.references.forEach(function(reference) { - var variable = reference.resolved; - - // Skips when the reference is: - // - initialization's. - // - referring to an undefined variable. - // - referring to a global environment variable (there're no identifiers). - // - located preceded by the variable (except in initializers). - // - allowed by options. - if (reference.init || - !variable || - variable.identifiers.length === 0 || - (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) || - isAllowed(variable, reference) - ) { - return; +module.exports = { + meta: { + docs: { + description: "disallow the use of variables before they are defined", + category: "Variables", + recommended: false + }, + + schema: [ + { + oneOf: [ + { + enum: ["nofunc"] + }, + { + type: "object", + properties: { + functions: {type: "boolean"}, + classes: {type: "boolean"} + }, + additionalProperties: false + } + ] } + ] + }, + + create: function(context) { + var options = parseOptions(context.options[0]); + + // Defines a function which checks whether or not a reference is allowed according to the option. + var isAllowed; + + if (options.functions && options.classes) { + isAllowed = alwaysFalse; + } else if (options.functions) { + isAllowed = isOuterClass; + } else if (options.classes) { + isAllowed = isFunction; + } else { + isAllowed = isFunctionOrOuterClass; + } - // Reports. - context.report({ - node: reference.identifier, - message: "'{{name}}' was used before it was defined", - data: reference.identifier + /** + * Finds and validates all variables in a given scope. + * @param {Scope} scope The scope object. + * @returns {void} + * @private + */ + function findVariablesInScope(scope) { + scope.references.forEach(function(reference) { + var variable = reference.resolved; + + // Skips when the reference is: + // - initialization's. + // - referring to an undefined variable. + // - referring to a global environment variable (there're no identifiers). + // - located preceded by the variable (except in initializers). + // - allowed by options. + if (reference.init || + !variable || + variable.identifiers.length === 0 || + (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) || + isAllowed(variable, reference) + ) { + return; + } + + // Reports. + context.report({ + node: reference.identifier, + message: "'{{name}}' was used before it was defined", + data: reference.identifier + }); }); - }); - } - - /** - * Validates variables inside of a node's scope. - * @param {ASTNode} node The node to check. - * @returns {void} - * @private - */ - function findVariables() { - var scope = context.getScope(); - - findVariablesInScope(scope); - } + } - var ruleDefinition = { - "Program:exit": function(node) { - var scope = context.getScope(), - ecmaFeatures = context.parserOptions.ecmaFeatures || {}; + /** + * Validates variables inside of a node's scope. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function findVariables() { + var scope = context.getScope(); findVariablesInScope(scope); - - // both Node.js and Modules have an extra scope - if (ecmaFeatures.globalReturn || node.sourceType === "module") { - findVariablesInScope(scope.childScopes[0]); - } } - }; - if (context.parserOptions.ecmaVersion >= 6) { - ruleDefinition["BlockStatement:exit"] = - ruleDefinition["SwitchStatement:exit"] = findVariables; + var ruleDefinition = { + "Program:exit": function(node) { + var scope = context.getScope(), + ecmaFeatures = context.parserOptions.ecmaFeatures || {}; + + findVariablesInScope(scope); - ruleDefinition["ArrowFunctionExpression:exit"] = function(node) { - if (node.body.type !== "BlockStatement") { - findVariables(node); + // both Node.js and Modules have an extra scope + if (ecmaFeatures.globalReturn || node.sourceType === "module") { + findVariablesInScope(scope.childScopes[0]); + } } }; - } else { - ruleDefinition["FunctionExpression:exit"] = - ruleDefinition["FunctionDeclaration:exit"] = - ruleDefinition["ArrowFunctionExpression:exit"] = findVariables; - } - return ruleDefinition; -}; + if (context.parserOptions.ecmaVersion >= 6) { + ruleDefinition["BlockStatement:exit"] = + ruleDefinition["SwitchStatement:exit"] = findVariables; + + ruleDefinition["ArrowFunctionExpression:exit"] = function(node) { + if (node.body.type !== "BlockStatement") { + findVariables(node); + } + }; + } else { + ruleDefinition["FunctionExpression:exit"] = + ruleDefinition["FunctionDeclaration:exit"] = + ruleDefinition["ArrowFunctionExpression:exit"] = findVariables; + } -module.exports.schema = [ - { - oneOf: [ - { - enum: ["nofunc"] - }, - { - type: "object", - properties: { - functions: {type: "boolean"}, - classes: {type: "boolean"} - }, - additionalProperties: false - } - ] + return ruleDefinition; } -]; +};