diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js index 610cdf2bacf..27dd11e5b2e 100644 --- a/lib/rules/max-len.js +++ b/lib/rules/max-len.js @@ -7,196 +7,9 @@ "use strict"; //------------------------------------------------------------------------------ -// Rule Definition +// Constants //------------------------------------------------------------------------------ -module.exports = function(context) { - - /* - * Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however: - * - They're matching an entire string that we know is a URI - * - We're matching part of a string where we think there *might* be a URL - * - We're only concerned about URLs, as picking out any URI would cause - * too many false positives - * - We don't care about matching the entire URL, any small segment is fine - */ - var URL_REGEXP = /[^:/?#]:\/\/[^?#]/; - - /** - * Computes the length of a line that may contain tabs. The width of each - * tab will be the number of spaces to the next tab stop. - * @param {string} line The line. - * @param {int} tabWidth The width of each tab stop in spaces. - * @returns {int} The computed line length. - * @private - */ - function computeLineLength(line, tabWidth) { - var extraCharacterCount = 0; - - line.replace(/\t/g, function(match, offset) { - var totalOffset = offset + extraCharacterCount, - previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0, - spaceCount = tabWidth - previousTabStopOffset; - - extraCharacterCount += spaceCount - 1; // -1 for the replaced tab - }); - return line.length + extraCharacterCount; - } - - // The options object must be the last option specified… - var lastOption = context.options[context.options.length - 1]; - var options = typeof lastOption === "object" ? Object.create(lastOption) : {}; - - // …but max code length… - if (typeof context.options[0] === "number") { - options.code = context.options[0]; - } - - // …and tabWidth can be optionally specified directly as integers. - if (typeof context.options[1] === "number") { - options.tabWidth = context.options[1]; - } - - var maxLength = options.code || 80, - tabWidth = options.tabWidth || 4, - ignorePattern = options.ignorePattern || null, - ignoreComments = options.ignoreComments || false, - ignoreTrailingComments = options.ignoreTrailingComments || options.ignoreComments || false, - ignoreUrls = options.ignoreUrls || false, - maxCommentLength = options.comments; - - if (ignorePattern) { - ignorePattern = new RegExp(ignorePattern); - } - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Tells if a given comment is trailing: it starts on the current line and - * extends to or past the end of the current line. - * @param {string} line The source line we want to check for a trailing comment on - * @param {number} lineNumber The one-indexed line number for line - * @param {ASTNode} comment The comment to inspect - * @returns {boolean} If the comment is trailing on the given line - */ - function isTrailingComment(line, lineNumber, comment) { - return comment && - (comment.loc.start.line === lineNumber && lineNumber <= comment.loc.end.line) && - (comment.loc.end.line > lineNumber || comment.loc.end.column === line.length); - } - - /** - * Tells if a comment encompasses the entire line. - * @param {string} line The source line with a trailing comment - * @param {number} lineNumber The one-indexed line number this is on - * @param {ASTNode} comment The comment to remove - * @returns {boolean} If the comment covers the entire line - */ - function isFullLineComment(line, lineNumber, comment) { - var start = comment.loc.start, - end = comment.loc.end; - - return comment && - (start.line < lineNumber || (start.line === lineNumber && start.column === 0)) && - (end.line > lineNumber || end.column === line.length); - } - - /** - * Gets the line after the comment and any remaining trailing whitespace is - * stripped. - * @param {string} line The source line with a trailing comment - * @param {number} lineNumber The one-indexed line number this is on - * @param {ASTNode} comment The comment to remove - * @returns {string} Line without comment and trailing whitepace - */ - function stripTrailingComment(line, lineNumber, comment) { - - // loc.column is zero-indexed - return line.slice(0, comment.loc.start.column).replace(/\s+$/, ""); - } - - /** - * Check the program for max length - * @param {ASTNode} node Node to examine - * @returns {void} - * @private - */ - function checkProgramForMaxLength(node) { - - // split (honors line-ending) - var lines = context.getSourceLines(), - - // list of comments to ignore - comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? context.getAllComments() : [], - - // we iterate over comments in parallel with the lines - commentsIndex = 0; - - lines.forEach(function(line, i) { - - // i is zero-indexed, line numbers are one-indexed - var lineNumber = i + 1; - - /* - * if we're checking comment length; we need to know whether this - * line is a comment - */ - var lineIsComment = false; - - /* - * We can short-circuit the comment checks if we're already out of - * comments to check. - */ - if (commentsIndex < comments.length) { - - // iterate over comments until we find one past the current line - do { - var comment = comments[++commentsIndex]; - } while (comment && comment.loc.start.line <= lineNumber); - - // and step back by one - comment = comments[--commentsIndex]; - - if (isFullLineComment(line, lineNumber, comment)) { - lineIsComment = true; - } else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) { - line = stripTrailingComment(line, lineNumber, comment); - } - } - if (ignorePattern && ignorePattern.test(line) || - ignoreUrls && URL_REGEXP.test(line)) { - - // ignore this line - return; - } - - var lineLength = computeLineLength(line, tabWidth); - - if (lineIsComment && ignoreComments) { - return; - } - - if (lineIsComment && lineLength > maxCommentLength) { - context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum comment line length of " + maxCommentLength + "."); - } else if (lineLength > maxLength) { - context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum line length of " + maxLength + "."); - } - }); - } - - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - "Program": checkProgramForMaxLength - }; - -}; - var OPTIONS_SCHEMA = { "type": "object", "properties": { @@ -238,8 +51,209 @@ var OPTIONS_OR_INTEGER_SCHEMA = { ] }; -module.exports.schema = [ - OPTIONS_OR_INTEGER_SCHEMA, - OPTIONS_OR_INTEGER_SCHEMA, - OPTIONS_SCHEMA -]; +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: "enforce a maximum line length", + category: "Stylistic Issues", + recommended: false + }, + + schema: [ + OPTIONS_OR_INTEGER_SCHEMA, + OPTIONS_OR_INTEGER_SCHEMA, + OPTIONS_SCHEMA + ] + }, + + create: function(context) { + + /* + * Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however: + * - They're matching an entire string that we know is a URI + * - We're matching part of a string where we think there *might* be a URL + * - We're only concerned about URLs, as picking out any URI would cause + * too many false positives + * - We don't care about matching the entire URL, any small segment is fine + */ + var URL_REGEXP = /[^:/?#]:\/\/[^?#]/; + + /** + * Computes the length of a line that may contain tabs. The width of each + * tab will be the number of spaces to the next tab stop. + * @param {string} line The line. + * @param {int} tabWidth The width of each tab stop in spaces. + * @returns {int} The computed line length. + * @private + */ + function computeLineLength(line, tabWidth) { + var extraCharacterCount = 0; + + line.replace(/\t/g, function(match, offset) { + var totalOffset = offset + extraCharacterCount, + previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0, + spaceCount = tabWidth - previousTabStopOffset; + + extraCharacterCount += spaceCount - 1; // -1 for the replaced tab + }); + return line.length + extraCharacterCount; + } + + // The options object must be the last option specified… + var lastOption = context.options[context.options.length - 1]; + var options = typeof lastOption === "object" ? Object.create(lastOption) : {}; + + // …but max code length… + if (typeof context.options[0] === "number") { + options.code = context.options[0]; + } + + // …and tabWidth can be optionally specified directly as integers. + if (typeof context.options[1] === "number") { + options.tabWidth = context.options[1]; + } + + var maxLength = options.code || 80, + tabWidth = options.tabWidth || 4, + ignorePattern = options.ignorePattern || null, + ignoreComments = options.ignoreComments || false, + ignoreTrailingComments = options.ignoreTrailingComments || options.ignoreComments || false, + ignoreUrls = options.ignoreUrls || false, + maxCommentLength = options.comments; + + if (ignorePattern) { + ignorePattern = new RegExp(ignorePattern); + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Tells if a given comment is trailing: it starts on the current line and + * extends to or past the end of the current line. + * @param {string} line The source line we want to check for a trailing comment on + * @param {number} lineNumber The one-indexed line number for line + * @param {ASTNode} comment The comment to inspect + * @returns {boolean} If the comment is trailing on the given line + */ + function isTrailingComment(line, lineNumber, comment) { + return comment && + (comment.loc.start.line === lineNumber && lineNumber <= comment.loc.end.line) && + (comment.loc.end.line > lineNumber || comment.loc.end.column === line.length); + } + + /** + * Tells if a comment encompasses the entire line. + * @param {string} line The source line with a trailing comment + * @param {number} lineNumber The one-indexed line number this is on + * @param {ASTNode} comment The comment to remove + * @returns {boolean} If the comment covers the entire line + */ + function isFullLineComment(line, lineNumber, comment) { + var start = comment.loc.start, + end = comment.loc.end; + + return comment && + (start.line < lineNumber || (start.line === lineNumber && start.column === 0)) && + (end.line > lineNumber || end.column === line.length); + } + + /** + * Gets the line after the comment and any remaining trailing whitespace is + * stripped. + * @param {string} line The source line with a trailing comment + * @param {number} lineNumber The one-indexed line number this is on + * @param {ASTNode} comment The comment to remove + * @returns {string} Line without comment and trailing whitepace + */ + function stripTrailingComment(line, lineNumber, comment) { + + // loc.column is zero-indexed + return line.slice(0, comment.loc.start.column).replace(/\s+$/, ""); + } + + /** + * Check the program for max length + * @param {ASTNode} node Node to examine + * @returns {void} + * @private + */ + function checkProgramForMaxLength(node) { + + // split (honors line-ending) + var lines = context.getSourceLines(), + + // list of comments to ignore + comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? context.getAllComments() : [], + + // we iterate over comments in parallel with the lines + commentsIndex = 0; + + lines.forEach(function(line, i) { + + // i is zero-indexed, line numbers are one-indexed + var lineNumber = i + 1; + + /* + * if we're checking comment length; we need to know whether this + * line is a comment + */ + var lineIsComment = false; + + /* + * We can short-circuit the comment checks if we're already out of + * comments to check. + */ + if (commentsIndex < comments.length) { + + // iterate over comments until we find one past the current line + do { + var comment = comments[++commentsIndex]; + } while (comment && comment.loc.start.line <= lineNumber); + + // and step back by one + comment = comments[--commentsIndex]; + + if (isFullLineComment(line, lineNumber, comment)) { + lineIsComment = true; + } else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) { + line = stripTrailingComment(line, lineNumber, comment); + } + } + if (ignorePattern && ignorePattern.test(line) || + ignoreUrls && URL_REGEXP.test(line)) { + + // ignore this line + return; + } + + var lineLength = computeLineLength(line, tabWidth); + + if (lineIsComment && ignoreComments) { + return; + } + + if (lineIsComment && lineLength > maxCommentLength) { + context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum comment line length of " + maxCommentLength + "."); + } else if (lineLength > maxLength) { + context.report(node, { line: lineNumber, column: 0 }, "Line " + (i + 1) + " exceeds the maximum line length of " + maxLength + "."); + } + }); + } + + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + "Program": checkProgramForMaxLength + }; + + } +}; diff --git a/lib/rules/max-nested-callbacks.js b/lib/rules/max-nested-callbacks.js index 21b411b2513..bc1538df169 100644 --- a/lib/rules/max-nested-callbacks.js +++ b/lib/rules/max-nested-callbacks.js @@ -10,94 +10,104 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce a maximum depth that callbacks can be nested", + category: "Stylistic Issues", + recommended: false + }, + + schema: [ + { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "object", + "properties": { + "maximum": { + "type": "integer", + "minimum": 0 + }, + "max": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false + } + ] + } + ] + }, - //-------------------------------------------------------------------------- - // Constants - //-------------------------------------------------------------------------- - var option = context.options[0], - THRESHOLD = 10; + create: function(context) { - if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { - THRESHOLD = option.maximum; - } - if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { - THRESHOLD = option.max; - } - if (typeof option === "number") { - THRESHOLD = option; - } + //-------------------------------------------------------------------------- + // Constants + //-------------------------------------------------------------------------- + var option = context.options[0], + THRESHOLD = 10; - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { + THRESHOLD = option.maximum; + } + if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { + THRESHOLD = option.max; + } + if (typeof option === "number") { + THRESHOLD = option; + } - var callbackStack = []; + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - /** - * Checks a given function node for too many callbacks. - * @param {ASTNode} node The node to check. - * @returns {void} - * @private - */ - function checkFunction(node) { - var parent = node.parent; + var callbackStack = []; - if (parent.type === "CallExpression") { - callbackStack.push(node); - } + /** + * Checks a given function node for too many callbacks. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkFunction(node) { + var parent = node.parent; - if (callbackStack.length > THRESHOLD) { - var opts = {num: callbackStack.length, max: THRESHOLD}; + if (parent.type === "CallExpression") { + callbackStack.push(node); + } - context.report(node, "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", opts); - } - } + if (callbackStack.length > THRESHOLD) { + var opts = {num: callbackStack.length, max: THRESHOLD}; - /** - * Pops the call stack. - * @returns {void} - * @private - */ - function popStack() { - callbackStack.pop(); - } + context.report(node, "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", opts); + } + } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + /** + * Pops the call stack. + * @returns {void} + * @private + */ + function popStack() { + callbackStack.pop(); + } - return { - "ArrowFunctionExpression": checkFunction, - "ArrowFunctionExpression:exit": popStack, + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - "FunctionExpression": checkFunction, - "FunctionExpression:exit": popStack - }; + return { + "ArrowFunctionExpression": checkFunction, + "ArrowFunctionExpression:exit": popStack, -}; + "FunctionExpression": checkFunction, + "FunctionExpression:exit": popStack + }; -module.exports.schema = [ - { - "oneOf": [ - { - "type": "integer", - "minimum": 0 - }, - { - "type": "object", - "properties": { - "maximum": { - "type": "integer", - "minimum": 0 - }, - "max": { - "type": "integer", - "minimum": 0 - } - }, - "additionalProperties": false - } - ] } -]; +}; diff --git a/lib/rules/max-params.js b/lib/rules/max-params.js index d1cfe470922..ea720469694 100644 --- a/lib/rules/max-params.js +++ b/lib/rules/max-params.js @@ -11,65 +11,75 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce a maximum number of parameters in `function` definitions", + category: "Stylistic Issues", + recommended: false + }, - var option = context.options[0], - numParams = 3; - - if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { - numParams = option.maximum; - } - if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { - numParams = option.max; - } - if (typeof option === "number") { - numParams = option; - } - - /** - * Checks a function to see if it has too many parameters. - * @param {ASTNode} node The node to check. - * @returns {void} - * @private - */ - function checkFunction(node) { - if (node.params.length > numParams) { - context.report(node, "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", { - count: node.params.length, - max: numParams - }); - } - } - - return { - "FunctionDeclaration": checkFunction, - "ArrowFunctionExpression": checkFunction, - "FunctionExpression": checkFunction - }; - -}; - -module.exports.schema = [ - { - "oneOf": [ - { - "type": "integer", - "minimum": 0 - }, + schema: [ { - "type": "object", - "properties": { - "maximum": { + "oneOf": [ + { "type": "integer", "minimum": 0 }, - "max": { - "type": "integer", - "minimum": 0 + { + "type": "object", + "properties": { + "maximum": { + "type": "integer", + "minimum": 0 + }, + "max": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false } - }, - "additionalProperties": false + ] } ] + }, + + create: function(context) { + + var option = context.options[0], + numParams = 3; + + if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { + numParams = option.maximum; + } + if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { + numParams = option.max; + } + if (typeof option === "number") { + numParams = option; + } + + /** + * Checks a function to see if it has too many parameters. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkFunction(node) { + if (node.params.length > numParams) { + context.report(node, "This function has too many parameters ({{count}}). Maximum allowed is {{max}}.", { + count: node.params.length, + max: numParams + }); + } + } + + return { + "FunctionDeclaration": checkFunction, + "ArrowFunctionExpression": checkFunction, + "FunctionExpression": checkFunction + }; + } -]; +}; diff --git a/lib/rules/max-statements-per-line.js b/lib/rules/max-statements-per-line.js index 465c5a00b0e..56b6eb79a8e 100644 --- a/lib/rules/max-statements-per-line.js +++ b/lib/rules/max-statements-per-line.js @@ -11,96 +11,106 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce a maximum number of statements allowed per line", + category: "Stylistic Issues", + recommended: false + }, - var options = context.options[0] || {}, - lastStatementLine = 0, - numberOfStatementsOnThisLine = 0, - maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1; + schema: [ + { + "type": "object", + "properties": { + "max": { + "type": "integer" + } + }, + "additionalProperties": false + } + ] + }, - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + create: function(context) { - /** - * Reports a node - * @param {ASTNode} node The node to report - * @returns {void} - * @private - */ - function report(node) { - context.report( - node, - "This line has too many statements. Maximum allowed is {{max}}.", - { max: maxStatementsPerLine }); - } + var options = context.options[0] || {}, + lastStatementLine = 0, + numberOfStatementsOnThisLine = 0, + maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1; - /** - * Enforce a maximum number of statements per line - * @param {ASTNode} nodes Array of nodes to evaluate - * @returns {void} - * @private - */ - function enforceMaxStatementsPerLine(nodes) { - if (nodes.length < 1) { - return; - } + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - for (var i = 0, l = nodes.length; i < l; ++i) { - var currentStatement = nodes[i]; + /** + * Reports a node + * @param {ASTNode} node The node to report + * @returns {void} + * @private + */ + function report(node) { + context.report( + node, + "This line has too many statements. Maximum allowed is {{max}}.", + { max: maxStatementsPerLine }); + } - if (currentStatement.loc.start.line === lastStatementLine) { - ++numberOfStatementsOnThisLine; - } else { - numberOfStatementsOnThisLine = 1; - lastStatementLine = currentStatement.loc.end.line; + /** + * Enforce a maximum number of statements per line + * @param {ASTNode} nodes Array of nodes to evaluate + * @returns {void} + * @private + */ + function enforceMaxStatementsPerLine(nodes) { + if (nodes.length < 1) { + return; } - if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) { - report(currentStatement); + + for (var i = 0, l = nodes.length; i < l; ++i) { + var currentStatement = nodes[i]; + + if (currentStatement.loc.start.line === lastStatementLine) { + ++numberOfStatementsOnThisLine; + } else { + numberOfStatementsOnThisLine = 1; + lastStatementLine = currentStatement.loc.end.line; + } + if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) { + report(currentStatement); + } } } - } - /** - * Check each line in the body of a node - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkLinesInBody(node) { - enforceMaxStatementsPerLine(node.body); - } - - /** - * Check each line in the consequent of a switch case - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkLinesInConsequent(node) { - enforceMaxStatementsPerLine(node.consequent); - } + /** + * Check each line in the body of a node + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkLinesInBody(node) { + enforceMaxStatementsPerLine(node.body); + } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + /** + * Check each line in the consequent of a switch case + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkLinesInConsequent(node) { + enforceMaxStatementsPerLine(node.consequent); + } - return { - "Program": checkLinesInBody, - "BlockStatement": checkLinesInBody, - "SwitchCase": checkLinesInConsequent - }; + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- -}; + return { + "Program": checkLinesInBody, + "BlockStatement": checkLinesInBody, + "SwitchCase": checkLinesInConsequent + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "max": { - "type": "integer" - } - }, - "additionalProperties": false } -]; +}; diff --git a/lib/rules/max-statements.js b/lib/rules/max-statements.js index e608958ebae..45599152210 100644 --- a/lib/rules/max-statements.js +++ b/lib/rules/max-statements.js @@ -10,141 +10,151 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "enforce a maximum number of statements allowed in `function` blocks", + category: "Stylistic Issues", + recommended: false + }, + + schema: [ + { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "object", + "properties": { + "maximum": { + "type": "integer", + "minimum": 0 + }, + "max": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false + } + ] + }, + { + "type": "object", + "properties": { + "ignoreTopLevelFunctions": { + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + }, - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + create: function(context) { - var functionStack = [], - option = context.options[0], - maxStatements = 10, - ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false, - topLevelFunctions = []; + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { - maxStatements = option.maximum; - } - if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { - maxStatements = option.max; - } - if (typeof option === "number") { - maxStatements = option; - } + var functionStack = [], + option = context.options[0], + maxStatements = 10, + ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false, + topLevelFunctions = []; - /** - * Reports a node if it has too many statements - * @param {ASTNode} node node to evaluate - * @param {int} count Number of statements in node - * @param {int} max Maximum number of statements allowed - * @returns {void} - * @private - */ - function reportIfTooManyStatements(node, count, max) { - if (count > max) { - context.report( - node, - "This function has too many statements ({{count}}). Maximum allowed is {{max}}.", - { count: count, max: max }); + if (typeof option === "object" && option.hasOwnProperty("maximum") && typeof option.maximum === "number") { + maxStatements = option.maximum; + } + if (typeof option === "object" && option.hasOwnProperty("max") && typeof option.max === "number") { + maxStatements = option.max; + } + if (typeof option === "number") { + maxStatements = option; } - } - - /** - * When parsing a new function, store it in our function stack - * @returns {void} - * @private - */ - function startFunction() { - functionStack.push(0); - } - /** - * Evaluate the node at the end of function - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function endFunction(node) { - var count = functionStack.pop(); - - if (ignoreTopLevelFunctions && functionStack.length === 0) { - topLevelFunctions.push({ node: node, count: count}); - } else { - reportIfTooManyStatements(node, count, maxStatements); + /** + * Reports a node if it has too many statements + * @param {ASTNode} node node to evaluate + * @param {int} count Number of statements in node + * @param {int} max Maximum number of statements allowed + * @returns {void} + * @private + */ + function reportIfTooManyStatements(node, count, max) { + if (count > max) { + context.report( + node, + "This function has too many statements ({{count}}). Maximum allowed is {{max}}.", + { count: count, max: max }); + } } - } - /** - * Increment the count of the functions - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function countStatements(node) { - functionStack[functionStack.length - 1] += node.body.length; - } + /** + * When parsing a new function, store it in our function stack + * @returns {void} + * @private + */ + function startFunction() { + functionStack.push(0); + } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + /** + * Evaluate the node at the end of function + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function endFunction(node) { + var count = functionStack.pop(); + + if (ignoreTopLevelFunctions && functionStack.length === 0) { + topLevelFunctions.push({ node: node, count: count}); + } else { + reportIfTooManyStatements(node, count, maxStatements); + } + } - return { - "FunctionDeclaration": startFunction, - "FunctionExpression": startFunction, - "ArrowFunctionExpression": startFunction, + /** + * Increment the count of the functions + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function countStatements(node) { + functionStack[functionStack.length - 1] += node.body.length; + } - "BlockStatement": countStatements, + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - "FunctionDeclaration:exit": endFunction, - "FunctionExpression:exit": endFunction, - "ArrowFunctionExpression:exit": endFunction, + return { + "FunctionDeclaration": startFunction, + "FunctionExpression": startFunction, + "ArrowFunctionExpression": startFunction, - "Program:exit": function() { - if (topLevelFunctions.length === 1) { - return; - } + "BlockStatement": countStatements, - topLevelFunctions.forEach(function(element) { - var count = element.count; - var node = element.node; + "FunctionDeclaration:exit": endFunction, + "FunctionExpression:exit": endFunction, + "ArrowFunctionExpression:exit": endFunction, - reportIfTooManyStatements(node, count, maxStatements); - }); - } - }; + "Program:exit": function() { + if (topLevelFunctions.length === 1) { + return; + } -}; + topLevelFunctions.forEach(function(element) { + var count = element.count; + var node = element.node; -module.exports.schema = [ - { - "oneOf": [ - { - "type": "integer", - "minimum": 0 - }, - { - "type": "object", - "properties": { - "maximum": { - "type": "integer", - "minimum": 0 - }, - "max": { - "type": "integer", - "minimum": 0 - } - }, - "additionalProperties": false - } - ] - }, - { - "type": "object", - "properties": { - "ignoreTopLevelFunctions": { - "type": "boolean" + reportIfTooManyStatements(node, count, maxStatements); + }); } - }, - "additionalProperties": false + }; + } -]; +}; diff --git a/lib/rules/new-cap.js b/lib/rules/new-cap.js index f43c59fd999..3aec5c0242e 100644 --- a/lib/rules/new-cap.js +++ b/lib/rules/new-cap.js @@ -76,174 +76,184 @@ function calculateCapIsNewExceptions(config) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require constructor `function` names to begin with a capital letter", + category: "Stylistic Issues", + recommended: false + }, + + schema: [ + { + "type": "object", + "properties": { + "newIsCap": { + "type": "boolean" + }, + "capIsNew": { + "type": "boolean" + }, + "newIsCapExceptions": { + "type": "array", + "items": { + "type": "string" + } + }, + "capIsNewExceptions": { + "type": "array", + "items": { + "type": "string" + } + }, + "properties": { + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + }, + + create: function(context) { - var config = context.options[0] ? lodash.assign({}, context.options[0]) : {}; + var config = context.options[0] ? lodash.assign({}, context.options[0]) : {}; - config.newIsCap = config.newIsCap !== false; - config.capIsNew = config.capIsNew !== false; - var skipProperties = config.properties === false; + config.newIsCap = config.newIsCap !== false; + config.capIsNew = config.capIsNew !== false; + var skipProperties = config.properties === false; - var newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {}); + var newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {}); - var capIsNewExceptions = calculateCapIsNewExceptions(config); + var capIsNewExceptions = calculateCapIsNewExceptions(config); - var listeners = {}; + var listeners = {}; - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - /** - * Get exact callee name from expression - * @param {ASTNode} node CallExpression or NewExpression node - * @returns {string} name - */ - function extractNameFromExpression(node) { + /** + * Get exact callee name from expression + * @param {ASTNode} node CallExpression or NewExpression node + * @returns {string} name + */ + function extractNameFromExpression(node) { - var name = "", - property; + var name = "", + property; - if (node.callee.type === "MemberExpression") { - property = node.callee.property; + if (node.callee.type === "MemberExpression") { + property = node.callee.property; - if (property.type === "Literal" && (typeof property.value === "string")) { - name = property.value; - } else if (property.type === "Identifier" && !node.callee.computed) { - name = property.name; + if (property.type === "Literal" && (typeof property.value === "string")) { + name = property.value; + } else if (property.type === "Identifier" && !node.callee.computed) { + name = property.name; + } + } else { + name = node.callee.name; } - } else { - name = node.callee.name; + return name; } - return name; - } - /** - * Returns the capitalization state of the string - - * Whether the first character is uppercase, lowercase, or non-alphabetic - * @param {string} str String - * @returns {string} capitalization state: "non-alpha", "lower", or "upper" - */ - function getCap(str) { - var firstChar = str.charAt(0); - - var firstCharLower = firstChar.toLowerCase(); - var firstCharUpper = firstChar.toUpperCase(); - - if (firstCharLower === firstCharUpper) { - - // char has no uppercase variant, so it's non-alphabetic - return "non-alpha"; - } else if (firstChar === firstCharLower) { - return "lower"; - } else { - return "upper"; + /** + * Returns the capitalization state of the string - + * Whether the first character is uppercase, lowercase, or non-alphabetic + * @param {string} str String + * @returns {string} capitalization state: "non-alpha", "lower", or "upper" + */ + function getCap(str) { + var firstChar = str.charAt(0); + + var firstCharLower = firstChar.toLowerCase(); + var firstCharUpper = firstChar.toUpperCase(); + + if (firstCharLower === firstCharUpper) { + + // char has no uppercase variant, so it's non-alphabetic + return "non-alpha"; + } else if (firstChar === firstCharLower) { + return "lower"; + } else { + return "upper"; + } } - } - /** - * Check if capitalization is allowed for a CallExpression - * @param {Object} allowedMap Object mapping calleeName to a Boolean - * @param {ASTNode} node CallExpression node - * @param {string} calleeName Capitalized callee name from a CallExpression - * @returns {Boolean} Returns true if the callee may be capitalized - */ - function isCapAllowed(allowedMap, node, calleeName) { - if (allowedMap[calleeName] || allowedMap[context.getSource(node.callee)]) { - return true; - } + /** + * Check if capitalization is allowed for a CallExpression + * @param {Object} allowedMap Object mapping calleeName to a Boolean + * @param {ASTNode} node CallExpression node + * @param {string} calleeName Capitalized callee name from a CallExpression + * @returns {Boolean} Returns true if the callee may be capitalized + */ + function isCapAllowed(allowedMap, node, calleeName) { + if (allowedMap[calleeName] || allowedMap[context.getSource(node.callee)]) { + return true; + } - if (calleeName === "UTC" && node.callee.type === "MemberExpression") { + if (calleeName === "UTC" && node.callee.type === "MemberExpression") { + + // allow if callee is Date.UTC + return node.callee.object.type === "Identifier" && + node.callee.object.name === "Date"; + } - // allow if callee is Date.UTC - return node.callee.object.type === "Identifier" && - node.callee.object.name === "Date"; + return skipProperties && node.callee.type === "MemberExpression"; } - return skipProperties && node.callee.type === "MemberExpression"; - } + /** + * Reports the given message for the given node. The location will be the start of the property or the callee. + * @param {ASTNode} node CallExpression or NewExpression node. + * @param {string} message The message to report. + * @returns {void} + */ + function report(node, message) { + var callee = node.callee; + + if (callee.type === "MemberExpression") { + callee = callee.property; + } - /** - * Reports the given message for the given node. The location will be the start of the property or the callee. - * @param {ASTNode} node CallExpression or NewExpression node. - * @param {string} message The message to report. - * @returns {void} - */ - function report(node, message) { - var callee = node.callee; - - if (callee.type === "MemberExpression") { - callee = callee.property; + context.report(node, callee.loc.start, message); } - context.report(node, callee.loc.start, message); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - if (config.newIsCap) { - listeners.NewExpression = function(node) { + if (config.newIsCap) { + listeners.NewExpression = function(node) { - var constructorName = extractNameFromExpression(node); + var constructorName = extractNameFromExpression(node); - if (constructorName) { - var capitalization = getCap(constructorName); - var isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName); + if (constructorName) { + var capitalization = getCap(constructorName); + var isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName); - if (!isAllowed) { - report(node, "A constructor name should not start with a lowercase letter."); + if (!isAllowed) { + report(node, "A constructor name should not start with a lowercase letter."); + } } - } - }; - } + }; + } - if (config.capIsNew) { - listeners.CallExpression = function(node) { + if (config.capIsNew) { + listeners.CallExpression = function(node) { - var calleeName = extractNameFromExpression(node); + var calleeName = extractNameFromExpression(node); - if (calleeName) { - var capitalization = getCap(calleeName); - var isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName); + if (calleeName) { + var capitalization = getCap(calleeName); + var isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName); - if (!isAllowed) { - report(node, "A function with a name starting with an uppercase letter should only be used as a constructor."); + if (!isAllowed) { + report(node, "A function with a name starting with an uppercase letter should only be used as a constructor."); + } } - } - }; - } - - return listeners; -}; + }; + } -module.exports.schema = [ - { - "type": "object", - "properties": { - "newIsCap": { - "type": "boolean" - }, - "capIsNew": { - "type": "boolean" - }, - "newIsCapExceptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "capIsNewExceptions": { - "type": "array", - "items": { - "type": "string" - } - }, - "properties": { - "type": "boolean" - } - }, - "additionalProperties": false + return listeners; } -]; +}; diff --git a/lib/rules/new-parens.js b/lib/rules/new-parens.js index 13c8e3d8248..e51dffc8e52 100644 --- a/lib/rules/new-parens.js +++ b/lib/rules/new-parens.js @@ -9,22 +9,32 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - return { - - "NewExpression": function(node) { - var tokens = context.getTokens(node); - var prenticesTokens = tokens.filter(function(token) { - return token.value === "(" || token.value === ")"; - }); - - if (prenticesTokens.length < 2) { - context.report(node, "Missing '()' invoking a constructor"); +module.exports = { + meta: { + docs: { + description: "require parentheses when invoking a constructor with no arguments", + category: "Stylistic Issues", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + return { + + "NewExpression": function(node) { + var tokens = context.getTokens(node); + var prenticesTokens = tokens.filter(function(token) { + return token.value === "(" || token.value === ")"; + }); + + if (prenticesTokens.length < 2) { + context.report(node, "Missing '()' invoking a constructor"); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/newline-after-var.js b/lib/rules/newline-after-var.js index 34431e2c8cd..bcb04eb19ca 100644 --- a/lib/rules/newline-after-var.js +++ b/lib/rules/newline-after-var.js @@ -12,159 +12,169 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var ALWAYS_MESSAGE = "Expected blank line after variable declarations.", - NEVER_MESSAGE = "Unexpected blank line after variable declarations."; - - var sourceCode = context.getSourceCode(); - - // Default `mode` to "always". - var mode = context.options[0] === "never" ? "never" : "always"; - - // Cache starting and ending line numbers of comments for faster lookup - var commentEndLine = context.getAllComments().reduce(function(result, token) { - result[token.loc.start.line] = token.loc.end.line; - return result; - }, {}); - - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Determine if provided keyword is a variable declaration - * @private - * @param {string} keyword - keyword to test - * @returns {boolean} True if `keyword` is a type of var - */ - function isVar(keyword) { - return keyword === "var" || keyword === "let" || keyword === "const"; - } - - /** - * Determine if provided keyword is a variant of for specifiers - * @private - * @param {string} keyword - keyword to test - * @returns {boolean} True if `keyword` is a variant of for specifier - */ - function isForTypeSpecifier(keyword) { - return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement"; - } - - /** - * Determine if provided keyword is an export specifiers - * @private - * @param {string} nodeType - nodeType to test - * @returns {boolean} True if `nodeType` is an export specifier - */ - function isExportSpecifier(nodeType) { - return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" || - nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration"; - } - - /** - * Determine if provided node is the last of their parent block. - * @private - * @param {ASTNode} node - node to test - * @returns {boolean} True if `node` is last of their parent block. - */ - function isLastNode(node) { - var token = sourceCode.getTokenAfter(node); - - return !token || (token.type === "Punctuator" && token.value === "}"); - } - - /** - * Determine if a token starts more than one line after a comment ends - * @param {token} token The token being checked - * @param {integer} commentStartLine The line number on which the comment starts - * @returns {boolean} True if `token` does not start immediately after a comment - */ - function hasBlankLineAfterComment(token, commentStartLine) { - var commentEnd = commentEndLine[commentStartLine]; - - // If there's another comment, repeat check for blank line - if (commentEndLine[commentEnd + 1]) { - return hasBlankLineAfterComment(token, commentEnd + 1); +module.exports = { + meta: { + docs: { + description: "require or disallow an empty line after `var` declarations", + category: "Stylistic Issues", + recommended: false + }, + + schema: [ + { + "enum": ["never", "always"] + } + ] + }, + + create: function(context) { + + var ALWAYS_MESSAGE = "Expected blank line after variable declarations.", + NEVER_MESSAGE = "Unexpected blank line after variable declarations."; + + var sourceCode = context.getSourceCode(); + + // Default `mode` to "always". + var mode = context.options[0] === "never" ? "never" : "always"; + + // Cache starting and ending line numbers of comments for faster lookup + var commentEndLine = context.getAllComments().reduce(function(result, token) { + result[token.loc.start.line] = token.loc.end.line; + return result; + }, {}); + + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Determine if provided keyword is a variable declaration + * @private + * @param {string} keyword - keyword to test + * @returns {boolean} True if `keyword` is a type of var + */ + function isVar(keyword) { + return keyword === "var" || keyword === "let" || keyword === "const"; } - return (token.loc.start.line > commentEndLine[commentStartLine] + 1); - } - - /** - * Checks that a blank line exists after a variable declaration when mode is - * set to "always", or checks that there is no blank line when mode is set - * to "never" - * @private - * @param {ASTNode} node - `VariableDeclaration` node to test - * @returns {void} - */ - function checkForBlankLine(node) { - var lastToken = sourceCode.getLastToken(node), - nextToken = sourceCode.getTokenAfter(node), - nextLineNum = lastToken.loc.end.line + 1, - noNextLineToken, - hasNextLineComment; - - // Ignore if there is no following statement - if (!nextToken) { - return; + /** + * Determine if provided keyword is a variant of for specifiers + * @private + * @param {string} keyword - keyword to test + * @returns {boolean} True if `keyword` is a variant of for specifier + */ + function isForTypeSpecifier(keyword) { + return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement"; } - // Ignore if parent of node is a for variant - if (isForTypeSpecifier(node.parent.type)) { - return; + /** + * Determine if provided keyword is an export specifiers + * @private + * @param {string} nodeType - nodeType to test + * @returns {boolean} True if `nodeType` is an export specifier + */ + function isExportSpecifier(nodeType) { + return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" || + nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration"; } - // Ignore if parent of node is an export specifier - if (isExportSpecifier(node.parent.type)) { - return; - } + /** + * Determine if provided node is the last of their parent block. + * @private + * @param {ASTNode} node - node to test + * @returns {boolean} True if `node` is last of their parent block. + */ + function isLastNode(node) { + var token = sourceCode.getTokenAfter(node); - // Some coding styles use multiple `var` statements, so do nothing if - // the next token is a `var` statement. - if (nextToken.type === "Keyword" && isVar(nextToken.value)) { - return; + return !token || (token.type === "Punctuator" && token.value === "}"); } - // Ignore if it is last statement in a block - if (isLastNode(node)) { - return; + /** + * Determine if a token starts more than one line after a comment ends + * @param {token} token The token being checked + * @param {integer} commentStartLine The line number on which the comment starts + * @returns {boolean} True if `token` does not start immediately after a comment + */ + function hasBlankLineAfterComment(token, commentStartLine) { + var commentEnd = commentEndLine[commentStartLine]; + + // If there's another comment, repeat check for blank line + if (commentEndLine[commentEnd + 1]) { + return hasBlankLineAfterComment(token, commentEnd + 1); + } + + return (token.loc.start.line > commentEndLine[commentStartLine] + 1); } - // Next statement is not a `var`... - noNextLineToken = nextToken.loc.start.line > nextLineNum; - hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined"); - - if (mode === "never" && noNextLineToken && !hasNextLineComment) { - context.report(node, NEVER_MESSAGE, { identifier: node.name }); + /** + * Checks that a blank line exists after a variable declaration when mode is + * set to "always", or checks that there is no blank line when mode is set + * to "never" + * @private + * @param {ASTNode} node - `VariableDeclaration` node to test + * @returns {void} + */ + function checkForBlankLine(node) { + var lastToken = sourceCode.getLastToken(node), + nextToken = sourceCode.getTokenAfter(node), + nextLineNum = lastToken.loc.end.line + 1, + noNextLineToken, + hasNextLineComment; + + // Ignore if there is no following statement + if (!nextToken) { + return; + } + + // Ignore if parent of node is a for variant + if (isForTypeSpecifier(node.parent.type)) { + return; + } + + // Ignore if parent of node is an export specifier + if (isExportSpecifier(node.parent.type)) { + return; + } + + // Some coding styles use multiple `var` statements, so do nothing if + // the next token is a `var` statement. + if (nextToken.type === "Keyword" && isVar(nextToken.value)) { + return; + } + + // Ignore if it is last statement in a block + if (isLastNode(node)) { + return; + } + + // Next statement is not a `var`... + noNextLineToken = nextToken.loc.start.line > nextLineNum; + hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined"); + + if (mode === "never" && noNextLineToken && !hasNextLineComment) { + context.report(node, NEVER_MESSAGE, { identifier: node.name }); + } + + // Token on the next line, or comment without blank line + if ( + mode === "always" && ( + !noNextLineToken || + hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum) + ) + ) { + context.report(node, ALWAYS_MESSAGE, { identifier: node.name }); + } } - // Token on the next line, or comment without blank line - if ( - mode === "always" && ( - !noNextLineToken || - hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum) - ) - ) { - context.report(node, ALWAYS_MESSAGE, { identifier: node.name }); - } - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return { - "VariableDeclaration": checkForBlankLine - }; + return { + "VariableDeclaration": checkForBlankLine + }; -}; - -module.exports.schema = [ - { - "enum": ["never", "always"] } -]; +}; diff --git a/lib/rules/newline-before-return.js b/lib/rules/newline-before-return.js index 8dc4279a217..7f9dddb2e9c 100644 --- a/lib/rules/newline-before-return.js +++ b/lib/rules/newline-before-return.js @@ -11,149 +11,159 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var sourceCode = context.getSourceCode(); - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Tests whether node is preceded by supplied tokens - * @param {ASTNode} node - node to check - * @param {array} testTokens - array of tokens to test against - * @returns {boolean} Whether or not the node is preceded by one of the supplied tokens - * @private - */ - function isPrecededByTokens(node, testTokens) { - var tokenBefore = sourceCode.getTokenBefore(node); - - return testTokens.some(function(token) { - return tokenBefore.value === token; - }); - } +module.exports = { + meta: { + docs: { + description: "require an empty line before `return` statements", + category: "Stylistic Issues", + recommended: false + }, - /** - * Checks whether node is the first node after statement or in block - * @param {ASTNode} node - node to check - * @returns {boolean} Whether or not the node is the first node after statement or in block - * @private - */ - function isFirstNode(node) { - var parentType = node.parent.type; - - if (node.parent.body) { - return Array.isArray(node.parent.body) - ? node.parent.body[0] === node - : node.parent.body === node; - } + schema: [] + }, - if (parentType === "IfStatement") { - return isPrecededByTokens(node, ["else", ")"]); - } else if (parentType === "DoWhileStatement") { - return isPrecededByTokens(node, ["do"]); - } else if (parentType === "SwitchCase") { - return isPrecededByTokens(node, [":"]); - } else { - return isPrecededByTokens(node, [")"]); - } - } + create: function(context) { + var sourceCode = context.getSourceCode(); - /** - * Returns the number of lines of comments that precede the node - * @param {ASTNode} node - node to check for overlapping comments - * @param {number} lineNumTokenBefore - line number of previous token, to check for overlapping comments - * @returns {number} Number of lines of comments that precede the node - * @private - */ - function calcCommentLines(node, lineNumTokenBefore) { - var comments = sourceCode.getComments(node).leading, - numLinesComments = 0; - - if (!comments.length) { - return numLinesComments; + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Tests whether node is preceded by supplied tokens + * @param {ASTNode} node - node to check + * @param {array} testTokens - array of tokens to test against + * @returns {boolean} Whether or not the node is preceded by one of the supplied tokens + * @private + */ + function isPrecededByTokens(node, testTokens) { + var tokenBefore = sourceCode.getTokenBefore(node); + + return testTokens.some(function(token) { + return tokenBefore.value === token; + }); } - comments.forEach(function(comment) { - numLinesComments++; + /** + * Checks whether node is the first node after statement or in block + * @param {ASTNode} node - node to check + * @returns {boolean} Whether or not the node is the first node after statement or in block + * @private + */ + function isFirstNode(node) { + var parentType = node.parent.type; - if (comment.type === "Block") { - numLinesComments += comment.loc.end.line - comment.loc.start.line; + if (node.parent.body) { + return Array.isArray(node.parent.body) + ? node.parent.body[0] === node + : node.parent.body === node; } - // avoid counting lines with inline comments twice - if (comment.loc.start.line === lineNumTokenBefore) { - numLinesComments--; + if (parentType === "IfStatement") { + return isPrecededByTokens(node, ["else", ")"]); + } else if (parentType === "DoWhileStatement") { + return isPrecededByTokens(node, ["do"]); + } else if (parentType === "SwitchCase") { + return isPrecededByTokens(node, [":"]); + } else { + return isPrecededByTokens(node, [")"]); } + } - if (comment.loc.end.line === node.loc.start.line) { - numLinesComments--; + /** + * Returns the number of lines of comments that precede the node + * @param {ASTNode} node - node to check for overlapping comments + * @param {number} lineNumTokenBefore - line number of previous token, to check for overlapping comments + * @returns {number} Number of lines of comments that precede the node + * @private + */ + function calcCommentLines(node, lineNumTokenBefore) { + var comments = sourceCode.getComments(node).leading, + numLinesComments = 0; + + if (!comments.length) { + return numLinesComments; } - }); - return numLinesComments; - } + comments.forEach(function(comment) { + numLinesComments++; - /** - * Checks whether node is preceded by a newline - * @param {ASTNode} node - node to check - * @returns {boolean} Whether or not the node is preceded by a newline - * @private - */ - function hasNewlineBefore(node) { - var tokenBefore = sourceCode.getTokenBefore(node), - lineNumNode = node.loc.start.line, - lineNumTokenBefore, - commentLines; + if (comment.type === "Block") { + numLinesComments += comment.loc.end.line - comment.loc.start.line; + } + + // avoid counting lines with inline comments twice + if (comment.loc.start.line === lineNumTokenBefore) { + numLinesComments--; + } + + if (comment.loc.end.line === node.loc.start.line) { + numLinesComments--; + } + }); + + return numLinesComments; + } /** - * Global return (at the beginning of a script) is a special case. - * If there is no token before `return`, then we expect no line - * break before the return. Comments are allowed to occupy lines - * before the global return, just no blank lines. - * Setting lineNumTokenBefore to zero in that case results in the - * desired behavior. + * Checks whether node is preceded by a newline + * @param {ASTNode} node - node to check + * @returns {boolean} Whether or not the node is preceded by a newline + * @private */ - if (tokenBefore) { - lineNumTokenBefore = tokenBefore.loc.end.line; - } else { - lineNumTokenBefore = 0; // global return at beginning of script - } + function hasNewlineBefore(node) { + var tokenBefore = sourceCode.getTokenBefore(node), + lineNumNode = node.loc.start.line, + lineNumTokenBefore, + commentLines; + + /** + * Global return (at the beginning of a script) is a special case. + * If there is no token before `return`, then we expect no line + * break before the return. Comments are allowed to occupy lines + * before the global return, just no blank lines. + * Setting lineNumTokenBefore to zero in that case results in the + * desired behavior. + */ + if (tokenBefore) { + lineNumTokenBefore = tokenBefore.loc.end.line; + } else { + lineNumTokenBefore = 0; // global return at beginning of script + } - commentLines = calcCommentLines(node, lineNumTokenBefore); + commentLines = calcCommentLines(node, lineNumTokenBefore); - return (lineNumNode - lineNumTokenBefore - commentLines) > 1; - } + return (lineNumNode - lineNumTokenBefore - commentLines) > 1; + } - /** - * Reports expected/unexpected newline before return statement - * @param {ASTNode} node - the node to report in the event of an error - * @param {boolean} isExpected - whether the newline is expected or not - * @returns {void} - * @private - */ - function reportError(node, isExpected) { - var expected = isExpected ? "Expected" : "Unexpected"; - - context.report({ - node: node, - message: expected + " newline before return statement." - }); - } + /** + * Reports expected/unexpected newline before return statement + * @param {ASTNode} node - the node to report in the event of an error + * @param {boolean} isExpected - whether the newline is expected or not + * @returns {void} + * @private + */ + function reportError(node, isExpected) { + var expected = isExpected ? "Expected" : "Unexpected"; - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + context.report({ + node: node, + message: expected + " newline before return statement." + }); + } - return { - ReturnStatement: function(node) { - if (isFirstNode(node) && hasNewlineBefore(node)) { - reportError(node, false); - } else if (!isFirstNode(node) && !hasNewlineBefore(node)) { - reportError(node, true); + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ReturnStatement: function(node) { + if (isFirstNode(node) && hasNewlineBefore(node)) { + reportError(node, false); + } else if (!isFirstNode(node) && !hasNewlineBefore(node)) { + reportError(node, true); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/newline-per-chained-call.js b/lib/rules/newline-per-chained-call.js index f7df4c29d66..5ec6c5f8efd 100644 --- a/lib/rules/newline-per-chained-call.js +++ b/lib/rules/newline-per-chained-call.js @@ -12,45 +12,55 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "require a newline after each call in a method chain", + category: "Stylistic Issues", + recommended: false + }, - var options = context.options[0] || {}, - ignoreChainWithDepth = options.ignoreChainWithDepth || 2; + schema: [{ + "type": "object", + "properties": { + "ignoreChainWithDepth": { + "type": "integer", + "minimum": 1, + "maximum": 10 + } + }, + "additionalProperties": false + }] + }, - return { - "CallExpression:exit": function(node) { - if (!node.callee || node.callee.type !== "MemberExpression") { - return; - } + create: function(context) { - var callee = node.callee; - var parent = callee.object; - var depth = 1; + var options = context.options[0] || {}, + ignoreChainWithDepth = options.ignoreChainWithDepth || 2; - while (parent && parent.callee) { - depth += 1; - parent = parent.callee.object; - } + return { + "CallExpression:exit": function(node) { + if (!node.callee || node.callee.type !== "MemberExpression") { + return; + } + + var callee = node.callee; + var parent = callee.object; + var depth = 1; - if (depth > ignoreChainWithDepth && callee.property.loc.start.line === callee.object.loc.end.line) { - context.report( - callee.property, - callee.property.loc.start, - "Expected line break after `" + context.getSource(callee.object).replace(/\r\n|\r|\n/g, "\\n") + "`." - ); + while (parent && parent.callee) { + depth += 1; + parent = parent.callee.object; + } + + if (depth > ignoreChainWithDepth && callee.property.loc.start.line === callee.object.loc.end.line) { + context.report( + callee.property, + callee.property.loc.start, + "Expected line break after `" + context.getSource(callee.object).replace(/\r\n|\r|\n/g, "\\n") + "`." + ); + } } - } - }; + }; + } }; - -module.exports.schema = [{ - "type": "object", - "properties": { - "ignoreChainWithDepth": { - "type": "integer", - "minimum": 1, - "maximum": 10 - } - }, - "additionalProperties": false -}]; diff --git a/lib/rules/no-alert.js b/lib/rules/no-alert.js index ea1e6895448..9700f8c57f4 100644 --- a/lib/rules/no-alert.js +++ b/lib/rules/no-alert.js @@ -98,39 +98,49 @@ function isGlobalThisReferenceOrGlobalWindow(scope, globalScope, node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var globalScope; +module.exports = { + meta: { + docs: { + description: "disallow the use of `alert`, `confirm`, and `prompt`", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "Program": function() { - globalScope = context.getScope(); - }, + create: function(context) { + var globalScope; - "CallExpression": function(node) { - var callee = node.callee, - identifierName, - currentScope = context.getScope(); + return { - // without window. - if (callee.type === "Identifier") { - identifierName = callee.name; + "Program": function() { + globalScope = context.getScope(); + }, - if (!isShadowed(currentScope, globalScope, callee) && isProhibitedIdentifier(callee.name)) { - report(context, node, identifierName); - } + "CallExpression": function(node) { + var callee = node.callee, + identifierName, + currentScope = context.getScope(); + + // without window. + if (callee.type === "Identifier") { + identifierName = callee.name; + + if (!isShadowed(currentScope, globalScope, callee) && isProhibitedIdentifier(callee.name)) { + report(context, node, identifierName); + } - } else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, globalScope, callee.object)) { - identifierName = getPropertyName(callee); + } else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, globalScope, callee.object)) { + identifierName = getPropertyName(callee); - if (isProhibitedIdentifier(identifierName)) { - report(context, node, identifierName); + if (isProhibitedIdentifier(identifierName)) { + report(context, node, identifierName); + } } - } - } - }; + } + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-array-constructor.js b/lib/rules/no-array-constructor.js index ecf5837228b..417da13f023 100644 --- a/lib/rules/no-array-constructor.js +++ b/lib/rules/no-array-constructor.js @@ -9,29 +9,39 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Disallow construction of dense arrays using the Array constructor - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function check(node) { - if ( - node.arguments.length !== 1 && - node.callee.type === "Identifier" && - node.callee.name === "Array" - ) { - context.report(node, "The array literal notation [] is preferrable."); +module.exports = { + meta: { + docs: { + description: "disallow `Array` constructors", + category: "Stylistic Issues", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + /** + * Disallow construction of dense arrays using the Array constructor + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function check(node) { + if ( + node.arguments.length !== 1 && + node.callee.type === "Identifier" && + node.callee.name === "Array" + ) { + context.report(node, "The array literal notation [] is preferrable."); + } } - } - return { - "CallExpression": check, - "NewExpression": check - }; + return { + "CallExpression": check, + "NewExpression": check + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-bitwise.js b/lib/rules/no-bitwise.js index 9655525396e..86e7992ecdb 100644 --- a/lib/rules/no-bitwise.js +++ b/lib/rules/no-bitwise.js @@ -18,82 +18,92 @@ var BITWISE_OPERATORS = [ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0] || {}; - var allowed = options.allow || []; - var int32Hint = options.int32Hint === true; - - /** - * Reports an unexpected use of a bitwise operator. - * @param {ASTNode} node Node which contains the bitwise operator. - * @returns {void} - */ - function report(node) { - context.report(node, "Unexpected use of '{{operator}}'.", { operator: node.operator }); - } +module.exports = { + meta: { + docs: { + description: "disallow bitwise operators", + category: "Stylistic Issues", + recommended: false + }, - /** - * Checks if the given node has a bitwise operator. - * @param {ASTNode} node The node to check. - * @returns {boolean} Whether or not the node has a bitwise operator. - */ - function hasBitwiseOperator(node) { - return BITWISE_OPERATORS.indexOf(node.operator) !== -1; - } + schema: [ + { + "type": "object", + "properties": { + "allow": { + "type": "array", + "items": { + "enum": BITWISE_OPERATORS + }, + "uniqueItems": true + }, + "int32Hint": { + "type": "boolean" + } + }, + "additionalProperties": false + } + ] + }, - /** - * Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`. - * @param {ASTNode} node The node to check. - * @returns {boolean} Whether or not the node has a bitwise operator. - */ - function allowedOperator(node) { - return allowed.indexOf(node.operator) !== -1; - } + create: function(context) { + var options = context.options[0] || {}; + var allowed = options.allow || []; + var int32Hint = options.int32Hint === true; - /** - * Checks if the given bitwise operator is used for integer typecasting, i.e. "|0" - * @param {ASTNode} node The node to check. - * @returns {boolean} whether the node is used in integer typecasting. - */ - function isInt32Hint(node) { - return int32Hint && node.operator === "|" && node.right && - node.right.type === "Literal" && node.right.value === 0; - } + /** + * Reports an unexpected use of a bitwise operator. + * @param {ASTNode} node Node which contains the bitwise operator. + * @returns {void} + */ + function report(node) { + context.report(node, "Unexpected use of '{{operator}}'.", { operator: node.operator }); + } - /** - * Report if the given node contains a bitwise operator. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkNodeForBitwiseOperator(node) { - if (hasBitwiseOperator(node) && !allowedOperator(node) && !isInt32Hint(node)) { - report(node); + /** + * Checks if the given node has a bitwise operator. + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node has a bitwise operator. + */ + function hasBitwiseOperator(node) { + return BITWISE_OPERATORS.indexOf(node.operator) !== -1; } - } - return { - "AssignmentExpression": checkNodeForBitwiseOperator, - "BinaryExpression": checkNodeForBitwiseOperator, - "UnaryExpression": checkNodeForBitwiseOperator - }; + /** + * Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`. + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node has a bitwise operator. + */ + function allowedOperator(node) { + return allowed.indexOf(node.operator) !== -1; + } -}; + /** + * Checks if the given bitwise operator is used for integer typecasting, i.e. "|0" + * @param {ASTNode} node The node to check. + * @returns {boolean} whether the node is used in integer typecasting. + */ + function isInt32Hint(node) { + return int32Hint && node.operator === "|" && node.right && + node.right.type === "Literal" && node.right.value === 0; + } -module.exports.schema = [ - { - "type": "object", - "properties": { - "allow": { - "type": "array", - "items": { - "enum": BITWISE_OPERATORS - }, - "uniqueItems": true - }, - "int32Hint": { - "type": "boolean" + /** + * Report if the given node contains a bitwise operator. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkNodeForBitwiseOperator(node) { + if (hasBitwiseOperator(node) && !allowedOperator(node) && !isInt32Hint(node)) { + report(node); } - }, - "additionalProperties": false + } + + return { + "AssignmentExpression": checkNodeForBitwiseOperator, + "BinaryExpression": checkNodeForBitwiseOperator, + "UnaryExpression": checkNodeForBitwiseOperator + }; + } -]; +}; diff --git a/lib/rules/no-caller.js b/lib/rules/no-caller.js index aacb3feffbe..ac40939ba26 100644 --- a/lib/rules/no-caller.js +++ b/lib/rules/no-caller.js @@ -9,21 +9,31 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of `arguments.caller` or `arguments.callee`", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "MemberExpression": function(node) { - var objectName = node.object.name, - propertyName = node.property.name; + create: function(context) { - if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/)) { - context.report(node, "Avoid arguments.{{property}}.", { property: propertyName }); - } + return { - } - }; + "MemberExpression": function(node) { + var objectName = node.object.name, + propertyName = node.property.name; -}; + if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/)) { + context.report(node, "Avoid arguments.{{property}}.", { property: propertyName }); + } -module.exports.schema = []; + } + }; + + } +}; diff --git a/lib/rules/no-case-declarations.js b/lib/rules/no-case-declarations.js index 4969d4f1bf1..7f924c95ea4 100644 --- a/lib/rules/no-case-declarations.js +++ b/lib/rules/no-case-declarations.js @@ -9,40 +9,50 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Checks whether or not a node is a lexical declaration. - * @param {ASTNode} node A direct child statement of a switch case. - * @returns {boolean} Whether or not the node is a lexical declaration. - */ - function isLexicalDeclaration(node) { - switch (node.type) { - case "FunctionDeclaration": - case "ClassDeclaration": - return true; - case "VariableDeclaration": - return node.kind !== "var"; - default: - return false; +module.exports = { + meta: { + docs: { + description: "disallow lexical declarations in case clauses", + category: "Best Practices", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + /** + * Checks whether or not a node is a lexical declaration. + * @param {ASTNode} node A direct child statement of a switch case. + * @returns {boolean} Whether or not the node is a lexical declaration. + */ + function isLexicalDeclaration(node) { + switch (node.type) { + case "FunctionDeclaration": + case "ClassDeclaration": + return true; + case "VariableDeclaration": + return node.kind !== "var"; + default: + return false; + } } - } - return { - "SwitchCase": function(node) { - for (var i = 0; i < node.consequent.length; i++) { - var statement = node.consequent[i]; + return { + "SwitchCase": function(node) { + for (var i = 0; i < node.consequent.length; i++) { + var statement = node.consequent[i]; - if (isLexicalDeclaration(statement)) { - context.report({ - node: node, - message: "Unexpected lexical declaration in case block." - }); + if (isLexicalDeclaration(statement)) { + context.report({ + node: node, + message: "Unexpected lexical declaration in case block." + }); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-catch-shadow.js b/lib/rules/no-catch-shadow.js index 88eeb02fa61..2d0d00abe89 100644 --- a/lib/rules/no-catch-shadow.js +++ b/lib/rules/no-catch-shadow.js @@ -15,44 +15,54 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Check if the parameters are been shadowed - * @param {object} scope current scope - * @param {string} name parameter name - * @returns {boolean} True is its been shadowed - */ - function paramIsShadowing(scope, name) { - return astUtils.getVariableByName(scope, name) !== null; - } +module.exports = { + meta: { + docs: { + description: "disallow `catch` clause parameters from shadowing variables in the outer scope", + category: "Variables", + recommended: false + }, - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + schema: [] + }, - return { + create: function(context) { - "CatchClause": function(node) { - var scope = context.getScope(); + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - // When blockBindings is enabled, CatchClause creates its own scope - // so start from one upper scope to exclude the current node - if (scope.block === node) { - scope = scope.upper; - } + /** + * Check if the parameters are been shadowed + * @param {object} scope current scope + * @param {string} name parameter name + * @returns {boolean} True is its been shadowed + */ + function paramIsShadowing(scope, name) { + return astUtils.getVariableByName(scope, name) !== null; + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { - if (paramIsShadowing(scope, node.param.name)) { - context.report(node, "Value of '{{name}}' may be overwritten in IE 8 and earlier.", - { name: node.param.name }); + "CatchClause": function(node) { + var scope = context.getScope(); + + // When blockBindings is enabled, CatchClause creates its own scope + // so start from one upper scope to exclude the current node + if (scope.block === node) { + scope = scope.upper; + } + + if (paramIsShadowing(scope, node.param.name)) { + context.report(node, "Value of '{{name}}' may be overwritten in IE 8 and earlier.", + { name: node.param.name }); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-class-assign.js b/lib/rules/no-class-assign.js index 82f8e31fc34..a42f3ed1440 100644 --- a/lib/rules/no-class-assign.js +++ b/lib/rules/no-class-assign.js @@ -12,37 +12,47 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable - A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - astUtils.getModifyingReferences(variable.references).forEach(function(reference) { - context.report( - reference.identifier, - "'{{name}}' is a class.", - {name: reference.identifier.name}); - - }); - } +module.exports = { + meta: { + docs: { + description: "disallow reassigning class members", + category: "ECMAScript 6", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable - A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + astUtils.getModifyingReferences(variable.references).forEach(function(reference) { + context.report( + reference.identifier, + "'{{name}}' is a class.", + {name: reference.identifier.name}); + + }); + } + + /** + * Finds and reports references that are non initializer and writable. + * @param {ASTNode} node - A ClassDeclaration/ClassExpression node to check. + * @returns {void} + */ + function checkForClass(node) { + context.getDeclaredVariables(node).forEach(checkVariable); + } + + return { + "ClassDeclaration": checkForClass, + "ClassExpression": checkForClass + }; - /** - * Finds and reports references that are non initializer and writable. - * @param {ASTNode} node - A ClassDeclaration/ClassExpression node to check. - * @returns {void} - */ - function checkForClass(node) { - context.getDeclaredVariables(node).forEach(checkVariable); } - - return { - "ClassDeclaration": checkForClass, - "ClassExpression": checkForClass - }; - }; - -module.exports.schema = []; diff --git a/lib/rules/no-cond-assign.js b/lib/rules/no-cond-assign.js index f4bb8425cc7..9c6ae0b49a2 100644 --- a/lib/rules/no-cond-assign.js +++ b/lib/rules/no-cond-assign.js @@ -15,120 +15,130 @@ var NODE_DESCRIPTIONS = { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - var prohibitAssign = (context.options[0] || "except-parens"); - - /** - * Check whether an AST node is the test expression for a conditional statement. - * @param {!Object} node The node to test. - * @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`. - */ - function isConditionalTestExpression(node) { - return node.parent && - node.parent.test && - node === node.parent.test; - } - - /** - * Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement. - * @param {!Object} node The node to use at the start of the search. - * @returns {?Object} The closest ancestor node that represents a conditional statement. - */ - function findConditionalAncestor(node) { - var currentAncestor = node; - - do { - if (isConditionalTestExpression(currentAncestor)) { - return currentAncestor.parent; +module.exports = { + meta: { + docs: { + description: "disallow assignment operators in conditional expressions", + category: "Possible Errors", + recommended: true + }, + + schema: [ + { + "enum": ["except-parens", "always"] } - } while ((currentAncestor = currentAncestor.parent)); + ] + }, + + create: function(context) { + + var prohibitAssign = (context.options[0] || "except-parens"); + + /** + * Check whether an AST node is the test expression for a conditional statement. + * @param {!Object} node The node to test. + * @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`. + */ + function isConditionalTestExpression(node) { + return node.parent && + node.parent.test && + node === node.parent.test; + } - return null; - } + /** + * Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement. + * @param {!Object} node The node to use at the start of the search. + * @returns {?Object} The closest ancestor node that represents a conditional statement. + */ + function findConditionalAncestor(node) { + var currentAncestor = node; + + do { + if (isConditionalTestExpression(currentAncestor)) { + return currentAncestor.parent; + } + } while ((currentAncestor = currentAncestor.parent)); + + return null; + } - /** - * Check whether the code represented by an AST node is enclosed in parentheses. - * @param {!Object} node The node to test. - * @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`. - */ - function isParenthesised(node) { - var previousToken = context.getTokenBefore(node), - nextToken = context.getTokenAfter(node); - - return previousToken.value === "(" && previousToken.range[1] <= node.range[0] && - nextToken.value === ")" && nextToken.range[0] >= node.range[1]; - } + /** + * Check whether the code represented by an AST node is enclosed in parentheses. + * @param {!Object} node The node to test. + * @returns {boolean} `true` if the code is enclosed in parentheses; otherwise, `false`. + */ + function isParenthesised(node) { + var previousToken = context.getTokenBefore(node), + nextToken = context.getTokenAfter(node); + + return previousToken.value === "(" && previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && nextToken.range[0] >= node.range[1]; + } - /** - * Check whether the code represented by an AST node is enclosed in two sets of parentheses. - * @param {!Object} node The node to test. - * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`. - */ - function isParenthesisedTwice(node) { - var previousToken = context.getTokenBefore(node, 1), - nextToken = context.getTokenAfter(node, 1); - - return isParenthesised(node) && - previousToken.value === "(" && previousToken.range[1] <= node.range[0] && - nextToken.value === ")" && nextToken.range[0] >= node.range[1]; - } + /** + * Check whether the code represented by an AST node is enclosed in two sets of parentheses. + * @param {!Object} node The node to test. + * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`. + */ + function isParenthesisedTwice(node) { + var previousToken = context.getTokenBefore(node, 1), + nextToken = context.getTokenAfter(node, 1); + + return isParenthesised(node) && + previousToken.value === "(" && previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && nextToken.range[0] >= node.range[1]; + } + + /** + * Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses. + * @param {!Object} node The node for the conditional statement. + * @returns {void} + */ + function testForAssign(node) { + if (node.test && + (node.test.type === "AssignmentExpression") && + (node.type === "ForStatement" ? + !isParenthesised(node.test) : + !isParenthesisedTwice(node.test) + ) + ) { + + // must match JSHint's error message + context.report({ + node: node, + loc: node.test.loc.start, + message: "Expected a conditional expression and instead saw an assignment." + }); + } + } - /** - * Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses. - * @param {!Object} node The node for the conditional statement. - * @returns {void} - */ - function testForAssign(node) { - if (node.test && - (node.test.type === "AssignmentExpression") && - (node.type === "ForStatement" ? - !isParenthesised(node.test) : - !isParenthesisedTwice(node.test) - ) - ) { - - // must match JSHint's error message - context.report({ - node: node, - loc: node.test.loc.start, - message: "Expected a conditional expression and instead saw an assignment." - }); + /** + * Check whether an assignment expression is descended from a conditional statement's test expression. + * @param {!Object} node The node for the assignment expression. + * @returns {void} + */ + function testForConditionalAncestor(node) { + var ancestor = findConditionalAncestor(node); + + if (ancestor) { + context.report(ancestor, "Unexpected assignment within {{type}}.", { + type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type + }); + } } - } - /** - * Check whether an assignment expression is descended from a conditional statement's test expression. - * @param {!Object} node The node for the assignment expression. - * @returns {void} - */ - function testForConditionalAncestor(node) { - var ancestor = findConditionalAncestor(node); - - if (ancestor) { - context.report(ancestor, "Unexpected assignment within {{type}}.", { - type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type - }); + if (prohibitAssign === "always") { + return { + "AssignmentExpression": testForConditionalAncestor + }; } - } - if (prohibitAssign === "always") { return { - "AssignmentExpression": testForConditionalAncestor + "DoWhileStatement": testForAssign, + "ForStatement": testForAssign, + "IfStatement": testForAssign, + "WhileStatement": testForAssign }; - } - - return { - "DoWhileStatement": testForAssign, - "ForStatement": testForAssign, - "IfStatement": testForAssign, - "WhileStatement": testForAssign - }; -}; - -module.exports.schema = [ - { - "enum": ["except-parens", "always"] } -]; +}; diff --git a/lib/rules/no-confusing-arrow.js b/lib/rules/no-confusing-arrow.js index 8756cf431d3..09cf8d22bed 100644 --- a/lib/rules/no-confusing-arrow.js +++ b/lib/rules/no-confusing-arrow.js @@ -47,31 +47,41 @@ function isConditional(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var config = context.options[0] || {}; +module.exports = { + meta: { + docs: { + description: "disallow arrow functions where they could be confused with comparisons", + category: "ECMAScript 6", + recommended: false + }, - /** - * Reports if an arrow function contains an ambiguous conditional. - * @param {ASTNode} node - A node to check and report. - * @returns {void} - */ - function checkArrowFunc(node) { - var body = node.body; + schema: [{ + type: "object", + properties: { + allowParens: {type: "boolean"} + }, + additionalProperties: false + }] + }, + + create: function(context) { + var config = context.options[0] || {}; - if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(context, body))) { - context.report(node, "Arrow function used ambiguously with a conditional expression."); + /** + * Reports if an arrow function contains an ambiguous conditional. + * @param {ASTNode} node - A node to check and report. + * @returns {void} + */ + function checkArrowFunc(node) { + var body = node.body; + + if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(context, body))) { + context.report(node, "Arrow function used ambiguously with a conditional expression."); + } } - } - return { - "ArrowFunctionExpression": checkArrowFunc - }; + return { + "ArrowFunctionExpression": checkArrowFunc + }; + } }; - -module.exports.schema = [{ - type: "object", - properties: { - allowParens: {type: "boolean"} - }, - additionalProperties: false -}]; diff --git a/lib/rules/no-console.js b/lib/rules/no-console.js index 3efbbd4f24f..95b48d4ef52 100644 --- a/lib/rules/no-console.js +++ b/lib/rules/no-console.js @@ -10,47 +10,57 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of `console`", + category: "Possible Errors", + recommended: true + }, - return { + schema: [ + { + "type": "object", + "properties": { + "allow": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "additionalProperties": false + } + ] + }, - "MemberExpression": function(node) { + create: function(context) { - if (node.object.name === "console") { - var blockConsole = true; + return { - if (context.options.length > 0) { - var allowedProperties = context.options[0].allow; - var passedProperty = node.property.name; - var propertyIsAllowed = (allowedProperties.indexOf(passedProperty) > -1); + "MemberExpression": function(node) { - if (propertyIsAllowed) { - blockConsole = false; + if (node.object.name === "console") { + var blockConsole = true; + + if (context.options.length > 0) { + var allowedProperties = context.options[0].allow; + var passedProperty = node.property.name; + var propertyIsAllowed = (allowedProperties.indexOf(passedProperty) > -1); + + if (propertyIsAllowed) { + blockConsole = false; + } } - } - if (blockConsole) { - context.report(node, "Unexpected console statement."); + if (blockConsole) { + context.report(node, "Unexpected console statement."); + } } } - } - }; - -}; + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "allow": { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1, - "uniqueItems": true - } - }, - "additionalProperties": false } -]; +}; diff --git a/lib/rules/no-const-assign.js b/lib/rules/no-const-assign.js index d10e1b26d6c..98eefe5bc70 100644 --- a/lib/rules/no-const-assign.js +++ b/lib/rules/no-const-assign.js @@ -12,30 +12,40 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable - A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - astUtils.getModifyingReferences(variable.references).forEach(function(reference) { - context.report( - reference.identifier, - "'{{name}}' is constant.", - {name: reference.identifier.name}); - }); - } +module.exports = { + meta: { + docs: { + description: "disallow reassigning `const` variables", + category: "ECMAScript 6", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable - A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + astUtils.getModifyingReferences(variable.references).forEach(function(reference) { + context.report( + reference.identifier, + "'{{name}}' is constant.", + {name: reference.identifier.name}); + }); + } - return { - "VariableDeclaration": function(node) { - if (node.kind === "const") { - context.getDeclaredVariables(node).forEach(checkVariable); + return { + "VariableDeclaration": function(node) { + if (node.kind === "const") { + context.getDeclaredVariables(node).forEach(checkVariable); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-constant-condition.js b/lib/rules/no-constant-condition.js index 03c26a21cbd..b641a58a276 100644 --- a/lib/rules/no-constant-condition.js +++ b/lib/rules/no-constant-condition.js @@ -10,100 +10,110 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - - /** - * Checks if a branch node of LogicalExpression short circuits the whole condition - * @param {ASTNode} node The branch of main condition which needs to be checked - * @param {string} operator The operator of the main LogicalExpression. - * @returns {boolean} true when condition short circuits whole condition - */ - function isLogicalIdentity(node, operator) { - switch (node.type) { - case "Literal": - return (operator === "||" && node.value === true) || - (operator === "&&" && node.value === false); - case "LogicalExpression": - return isLogicalIdentity(node.left, node.operator) || - isLogicalIdentity(node.right, node.operator); - - // no default +module.exports = { + meta: { + docs: { + description: "disallow constant expressions in conditions", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + + /** + * Checks if a branch node of LogicalExpression short circuits the whole condition + * @param {ASTNode} node The branch of main condition which needs to be checked + * @param {string} operator The operator of the main LogicalExpression. + * @returns {boolean} true when condition short circuits whole condition + */ + function isLogicalIdentity(node, operator) { + switch (node.type) { + case "Literal": + return (operator === "||" && node.value === true) || + (operator === "&&" && node.value === false); + case "LogicalExpression": + return isLogicalIdentity(node.left, node.operator) || + isLogicalIdentity(node.right, node.operator); + + // no default + } + return false; } - return false; - } - /** - * Checks if a node has a constant truthiness value. - * @param {ASTNode} node The AST node to check. - * @param {boolean} inBooleanPosition `false` if checking branch of a condition. - * `true` in all other cases - * @returns {Bool} true when node's truthiness is constant - * @private - */ - function isConstant(node, inBooleanPosition) { - switch (node.type) { - case "Literal": - case "ArrowFunctionExpression": - case "FunctionExpression": - case "ObjectExpression": - case "ArrayExpression": - return true; - - case "UnaryExpression": - return (node.operator === "typeof" && inBooleanPosition) || - isConstant(node.argument, true); - - case "BinaryExpression": - return isConstant(node.left, false) && - isConstant(node.right, false) && - node.operator !== "in"; - case "LogicalExpression": - var isLeftConstant = isConstant(node.left, inBooleanPosition); - var isRightConstant = isConstant(node.right, inBooleanPosition); - var isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator)); - var isRightShortCircuit = (isRightConstant && isLogicalIdentity(node.right, node.operator)); - - return (isLeftConstant && isRightConstant) || isLeftShortCircuit || isRightShortCircuit; - case "AssignmentExpression": - return (node.operator === "=") && isConstant(node.right, inBooleanPosition); - - case "SequenceExpression": - return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition); - - // no default + /** + * Checks if a node has a constant truthiness value. + * @param {ASTNode} node The AST node to check. + * @param {boolean} inBooleanPosition `false` if checking branch of a condition. + * `true` in all other cases + * @returns {Bool} true when node's truthiness is constant + * @private + */ + function isConstant(node, inBooleanPosition) { + switch (node.type) { + case "Literal": + case "ArrowFunctionExpression": + case "FunctionExpression": + case "ObjectExpression": + case "ArrayExpression": + return true; + + case "UnaryExpression": + return (node.operator === "typeof" && inBooleanPosition) || + isConstant(node.argument, true); + + case "BinaryExpression": + return isConstant(node.left, false) && + isConstant(node.right, false) && + node.operator !== "in"; + case "LogicalExpression": + var isLeftConstant = isConstant(node.left, inBooleanPosition); + var isRightConstant = isConstant(node.right, inBooleanPosition); + var isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator)); + var isRightShortCircuit = (isRightConstant && isLogicalIdentity(node.right, node.operator)); + + return (isLeftConstant && isRightConstant) || isLeftShortCircuit || isRightShortCircuit; + case "AssignmentExpression": + return (node.operator === "=") && isConstant(node.right, inBooleanPosition); + + case "SequenceExpression": + return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition); + + // no default + } + return false; } - return false; - } - /** - * Reports when the given node contains a constant condition. - * @param {ASTNode} node The AST node to check. - * @returns {void} - * @private - */ - function checkConstantCondition(node) { - if (node.test && isConstant(node.test, true)) { - context.report(node, "Unexpected constant condition."); + /** + * Reports when the given node contains a constant condition. + * @param {ASTNode} node The AST node to check. + * @returns {void} + * @private + */ + function checkConstantCondition(node) { + if (node.test && isConstant(node.test, true)) { + context.report(node, "Unexpected constant condition."); + } } - } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return { - "ConditionalExpression": checkConstantCondition, - "IfStatement": checkConstantCondition, - "WhileStatement": checkConstantCondition, - "DoWhileStatement": checkConstantCondition, - "ForStatement": checkConstantCondition - }; + return { + "ConditionalExpression": checkConstantCondition, + "IfStatement": checkConstantCondition, + "WhileStatement": checkConstantCondition, + "DoWhileStatement": checkConstantCondition, + "ForStatement": checkConstantCondition + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-continue.js b/lib/rules/no-continue.js index 89fa1848e95..5cd88c61440 100644 --- a/lib/rules/no-continue.js +++ b/lib/rules/no-continue.js @@ -10,14 +10,24 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `continue` statements", + category: "Stylistic Issues", + recommended: false + }, - return { - "ContinueStatement": function(node) { - context.report(node, "Unexpected use of continue statement"); - } - }; + schema: [] + }, -}; + create: function(context) { + + return { + "ContinueStatement": function(node) { + context.report(node, "Unexpected use of continue statement"); + } + }; -module.exports.schema = []; + } +}; diff --git a/lib/rules/no-control-regex.js b/lib/rules/no-control-regex.js index 98d22840196..f6c9692f8bd 100644 --- a/lib/rules/no-control-regex.js +++ b/lib/rules/no-control-regex.js @@ -9,51 +9,61 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Get the regex expression - * @param {ASTNode} node node to evaluate - * @returns {*} Regex if found else null - * @private - */ - function getRegExp(node) { - if (node.value instanceof RegExp) { - return node.value; - } else if (typeof node.value === "string") { - - var parent = context.getAncestors().pop(); - - if ((parent.type === "NewExpression" || parent.type === "CallExpression") && - parent.callee.type === "Identifier" && parent.callee.name === "RegExp" - ) { - - // there could be an invalid regular expression string - try { - return new RegExp(node.value); - } catch (ex) { - return null; +module.exports = { + meta: { + docs: { + description: "disallow control characters in regular expressions", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + /** + * Get the regex expression + * @param {ASTNode} node node to evaluate + * @returns {*} Regex if found else null + * @private + */ + function getRegExp(node) { + if (node.value instanceof RegExp) { + return node.value; + } else if (typeof node.value === "string") { + + var parent = context.getAncestors().pop(); + + if ((parent.type === "NewExpression" || parent.type === "CallExpression") && + parent.callee.type === "Identifier" && parent.callee.name === "RegExp" + ) { + + // there could be an invalid regular expression string + try { + return new RegExp(node.value); + } catch (ex) { + return null; + } } } - } - return null; - } + return null; + } - return { - "Literal": function(node) { - var computedValue, - regex = getRegExp(node); + return { + "Literal": function(node) { + var computedValue, + regex = getRegExp(node); - if (regex) { - computedValue = regex.toString(); - if (/[\x00-\x1f]/.test(computedValue)) { - context.report(node, "Unexpected control character in regular expression."); + if (regex) { + computedValue = regex.toString(); + if (/[\x00-\x1f]/.test(computedValue)) { + context.report(node, "Unexpected control character in regular expression."); + } } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-debugger.js b/lib/rules/no-debugger.js index 7d86e76d4fd..285cc811ab7 100644 --- a/lib/rules/no-debugger.js +++ b/lib/rules/no-debugger.js @@ -9,14 +9,24 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow the use of `debugger`", + category: "Possible Errors", + recommended: true + }, - return { - "DebuggerStatement": function(node) { - context.report(node, "Unexpected 'debugger' statement."); - } - }; + schema: [] + }, -}; + create: function(context) { + + return { + "DebuggerStatement": function(node) { + context.report(node, "Unexpected 'debugger' statement."); + } + }; -module.exports.schema = []; + } +}; diff --git a/lib/rules/no-delete-var.js b/lib/rules/no-delete-var.js index d6ffbd107a6..a34ffaf5703 100644 --- a/lib/rules/no-delete-var.js +++ b/lib/rules/no-delete-var.js @@ -9,17 +9,27 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow deleting variables", + category: "Variables", + recommended: true + }, - return { + schema: [] + }, - "UnaryExpression": function(node) { - if (node.operator === "delete" && node.argument.type === "Identifier") { - context.report(node, "Variables should not be deleted."); + create: function(context) { + + return { + + "UnaryExpression": function(node) { + if (node.operator === "delete" && node.argument.type === "Identifier") { + context.report(node, "Variables should not be deleted."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-div-regex.js b/lib/rules/no-div-regex.js index 61e7a1c4d88..d556da64180 100644 --- a/lib/rules/no-div-regex.js +++ b/lib/rules/no-div-regex.js @@ -9,19 +9,29 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow division operators explicitly at the beginning of regular expressions", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "Literal": function(node) { - var token = context.getFirstToken(node); + create: function(context) { - if (token.type === "RegularExpression" && token.value[1] === "=") { - context.report(node, "A regular expression literal can be confused with '/='."); + return { + + "Literal": function(node) { + var token = context.getFirstToken(node); + + if (token.type === "RegularExpression" && token.value[1] === "=") { + context.report(node, "A regular expression literal can be confused with '/='."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-dupe-args.js b/lib/rules/no-dupe-args.js index 5de9b500b65..46bfc3f79d9 100644 --- a/lib/rules/no-dupe-args.js +++ b/lib/rules/no-dupe-args.js @@ -12,64 +12,74 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Checks whether or not a given definition is a parameter's. - * @param {escope.DefEntry} def - A definition to check. - * @returns {boolean} `true` if the definition is a parameter's. - */ - function isParameter(def) { - return def.type === "Parameter"; - } +module.exports = { + meta: { + docs: { + description: "disallow duplicate arguments in `function` definitions", + category: "Possible Errors", + recommended: true + }, - /** - * Determines if a given node has duplicate parameters. - * @param {ASTNode} node The node to check. - * @returns {void} - * @private - */ - function checkParams(node) { - var variables = context.getDeclaredVariables(node); - var keyMap = Object.create(null); + schema: [] + }, - for (var i = 0; i < variables.length; ++i) { - var variable = variables[i]; + create: function(context) { - // TODO(nagashima): Remove this duplication check after https://github.com/estools/escope/pull/79 - var key = "$" + variable.name; // to avoid __proto__. + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - if (!isParameter(variable.defs[0]) || keyMap[key]) { - continue; - } - keyMap[key] = true; + /** + * Checks whether or not a given definition is a parameter's. + * @param {escope.DefEntry} def - A definition to check. + * @returns {boolean} `true` if the definition is a parameter's. + */ + function isParameter(def) { + return def.type === "Parameter"; + } + + /** + * Determines if a given node has duplicate parameters. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkParams(node) { + var variables = context.getDeclaredVariables(node); + var keyMap = Object.create(null); + + for (var i = 0; i < variables.length; ++i) { + var variable = variables[i]; - // Checks and reports duplications. - var defs = variable.defs.filter(isParameter); + // TODO(nagashima): Remove this duplication check after https://github.com/estools/escope/pull/79 + var key = "$" + variable.name; // to avoid __proto__. - if (defs.length >= 2) { - context.report({ - node: node, - message: "Duplicate param '{{name}}'.", - data: {name: variable.name} - }); + if (!isParameter(variable.defs[0]) || keyMap[key]) { + continue; + } + keyMap[key] = true; + + // Checks and reports duplications. + var defs = variable.defs.filter(isParameter); + + if (defs.length >= 2) { + context.report({ + node: node, + message: "Duplicate param '{{name}}'.", + data: {name: variable.name} + }); + } } } - } - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - return { - "FunctionDeclaration": checkParams, - "FunctionExpression": checkParams - }; + return { + "FunctionDeclaration": checkParams, + "FunctionExpression": checkParams + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-dupe-class-members.js b/lib/rules/no-dupe-class-members.js index d61b05136d0..7e3ce2904f9 100644 --- a/lib/rules/no-dupe-class-members.js +++ b/lib/rules/no-dupe-class-members.js @@ -10,91 +10,101 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var stack = []; - - /** - * Gets state of a given member name. - * @param {string} name - A name of a member. - * @param {boolean} isStatic - A flag which specifies that is a static member. - * @returns {object} A state of a given member name. - * - retv.init {boolean} A flag which shows the name is declared as normal member. - * - retv.get {boolean} A flag which shows the name is declared as getter. - * - retv.set {boolean} A flag which shows the name is declared as setter. - */ - function getState(name, isStatic) { - var stateMap = stack[stack.length - 1]; - var key = "$" + name; // to avoid "__proto__". - - if (!stateMap[key]) { - stateMap[key] = { - nonStatic: {init: false, get: false, set: false}, - static: {init: false, get: false, set: false} - }; - } - - return stateMap[key][isStatic ? "static" : "nonStatic"]; - } - - /** - * Gets the name text of a given node. - * - * @param {ASTNode} node - A node to get the name. - * @returns {string} The name text of the node. - */ - function getName(node) { - switch (node.type) { - case "Identifier": return node.name; - case "Literal": return String(node.value); - - /* istanbul ignore next: syntax error */ - default: return ""; - } - } - - return { - - // Initializes the stack of state of member declarations. - "Program": function() { - stack = []; - }, - - // Initializes state of member declarations for the class. - "ClassBody": function() { - stack.push(Object.create(null)); - }, - - // Disposes the state for the class. - "ClassBody:exit": function() { - stack.pop(); +module.exports = { + meta: { + docs: { + description: "disallow duplicate class members", + category: "ECMAScript 6", + recommended: true }, - // Reports the node if its name has been declared already. - "MethodDefinition": function(node) { - if (node.computed) { - return; + schema: [] + }, + + create: function(context) { + var stack = []; + + /** + * Gets state of a given member name. + * @param {string} name - A name of a member. + * @param {boolean} isStatic - A flag which specifies that is a static member. + * @returns {object} A state of a given member name. + * - retv.init {boolean} A flag which shows the name is declared as normal member. + * - retv.get {boolean} A flag which shows the name is declared as getter. + * - retv.set {boolean} A flag which shows the name is declared as setter. + */ + function getState(name, isStatic) { + var stateMap = stack[stack.length - 1]; + var key = "$" + name; // to avoid "__proto__". + + if (!stateMap[key]) { + stateMap[key] = { + nonStatic: {init: false, get: false, set: false}, + static: {init: false, get: false, set: false} + }; } - var name = getName(node.key); - var state = getState(name, node.static); - var isDuplicate = false; - - if (node.kind === "get") { - isDuplicate = (state.init || state.get); - state.get = true; - } else if (node.kind === "set") { - isDuplicate = (state.init || state.set); - state.set = true; - } else { - isDuplicate = (state.init || state.get || state.set); - state.init = true; - } + return stateMap[key][isStatic ? "static" : "nonStatic"]; + } - if (isDuplicate) { - context.report(node, "Duplicate name '{{name}}'.", {name: name}); + /** + * Gets the name text of a given node. + * + * @param {ASTNode} node - A node to get the name. + * @returns {string} The name text of the node. + */ + function getName(node) { + switch (node.type) { + case "Identifier": return node.name; + case "Literal": return String(node.value); + + /* istanbul ignore next: syntax error */ + default: return ""; } } - }; -}; -module.exports.schema = []; + return { + + // Initializes the stack of state of member declarations. + "Program": function() { + stack = []; + }, + + // Initializes state of member declarations for the class. + "ClassBody": function() { + stack.push(Object.create(null)); + }, + + // Disposes the state for the class. + "ClassBody:exit": function() { + stack.pop(); + }, + + // Reports the node if its name has been declared already. + "MethodDefinition": function(node) { + if (node.computed) { + return; + } + + var name = getName(node.key); + var state = getState(name, node.static); + var isDuplicate = false; + + if (node.kind === "get") { + isDuplicate = (state.init || state.get); + state.get = true; + } else if (node.kind === "set") { + isDuplicate = (state.init || state.set); + state.set = true; + } else { + isDuplicate = (state.init || state.get || state.set); + state.init = true; + } + + if (isDuplicate) { + context.report(node, "Duplicate name '{{name}}'.", {name: name}); + } + } + }; + } +}; diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js index e07f081b4b8..d51cfc75e07 100644 --- a/lib/rules/no-dupe-keys.js +++ b/lib/rules/no-dupe-keys.js @@ -11,38 +11,48 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow duplicate keys in object literals", + category: "Possible Errors", + recommended: true + }, - return { + schema: [] + }, - "ObjectExpression": function(node) { + create: function(context) { - // Object that will be a map of properties--safe because we will - // prefix all of the keys. - var nodeProps = Object.create(null); + return { - node.properties.forEach(function(property) { + "ObjectExpression": function(node) { - if (property.type !== "Property") { - return; - } + // Object that will be a map of properties--safe because we will + // prefix all of the keys. + var nodeProps = Object.create(null); - var keyName = property.key.name || property.key.value, - key = property.kind + "-" + keyName, - checkProperty = (!property.computed || property.key.type === "Literal"); + node.properties.forEach(function(property) { - if (checkProperty) { - if (nodeProps[key]) { - context.report(node, property.loc.start, "Duplicate key '{{key}}'.", { key: keyName }); - } else { - nodeProps[key] = true; + if (property.type !== "Property") { + return; } - } - }); - } - }; + var keyName = property.key.name || property.key.value, + key = property.kind + "-" + keyName, + checkProperty = (!property.computed || property.key.type === "Literal"); -}; + if (checkProperty) { + if (nodeProps[key]) { + context.report(node, property.loc.start, "Duplicate key '{{key}}'.", { key: keyName }); + } else { + nodeProps[key] = true; + } + } + }); -module.exports.schema = []; + } + }; + + } +}; diff --git a/lib/rules/no-duplicate-case.js b/lib/rules/no-duplicate-case.js index d56fca76966..b2dc2130119 100644 --- a/lib/rules/no-duplicate-case.js +++ b/lib/rules/no-duplicate-case.js @@ -12,23 +12,33 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - return { - "SwitchStatement": function(node) { - var mapping = {}; - - node.cases.forEach(function(switchCase) { - var key = context.getSource(switchCase.test); - - if (mapping[key]) { - context.report(switchCase, "Duplicate case label."); - } else { - mapping[key] = switchCase; - } - }); - } - }; -}; +module.exports = { + meta: { + docs: { + description: "disallow duplicate case labels", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { -module.exports.schema = []; + return { + "SwitchStatement": function(node) { + var mapping = {}; + + node.cases.forEach(function(switchCase) { + var key = context.getSource(switchCase.test); + + if (mapping[key]) { + context.report(switchCase, "Duplicate case label."); + } else { + mapping[key] = switchCase; + } + }); + } + }; + } +}; diff --git a/lib/rules/no-duplicate-imports.js b/lib/rules/no-duplicate-imports.js index 6c0edbb9990..9d9af923129 100644 --- a/lib/rules/no-duplicate-imports.js +++ b/lib/rules/no-duplicate-imports.js @@ -98,29 +98,39 @@ function handleExports(context, importsInFile, exportsInFile) { }; } -module.exports = function(context) { - var includeExports = (context.options[0] || {}).includeExports, - importsInFile = [], - exportsInFile = []; +module.exports = { + meta: { + docs: { + description: "disallow duplicate module imports", + category: "ECMAScript 6", + recommended: false + }, + + schema: [{ + "type": "object", + "properties": { + "includeExports": { + "type": "boolean" + } + }, + "additionalProperties": false + }] + }, - var handlers = { - "ImportDeclaration": handleImports(context, includeExports, importsInFile, exportsInFile) - }; + create: function(context) { + var includeExports = (context.options[0] || {}).includeExports, + importsInFile = [], + exportsInFile = []; - if (includeExports) { - handlers.ExportNamedDeclaration = handleExports(context, importsInFile, exportsInFile); - handlers.ExportAllDeclaration = handleExports(context, importsInFile, exportsInFile); - } - - return handlers; -}; + var handlers = { + "ImportDeclaration": handleImports(context, includeExports, importsInFile, exportsInFile) + }; -module.exports.schema = [{ - "type": "object", - "properties": { - "includeExports": { - "type": "boolean" + if (includeExports) { + handlers.ExportNamedDeclaration = handleExports(context, importsInFile, exportsInFile); + handlers.ExportAllDeclaration = handleExports(context, importsInFile, exportsInFile); } - }, - "additionalProperties": false -}]; + + return handlers; + } +}; diff --git a/lib/rules/no-else-return.js b/lib/rules/no-else-return.js index 33a519e4e0b..d8cf2224dbe 100644 --- a/lib/rules/no-else-return.js +++ b/lib/rules/no-else-return.js @@ -9,140 +9,150 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Display the context report if rule is violated - * - * @param {Node} node The 'else' node - * @returns {void} - */ - function displayReport(node) { - context.report(node, "Unexpected 'else' after 'return'."); - } +module.exports = { + meta: { + docs: { + description: "disallow `else` blocks after `return` statements in `if` statements", + category: "Best Practices", + recommended: false + }, + + schema: [] + }, + + create: function(context) { + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Display the context report if rule is violated + * + * @param {Node} node The 'else' node + * @returns {void} + */ + function displayReport(node) { + context.report(node, "Unexpected 'else' after 'return'."); + } - /** - * Check to see if the node is a ReturnStatement - * - * @param {Node} node The node being evaluated - * @returns {boolean} True if node is a return - */ - function checkForReturn(node) { - return node.type === "ReturnStatement"; - } + /** + * Check to see if the node is a ReturnStatement + * + * @param {Node} node The node being evaluated + * @returns {boolean} True if node is a return + */ + function checkForReturn(node) { + return node.type === "ReturnStatement"; + } - /** - * Naive return checking, does not iterate through the whole - * BlockStatement because we make the assumption that the ReturnStatement - * will be the last node in the body of the BlockStatement. - * - * @param {Node} node The consequent/alternate node - * @returns {boolean} True if it has a return - */ - function naiveHasReturn(node) { - if (node.type === "BlockStatement") { - var body = node.body, - lastChildNode = body[body.length - 1]; - - return lastChildNode && checkForReturn(lastChildNode); + /** + * Naive return checking, does not iterate through the whole + * BlockStatement because we make the assumption that the ReturnStatement + * will be the last node in the body of the BlockStatement. + * + * @param {Node} node The consequent/alternate node + * @returns {boolean} True if it has a return + */ + function naiveHasReturn(node) { + if (node.type === "BlockStatement") { + var body = node.body, + lastChildNode = body[body.length - 1]; + + return lastChildNode && checkForReturn(lastChildNode); + } + return checkForReturn(node); } - return checkForReturn(node); - } - /** - * Check to see if the node is valid for evaluation, - * meaning it has an else and not an else-if - * - * @param {Node} node The node being evaluated - * @returns {boolean} True if the node is valid - */ - function hasElse(node) { - return node.alternate && node.consequent && node.alternate.type !== "IfStatement"; - } + /** + * Check to see if the node is valid for evaluation, + * meaning it has an else and not an else-if + * + * @param {Node} node The node being evaluated + * @returns {boolean} True if the node is valid + */ + function hasElse(node) { + return node.alternate && node.consequent && node.alternate.type !== "IfStatement"; + } - /** - * If the consequent is an IfStatement, check to see if it has an else - * and both its consequent and alternate path return, meaning this is - * a nested case of rule violation. If-Else not considered currently. - * - * @param {Node} node The consequent node - * @returns {boolean} True if this is a nested rule violation - */ - function checkForIf(node) { - return node.type === "IfStatement" && hasElse(node) && - naiveHasReturn(node.alternate) && naiveHasReturn(node.consequent); - } + /** + * If the consequent is an IfStatement, check to see if it has an else + * and both its consequent and alternate path return, meaning this is + * a nested case of rule violation. If-Else not considered currently. + * + * @param {Node} node The consequent node + * @returns {boolean} True if this is a nested rule violation + */ + function checkForIf(node) { + return node.type === "IfStatement" && hasElse(node) && + naiveHasReturn(node.alternate) && naiveHasReturn(node.consequent); + } - /** - * Check the consequent/body node to make sure it is not - * a ReturnStatement or an IfStatement that returns on both - * code paths. - * - * @param {Node} node The consequent or body node - * @param {Node} alternate The alternate node - * @returns {boolean} `true` if it is a Return/If node that always returns. - */ - function checkForReturnOrIf(node) { - return checkForReturn(node) || checkForIf(node); - } + /** + * Check the consequent/body node to make sure it is not + * a ReturnStatement or an IfStatement that returns on both + * code paths. + * + * @param {Node} node The consequent or body node + * @param {Node} alternate The alternate node + * @returns {boolean} `true` if it is a Return/If node that always returns. + */ + function checkForReturnOrIf(node) { + return checkForReturn(node) || checkForIf(node); + } - /** - * Check whether a node returns in every codepath. - * @param {Node} node The node to be checked - * @returns {boolean} `true` if it returns on every codepath. - */ - function alwaysReturns(node) { - if (node.type === "BlockStatement") { + /** + * Check whether a node returns in every codepath. + * @param {Node} node The node to be checked + * @returns {boolean} `true` if it returns on every codepath. + */ + function alwaysReturns(node) { + if (node.type === "BlockStatement") { - // If we have a BlockStatement, check each consequent body node. - return node.body.some(checkForReturnOrIf); - } else { + // If we have a BlockStatement, check each consequent body node. + return node.body.some(checkForReturnOrIf); + } else { - /* - * If not a block statement, make sure the consequent isn't a - * ReturnStatement or an IfStatement with returns on both paths. - */ - return checkForReturnOrIf(node); + /* + * If not a block statement, make sure the consequent isn't a + * ReturnStatement or an IfStatement with returns on both paths. + */ + return checkForReturnOrIf(node); + } } - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - return { + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - "IfStatement": function(node) { - var parent = context.getAncestors().pop(), - consequents, - alternate; + return { - // Only "top-level" if statements are checked, meaning the first `if` - // in a `if-else-if-...` chain. - if (parent.type === "IfStatement" && parent.alternate === node) { - return; - } + "IfStatement": function(node) { + var parent = context.getAncestors().pop(), + consequents, + alternate; - for (consequents = []; node.type === "IfStatement"; node = node.alternate) { - if (!node.alternate) { + // Only "top-level" if statements are checked, meaning the first `if` + // in a `if-else-if-...` chain. + if (parent.type === "IfStatement" && parent.alternate === node) { return; } - consequents.push(node.consequent); - alternate = node.alternate; - } - if (consequents.every(alwaysReturns)) { - displayReport(alternate); + for (consequents = []; node.type === "IfStatement"; node = node.alternate) { + if (!node.alternate) { + return; + } + consequents.push(node.consequent); + alternate = node.alternate; + } + + if (consequents.every(alwaysReturns)) { + displayReport(alternate); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-empty-character-class.js b/lib/rules/no-empty-character-class.js index d6341a124a4..0e46adad119 100644 --- a/lib/rules/no-empty-character-class.js +++ b/lib/rules/no-empty-character-class.js @@ -27,20 +27,30 @@ var regex = /^\/([^\\[]|\\.|\[([^\\\]]|\\.)+\])*\/[gimuy]*$/; // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow empty character classes in regular expressions", + category: "Possible Errors", + recommended: true + }, - return { + schema: [] + }, - "Literal": function(node) { - var token = context.getFirstToken(node); + create: function(context) { - if (token.type === "RegularExpression" && !regex.test(token.value)) { - context.report(node, "Empty class."); + return { + + "Literal": function(node) { + var token = context.getFirstToken(node); + + if (token.type === "RegularExpression" && !regex.test(token.value)) { + context.report(node, "Empty class."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-empty-function.js b/lib/rules/no-empty-function.js index 9ff304d4d7d..892b4db0f13 100644 --- a/lib/rules/no-empty-function.js +++ b/lib/rules/no-empty-function.js @@ -96,55 +96,65 @@ function getKind(node) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var options = context.options[0] || {}; - var allowed = options.allow || []; - - /** - * Reports a given function node if the node matches the following patterns. - * - * - Not allowed by options. - * - The body is empty. - * - The body doesn't have any comments. - * - * @param {ASTNode} node - A function node to report. This is one of - * an ArrowFunctionExpression, a FunctionDeclaration, or a - * FunctionExpression. - * @returns {void} - */ - function reportIfEmpty(node) { - var kind = getKind(node); - - if (allowed.indexOf(kind) === -1 && - node.body.type === "BlockStatement" && - node.body.body.length === 0 && - context.getComments(node.body).trailing.length === 0 - ) { - context.report({ - node: node, - loc: node.body.loc.start, - message: "Unexpected empty " + SHOW_KIND[kind] + "." - }); - } - } - - return { - ArrowFunctionExpression: reportIfEmpty, - FunctionDeclaration: reportIfEmpty, - FunctionExpression: reportIfEmpty - }; -}; +module.exports = { + meta: { + docs: { + description: "disallow empty functions", + category: "Best Practices", + recommended: false + }, -module.exports.schema = [ - { - type: "object", - properties: { - allow: { - type: "array", - items: {enum: ALLOW_OPTIONS}, - uniqueItems: true + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: {enum: ALLOW_OPTIONS}, + uniqueItems: true + } + }, + additionalProperties: false } - }, - additionalProperties: false + ] + }, + + create: function(context) { + var options = context.options[0] || {}; + var allowed = options.allow || []; + + /** + * Reports a given function node if the node matches the following patterns. + * + * - Not allowed by options. + * - The body is empty. + * - The body doesn't have any comments. + * + * @param {ASTNode} node - A function node to report. This is one of + * an ArrowFunctionExpression, a FunctionDeclaration, or a + * FunctionExpression. + * @returns {void} + */ + function reportIfEmpty(node) { + var kind = getKind(node); + + if (allowed.indexOf(kind) === -1 && + node.body.type === "BlockStatement" && + node.body.body.length === 0 && + context.getComments(node.body).trailing.length === 0 + ) { + context.report({ + node: node, + loc: node.body.loc.start, + message: "Unexpected empty " + SHOW_KIND[kind] + "." + }); + } + } + + return { + ArrowFunctionExpression: reportIfEmpty, + FunctionDeclaration: reportIfEmpty, + FunctionExpression: reportIfEmpty + }; } -]; +}; diff --git a/lib/rules/no-empty-pattern.js b/lib/rules/no-empty-pattern.js index aa8515ad191..85699dcae6e 100644 --- a/lib/rules/no-empty-pattern.js +++ b/lib/rules/no-empty-pattern.js @@ -10,19 +10,29 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - return { - "ObjectPattern": function(node) { - if (node.properties.length === 0) { - context.report(node, "Unexpected empty object pattern."); - } +module.exports = { + meta: { + docs: { + description: "disallow empty destructuring patterns", + category: "Best Practices", + recommended: true }, - "ArrayPattern": function(node) { - if (node.elements.length === 0) { - context.report(node, "Unexpected empty array pattern."); + + schema: [] + }, + + create: function(context) { + return { + "ObjectPattern": function(node) { + if (node.properties.length === 0) { + context.report(node, "Unexpected empty object pattern."); + } + }, + "ArrayPattern": function(node) { + if (node.elements.length === 0) { + context.report(node, "Unexpected empty array pattern."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-empty.js b/lib/rules/no-empty.js index df2f2b60023..08f635d0def 100644 --- a/lib/rules/no-empty.js +++ b/lib/rules/no-empty.js @@ -12,53 +12,63 @@ var FUNCTION_TYPE = /^(?:ArrowFunctionExpression|Function(?:Declaration|Expression))$/; -module.exports = function(context) { - var options = context.options[0] || {}, - allowEmptyCatch = options.allowEmptyCatch || false; - - return { - "BlockStatement": function(node) { +module.exports = { + meta: { + docs: { + description: "disallow empty block statements", + category: "Possible Errors", + recommended: true + }, - // if the body is not empty, we can just return immediately - if (node.body.length !== 0) { - return; + schema: [ + { + "type": "object", + "properties": { + "allowEmptyCatch": { + "type": "boolean" + } + }, + "additionalProperties": false } + ] + }, - // a function is generally allowed to be empty - if (FUNCTION_TYPE.test(node.parent.type)) { - return; - } + create: function(context) { + var options = context.options[0] || {}, + allowEmptyCatch = options.allowEmptyCatch || false; - if (allowEmptyCatch && node.parent.type === "CatchClause") { - return; - } + return { + "BlockStatement": function(node) { - // any other block is only allowed to be empty, if it contains a comment - if (context.getComments(node).trailing.length > 0) { - return; - } + // if the body is not empty, we can just return immediately + if (node.body.length !== 0) { + return; + } - context.report(node, "Empty block statement."); - }, + // a function is generally allowed to be empty + if (FUNCTION_TYPE.test(node.parent.type)) { + return; + } - "SwitchStatement": function(node) { + if (allowEmptyCatch && node.parent.type === "CatchClause") { + return; + } - if (typeof node.cases === "undefined" || node.cases.length === 0) { - context.report(node, "Empty switch statement."); - } - } - }; + // any other block is only allowed to be empty, if it contains a comment + if (context.getComments(node).trailing.length > 0) { + return; + } -}; + context.report(node, "Empty block statement."); + }, + + "SwitchStatement": function(node) { -module.exports.schema = [ - { - "type": "object", - "properties": { - "allowEmptyCatch": { - "type": "boolean" + if (typeof node.cases === "undefined" || node.cases.length === 0) { + context.report(node, "Empty switch statement."); + } } - }, - "additionalProperties": false + }; + } -]; +}; diff --git a/lib/rules/no-eq-null.js b/lib/rules/no-eq-null.js index 92d88920ae6..74495c7ab8f 100644 --- a/lib/rules/no-eq-null.js +++ b/lib/rules/no-eq-null.js @@ -10,20 +10,30 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: { + description: "disallow `null` comparisons without type-checking operators", + category: "Best Practices", + recommended: false + }, - return { + schema: [] + }, - "BinaryExpression": function(node) { - var badOperator = node.operator === "==" || node.operator === "!="; + create: function(context) { - if (node.right.type === "Literal" && node.right.raw === "null" && badOperator || - node.left.type === "Literal" && node.left.raw === "null" && badOperator) { - context.report(node, "Use ‘===’ to compare with ‘null’."); + return { + + "BinaryExpression": function(node) { + var badOperator = node.operator === "==" || node.operator === "!="; + + if (node.right.type === "Literal" && node.right.raw === "null" && badOperator || + node.left.type === "Literal" && node.left.raw === "null" && badOperator) { + context.report(node, "Use ‘===’ to compare with ‘null’."); + } } - } - }; + }; + } }; - -module.exports.schema = []; diff --git a/lib/rules/no-eval.js b/lib/rules/no-eval.js index 7c47c892f95..651990f58b1 100644 --- a/lib/rules/no-eval.js +++ b/lib/rules/no-eval.js @@ -77,140 +77,170 @@ function isMember(node, name) { // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - var allowIndirect = Boolean( - context.options[0] && - context.options[0].allowIndirect - ); - var sourceCode = context.getSourceCode(); - var funcInfo = null; - - /** - * Pushs a variable scope (Program or Function) information to the stack. - * - * This is used in order to check whether or not `this` binding is a - * reference to the global object. - * - * @param {ASTNode} node - A node of the scope. This is one of Program, - * FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression. - * @returns {void} - */ - function enterVarScope(node) { - var strict = context.getScope().isStrict; - - funcInfo = { - upper: funcInfo, - node: node, - strict: strict, - defaultThis: false, - initialized: strict - }; - } +module.exports = { + meta: { + docs: { + description: "disallow the use of `eval()`", + category: "Best Practices", + recommended: false + }, - /** - * Pops a variable scope from the stack. - * - * @returns {void} - */ - function exitVarScope() { - funcInfo = funcInfo.upper; - } + schema: [ + { + "type": "object", + "properties": { + "allowIndirect": {"type": "boolean"} + }, + "additionalProperties": false + } + ] + }, + + create: function(context) { + var allowIndirect = Boolean( + context.options[0] && + context.options[0].allowIndirect + ); + var sourceCode = context.getSourceCode(); + var funcInfo = null; + + /** + * Pushs a variable scope (Program or Function) information to the stack. + * + * This is used in order to check whether or not `this` binding is a + * reference to the global object. + * + * @param {ASTNode} node - A node of the scope. This is one of Program, + * FunctionDeclaration, FunctionExpression, and ArrowFunctionExpression. + * @returns {void} + */ + function enterVarScope(node) { + var strict = context.getScope().isStrict; - /** - * Reports a given node. - * - * `node` is `Identifier` or `MemberExpression`. - * The parent of `node` might be `CallExpression`. - * - * The location of the report is always `eval` `Identifier` (or possibly - * `Literal`). The type of the report is `CallExpression` if the parent is - * `CallExpression`. Otherwise, it's the given node type. - * - * @param {ASTNode} node - A node to report. - * @returns {void} - */ - function report(node) { - var locationNode = node; - var parent = node.parent; - - if (node.type === "MemberExpression") { - locationNode = node.property; + funcInfo = { + upper: funcInfo, + node: node, + strict: strict, + defaultThis: false, + initialized: strict + }; } - if (parent.type === "CallExpression" && parent.callee === node) { - node = parent; + + /** + * Pops a variable scope from the stack. + * + * @returns {void} + */ + function exitVarScope() { + funcInfo = funcInfo.upper; } - context.report({ - node: node, - loc: locationNode.loc.start, - message: "eval can be harmful." - }); - } + /** + * Reports a given node. + * + * `node` is `Identifier` or `MemberExpression`. + * The parent of `node` might be `CallExpression`. + * + * The location of the report is always `eval` `Identifier` (or possibly + * `Literal`). The type of the report is `CallExpression` if the parent is + * `CallExpression`. Otherwise, it's the given node type. + * + * @param {ASTNode} node - A node to report. + * @returns {void} + */ + function report(node) { + var locationNode = node; + var parent = node.parent; + + if (node.type === "MemberExpression") { + locationNode = node.property; + } + if (parent.type === "CallExpression" && parent.callee === node) { + node = parent; + } - /** - * Reports accesses of `eval` via the global object. - * - * @param {escope.Scope} globalScope - The global scope. - * @returns {void} - */ - function reportAccessingEvalViaGlobalObject(globalScope) { - for (var i = 0; i < candidatesOfGlobalObject.length; ++i) { - var name = candidatesOfGlobalObject[i]; - var variable = astUtils.getVariableByName(globalScope, name); + context.report({ + node: node, + loc: locationNode.loc.start, + message: "eval can be harmful." + }); + } - if (!variable) { - continue; - } + /** + * Reports accesses of `eval` via the global object. + * + * @param {escope.Scope} globalScope - The global scope. + * @returns {void} + */ + function reportAccessingEvalViaGlobalObject(globalScope) { + for (var i = 0; i < candidatesOfGlobalObject.length; ++i) { + var name = candidatesOfGlobalObject[i]; + var variable = astUtils.getVariableByName(globalScope, name); + + if (!variable) { + continue; + } - var references = variable.references; + var references = variable.references; - for (var j = 0; j < references.length; ++j) { - var identifier = references[j].identifier; - var node = identifier.parent; + for (var j = 0; j < references.length; ++j) { + var identifier = references[j].identifier; + var node = identifier.parent; - // To detect code like `window.window.eval`. - while (isMember(node, name)) { - node = node.parent; - } + // To detect code like `window.window.eval`. + while (isMember(node, name)) { + node = node.parent; + } - // Reports. - if (isMember(node, "eval")) { - report(node); + // Reports. + if (isMember(node, "eval")) { + report(node); + } } } } - } - /** - * Reports all accesses of `eval` (excludes direct calls to eval). - * - * @param {escope.Scope} globalScope - The global scope. - * @returns {void} - */ - function reportAccessingEval(globalScope) { - var variable = astUtils.getVariableByName(globalScope, "eval"); - - if (!variable) { - return; - } + /** + * Reports all accesses of `eval` (excludes direct calls to eval). + * + * @param {escope.Scope} globalScope - The global scope. + * @returns {void} + */ + function reportAccessingEval(globalScope) { + var variable = astUtils.getVariableByName(globalScope, "eval"); + + if (!variable) { + return; + } - var references = variable.references; + var references = variable.references; - for (var i = 0; i < references.length; ++i) { - var reference = references[i]; - var id = reference.identifier; + for (var i = 0; i < references.length; ++i) { + var reference = references[i]; + var id = reference.identifier; - if (id.name === "eval" && !astUtils.isCallee(id)) { + if (id.name === "eval" && !astUtils.isCallee(id)) { - // Is accessing to eval (excludes direct calls to eval) - report(id); + // Is accessing to eval (excludes direct calls to eval) + report(id); + } } } - } - if (allowIndirect) { + if (allowIndirect) { + + // Checks only direct calls to eval. It's simple! + return { + "CallExpression:exit": function(node) { + var callee = node.callee; + + if (isIdentifier(callee, "eval")) { + report(callee); + } + } + }; + } - // Checks only direct calls to eval. It's simple! return { "CallExpression:exit": function(node) { var callee = node.callee; @@ -218,84 +248,64 @@ module.exports = function(context) { if (isIdentifier(callee, "eval")) { report(callee); } - } - }; - } - - return { - "CallExpression:exit": function(node) { - var callee = node.callee; - - if (isIdentifier(callee, "eval")) { - report(callee); - } - }, - - "Program": function(node) { - var scope = context.getScope(), - features = context.parserOptions.ecmaFeatures || {}, - strict = - scope.isStrict || - node.sourceType === "module" || - (features.globalReturn && scope.childScopes[0].isStrict); - - funcInfo = { - upper: null, - node: node, - strict: strict, - defaultThis: true, - initialized: true - }; - }, - - "Program:exit": function() { - var globalScope = context.getScope(); - - exitVarScope(); - reportAccessingEval(globalScope); - reportAccessingEvalViaGlobalObject(globalScope); - }, - - "FunctionDeclaration": enterVarScope, - "FunctionDeclaration:exit": exitVarScope, - "FunctionExpression": enterVarScope, - "FunctionExpression:exit": exitVarScope, - "ArrowFunctionExpression": enterVarScope, - "ArrowFunctionExpression:exit": exitVarScope, - - "ThisExpression": function(node) { - if (!isMember(node.parent, "eval")) { - return; - } + }, + + "Program": function(node) { + var scope = context.getScope(), + features = context.parserOptions.ecmaFeatures || {}, + strict = + scope.isStrict || + node.sourceType === "module" || + (features.globalReturn && scope.childScopes[0].isStrict); + + funcInfo = { + upper: null, + node: node, + strict: strict, + defaultThis: true, + initialized: true + }; + }, + + "Program:exit": function() { + var globalScope = context.getScope(); + + exitVarScope(); + reportAccessingEval(globalScope); + reportAccessingEvalViaGlobalObject(globalScope); + }, + + "FunctionDeclaration": enterVarScope, + "FunctionDeclaration:exit": exitVarScope, + "FunctionExpression": enterVarScope, + "FunctionExpression:exit": exitVarScope, + "ArrowFunctionExpression": enterVarScope, + "ArrowFunctionExpression:exit": exitVarScope, + + "ThisExpression": function(node) { + if (!isMember(node.parent, "eval")) { + return; + } - /* - * `this.eval` is found. - * Checks whether or not the value of `this` is the global object. - */ - if (!funcInfo.initialized) { - funcInfo.initialized = true; - funcInfo.defaultThis = astUtils.isDefaultThisBinding( - funcInfo.node, - sourceCode - ); - } + /* + * `this.eval` is found. + * Checks whether or not the value of `this` is the global object. + */ + if (!funcInfo.initialized) { + funcInfo.initialized = true; + funcInfo.defaultThis = astUtils.isDefaultThisBinding( + funcInfo.node, + sourceCode + ); + } - if (!funcInfo.strict && funcInfo.defaultThis) { + if (!funcInfo.strict && funcInfo.defaultThis) { - // `this.eval` is possible built-in `eval`. - report(node.parent); + // `this.eval` is possible built-in `eval`. + report(node.parent); + } } - } - }; - -}; + }; -module.exports.schema = [ - { - "type": "object", - "properties": { - "allowIndirect": {"type": "boolean"} - }, - "additionalProperties": false } -]; +}; diff --git a/lib/rules/no-ex-assign.js b/lib/rules/no-ex-assign.js index e658e475b46..8cd37c9bbb1 100644 --- a/lib/rules/no-ex-assign.js +++ b/lib/rules/no-ex-assign.js @@ -11,27 +11,37 @@ var astUtils = require("../ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { - - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable - A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - astUtils.getModifyingReferences(variable.references).forEach(function(reference) { - context.report( - reference.identifier, - "Do not assign to the exception parameter."); - }); - } - - return { - "CatchClause": function(node) { - context.getDeclaredVariables(node).forEach(checkVariable); +module.exports = { + meta: { + docs: { + description: "disallow reassigning exceptions in `catch` clauses", + category: "Possible Errors", + recommended: true + }, + + schema: [] + }, + + create: function(context) { + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable - A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + astUtils.getModifyingReferences(variable.references).forEach(function(reference) { + context.report( + reference.identifier, + "Do not assign to the exception parameter."); + }); } - }; -}; + return { + "CatchClause": function(node) { + context.getDeclaredVariables(node).forEach(checkVariable); + } + }; -module.exports.schema = []; + } +};