diff --git a/.eslintrc.yml b/.eslintrc.yml index 3ec45cc..9d62af0 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -22,3 +22,16 @@ rules: - error - declaration - allowArrowFunctions: true + quote-props: + - error + - consistent + no-undefined: off + no-multi-spaces: + - error + - exceptions: { VariableDeclarator: true } + curly: + - error + - multi-line + key-spacing: + - error + - mode: minimum diff --git a/lib/rules/amd-function-arity.js b/lib/rules/amd-function-arity.js index ce2d423..e755e0e 100644 --- a/lib/rules/amd-function-arity.js +++ b/lib/rules/amd-function-arity.js @@ -1,93 +1,110 @@ /** - * @fileoverview Ensure AMD-style callbacks contain correct number of arguments - * @author Kevin Partington + * @file Rule to ensure AMD-style callbacks contain correct number of + * arguments + * @author Kevin Partington */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); const ast = require("../utils/ast"); -module.exports = { - meta: { - docs: {}, - schema: [ - { - "type": "object", - "properties": { - "allowExtraDependencies": { - anyOf: [ - { - "type": "boolean", - "default": false - }, - { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string" - } - } - ] +const hasCallback = ast.hasCallback; +const getDependencyNodes = rjs.getDependencyNodes; +const getModuleFunction = rjs.getModuleFunction; +const isAmdDefine = rjs.isAmdDefine; +const isRequireCall = rjs.isRequireCall; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Ensure AMD-style callbacks contain correct number of arguments", + category: "Stylistic Choices", + recommended: false +}; + +const schema = [ + { + type: "object", + properties: { + allowExtraDependencies: { + anyOf: [ + { + type: "boolean", + default: false + }, + { + type: "array", + uniqueItems: true, + items: { + type: "string" + } } - }, - "additionalProperties": false + ] } - ] - }, + }, + additionalProperties: false + } +]; - create: function (context) { - const allowExtraDependencies = (context.options && context.options[0] && context.options[0].allowExtraDependencies) || false; - const TOO_MANY_PARAMS_MESSAGE = "Too many parameters in {{functionName}} callback (expected {{expected}}, found {{actual}})."; - const TOO_FEW_PARAMS_MESSAGE = "Not enough parameters in {{functionName}} callback (expected {{expected}}, found {{actual}})."; +const defaults = { + allowExtraDependencies: false +}; - function allUnassignedPathsAreAllowed(dependencyNodes, callbackParams) { - if (typeof allowExtraDependencies === "boolean") { - return allowExtraDependencies; - } +// ----------------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------------- - const unassignedPaths = dependencyNodes.slice(callbackParams.length); +const isBoolean = (value) => typeof value === "boolean"; +const unassigned = (paths, params) => paths.slice(params.length); +const includes = (list) => (path) => list.indexOf(path.value) !== -1; - return unassignedPaths.every(function (path) { - return allowExtraDependencies.indexOf(path.value) !== -1; - }); - } +const isAmdWithCallback = (node) => + isAmdDefine(node) || isRequireCall(node) && hasCallback(node); + +const reportTooFew = (expected, actual) => + `Not enough parameters in callback (expected ${expected}, found ${actual}).`; - function checkArity(node, funcName) { - const dependencyNodes = util.getDependencyNodes(node); +const reportTooMany = (expected, actual) => + `Too many parameters in callback (expected ${expected}, found ${actual}).`; - if (!dependencyNodes) { - return; +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + const opts = Object.assign({}, defaults, context.options[0]); + const allowed = opts.allowExtraDependencies; + + const isAllowed = (paths) => + isBoolean(allowed) ? allowed : paths.every(includes(allowed)); + + return { + CallExpression(node) { + if (!isAmdWithCallback(node)) return; + + const paths = getDependencyNodes(node); + const pathCount = paths.length; + + if (!pathCount) return; + + const params = getModuleFunction(node).params; + const paramCount = params.length; + + if (pathCount < paramCount) { + context.report(node, reportTooMany(pathCount, paramCount)); } - const dependencyCount = dependencyNodes.length; - const callbackParams = util.getAmdCallback(node).params; - const actualParamCount = callbackParams.length; - - if (dependencyCount < actualParamCount) { - context.report(node, TOO_MANY_PARAMS_MESSAGE, { - functionName: funcName, - expected: dependencyCount, - actual: actualParamCount - }); - } else if (dependencyCount > actualParamCount && !allUnassignedPathsAreAllowed(dependencyNodes, callbackParams)) { - context.report(node, TOO_FEW_PARAMS_MESSAGE, { - functionName: funcName, - expected: dependencyCount, - actual: actualParamCount - }); + if (pathCount > paramCount && !isAllowed(unassigned(paths, params))) { + context.report(node, reportTooFew(pathCount, paramCount)); } } + }; +} - return { - "CallExpression": function (node) { - /* istanbul ignore else: correctly does nothing */ - if (util.isDefineCall(node) && util.isAmdDefine(node)) { - checkArity(node, "define"); - } else if (util.isRequireCall(node) && ast.hasCallback(node)) { - checkArity(node, node.callee.name); - } - } - }; - } +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/enforce-define.js b/lib/rules/enforce-define.js index 6720805..bae086c 100644 --- a/lib/rules/enforce-define.js +++ b/lib/rules/enforce-define.js @@ -1,68 +1,77 @@ /** - * @fileoverview Disallow files that are not wrapped in a call to `define` - * @author Casey Visco + * @file Rule to disallow files that are not wrapped in a call to `define` + * @author Casey Visco */ "use strict"; const path = require("path"); -const util = require("../util"); +const rjs = require("../utils/rjs"); const ast = require("../utils/ast"); -module.exports = { - meta: { - docs: {}, - schema: [ +const isDefineCall = rjs.isDefineCall; +const isExprStatement = ast.isExprStatement; +const basename = path.basename; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow files that are not wrapped in a call to `define`", + category: "Stylistic Choices", + recommended: false +}; + +const schema = [ + { + anyOf: [ { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string" - } - } - ] + type: "string" + }, + { + type: "array", + uniqueItems: true, + items: { + type: "string" + } } ] - }, - - create: function (context) { - const MESSAGE = "File must be wrapped in a `define` call"; - const ignoredFiles = context.options[0] || []; - - /** - * Determine if supplied `filename` is allowed to not be wrapped in a - * `define` call. - * @private - * @param {String} filename - filename to test - * @returns {Boolean} true if filename is allowed - */ - function isIgnoredFile(filename) { - const basename = path.basename(filename); - return ignoredFiles.indexOf(basename) !== -1; - } + } +]; - return { - "Program": function (node) { - const filename = context.getFilename(); +const message = "File must be wrapped in a `define` call"; - if (isIgnoredFile(filename)) { - return; - } +// ----------------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------------- - for (let i = 0, length = node.body.length; i < length; i += 1) { - const child = node.body[i]; +const includes = (list, item) => list.indexOf(item) !== -1; - if (!(ast.isExprStatement(child) && util.isDefineCall(child.expression))) { - context.report(node, MESSAGE); - break; - } - } +const isDefineExpr = (child) => + isExprStatement(child) && isDefineCall(child.expression); + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + const ignoredFiles = context.options[0] || []; + + return { + Program(node) { + const filename = basename(context.getFilename()); + + if (includes(ignoredFiles, filename)) return; + + if (!node.body.every(isDefineExpr)) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-amd-define.js b/lib/rules/no-amd-define.js index d1284c7..774695c 100644 --- a/lib/rules/no-amd-define.js +++ b/lib/rules/no-amd-define.js @@ -1,27 +1,43 @@ /** - * @fileoverview Disallow use of AMD (dependency array) form of `define` - * @author Casey Visco + * @file Rule to disallow use of AMD (dependency array) form of `define` + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const MESSAGE = "AMD form of `define` is not allowed."; - - return { - "CallExpression": function (node) { - if (util.isDefineCall(node) && util.isAmdDefine(node)) { - context.report(node, MESSAGE); - } +const isAmdDefine = rjs.isAmdDefine; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow use of AMD (dependency array) form of `define`", + category: "Stylistic Choices", + recommended: false +}; + +const schema = []; + +const message = "AMD form of `define` is not allowed."; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + return { + CallExpression(node) { + if (isAmdDefine(node)) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-assign-exports.js b/lib/rules/no-assign-exports.js index 8e7aacf..5d4d108 100644 --- a/lib/rules/no-assign-exports.js +++ b/lib/rules/no-assign-exports.js @@ -1,47 +1,54 @@ /** - * @fileoverview Disallow assignment to `exports` when using Simplified CommonJS Wrapper - * @author Casey Visco + * @file Rule to disallow assignment to `exports` when using Simplified + * CommonJS Wrapper + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const ast = require("../utils/ast"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const MESSAGE = "Invalid assignment to `exports`."; - const fnStack = []; - - /** - * Determine if supplied `node` represents an assignment to `exports`. - * @private - * @param {ASTNode} node - AssignmentExpression node to test - * @returns {Boolean} true if represents assignment to `exports` - */ - function isExportsAssignment(node) { - return node.left.type === "Identifier" && - node.left.name === "exports"; - } +const ancestor = ast.ancestor; +const isCommonJsWrapper = rjs.isCommonJsWrapper; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow assignment to `exports` when using Simplified CommonJS Wrapper", + category: "Possible Errors", + recommended: true +}; + +const schema = []; - return { - "FunctionExpression": function (node) { - fnStack.push(util.isDefineCall(node.parent) && util.isCommonJsWrapper(node.parent)); - }, +const message = "Invalid assignment to `exports`."; - "FunctionExpression:exit": function () { - fnStack.pop(); - }, +// ----------------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------------- - "AssignmentExpression": function (node) { - if (isExportsAssignment(node) && util.isInsideModuleDef(fnStack)) { - context.report(node, MESSAGE); - } +const assignsToExports = (node) => + node.left.type === "Identifier" && + node.left.name === "exports"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + return { + AssignmentExpression(node) { + if (assignsToExports(node) && ancestor(isCommonJsWrapper, node)) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-assign-require.js b/lib/rules/no-assign-require.js index 1e5a4cb..6cb1100 100644 --- a/lib/rules/no-assign-require.js +++ b/lib/rules/no-assign-require.js @@ -1,79 +1,65 @@ /** - * @fileoverview Rule to disallow assignment to `require` or `window.require` - * @author Casey Visco + * @file Rule to disallow assignment to `require` or `window.require` + * @author Casey Visco */ "use strict"; -const isIdentifier = require("../utils/ast").isIdentifier; -const isMemberExpr = require("../utils/ast").isMemberExpr; +const ast = require("../utils/ast"); + +const isIdentifier = ast.isIdentifier; +const isMemberExpr = ast.isMemberExpr; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow assignment to `require` or `window.require`", + category: "Stylistic Choices", + recommended: false +}; + +const schema = []; + +const message = "Invalid assignment to `require`."; // ----------------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------------- -/** - * Determine if supplied `node` represents a `require` identifier - * @private - * @param {ASTNode} node - node to test - * @returns {Boolean} true if `require` identifier - */ -function isRequireIdentifier(node) { - return isIdentifier(node) && node.name === "require"; -} +const isWindow = (node) => + isIdentifier(node) && node.name === "window"; -/** - * Determine if supplied `node` represents a `window` identifier - * @private - * @param {ASTNode} node - node to test - * @returns {Boolean} true if `window` identifier - */ -function isWindowIdentifier(node) { - return isIdentifier(node) && node.name === "window"; -} +const isRequire = (node) => + isIdentifier(node) && node.name === "require"; -/** - * Determine if supplied `node` represents a `window.require` - * MemberExpression. - * @private - * @param {ASTNode} node - node to test - * @returns {Boolean} true if represents `window.require` expression - */ -function isWindowRequireExpr(node) { - return isMemberExpr(node) && - isWindowIdentifier(node.object) && - isRequireIdentifier(node.property); -} +const isWindowRequire = (node) => + isMemberExpr(node) && + isWindow(node.object) && + isRequire(node.property); // ----------------------------------------------------------------------------- // Rule Definition // ----------------------------------------------------------------------------- -const ERROR_MSG = "Invalid assignment to `require`."; - -module.exports = { - meta: { - docs: { - description: "Disallow assignment to `require` or `window.require`", - category: "Stylistic Choices", - recommended: false +function create(context) { + return { + AssignmentExpression(node) { + if (isRequire(node.left) || isWindowRequire(node.left)) { + context.report(node, message); + } }, - schema: [] - }, - - create: function (context) { - return { - "AssignmentExpression": function (node) { - if (isRequireIdentifier(node.left) || isWindowRequireExpr(node.left)) { - context.report(node, ERROR_MSG); - } - }, - "VariableDeclarator": function (node) { - if (isRequireIdentifier(node.id)) { - context.report(node, ERROR_MSG); - } + VariableDeclarator(node) { + if (isRequire(node.id)) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-commonjs-exports.js b/lib/rules/no-commonjs-exports.js index aa00dd1..44c526a 100644 --- a/lib/rules/no-commonjs-exports.js +++ b/lib/rules/no-commonjs-exports.js @@ -1,49 +1,55 @@ /** - * @fileoverview Disallow use of `exports` in a module definition when using Simplified CommonJS Wrapper - * @author Casey Visco + * @file Rule to disallow use of `exports` in a module definition when using + * Simplified CommonJS Wrapper + * @author Casey Visco */ "use strict"; -const util = require("../util"); const ast = require("../utils/ast"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const MESSAGE = "Unexpected use of `exports` in module definition."; - const fnStack = []; - - /** - * Determine if supplied `node` represents an assignment to a property of - * the `exports` object. - * @private - * @param {ASTNode} node - AssignmentExpression node to test - * @returns {Boolean} true if represents assignment to property of `exports` - */ - function isExportsPropertyAssignment(node) { - return ast.isMemberExpr(node.left) && - node.left.object.name === "exports"; - } +const ancestor = ast.ancestor; +const isMemberExpr = ast.isMemberExpr; +const isCommonJsWrapper = rjs.isCommonJsWrapper; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow use of `exports` in a module definition when using Simplified CommonJS Wrapper", + category: "Stylistic Choices", + recommended: true +}; + +const schema = []; - return { - "FunctionExpression": function (node) { - fnStack.push(util.isDefineCall(node.parent) && util.isCommonJsWrapper(node.parent)); - }, +const messages = "Unexpected use of `exports` in module definition."; - "FunctionExpression:exit": function () { - fnStack.pop(); - }, +// ----------------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------------- - "AssignmentExpression": function (node) { - if (isExportsPropertyAssignment(node) && util.isInsideModuleDef(fnStack)) { - context.report(node, MESSAGE); - } +const assignsToExportsProperty = (node) => + isMemberExpr(node.left) && + node.left.object.name === "exports"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + return { + AssignmentExpression(node) { + if (assignsToExportsProperty(node) && ancestor(isCommonJsWrapper, node)) { + context.report(node, messages); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-commonjs-module-exports.js b/lib/rules/no-commonjs-module-exports.js index 1d11a41..4dd3358 100644 --- a/lib/rules/no-commonjs-module-exports.js +++ b/lib/rules/no-commonjs-module-exports.js @@ -1,49 +1,56 @@ /** - * @fileoverview Disallow use of `module.exports` in a module definition when using Simplified CommonJS Wrapper - * @author Casey Visco + * @file Rule to disallow use of `module.exports` in a module definition when + * using Simplified CommonJS Wrapper + * @author Casey Visco */ "use strict"; -const util = require("../util"); const ast = require("../utils/ast"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const MESSAGE = "Unexpected use of `module.exports` in module definition."; - const fnStack = []; - - /** - * Determine if supplied `node` represents an assignment to `module.exports` - * @private - * @param {ASTNode} node - AssignmentExpression node to test - * @returns {Boolean} true if represents assignment to `module.exports` - */ - function isModuleExportsAssignment(node) { - return ast.isMemberExpr(node.left) && - node.left.object.name === "module" && - node.left.property.name === "exports"; - } +const ancestor = ast.ancestor; +const isMemberExpr = ast.isMemberExpr; +const isCommonJsWrapper = rjs.isCommonJsWrapper; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow use of `module.exports` in a module definition when using Simplified CommonJS Wrapper", + category: "Stylistic Choices", + recommended: true +}; + +const schema = []; - return { - "FunctionExpression": function (node) { - fnStack.push(util.isDefineCall(node.parent) && util.isCommonJsWrapper(node.parent)); - }, +const message = "Unexpected use of `module.exports` in module definition."; - "FunctionExpression:exit": function () { - fnStack.pop(); - }, +// ----------------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------------- - "AssignmentExpression": function (node) { - if (isModuleExportsAssignment(node) && util.isInsideModuleDef(fnStack)) { - context.report(node, MESSAGE); - } +const assignsToModuleExports = (node) => + isMemberExpr(node.left) && + node.left.object.name === "module" && + node.left.property.name === "exports"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + return { + AssignmentExpression(node) { + if (assignsToModuleExports(node) && ancestor(isCommonJsWrapper, node)) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-commonjs-return.js b/lib/rules/no-commonjs-return.js index bac4616..f29c882 100644 --- a/lib/rules/no-commonjs-return.js +++ b/lib/rules/no-commonjs-return.js @@ -1,63 +1,56 @@ /** - * @fileoverview Disallow use of `return` statement in a module definition - * when using Simplified CommonJS Wrapper - * - * @author Casey Visco + * @file Rule to disallow use of `return` statement in a module definition + * when using Simplified CommonJS Wrapper + * @author Casey Visco */ "use strict"; -const isDefineCall = require("../util").isDefineCall; -const isCommonJsWrapper = require("../util").isCommonJsWrapper; +const ast = require("../utils/ast"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const MESSAGE = "Unexpected `return` in module definition."; - const functions = []; - - /** - * Mark entrance into a function by pushing a new object onto the functions - * stack. Store whether function is the actual module definition. - * @private - * @param {ASTNode} node - function node to store - * @returns {void} - */ - function enterFunction(node) { - const parent = node.parent; - - functions.push({ - isModuleDef: isDefineCall(parent) && isCommonJsWrapper(parent) - }); - } +const nearest = ast.nearest; +const isCommonJsWrapper = rjs.isCommonJsWrapper; - /** - * Mark exit of a function by popping it off the functions stack. - * @private - * @returns {void} - */ - function exitFunction() { - functions.pop(); - } +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow use of `return` statement in a module definition when", + category: "Stylistic Choices", + recommended: true +}; + +const schema = []; - return { - "FunctionDeclaration": enterFunction, - "FunctionExpression": enterFunction, +const message = "Unexpected `return` in module definition."; - "FunctionDeclaration:exit": exitFunction, - "FunctionExpression:exit": exitFunction, +// ----------------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------------- - "ReturnStatement": function (node) { - const fn = functions[functions.length - 1]; +const isFunction = (node) => + node.type === "FunctionDeclaration" || + node.type === "FunctionExpression"; - if (fn.isModuleDef) { - context.report(node, MESSAGE); - } +const nearestFunction = (node) => nearest(isFunction, node); + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + return { + ReturnStatement(node) { + if (isCommonJsWrapper(nearestFunction(node).parent)) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-commonjs-wrapper.js b/lib/rules/no-commonjs-wrapper.js index ed80fb2..8911d8c 100644 --- a/lib/rules/no-commonjs-wrapper.js +++ b/lib/rules/no-commonjs-wrapper.js @@ -1,27 +1,43 @@ /** - * @fileoverview Disallow use of Simplified CommonJS Wrapper form of `define` - * @author Casey Visco + * @file Rule to disallow use of Simplified CommonJS Wrapper form of `define` + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const MESSAGE = "Simplified CommonJS Wrapper form of `define` is not allowed"; - - return { - "CallExpression": function (node) { - if (util.isDefineCall(node) && util.isCommonJsWrapper(node)) { - context.report(node, MESSAGE); - } +const isCommonJsWrapper = rjs.isCommonJsWrapper; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow use of Simplified CommonJS Wrapper form of `define`", + category: "Stylistic Choices", + recommended: true +}; + +const schema = []; + +const message = "Simplified CommonJS Wrapper form of `define` is not allowed"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + return { + CallExpression(node) { + if (isCommonJsWrapper(node)) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-conditional-require.js b/lib/rules/no-conditional-require.js index 6f57137..9c9fd06 100644 --- a/lib/rules/no-conditional-require.js +++ b/lib/rules/no-conditional-require.js @@ -1,52 +1,53 @@ /** - * @fileoverview Rule to disallow use of conditional `require` calls - * @author Casey Visco + * @file Rule to disallow use of conditional `require` calls + * @author Casey Visco */ "use strict"; -const isRequireCall = require("../util").isRequireCall; -const ancestor = require("../utils/ast").ancestor; +const ast = require("../utils/ast"); +const rjs = require("../utils/rjs"); + +const ancestor = ast.ancestor; +const isRequireCall = rjs.isRequireCall; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow use of conditional `require` calls", + category: "Stylistic Choices", + recommended: true +}; + +const schema = []; + +const message = "Conditional `require` calls are not allowed."; // ----------------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------------- -/** - * Test if supplied `node` represents a conditional—either an `if` statement - * or a ternary expression. - * @private - * @param {ASTNode} node - node to test - * @returns {Boolean} true if node represents a conditional - */ -function isConditional(node) { - return node.type === "IfStatement" || - node.type === "ConditionalExpression"; -} +const isConditional = (node) => + node.type === "IfStatement" || + node.type === "ConditionalExpression"; // ----------------------------------------------------------------------------- // Rule Definition // ----------------------------------------------------------------------------- -const ERROR_MSG = "Conditional `require` calls are not allowed."; +function create(context) { + return { + CallExpression(node) { + if (isRequireCall(node) && ancestor(isConditional, node)) { + context.report(node, message); + } + } + }; +} module.exports = { - meta: { - docs: { - description: "Disallow use of conditional `require` calls", - category: "Stylistic Choices", - recommended: true - }, - schema: [] - }, - - create: function (context) { - return { - "CallExpression": function (node) { - if (isRequireCall(node) && ancestor(isConditional, node)) { - context.report(node, ERROR_MSG); - } - } - }; - } + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-dynamic-require.js b/lib/rules/no-dynamic-require.js index f85cb50..45f6246 100644 --- a/lib/rules/no-dynamic-require.js +++ b/lib/rules/no-dynamic-require.js @@ -1,55 +1,54 @@ /** - * @fileoverview Rule to disallow dynamically generated paths in `require` call - * @author Casey Visco + * @file Rule to disallow dynamically generated paths in `require` call + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); const ast = require("../utils/ast"); -const isRequireCall = util.isRequireCall; +const isRequireCall = rjs.isRequireCall; const isStringLiteral = ast.isStringLiteral; const isStringLiteralArray = ast.isStringLiteralArray; +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow use of dynamically generated paths in a require call", + category: "Stylistic Choices", + recommended: false +}; + +const schema = []; + +const message = "Dynamic `require` calls are not allowed."; + // ----------------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------------- -/** - * Determine if supplied `node` represents either a String literal or an Array - * of String literals, indicating a static list of dependencies. - * @private - * @param {ASTNode} node - node to test - * @returns {Boolean} true if node represents static dependencies - */ -function isStatic(node) { - return isStringLiteral(node) || isStringLiteralArray(node); -} +const hasStaticDependencies = (node) => + isStringLiteral(node.arguments[0]) || + isStringLiteralArray(node.arguments[0]); // ----------------------------------------------------------------------------- // Rule Definition // ----------------------------------------------------------------------------- -const ERROR_MSG = "Dynamic `require` calls are not allowed."; +function create(context) { + return { + CallExpression(node) { + if (isRequireCall(node) && !hasStaticDependencies(node)) { + context.report(node, message); + } + } + }; +} module.exports = { - meta: { - docs: { - description: "Disallow use of dynamically generated paths in a require call", - category: "Stylistic Choices", - recommended: false - }, - schema: [] - }, - - create: function (context) { - return { - "CallExpression": function (node) { - if (isRequireCall(node) && !isStatic(node.arguments[0])) { - context.report(node, ERROR_MSG); - } - } - }; - } + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-function-define.js b/lib/rules/no-function-define.js index 6fcd5fa..9ed56e8 100644 --- a/lib/rules/no-function-define.js +++ b/lib/rules/no-function-define.js @@ -1,27 +1,43 @@ /** - * @fileoverview Disallow use of simple function form of `define` - * @author Casey Visco + * @file Rule to disallow use of simple function form of `define` + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const MESSAGE = "Simple function form of `define` is not allowed"; - - return { - "CallExpression": function (node) { - if (util.isDefineCall(node) && util.isFunctionDefine(node)) { - context.report(node, MESSAGE); - } +const isFunctionDefine = rjs.isFunctionDefine; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow use of simple function form of `define`", + category: "Stylistic Choices", + recommended: false +}; + +const schema = []; + +const message = "Simple function form of `define` is not allowed"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + return { + CallExpression(node) { + if (isFunctionDefine(node)) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-invalid-define.js b/lib/rules/no-invalid-define.js index 74638e7..288ad9b 100644 --- a/lib/rules/no-invalid-define.js +++ b/lib/rules/no-invalid-define.js @@ -1,27 +1,44 @@ /** - * @fileoverview Disallow invalid or undesired forms of `define` - * @author Casey Visco + * @file Rule to disallow invalid or undesired forms of `define` + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const MESSAGE = "Invalid module definition"; - - return { - "CallExpression": function (node) { - if (util.isDefineCall(node) && !util.isValidDefine(node)) { - context.report(node, MESSAGE); - } +const isDefineCall = rjs.isDefineCall; +const isValidDefine = rjs.isValidDefine; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow invalid or undesired forms of `define`", + category: "Possible Errors", + recommended: true +}; + +const schema = []; + +const message = "Invalid module definition"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + return { + CallExpression(node) { + if (isDefineCall(node) && !isValidDefine(node)) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-invalid-require.js b/lib/rules/no-invalid-require.js index a2cda2c..b43b818 100644 --- a/lib/rules/no-invalid-require.js +++ b/lib/rules/no-invalid-require.js @@ -1,27 +1,44 @@ /** - * @fileoverview Disallow invalid or undesired forms of `require` - * @author Casey Visco + * @file Rule to disallow invalid or undesired forms of `require` + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const MESSAGE = "Invalid arguments provided to `require` call."; - - return { - "CallExpression": function (node) { - if (util.isRequireCall(node) && !util.isValidRequire(node)) { - context.report(node, MESSAGE); - } +const isRequireCall = rjs.isRequireCall; +const isValidRequire = rjs.isValidRequire; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow invalid or undesired forms of `require`", + category: "Possible Errors", + recommended: true +}; + +const schema = []; + +const message = "Invalid arguments provided to `require` call."; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + return { + CallExpression(node) { + if (isRequireCall(node) && !isValidRequire(node)) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-js-extension.js b/lib/rules/no-js-extension.js index 815bbcf..71c7a8f 100644 --- a/lib/rules/no-js-extension.js +++ b/lib/rules/no-js-extension.js @@ -1,70 +1,72 @@ /** - * @fileoverview Rule to disallow `.js` extension in dependency paths - * @author Casey Visco + * @file Rule to disallow `.js` extension in dependency paths + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); -const isDefineCall = util.isDefineCall; -const isRequireCall = util.isRequireCall; -const getDependencyStringNodes = util.getDependencyStringNodes; +const isAmdCall = rjs.isAmdCall; +const getDependencyStringNodes = rjs.getDependencyStringNodes; // ----------------------------------------------------------------------------- -// Rule Definition +// Configuration // ----------------------------------------------------------------------------- -const ERROR_MSG = "Don't use .js extension in dependency path."; +const docs = { + description: "Disallow `.js` extension in dependency paths", + category: "Possible Errors", + recommended: true +}; -module.exports = { - meta: { - docs: { - description: "Disallow `.js` extension in dependency paths", - category: "Possible Errors", - recommended: true - }, - schema: [ - { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string" - } - } - ] - }, - - create: function (context) { - const pluginsToCheck = context.options[0] || []; - const LISTED_PLUGINS = new RegExp("^(" + pluginsToCheck.join("|") + ")\!", "i"); - const ALL_PLUGINS = /^(\w+)\!/i; - - /** - * Determines if provided `node` should be checked for .js extension or - * not. Path's to check either do not have a plugin prefix OR have a - * plugin prefix that's supplied as an option to the rule. - * @private - * @param {String} path - path to test - * @returns {Boolean} true if path should be checked for .js extension - */ - function shouldBeChecked(path) { - return path.match(LISTED_PLUGINS) || !path.match(ALL_PLUGINS); +const schema = [ + { + type: "array", + uniqueItems: true, + items: { + type: "string" } + } +]; + +const message = "Don't use .js extension in dependency path."; + +// ----------------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------------- + +const append = (str) => (val) => val + str; +const report = (context) => (node) => context.report(node, message); + +const hasJsExtension = (node) => node.value.trim().endsWith(".js"); +const startsWithOneOf = (list, str) => list.some((val) => str.startsWith(val)); +const hasNoPlugin = (str) => !str.includes("!"); - function check(node) { - const path = node.value.trim(); - if (shouldBeChecked(path) && path.endsWith(".js")) { - context.report(node, ERROR_MSG); - } +const shouldBeChecked = (plugins) => (node) => + startsWithOneOf(plugins, node.value) || + hasNoPlugin(node.value); + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + const plugins = (context.options[0] || []).map(append("!")); + + return { + CallExpression(node) { + if (!isAmdCall(node)) return; + + getDependencyStringNodes(node) + .filter(shouldBeChecked(plugins)) + .filter(hasJsExtension) + .forEach(report(context)); } + }; +} - return { - "CallExpression": function (node) { - if (isDefineCall(node) || isRequireCall(node)) { - getDependencyStringNodes(node).forEach(check); - } - } - }; - } +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-multiple-define.js b/lib/rules/no-multiple-define.js index 555bd8d..06d51e1 100644 --- a/lib/rules/no-multiple-define.js +++ b/lib/rules/no-multiple-define.js @@ -1,32 +1,45 @@ /** - * @fileoverview Disallow multiple `define` calls in a single file - * @author Casey Visco + * @file Rule to disallow multiple `define` calls in a single file + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const MESSAGE = "Multiple `define` calls in a single file are not permitted"; - let defineCalls = 0; - - return { - "CallExpression": function (node) { - if (util.isDefineCall(node)) { - ++defineCalls; - - if (defineCalls > 1) { - context.report(node, MESSAGE); - } - } +const isDefineCall = rjs.isDefineCall; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow multiple `define` calls in a single file", + category: "Stylistic Choices", + recommended: false +}; + +const schema = []; + +const message = "Multiple `define` calls in a single file are not permitted"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + let defineCalls = 0; + + return { + CallExpression: function (node) { + if (isDefineCall(node) && ++defineCalls > 1) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-named-define.js b/lib/rules/no-named-define.js index 4aa98ff..fa19c1a 100644 --- a/lib/rules/no-named-define.js +++ b/lib/rules/no-named-define.js @@ -1,27 +1,43 @@ /** - * @fileoverview Disallow use of named module form of `define` - * @author Casey Visco + * @file Rule to disallow use of named module form of `define` + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const MESSAGE = "Named module form of `define` is not allowed"; - - return { - "CallExpression": function (node) { - if (util.isDefineCall(node) && util.isNamedDefine(node)) { - context.report(node, MESSAGE); - } +const isNamedDefine = rjs.isNamedDefine; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow use of named module form of `define`", + category: "Stylistic Choices", + recommended: false +}; + +const schema = []; + +const message = "Named module form of `define` is not allowed"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + return { + CallExpression(node) { + if (isNamedDefine(node)) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-object-define.js b/lib/rules/no-object-define.js index a9aac05..1a23b85 100644 --- a/lib/rules/no-object-define.js +++ b/lib/rules/no-object-define.js @@ -1,27 +1,43 @@ /** - * @fileoverview Disallow use of Simple Name/Value Pairs form of `define` - * @author Casey Visco + * @file Rule to disallow use of Simple Name/Value Pairs form of `define` + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const MESSAGE = "Simple Name/Value Pairs form of `define` is not allowed"; - - return { - "CallExpression": function (node) { - if (util.isDefineCall(node) && util.isObjectDefine(node)) { - context.report(node, MESSAGE); - } +const isObjectDefine = rjs.isObjectDefine; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow use of Simple Name/Value Pairs form of `define`", + category: "Stylistic Choices", + recommended: false +}; + +const schema = []; + +const message = "Simple Name/Value Pairs form of `define` is not allowed"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + return { + CallExpression(node) { + if (isObjectDefine(node)) { + context.report(node, message); } - }; - } + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/no-require-tourl.js b/lib/rules/no-require-tourl.js index 7636bc9..506b6a8 100644 --- a/lib/rules/no-require-tourl.js +++ b/lib/rules/no-require-tourl.js @@ -1,39 +1,62 @@ /** - * @fileoverview Disallow use of `require.toUrl` and `require.nameToUrl` - * @author Casey Visco + * @file Rule to disallow use of `require.toUrl` and `require.nameToUrl` + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); const ast = require("../utils/ast"); -module.exports = { - meta: { - docs: {}, - schema: [] - }, - - create: function (context) { - const TO_URL_MESSAGE = "Use of `require.toUrl` is not allowed."; - const NAME_TO_URL_MESSAGE = "Use of `require.nameToUrl` is not allowed."; - - function isRequireMemberCall(node, methodName) { - return ast.isMemberExpr(node) && - ast.isIdentifier(node.object) && - util.isRequireIdentifier(node.object) && - ast.isIdentifier(node.property) && - node.property.name === methodName; - } +const isMemberExpr = ast.isMemberExpr; +const isIdentifier = ast.isIdentifier; +const isRequireIdentifier = rjs.isRequireIdentifier; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Disallow use of `require.toUrl` and `require.nameToUrl`", + category: "Stylistic Choices", + recommended: false +}; + +const schema = []; + +// ----------------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------------- + +const isRequireMemberCall = (node, method) => + isMemberExpr(node) && + isIdentifier(node.object) && + isRequireIdentifier(node.object) && + isIdentifier(node.property) && + node.property.name === method; - return { - "CallExpression": function (node) { - if (isRequireMemberCall(node.callee, "toUrl")) { - context.report(node, TO_URL_MESSAGE); - } else if (isRequireMemberCall(node.callee, "nameToUrl")) { - context.report(node, NAME_TO_URL_MESSAGE); - } - } - }; +const message = (prop) => `Use of \`require.${prop}\` is not allowed.`; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +function create(context) { + function check(node, method) { + if (isRequireMemberCall(node.callee, method)) { + context.report(node, message(method)); + } } + + return { + CallExpression(node) { + check(node, "toUrl"); + check(node, "nameToUrl"); + } + }; +} + +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/rules/one-dependency-per-line.js b/lib/rules/one-dependency-per-line.js index b61e589..845bd61 100644 --- a/lib/rules/one-dependency-per-line.js +++ b/lib/rules/one-dependency-per-line.js @@ -1,29 +1,89 @@ /** - * @fileoverview Rule to enforce or disallow one dependency per line. - * @author Casey Visco + * @file Rule to enforce or disallow one dependency per line. + * @author Casey Visco */ "use strict"; -const util = require("../util"); +const rjs = require("../utils/rjs"); const ast = require("../utils/ast"); -const isDefineCall = util.isDefineCall; -const isRequireCall = util.isRequireCall; +const isAmdCall = rjs.isAmdCall; +const withoutModuleId = rjs.withoutModuleId; const isArrayExpr = ast.isArrayExpr; const isFunctionExpr = ast.isFunctionExpr; -const isStringLiteral = ast.isStringLiteral; + +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- + +const docs = { + description: "Require or disallow one dependency per line", + category: "Stylistic Choices", + recommended: false +}; + +const schema = [ + { + type: "object", + properties: { + paths: { + oneOf: [ + { + enum: ["always", "never"] + }, + { + type: "number", + minimum: 0 + } + ] + }, + names: { + oneOf: [ + { + enum: ["always", "never"] + }, + { + type: "number", + minimum: 0 + } + ] + } + }, + additionalProperties: false + } +]; + +const defaults = { + paths: "always", + names: "always" +}; + +const messages = { + always: { + paths: "Only one dependency path is permitted per line.", + names: "Only one dependency name is permitted per line." + }, + never: { + paths: "Dependency paths must appear on one line.", + names: "Dependency names must appear on one line." + } +}; // ----------------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------------- -const pad = (amount) => (value) => " ".repeat(amount) + value; -const line = (node) => node.loc.start.line; +const pad = (amount) => (val) => " ".repeat(amount) + val; +const line = (node) => node.loc.start.line; const column = (node) => node.loc.start.column; const unique = (list) => Array.from(new Set(list)); + const hasDuplicates = (list) => unique(list).length < list.length; -const hasMultiple = (list) => unique(list).length > 1; +const hasMultiple = (list) => unique(list).length > 1; +const isLongerThan = (val, list) => Number.isInteger(val) && list.length > val; +const isAlways = (val) => val === "always"; +const isNever = (val) => val === "never"; const indentation = (node) => { const statement = node.body.body[0]; @@ -31,21 +91,13 @@ const indentation = (node) => { }; const formatPaths = (indent) => (node) => { - const paths = node.elements - .map(v => v.raw) - .map(pad(indent)) - .join(",\n"); - + const paths = node.elements.map(v => v.raw).map(pad(indent)).join(",\n"); return `[\n${paths}\n]`; }; const formatNames = (indent, context) => (node) => { + const names = node.params.map(v => v.name).map(pad(indent)).join(",\n"); const body = context.getSourceCode().getText(node.body); - const names = node.params - .map(v => v.name) - .map(pad(indent)) - .join(",\n"); - return `function (\n${names}\n) ${body}`; }; @@ -53,117 +105,44 @@ const formatNames = (indent, context) => (node) => { // Rule Definition // ----------------------------------------------------------------------------- -const ALWAYS_MSG = { - paths: "Only one dependency path is permitted per line.", - names: "Only one dependency name is permitted per line." -}; +function create(context) { + const settings = Object.assign({}, defaults, context.options[0]); -const NEVER_MSG = { - paths: "Dependency paths must appear on one line.", - names: "Dependency names must appear on one line." -}; + function check(setting, node, list, format) { + const val = settings[setting]; + const lines = list.map(line); -module.exports = { - meta: { - docs: { - description: "Require or disallow one dependency per line", - category: "Stylistic Choices", - recommended: false - }, - fixable: "code", - schema: [ - { - "type": "object", - "properties": { - "paths": { - "oneOf": [ - { - "enum": ["always", "never"] - }, - { - "type": "number", - "minimum": 0 - } - ] - }, - "names": { - "oneOf": [ - { - "enum": ["always", "never"] - }, - { - "type": "number", - "minimum": 0 - } - ] - } - }, - "additionalProperties": false - } - ] - }, - - create: function (context) { - const options = context.options[0] || {}; - - const settings = { - paths: "paths" in options ? options.paths : "always", - names: "names" in options ? options.names : "always" - }; - - function isAlways(setting, list) { - const value = settings[setting]; - switch (value) { - case "always": - return true; - case "never": - return false; - default: - return list.length > value; - } + if ((isLongerThan(val, lines) || isAlways(val)) && hasDuplicates(lines)) { + const message = messages.always[setting]; + const fix = (f) => f.replaceTextRange(node.range, format(node)); + context.report({ node, message, fix }); } - function isNever(setting) { - return settings[setting] === "never"; + if (isNever(val) && hasMultiple(lines)) { + const message = messages.never[setting]; + context.report({ node, message }); } + } - function check(setting, node, list, format) { - const lines = list.map(line); - const fix = (fixer) => fixer.replaceTextRange(node.range, format(node)); - - if (isAlways(setting, lines) && hasDuplicates(lines)) { - context.report({ node, fix, message: ALWAYS_MSG[setting] }); - } else if (isNever(setting) && hasMultiple(lines)) { - context.report({ node, message: NEVER_MSG[setting] }); - } - } - - return { - "CallExpression": function (node) { - let args = node.arguments; - - if (!isDefineCall(node) && !isRequireCall(node) || args.length < 2) { - return; - } + return { + CallExpression(node) { + if (!isAmdCall(node) || node.arguments.length < 2) return; - // Remove named module id if present - if (isDefineCall(node) && isStringLiteral(args[0])) { - args = args.slice(1); - } + const args = withoutModuleId(node.arguments); + const deps = args[0]; + const func = args[1]; - const deps = args[0]; - const func = args[1]; + if (!isArrayExpr(deps) || !isFunctionExpr(func)) return; - // We can only work with valid AMD-Style require or define calls - if (!isArrayExpr(deps) || !isFunctionExpr(func)) { - return; - } + const indent = indentation(func); - const indent = indentation(args[1]); + check("paths", deps, deps.elements, formatPaths(indent)); + check("names", func, func.params, formatNames(indent, context)); + } + }; +} - check("paths", deps, deps.elements, formatPaths(indent)); - check("names", func, func.params, formatNames(indent, context)); - } - }; - } +module.exports = { + meta: { docs, schema, fixable: "code" }, + create }; diff --git a/lib/rules/sort-amd-paths.js b/lib/rules/sort-amd-paths.js index 8c2815b..ed9697b 100644 --- a/lib/rules/sort-amd-paths.js +++ b/lib/rules/sort-amd-paths.js @@ -1,384 +1,194 @@ /** - * @fileoverview Ensure that required paths are ordered alphabetically - * @author Ondřej Brejla + * @file Rule to ensure that required paths are ordered alphabetically + * @author Ondřej Brejla + * @author Casey Visco */ "use strict"; -const path = require("path"); -const util = require("../util"); +const rjs = require("../utils/rjs"); -module.exports = { - meta: { - docs: {}, - schema: [ - { - "type": "object", - "properties": { - "compare": { - "enum": ["dirname-basename", "fullpath", "basename"] - }, - "sortPlugins": { - "enum": ["preserve", "first", "last", "ignore"] - }, - "ignoreCase": { - "type": "boolean" - } - }, - "additionalProperties": false - } - ] - }, - - create: function (context) { - const MESSAGE = "Required paths are not in alphabetical order (expected '{{expected}}')."; - const ALL_PLUGINS_PREFIX_REGEX = /^(\w+\!)?(.*)/i; - const options = context.options[0] || {}; - const settings = { - compare: "compare" in options ? options.compare : "dirname-basename", - sortPlugins: "sortPlugins" in options ? options.sortPlugins : "preserve", - ignoreCase: "ignoreCase" in options ? options.ignoreCase : true - }; - - /** - * Determine if `firstArray` equals `secondArray`. Otherwise prints warning. - * - * @private - * @param {Array} firstArray - first array for comparison - * @param {Array} secondArray - second array for comparison - * @returns {void} - */ - function checkArrayEquality(firstArray, secondArray) { - for (let i = 0; i < firstArray.length; i++) { - if (firstArray[i] !== secondArray[i]) { - context.report(firstArray[i], MESSAGE, { - expected: secondArray[i].value - }); - break; - } - } - } - - /** - * Determine if dependency string nodes of supplied `node` are in - * alphabetical order - * - * @private - * @param {ASTNode} node - node to test - * @returns {void} - */ - function checkAlphabeticalOrder(node) { - const callbackParams = util.getAmdCallback(node).params; - const dependencyStringNodes = util.getDependencyStringNodes(node); - let dependencyStringNodesToCheck = dependencyStringNodes; - - if (dependencyStringNodes.length > callbackParams.length) { - dependencyStringNodesToCheck = dependencyStringNodes.slice(0, callbackParams.length); - } - - if (dependencyStringNodesToCheck) { - checkArrayEquality(dependencyStringNodesToCheck, dependencyStringNodesToCheck.slice().sort(sortAlphabetically)); - } - } - - /** - * Sorting function for alphabetical sort of passed string nodes. - * - * @private - * @param {ASTNode} firstStringNode - first string node to sort - * @param {ASTNode} secondStringNode - second string node to sort - * @returns {Number} 0 when nodes are equal, 1 when `firstStringNode` - * should be placed after the `secondStringNode` and -1 - * when `firstStringNode` should be before the - * `secondStringNode` - */ - function sortAlphabetically(firstStringNode, secondStringNode) { - let result = 0; - let firstPath = firstStringNode.value; - let secondPath = secondStringNode.value; - - if (settings.ignoreCase) { - firstPath = firstPath.toLowerCase(); - secondPath = secondPath.toLowerCase(); - } - - if (settings.compare === "basename") { - result = doBasenameComparison(firstPath, secondPath); - } else { - result = sortPaths(firstPath, secondPath); - } - - return result; - } - - /** - * Sorting functin used when "dirname-basename" or "fullpath" option is set. - * - * @private - * @param {String} firstPath - first path to sort - * @param {String} secondPath - second path to sort - * @returns {Number} 0 when paths are equal, - * 1 when `firstPath` should be placed after the - * `secondPath` and - * -1 when `firstPath` should be before the `secondPath` - */ - function sortPaths(firstPath, secondPath) { - let result = 0; - - if (settings.sortPlugins === "ignore") { - firstPath = ALL_PLUGINS_PREFIX_REGEX.exec(firstPath)[2]; - secondPath = ALL_PLUGINS_PREFIX_REGEX.exec(secondPath)[2]; - } +const isAmdDefine = rjs.isAmdDefine; +const isAmdRequire = rjs.isAmdRequire; +const getModuleFunction = rjs.getModuleFunction; +const getDependencyStringNodes = rjs.getDependencyStringNodes; - if (settings.compare === "dirname-basename") { - result = checkDirnameBasename(firstPath, secondPath); - } else { - // fullpath - result = doComparison(firstPath, secondPath); - } +// ----------------------------------------------------------------------------- +// Configuration +// ----------------------------------------------------------------------------- - return result; - } - - /** - * Sorting function used when "dirname-basename" option is set. - * - * @private - * @param {String} firstPath - first path to sort - * @param {String} secondPath - second path to sort - * @returns {Number} 0 when paths are equal, 1 when `firstPath` should - * be placed after the `secondPath` and -1 when - * `firstPath` should be before the `secondPath` - */ - function checkDirnameBasename(firstPath, secondPath) { - const firstDirname = path.dirname(firstPath); - const firstBasename = path.basename(firstPath); - const secondDirname = path.dirname(secondPath); - const secondBasename = path.basename(secondPath); - - let result = doComparison(firstDirname, secondDirname); +const docs = { + description: "Ensure that required paths are ordered alphabetically", + category: "Stylistic Choices", + recommended: false +}; - if (result === 0) { - result = doComparison(firstBasename, secondBasename); - } +const schema = [ + { + type: "object", + properties: { + compare: { + enum: ["dirname-basename", "fullpath", "basename"] + }, + sortPlugins: { + enum: ["preserve", "first", "last", "ignore"] + }, + ignoreCase: { + type: "boolean" + } + }, + additionalProperties: false + } +]; - return result; - } +const defaults = { + "compare": "dirname-basename", + "sortPlugins": "preserve", + "ignoreCase": true +}; - /** - * Pure alphabetical sorting which takes basenames into account. - * - * @private - * @param {String} firstString - first string to alphabetical sort - * @param {String} secondString - second string to alphabetical sort - * @returns {Number} 0 when strings are equal, - * 1 when `firstString` should be placed after the - * `secondString` and - * -1 when `firstString` should be before the `secondString` - */ - function doBasenameComparison(firstString, secondString) { - let result = doPluginComparison(firstString, secondString); +// ----------------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------------- - if (result === 0) { - result = doCharacterComparison(path.basename(firstString), path.basename(secondString)); - } +const PLUGIN_REGEX = /^\w+\!/; +const PATH_SEPARATOR = "/"; - return result; - } +/** + * Create a function that returns the additive inverse of the provided function. + * @param {Function} fn - function to invert + * @returns {Function} inverted function + */ +const inverse = (fn) => function () { + return fn.apply(null, arguments) * -1; +}; - /** - * Pure alphabetical sorting function. - * - * @private - * @param {String} firstString - first string to alphabetical sort - * @param {String} secondString - second string to alphabetical sort - * @returns {Number} 0 when strings are equal, 1 when `firstString` should - * be placed after the `secondString` and -1 when - * `firstString` should be before the `secondString` - */ - function doComparison(firstString, secondString) { - let result = doPluginComparison(firstString, secondString); +/** + * Create a comparator that iteratively applies each provided function. Each + * function should itself be a valid comparator (returning a negative number, + * positive number or zero). This allows for sorting based on multiple criteria. + * @param {Function[]} fns - array of comparator functions to apply + * @returns {Function} multi-criteria comparator + */ +const comparator = (fns) => (a, b) => fns.reduce((n, f) => n ? n : f(a, b), 0); - if (result === 0) { - result = doCharacterComparison(firstString, secondString); - } +/** + * Compare two string values. Comparison is done based on character value, so + * uppercase letters are placed before lowercase ones. + * @param {String} a - first value to compare + * @param {String} b - second value to compare + * @returns {Number} negative if `a` occurs before `b`; positive if `a` occurs + * after `b`; 0 if they are equivalent in sort order + */ +const stringCompare = (a, b) => { + if (a < b) return -1; + if (a > b) return 1; + return 0; +}; - return result; - } +/** + * Compare two values based on existence, with values that exist sorted + * before those that don't. + * @param {*} a - first value to compare + * @param {*} b - second value to compare + * @returns {Number} negative if `a` occurs before `b`; positive if `a` occurs + * after `b`; 0 if they are equivalent in sort order + */ +const existentialCompare = (a, b) => { + if (a && !b) return -1; + if (!a && b) return 1; + return 0; +}; - /** - * Sorts path strings according to `sortPlugin` option. - * - * @private - * @param {String} firstString - first string to alphabetical sort - * @param {String} secondString - second string to alphabetical sort - * @returns {Number} 0 when strings are equal, - * 1 when `firstString` should be placed after the - * `secondString` and - * -1 when `firstString` should be before the `secondString` - */ - function doPluginComparison(firstString, secondString) { - let result = 0; +/** + * Compare two array values. + * @param {Array} a - first array to compare + * @param {Array} b - second array to compare + * @returns {Number} negative if `a` occurs before `b`; positive if `a` occurs + * after `b`; 0 if they are equivalent in sort order + */ +const arrayCompare = (a, b) => { + const length = Math.max(a.length, b.length); + for (let i = 0; i < length; i += 1) { + if (!(i in a)) return -1; + if (!(i in b)) return 1; + if (a[i] < b[i]) return -1; + if (a[i] > b[i]) return 1; + } + return 0; +}; - if (settings.sortPlugins === "first") { - result = doPluginFirstComparison(firstString, secondString); - } else if (settings.sortPlugins === "last") { - result = doPluginLastComparison(firstString, secondString); - } +const toPathDescriptor = (opts) => (node) => { + let value = opts.ignoreCase ? node.value.toLowerCase() : node.value; - return result; - } + if (opts.sortPlugins === "ignore") { + value = value.replace(PLUGIN_REGEX, ""); + } - /** - * Sorts path strings which start with plugin prefix before path strings - * which do not. - * - * @param {String} firstString - first string to alphabetical sort - * @param {String} secondString - second string to alphabetical sort - * @returns {Number} 0 when both strings have no plugin prefix, or when both - * strings have plugin prefix, - * 1 when `firstString` does not have and `secondString` - * has plugin prefix - * -1 when `firstString` has and `secondString` does not - * have plugin prefix - */ - function doPluginFirstComparison(firstString, secondString) { - const firstStringPlugin = ALL_PLUGINS_PREFIX_REGEX.exec(firstString)[1]; - const secondStringPlugin = ALL_PLUGINS_PREFIX_REGEX.exec(secondString)[1]; - let result = 0; + const plugin = PLUGIN_REGEX.test(value); + const path = value.split(PATH_SEPARATOR); + const basename = path.slice(-1)[0]; + const dirnames = path.slice(0, -1); - if (firstStringPlugin && !secondStringPlugin) { - result = -1; - } else if (!firstStringPlugin && secondStringPlugin) { - result = 1; - } + return { node, value, plugin, basename, dirnames }; +}; - return result; - } +// Specific Comparators +const comparePlugins = (a, b) => existentialCompare(a.plugin, b.plugin); +const compareDirnames = (a, b) => arrayCompare(a.dirnames, b.dirnames); +const compareBasenames = (a, b) => stringCompare(a.basename, b.basename); +const compareFullPath = (a, b) => stringCompare(a.value, b.value); + +const message = (expected) => + `Required paths are not in alphabetical order (expected '${expected}').`; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +const comparators = { + plugin: { + "first": [comparePlugins], + "last": [inverse(comparePlugins)], + "ignore": [], + "preserve": [] + }, + path: { + "dirname-basename": [compareDirnames, compareBasenames], + "basename": [compareBasenames], + "fullpath": [compareFullPath] + } +}; - /** - * Sorts path strings which start with plugin prefix after path strings - * which do not. - * - * @param {String} firstString - first string to alphabetical sort - * @param {String} secondString - second string to alphabetical sort - * @returns {Number} 0 when both strings have no plugin prefix, or when both - * strings have plugin prefix, - * 1 when `firstString` has and `secondString` does not - * have plugin prefix - * -1 when `firstString` does not have and `secondString` - * has plugin prefix - */ - function doPluginLastComparison(firstString, secondString) { - const firstStringPlugin = ALL_PLUGINS_PREFIX_REGEX.exec(firstString)[1]; - const secondStringPlugin = ALL_PLUGINS_PREFIX_REGEX.exec(secondString)[1]; - let result = 0; +function create(context) { + const opts = Object.assign({}, defaults, context.options[0]); - if (firstStringPlugin && !secondStringPlugin) { - result = 1; - } else if (!firstStringPlugin && secondStringPlugin) { - result = -1; - } + return { + CallExpression(node) { + if (!isAmdDefine(node) && !isAmdRequire(node)) return; - return result; - } + const arity = getModuleFunction(node).params.length; + const paths = getDependencyStringNodes(node).slice(0, arity); - /** - * Sorts alphabetically by char-by-char comparison. - * - * @param {String} firstString - first string to alphabetical sort - * @param {String} secondString - second string to alphabetical sort - * @returns {Number} 0 when strings are equal, - * 1 when `firstString` should be placed after the - * `secondString` and - * -1 when `firstString` should be before the `secondString` - */ - function doCharacterComparison(firstString, secondString) { - const firstStringLength = firstString.length; - const secondStringLength = secondString.length; - const maxIteration = Math.min(firstStringLength, secondStringLength); - let result = 0; + const criteria = [] + .concat(comparators.plugin[opts.sortPlugins]) + .concat(comparators.path[opts.compare]); - for (let i = 0; i < maxIteration; i++) { - const firstStringChar = firstString[i]; - const secondStringChar = secondString[i]; - result = compareCharacters(firstStringChar, secondStringChar); + const sorted = paths.slice() + .map(toPathDescriptor(opts)) + .sort(comparator(criteria)); - if (result !== 0) { + for (let i = 0; i < paths.length; i++) { + if (paths[i] !== sorted[i].node) { + context.report(paths[i], message(sorted[i].node.value)); break; } } - - if (result === 0) { - result = compareStringLength(firstStringLength, secondStringLength); - } - - return result; - } - - /** - * Compares two characters. It preferes to have "slash" before any other - * character. - * - * @param {String} firstChar - first character to compare - * @param {String} secondChar - second character to compare - * @returns {Number} 0 when `firstChar` equals `secondChar`, - * 1 when `firstChar` is smaller then `secondChar` while - * `secondChar` is "slash", or `firstChar` is greater - * then `secondChar` while `firstChar` is not a "slash", - * -1 when `firstChar` is greater then `secondChar` while - * `firstChar` is "slash", or `firstChar` is smaller - * then `secondChar` while `secondChar` is not "slash" - */ - function compareCharacters(firstChar, secondChar) { - let result = 0; - - if (firstChar < secondChar) { - if (secondChar === "/") { - result = 1; - } else { - result = -1; - } - } else if (firstChar > secondChar) { - if (firstChar === "/") { - result = -1; - } else { - result = 1; - } - } - - return result; - } - - /** - * Compares two string lengths. - * - * @param {Number} firstStringLength - length of the first compared string - * @param {Number} secondStringLength - length of the second compared string - * @returns {Number} 0 when string lengths are equal, 1 when - * `firstStringLength` is greater then `secondStringLength` - * and -1 when `firstStringLength` is smaller then - * `secondStringLength` - */ - function compareStringLength(firstStringLength, secondStringLength) { - let result = 0; - - if (firstStringLength < secondStringLength) { - result = -1; - } else if (firstStringLength > secondStringLength) { - result = 1; - } - - return result; } + }; +} - return { - "CallExpression": function (node) { - if ((util.isDefineCall(node) && util.isAmdDefine(node)) - || (util.isRequireCall(node) && util.isAmdRequire(node))) { - checkAlphabeticalOrder(node); - } - } - }; - } +module.exports = { + meta: { docs, schema }, + create }; diff --git a/lib/util.js b/lib/util.js deleted file mode 100644 index 20d145a..0000000 --- a/lib/util.js +++ /dev/null @@ -1,350 +0,0 @@ -/** - * @fileoverview Predicates for testing define signatures - * @author Casey Visco - */ - -"use strict"; - -const ast = require("./utils/ast"); - -const hasParams = ast.hasParams; -const isLiteral = ast.isLiteral; -const isStringLiteral = ast.isStringLiteral; -const isArrayExpr = ast.isArrayExpr; -const isObjectExpr = ast.isObjectExpr; -const isFunctionExpr = ast.isFunctionExpr; - -const COMMON_JS_PARAMS = ["require", "exports", "module"]; - -/** - * Determine if supplied `node` has a parameter list that meets the requirements - * for a Simplified CommonJS Wrapper. - * - * @private - * @param {ASTNode} node - FunctionExpression node to test - * @returns {Boolean} true if has a CommonJS Wrapper parameter list - */ -function hasCommonJsParams(node) { - return node.params.length && node.params.every(function (param, index) { - return param.name === COMMON_JS_PARAMS[index]; - }); -} - -/** - * Determine if supplied `node` represents a "simple" function expression. That - * is, one without any parameter list. - * - * @private - * @param {ASTNode} node - node to test - * @returns {Boolean} true if represents a simple function expression - */ -function isSimpleFuncExpr(node) { - return isFunctionExpr(node) && !hasParams(node); -} - -/** - * Determine if supplied `node` represents a function expression compatible with - * the Simplfied CommonJS Wrapper. - * - * @private - * @param {ASTNode} node - node to test - * @returns {Boolean} true if represents a CommonJS function expression - */ -function isCommonJsFuncExpr(node) { - return isFunctionExpr(node) && hasCommonJsParams(node); -} - -/** - * Determine if supplied `node` represents a call to `define`. - * - * @public - * @param {ASTNode} node - CallExpression node to test - * @returns {Boolean} true if represents a call to `define` - */ -function isDefineCall(node) { - return node && - node.type === "CallExpression" && - node.callee.type === "Identifier" && - node.callee.name === "define"; -} - -/** - * Determine if supplied `node` represents a module definition function with - * a dependency array. This is the classic AMD style module definition. - * - * @see http://requirejs.org/docs/api.html#defdep - * - * @public - * @param {ASTNode} node - CallExpression node to test - * @returns {Boolean} true if represents an AMD style module definition - */ -function isAmdDefine(node) { - const args = node.arguments; - return args.length === 2 && isArrayExpr(args[0]) && isFunctionExpr(args[1]) || - args.length === 3 && isStringLiteral(args[0]) && isArrayExpr(args[1]) && isFunctionExpr(args[2]); -} - -/** - * Determine if supplied `node` represents a plain object module. This is - * referred to as the "Simple Name/Value Pairs" form of module in the - * RequireJS documentation. - * - * @see http://requirejs.org/docs/api.html#defsimple - * - * @public - * @param {ASTNode} node - CallExpression node to test - * @returns {Boolean} true if represents an Object Module Definition - */ -function isObjectDefine(node) { - const args = node.arguments; - return args.length === 1 && isObjectExpr(args[0]) || - args.length === 2 && isStringLiteral(args[0]) && isObjectExpr(args[1]); -} - -/** - * Determine if supplied `node` represents a module definition function with - * no dependency array. - * - * @see http://requirejs.org/docs/api.html#deffunc - * - * @public - * @param {ASTNode} node - CallExpression node to test - * @returns {Boolean} true if represents a Simple Function Definition - */ -function isFunctionDefine(node) { - const args = node.arguments; - return args.length === 1 && isSimpleFuncExpr(args[0]) || - args.length === 2 && isStringLiteral(args[0]) && isSimpleFuncExpr(args[1]); -} - -/** - * Determine if supplied `node` represents a module definition using the - * "Simplified CommonJS Wrapper" form. - * - * @see http://requirejs.org/docs/api.html#cjsmodule - * - * @public - * @param {ASTNode} node - CallExpression node to test - * @returns {Boolean} true if represents a Simplified CommonJS Wrapper - */ -function isCommonJsWrapper(node) { - const args = node.arguments; - return args.length === 1 && isCommonJsFuncExpr(args[0]) || - args.length === 2 && isStringLiteral(args[0]) && isCommonJsFuncExpr(args[1]); -} - -/** - * Determine if supplied `node` represents a named (or aliased) module - * definition function. - * - * @see http://requirejs.org/docs/api.html#modulename - * - * @public - * @param {ASTNode} node - CallExpression node to test - * @returns {Boolean} true if represents a named module definition function - */ -function isNamedDefine(node) { - const args = node.arguments; - - if (args.length < 2 || args.length > 3) { - return false; - } - - return isStringLiteral(args[0]); -} - -/** - * Determine if current function on the provided `stack` is a module - * definition function. - * - * @private - * @param {Array} stack - function stack to inspect - * @returns {Boolean} true if current function is a module definition - */ -function isInsideModuleDef(stack) { - return stack.length > 0 && stack[stack.length - 1]; -} - -/** - * Determine if supplied `node` represents a valid `define` format. - * - * @public - * @param {ASTNode} node - CallExpression node to test - * @returns {Boolean} true if represents a valid module definition function - */ -function isValidDefine(node) { - let args = node.arguments; - - // If the wrong number of arguments is present, we know it's invalid, - // so just return immediately - if (args.length < 1 || args.length > 3) { - return false; - } - - // Named modules start with a string literal, if present, we can remove - // it to continue testing the rest of the arguments - if (isStringLiteral(args[0])) { - args = args.slice(1); - } - - return args.length === 1 && (isObjectExpr(args[0]) || isFunctionExpr(args[0])) || - args.length === 2 && isArrayExpr(args[0]) && isFunctionExpr(args[1]); -} - -/** - * Determine if supplied `node` represents a `require` or `requirejs` - * identifier. Both are synonymous commands. - * - * @public - * @param {ASTNode} node - Identifier node to test - * @returns {Boolean} true if represents a require identifier. - */ -function isRequireIdentifier(node) { - return node.name === "require" || node.name === "requirejs"; -} - -/** - * Determine if supplied `node` represents a call to `require`. - * - * @public - * @param {ASTNode} node - CallExpression node to test - * @returns {Boolean} true if represents a call to `require` - */ -function isRequireCall(node) { - return node.type === "CallExpression" && - node.callee.type === "Identifier" && - isRequireIdentifier(node.callee); -} - -/** - * Determine if supplied `node` represents a valid `require` format. - * - * @public - * @param {ASTNode} node - CallExpression node to test - * @returns {Boolean} true if represents a valid `require` call - */ -function isValidRequire(node) { - const args = node.arguments; - - // If the wrong number of arguments is present, we know it's invalid, - // so just return immediately - if (args.length < 1 || args.length > 3) { - return false; - } - - // Single argument form should be a string literal, an array expression, - // or something that evalutes to one of those. Realistically, we can only - // test for a few obviously incorrect cases - if (args.length === 1) { - return !isObjectExpr(args[0]) && - !isFunctionExpr(args[0]); - } - - // For 2 or 3-argument forms, the tail arguments should be function - // expressions, or something that could evaluate to a function expression. - // Realistically, we can only test for a few obviously incorrect cases. - if (args.length > 1 && (isObjectExpr(args[1]) || isArrayExpr(args[1]) || isLiteral(args[1]))) { - return false; - } - - if (args.length > 2 && (isObjectExpr(args[2]) || isArrayExpr(args[2]) || isLiteral(args[2]))) { - return false; - } - - // For 2 or 3-argument forms, the first argument should be an array - // expression or something that evaluates to one. Again, realistically, we - // can only test for a few obviously incorrect cases - return !isLiteral(args[0]) && - !isObjectExpr(args[0]) && - !isFunctionExpr(args[0]); -} - -/** - * Determine if supplied `node` represents a require function with - * a dependency array. - * - * @public - * @param {ASTNode} node - CallExpression node to test - * @returns {Boolean} true if represents an AMD style require function - */ -function isAmdRequire(node) { - const args = node.arguments; - return args.length === 2 && isArrayExpr(args[0]) && isFunctionExpr(args[1]); -} - -/** - * Retrieve the dependency list from the provided `node`, without any filtering - * by dependency node type. - * - * @public - * @param {ASTNode} node CallExpression to inspect - * @returns {Array} list of dependency path nodes - */ -function getDependencyNodes(node) { - const args = node.arguments; - let nodes; - - if (isDefineCall(node) && args.length > 1) { - if (isArrayExpr(args[0])) { - nodes = args[0].elements; - } else if (isArrayExpr(args[1])) { - nodes = args[1].elements; - } - } else if (isRequireCall(node)) { - if (isArrayExpr(args[0])) { - nodes = args[0].elements; - } else if (isStringLiteral(args[0])) { - nodes = [ args[0] ]; - } - } - - return nodes; -} - -/** - * Retrieve the dependency list from the provided `node`, filtering by node - * type to return only string literal dependencies. - * - * @public - * @param {ASTNode} node - CallExpression to inspect - * @returns {Array} list of dependency path literals - */ -function getDependencyStringNodes(node) { - const nodes = getDependencyNodes(node) || []; - return nodes.filter(isStringLiteral); -} - -/** - * Retrieve the AMD callback function argument from the provided `node`. - * - * @private - * @param {ASTNode} node - node to check for a function expression argument - * @returns {ASTNode} callback function expression - */ -function getAmdCallback(node) { - return node.arguments.filter(isFunctionExpr)[0]; -} - -//------------------------------------------------------------------------------ -// Public -//------------------------------------------------------------------------------ - -module.exports = { - isDefineCall, - isAmdDefine, - isObjectDefine, - isFunctionDefine, - isCommonJsWrapper, - isNamedDefine, - isInsideModuleDef, - isValidDefine, - isRequireIdentifier, - isRequireCall, - isValidRequire, - isAmdRequire, - - // general utilities - getDependencyNodes, - getDependencyStringNodes, - getAmdCallback -}; diff --git a/lib/utils/ast.js b/lib/utils/ast.js index e8cc8a4..cfa7cb9 100644 --- a/lib/utils/ast.js +++ b/lib/utils/ast.js @@ -1,21 +1,16 @@ /** - * @fileoverview Helpers for testing AST Nodes - * @author Casey Visco + * @file Helper functions for working with AST Nodes + * @author Casey Visco */ "use strict"; -/** - * Alias native isArray - * @private - * @type {Function} - */ const isArray = Array.isArray; /** * Test if supplied `value` is an object. * @private - * @param {*} value - value to test + * @param {*} value - value to test * @returns {Boolean} true if value is an object */ function isObject(value) { @@ -25,7 +20,7 @@ function isObject(value) { /** * Test if supplied `node` represents an identifier. - * @param {ASTNode} node - node to test + * @param {ASTNode} node - node to test * @returns {Boolean} true if node represents an identifier */ function isIdentifier(node) { @@ -34,7 +29,7 @@ function isIdentifier(node) { /** * Test if supplied `node` represents a literal of any kind. - * @param {ASTNode} node - node to test + * @param {ASTNode} node - node to test * @returns {Boolean} true if node represents a literal */ function isLiteral(node) { @@ -43,7 +38,7 @@ function isLiteral(node) { /** * Test if supplied `node` represents a string literal. - * @param {ASTNode} node - node to test + * @param {ASTNode} node - node to test * @returns {Boolean} true if node represents a string literal */ function isStringLiteral(node) { @@ -52,7 +47,7 @@ function isStringLiteral(node) { /** * Test if supplied `node` represents an array expression. - * @param {ASTNode} node - node to test + * @param {ASTNode} node - node to test * @returns {Boolean} true if node represents an array expression */ function isArrayExpr(node) { @@ -61,7 +56,7 @@ function isArrayExpr(node) { /** * Test if supplied `node` represents an object expression. - * @param {ASTNode} node - node to test + * @param {ASTNode} node - node to test * @returns {Boolean} true if node represents an object expression */ function isObjectExpr(node) { @@ -70,7 +65,7 @@ function isObjectExpr(node) { /** * Test if supplied `node` represents a function expression. - * @param {ASTNode} node - node to test + * @param {ASTNode} node - node to test * @returns {Boolean} true if node represents a function expression */ function isFunctionExpr(node) { @@ -79,7 +74,7 @@ function isFunctionExpr(node) { /** * Test if supplied `node` represents a member expression. - * @param {ASTNode} node - node to test + * @param {ASTNode} node - node to test * @returns {Boolean} true if node represents a member expression */ function isMemberExpr(node) { @@ -88,7 +83,7 @@ function isMemberExpr(node) { /** * Test if supplied `node` represents an expression of any kind. - * @param {ASTNode} node - node to test + * @param {ASTNode} node - node to test * @returns {Boolean} true if node represents an expression statement */ function isExprStatement(node) { @@ -98,7 +93,7 @@ function isExprStatement(node) { /** * Test if supplied `node` represents an array of string literals. Empty * arrays are also valid here. - * @param {ASTNode} node - ArrayExpression node to test + * @param {ASTNode} node - ArrayExpression node to test * @returns {Boolean} true if node represents an array of string literals */ function isStringLiteralArray(node) { @@ -109,7 +104,7 @@ function isStringLiteralArray(node) { /** * Test if supplied `node` has parameters. - * @param {ASTNode} node - FunctionExpression node to test + * @param {ASTNode} node - FunctionExpression node to test * @returns {Boolean} true if node has at least one parameter */ function hasParams(node) { @@ -120,7 +115,7 @@ function hasParams(node) { /** * Test if supplied `node` has at least one callback argument - * @param {ASTNode} node - CallExpression node to test + * @param {ASTNode} node - CallExpression node to test * @returns {Boolean} true if node has at least one callback */ function hasCallback(node) { @@ -130,21 +125,33 @@ function hasCallback(node) { } /** - * Traverse parent nodes until one is found that satisfies `predicate` is found. - * @param {Function} predicate - predicate to test each ancestor against - * @param {ASTNode} node - child node to begin search at + * Determine if `node` has an ancestor satisfying `predicate`. + * @param {Function} predicate - predicate to test each ancestor against + * @param {ASTNode} node - child node to begin search at * @returns {Boolean} true if an ancestor satisfies `predicate` */ function ancestor(predicate, node) { while ((node = node.parent)) { - if (predicate(node)) { - return true; - } + if (predicate(node)) return true; } return false; } +/** + * Find the nearest ancestor satisfying `predicate`. + * @param {Function} predicate - predicate to test each ancestor against + * @param {ASTNode} node - child node to begin search at + * @returns {ASTNode|undefined} nearest ancestor satisfying `predicate`, if any + */ +function nearest(predicate, node) { + while ((node = node.parent)) { + if (predicate(node)) return node; + } + + return undefined; +} + module.exports = { isIdentifier, isLiteral, @@ -157,5 +164,6 @@ module.exports = { isStringLiteralArray, hasParams, hasCallback, - ancestor + ancestor, + nearest }; diff --git a/lib/utils/rjs.js b/lib/utils/rjs.js new file mode 100644 index 0000000..28eeb83 --- /dev/null +++ b/lib/utils/rjs.js @@ -0,0 +1,263 @@ +/** + * @file Helper functions for working with RequireJS-related nodes + * @author Casey Visco + */ + +"use strict"; + +const ast = require("./ast"); + +const hasParams = ast.hasParams; +const isLiteral = ast.isLiteral; +const isStringLiteral = ast.isStringLiteral; +const isArrayExpr = ast.isArrayExpr; +const isObjectExpr = ast.isObjectExpr; +const isFunctionExpr = ast.isFunctionExpr; + +const CJS_PARAMS = ["require", "exports", "module"]; + +/** + * Determine if supplied `node` has a parameter list that satisfies the + * requirements for a Simplified CommonJS Wrapper. + * @private + * @param {Array} params list of function parameters to test + * @returns {Boolean} true if `params` satisfies a CommonJS Wrapper format + */ +const hasCommonJsParams = (params) => + params.length && params.every((param, i) => param.name === CJS_PARAMS[i]); + +/** + * Determine if supplied `node` represents a function expression compatible with + * the Simplfied CommonJS Wrapper. + * @private + * @param {ASTNode} node - node to test + * @returns {Boolean} true if represents a CommonJS function expression + */ +const isCommonJsFuncExpr = (node) => + isFunctionExpr(node) && hasCommonJsParams(node.params); + +/** + * Determine if supplied `node` represents a "simple" function expression. That + * is, one without any parameter list. + * @private + * @param {ASTNode} node - node to test + * @returns {Boolean} true if represents a simple function expression + */ +const isSimpleFuncExpr = (node) => + isFunctionExpr(node) && !hasParams(node); + +/** + * Determine if supplied `node` represents a call to `define`. + * @param {ASTNode} node - CallExpression node to test + * @returns {Boolean} true if represents a call to `define` + */ +const isDefineCall = (node) => + node && + node.type === "CallExpression" && + node.callee.type === "Identifier" && + node.callee.name === "define"; + +/** + * Determine if supplied `node` represents a module definition function with + * a dependency array. This is the classic AMD style module definition. + * @see http://requirejs.org/docs/api.html#defdep + * @param {ASTNode} node - node to test + * @returns {Boolean} true if represents an AMD style module definition + */ +const isAmdDefine = (node) => + isDefineCall(node) && isAmdArgs(withoutModuleId(node.arguments)); + +/** + * Determine if supplied `node` represents a require function with + * a dependency array. + * @param {ASTNode} node - CallExpression node to test + * @returns {Boolean} true if represents an AMD style require function + */ +const isAmdRequire = (node) => + isRequireCall(node) && isAmdArgs(node.arguments); + +const isAmdArgs = (args) => + args.length === 2 && isArrayExpr(args[0]) && isFunctionExpr(args[1]); + +const withoutModuleId = (args) => + isStringLiteral(args[0]) ? args.slice(1) : args; + +/** + * Determine if supplied `node` represents a plain object module. This is + * referred to as the "Simple Name/Value Pairs" form of module in the + * RequireJS documentation. + * @see http://requirejs.org/docs/api.html#defsimple + * @param {ASTNode} node - CallExpression node to test + * @returns {Boolean} true if represents an Object Module Definition + */ +const isObjectDefine = (node) => + isDefineCall(node) && isObjectArgs(withoutModuleId(node.arguments)); + +const isObjectArgs = (args) => + args.length === 1 && isObjectExpr(args[0]); + +/** + * Determine if supplied `node` represents a module definition function with + * no dependency array. + * @see http://requirejs.org/docs/api.html#deffunc + * @param {ASTNode} node - CallExpression node to test + * @returns {Boolean} true if represents a Simple Function Definition + */ +const isFunctionDefine = (node) => + isDefineCall(node) && isFunctionArgs(withoutModuleId(node.arguments)); + +const isFunctionArgs = (args) => + args.length === 1 && isSimpleFuncExpr(args[0]); + +/** + * Determine if supplied `node` represents a module definition using the + * "Simplified CommonJS Wrapper" form. + * @see http://requirejs.org/docs/api.html#cjsmodule + * @param {ASTNode} node - CallExpression node to test + * @returns {Boolean} true if represents a Simplified CommonJS Wrapper + */ +const isCommonJsWrapper = (node) => + isDefineCall(node) && isCommonJsArgs(withoutModuleId(node.arguments)); + +const isCommonJsArgs = (args) => + args.length === 1 && isCommonJsFuncExpr(args[0]); + +/** + * Determine if supplied `node` represents a named (or aliased) module + * definition function. + * @see http://requirejs.org/docs/api.html#modulename + * @param {ASTNode} node - CallExpression node to test + * @returns {Boolean} true if represents a named module definition function + */ +const isNamedDefine = (node) => + isDefineCall(node) && isNamedArgs(node.arguments); + +const isNamedArgs = (args) => + (args.length === 2 || args.length === 3) && isStringLiteral(args[0]); + +/** + * Determine if supplied `node` represents a valid `define` format. + * @param {ASTNode} node - CallExpression node to test + * @returns {Boolean} true if represents a valid module definition function + */ +const isValidDefine = (node) => { + const args = withoutModuleId(node.arguments); + if (args.length === 1) return isObjectExpr(args[0]) || isFunctionExpr(args[0]); + if (args.length === 2) return isArrayExpr(args[0]) && isFunctionExpr(args[1]); + return false; +}; + +/** + * Determine if supplied `node` represents a `require` or `requirejs` + * identifier. Both are synonymous commands. + * @param {ASTNode} node - Identifier node to test + * @returns {Boolean} true if represents a require identifier. + */ +const isRequireIdentifier = (node) => + node.name === "require" || node.name === "requirejs"; + +/** + * Determine if supplied `node` represents a call to `require`. + * @param {ASTNode} node - CallExpression node to test + * @returns {Boolean} true if represents a call to `require` + */ +const isRequireCall = (node) => + node.type === "CallExpression" && + node.callee.type === "Identifier" && + isRequireIdentifier(node.callee); + + +/** + * Determine if supplied `node` represents a valid `require` format. + * @param {ASTNode} node - CallExpression node to test + * @returns {Boolean} true if represents a valid `require` call + */ +const isValidRequire = (node) => { + const args = node.arguments; + + // If the wrong number of arguments is present, we know it's invalid, + // so just return immediately + if (args.length < 1 || args.length > 3) return false; + + // Single argument form should be a string literal, an array expression, + // or something that evalutes to one of those. Realistically, we can only + // test for a few obviously incorrect cases + if (args.length === 1) { + return !isObjectExpr(args[0]) && + !isFunctionExpr(args[0]); + } + + // For 2 or 3-argument forms, the tail arguments should be function + // expressions, or something that could evaluate to a function expression. + // Realistically, we can only test for a few obviously incorrect cases. + if (args.length > 1 && isInvalidRequireArg(args[1])) return false; + if (args.length > 2 && isInvalidRequireArg(args[2])) return false; + + // For 2 or 3-argument forms, the first argument should be an array + // expression or something that evaluates to one. Again, realistically, we + // can only test for a few obviously incorrect cases + return !isLiteral(args[0]) && + !isObjectExpr(args[0]) && + !isFunctionExpr(args[0]); +}; + +const isInvalidRequireArg = (arg) => + isObjectExpr(arg) || isArrayExpr(arg) || isLiteral(arg); + +/** + * Retrieve the dependency list from the provided `node`, without any filtering + * by dependency node type. + * @param {ASTNode} node - CallExpression to inspect + * @returns {Array} list of dependency path nodes + */ +const getDependencyNodes = (node) => { + const args = node.arguments; + + if (isDefineCall(node) && args.length > 1) { + if (isArrayExpr(args[0])) return args[0].elements; + if (isArrayExpr(args[1])) return args[1].elements; + } else if (isRequireCall(node)) { + if (isArrayExpr(args[0])) return args[0].elements; + if (isStringLiteral(args[0])) return [ args[0] ]; + } + + return []; +}; + +/** + * Retrieve the dependency list from the provided `node`, filtering by node + * type to return only string literal dependencies. + * @param {ASTNode} node - CallExpression to inspect + * @returns {Array} list of dependency path literals + */ +const getDependencyStringNodes = (node) => + getDependencyNodes(node).filter(isStringLiteral); + +/** + * Retrieve the module definition function from the provided `node`. It will + * always be the first FunctionExpression in the arguments list. + * @param {ASTNode} node - node to check + * @returns {ASTNode} module definition function + */ +const getModuleFunction = (node) => node.arguments.find(isFunctionExpr); + +const isAmdCall = (node) => isDefineCall(node) || isRequireCall(node); + +module.exports = { + isRequireCall, + isDefineCall, + isAmdDefine, + isAmdRequire, + isObjectDefine, + isFunctionDefine, + isCommonJsWrapper, + isNamedDefine, + isValidDefine, + isRequireIdentifier, + isValidRequire, + getDependencyNodes, + getDependencyStringNodes, + getModuleFunction, + withoutModuleId, + isAmdCall +}; diff --git a/tests/fixtures.js b/tests/fixtures.js index 2e25417..1470e01 100644 --- a/tests/fixtures.js +++ b/tests/fixtures.js @@ -1,212 +1,5 @@ "use strict"; -exports.ALPHABETICAL_PATHS_BASENAME_CAPITAL = ` -define([ - 'aaa/bbb/Xxx', - 'aaa/bbb/ccc/ddd' -], function (ccc, aaa) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_BASENAME_INVALID_ORDER = ` -define([ - 'aaa/xxx', - 'xxx/aaa' -], function (ccc, aaa) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_BASENAME_PLUGIN_FIRST = ` -define([ - 'hhh!dwhat/ever5/ddd', - 'bbb!fwhat/ever2/fff', - 'awhat/ever1/aaa', - 'cwhat/ever3/ccc', - 'gwhat/ever4/ggg' -], function (ddd, fff, aaa, ccc, ggg) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_BASENAME_PLUGIN_LAST = ` -define([ - 'awhat/ever1/aaa', - 'cwhat/ever3/ccc', - 'gwhat/ever4/ggg', - 'hhh!dwhat/ever5/ddd', - 'bbb!fwhat/ever2/fff' -], function (aaa, ccc, ggg, ddd, fff) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_BASENAME_PLUGIN_PRESERVE_IGNORE = ` -define([ - 'awhat/ever1/aaa', - 'cwhat/ever3/ccc', - 'hhh!dwhat/ever5/ddd', - 'bbb!fwhat/ever2/fff', - 'gwhat/ever4/ggg' -], function (aaa, ccc, ddd, fff, ggg) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_BASENAME_VALID_ORDER = ` -define([ - 'xxx/aaa', - 'aaa/xxx' -], function (ccc, aaa) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_FIRST_LONGER_INVALID = ` -define([ - 'foo/bar/baz/Batttt', - 'foo/bar/baz/Bat' -], function (bat1, bat2) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_FULLPATH_INVALID = ` -define([ -'aaa/bbb/xxx', -'aaa/bbb/ccc/ddd' -], function (ccc, aaa) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_FULLPATH_VALID = ` -define([ -'aaa/bbb/ccc/ddd', -'aaa/bbb/xxx' -], function (ccc, aaa) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_IGNORED_PATHS = ` -define([ - 'aaa/bbb/xxx', - 'aaa/bbb/yyy', - 'aaa/bbb/zzz', - // following lines should be ignored - 'aaa/bbb/aaa', - 'aaa/bbb/bbb' -], function (xxx, yyy, zzz) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_INVALID_ORDER = ` -define([ - 'aaa/bbb/ccc', - 'aaa/bbb/Aaa' -], function (ccc, aaa) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_MORE_PATHS_IN_ARRAY = ` -define([ - 'base.dt/js/DesignerUtils', - 'components.dt/js/deleters/DeletersRegistry', - 'core/js/api/Listenable', - 'core/js/api/utils/KeyConstants', - 'pages.dt/js/api/Pages', - 'pages.dt/js/api/ViewGeneratorModes' -], function (aaa, ccc, ddd, bbb, yyy) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_NO_PATH = ` -define(function () { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_ONE_PATH_IN_ARRAY = ` -define([ - 'foo/bar/baz' -], function (baz) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_PLUGIN_FIRST = ` -define([ - 'bbb!fff/fff/fff1', - 'bbb!fff/fff/fff2', - 'aaa/aaa/aaa', - 'ccc/ccc/ccc', - 'ggg/ggg/ggg' -], function (fff1, fff2, aaa, ccc, ggg) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_PLUGIN_IGNORE = ` -define([ - 'aaa/aaa/aaa', - 'ccc/ccc/ccc', - 'bbb!fff/fff/fff1', - 'bbb!fff/fff/fff2', - 'ggg/ggg/ggg' -], function (aaa, ccc, fff1, fff2, ggg) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_PLUGIN_LAST = ` -define([ - 'aaa/aaa/aaa', - 'ccc/ccc/ccc', - 'ggg/ggg/ggg', - 'bbb!fff/fff/fff1', - 'bbb!fff/fff/fff2' -], function (aaa, ccc, ggg, fff1, fff2) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_PLUGIN_PRESERVE = ` -define([ - 'aaa/aaa/aaa', - 'bbb!fff/fff/fff', - 'ccc/ccc/ccc', - 'ggg/ggg/ggg', - 'hhh!ddd/ddd/ddd' -], function (aaa, fff, ccc, ggg, ddd) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_SLASH_PUNC_INVALID = ` -define([ - 'foo.bar/baz/Bat', - 'foo/bar/baz/Bat', - 'foo-bar/baz/Bat' -], function (bat1, bat2, bat3) { - /* ... */ -}); -`; - -exports.ALPHABETICAL_PATHS_SLASH_PUNC_VALID = ` -define([ - 'foo/bar/baz/Bat', - 'foo-bar/baz/Bat', - 'foo.bar/baz/Bat' -], function (bat1, bat2, bat3) { - /* ... */ -}); -`; - exports.AMD_DEFINE = ` define(['path/to/a', 'path/to/b'], function (a, b) { return { foo: 'bar' }; @@ -1052,14 +845,6 @@ define(function () { }); `; -exports.MULTIPLE_DEFINE_ONE_CALL = ` -if (typeof define === "function") { - define(function () { - return { foo: 'bar' }; - }); -} -`; - exports.NAMED_AMD_DEFINE = ` define('path/to/c', [ 'path/to/a', @@ -1156,45 +941,3 @@ define({ b: 'bar' }); `; - -exports.REQUIREJS_NAME_TO_URL = ` -define(['require'], function (require) { - var idUrl = requirejs.nameToUrl('id'); -}); -`; - -exports.REQUIREJS_TO_URL = ` -define(['require'], function (require) { - var cssUrl = requirejs.toUrl('./style.css'); -}); -`; - -exports.REQUIRE_NAME_TO_URL = ` -define(['require'], function (require) { - var idUrl = require.nameToUrl('id'); -}); -`; - -exports.REQUIRE_TO_URL = ` -define(['require'], function (require) { - var cssUrl = require.toUrl('./style.css'); -}); -`; - -exports.UNWRAPPED_FILE = ` -var foo = 'foo'; - -function bar() { - return foo; -} - -window.bar = bar; -`; - -exports.UNWRAPPED_FILE_NO_EXPRESSIONSTATEMENT = ` -var foo = 'foo'; - -function bar() { - return foo; -} -`; diff --git a/tests/lib/rules/amd-function-arity.js b/tests/lib/rules/amd-function-arity.js index 08d3030..590d060 100644 --- a/tests/lib/rules/amd-function-arity.js +++ b/tests/lib/rules/amd-function-arity.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `amd-function-arity` rule - * @author Kevin Partington + * @file Tests for `amd-function-arity` rule + * @author Kevin Partington */ "use strict"; @@ -9,15 +9,19 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/amd-function-arity"); -function tooManyParams(funcName, expected, actual) { +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +function tooManyParams(expected, actual) { return { - message: `Too many parameters in ${funcName} callback (expected ${expected}, found ${actual}).` + message: `Too many parameters in callback (expected ${expected}, found ${actual}).` }; } -function tooFewParams(funcName, expected, actual) { +function tooFewParams(expected, actual) { return { - message: `Not enough parameters in ${funcName} callback (expected ${expected}, found ${actual}).` + message: `Not enough parameters in callback (expected ${expected}, found ${actual}).` }; } @@ -135,198 +139,198 @@ testRule("amd-function-arity", rule, { { code: fixtures.AMD_DEFINE_TOO_MANY_CALLBACK_PARAMS, options: [{ allowExtraDependencies: true }], - errors: [tooManyParams("define", 2, 3)] + errors: [tooManyParams(2, 3)] }, { code: fixtures.AMD_NAMED_DEFINE_TOO_MANY_CALLBACK_PARAMS, options: [{ allowExtraDependencies: true }], - errors: [tooManyParams("define", 2, 3)] + errors: [tooManyParams(2, 3)] }, { code: fixtures.AMD_REQUIRE_TOO_MANY_CALLBACK_PARAMS, options: [{ allowExtraDependencies: true }], - errors: [tooManyParams("require", 2, 3)] + errors: [tooManyParams(2, 3)] }, { code: fixtures.AMD_REQUIREJS_TOO_MANY_CALLBACK_PARAMS, options: [{ allowExtraDependencies: true }], - errors: [tooManyParams("requirejs", 2, 3)] + errors: [tooManyParams(2, 3)] }, { code: fixtures.AMD_REQUIRE_TOO_MANY_CALLBACK_PARAMS_WITH_ERRBACK, options: [{ allowExtraDependencies: true }], - errors: [tooManyParams("require", 2, 3)] + errors: [tooManyParams(2, 3)] }, { code: fixtures.AMD_REQUIREJS_TOO_MANY_CALLBACK_PARAMS_WITH_ERRBACK, options: [{ allowExtraDependencies: true }], - errors: [tooManyParams("requirejs", 2, 3)] + errors: [tooManyParams(2, 3)] }, { code: fixtures.AMD_REQUIRE_SINGLEDEP_TOO_MANY_CALLBACK_PARAMS, options: [{ allowExtraDependencies: true }], - errors: [tooManyParams("require", 1, 2)] + errors: [tooManyParams(1, 2)] }, { code: fixtures.AMD_REQUIREJS_SINGLEDEP_TOO_MANY_CALLBACK_PARAMS, options: [{ allowExtraDependencies: true }], - errors: [tooManyParams("requirejs", 1, 2)] + errors: [tooManyParams(1, 2)] }, { code: fixtures.AMD_REQUIRE_SINGLEDEP_TOO_MANY_CALLBACK_PARAMS_WITH_ERRBACK, options: [{ allowExtraDependencies: true }], - errors: [tooManyParams("require", 1, 2)] + errors: [tooManyParams(1, 2)] }, { code: fixtures.AMD_REQUIREJS_SINGLEDEP_TOO_MANY_CALLBACK_PARAMS_WITH_ERRBACK, options: [{ allowExtraDependencies: true }], - errors: [tooManyParams("requirejs", 1, 2)] + errors: [tooManyParams(1, 2)] }, // Extra dependencies (invalid since option not specified) { code: fixtures.AMD_DEFINE_TOO_FEW_CALLBACK_PARAMS, - errors: [tooFewParams("define", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_NAMED_DEFINE_TOO_FEW_CALLBACK_PARAMS, - errors: [tooFewParams("define", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIRE_TOO_FEW_CALLBACK_PARAMS, - errors: [tooFewParams("require", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIREJS_TOO_FEW_CALLBACK_PARAMS, - errors: [tooFewParams("requirejs", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIRE_TOO_FEW_CALLBACK_PARAMS_WITH_ERRBACK, - errors: [tooFewParams("require", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIREJS_TOO_FEW_CALLBACK_PARAMS_WITH_ERRBACK, - errors: [tooFewParams("requirejs", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIRE_SINGLEDEP_TOO_FEW_CALLBACK_PARAMS, - errors: [tooFewParams("require", 1, 0)] + errors: [tooFewParams(1, 0)] }, { code: fixtures.AMD_REQUIREJS_SINGLEDEP_TOO_FEW_CALLBACK_PARAMS, - errors: [tooFewParams("requirejs", 1, 0)] + errors: [tooFewParams(1, 0)] }, { code: fixtures.AMD_REQUIRE_SINGLEDEP_TOO_FEW_CALLBACK_PARAMS_WITH_ERRBACK, - errors: [tooFewParams("require", 1, 0)] + errors: [tooFewParams(1, 0)] }, { code: fixtures.AMD_REQUIREJS_SINGLEDEP_TOO_FEW_CALLBACK_PARAMS_WITH_ERRBACK, - errors: [tooFewParams("requirejs", 1, 0)] + errors: [tooFewParams(1, 0)] }, // Extra dependencies (invalid since option set to false) { code: fixtures.AMD_DEFINE_TOO_FEW_CALLBACK_PARAMS, options: [{ allowExtraDependencies: false }], - errors: [tooFewParams("define", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_NAMED_DEFINE_TOO_FEW_CALLBACK_PARAMS, options: [{ allowExtraDependencies: false }], - errors: [tooFewParams("define", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIRE_TOO_FEW_CALLBACK_PARAMS, options: [{ allowExtraDependencies: false }], - errors: [tooFewParams("require", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIREJS_TOO_FEW_CALLBACK_PARAMS, options: [{ allowExtraDependencies: false }], - errors: [tooFewParams("requirejs", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIRE_TOO_FEW_CALLBACK_PARAMS_WITH_ERRBACK, options: [{ allowExtraDependencies: false }], - errors: [tooFewParams("require", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIREJS_TOO_FEW_CALLBACK_PARAMS_WITH_ERRBACK, options: [{ allowExtraDependencies: false }], - errors: [tooFewParams("requirejs", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIRE_SINGLEDEP_TOO_FEW_CALLBACK_PARAMS, options: [{ allowExtraDependencies: false }], - errors: [tooFewParams("require", 1, 0)] + errors: [tooFewParams(1, 0)] }, { code: fixtures.AMD_REQUIREJS_SINGLEDEP_TOO_FEW_CALLBACK_PARAMS, options: [{ allowExtraDependencies: false }], - errors: [tooFewParams("requirejs", 1, 0)] + errors: [tooFewParams(1, 0)] }, { code: fixtures.AMD_REQUIRE_SINGLEDEP_TOO_FEW_CALLBACK_PARAMS_WITH_ERRBACK, options: [{ allowExtraDependencies: false }], - errors: [tooFewParams("require", 1, 0)] + errors: [tooFewParams(1, 0)] }, { code: fixtures.AMD_REQUIREJS_SINGLEDEP_TOO_FEW_CALLBACK_PARAMS_WITH_ERRBACK, options: [{ allowExtraDependencies: false }], - errors: [tooFewParams("requirejs", 1, 0)] + errors: [tooFewParams(1, 0)] }, // Extra dependencies (invalid since allowed paths are not the extra dependencies) { code: fixtures.AMD_DEFINE_TOO_FEW_CALLBACK_PARAMS, options: [{ allowExtraDependencies: ["x", "y"] }], - errors: [tooFewParams("define", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_NAMED_DEFINE_TOO_FEW_CALLBACK_PARAMS, options: [{ allowExtraDependencies: ["x", "y"] }], - errors: [tooFewParams("define", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIRE_TOO_FEW_CALLBACK_PARAMS, options: [{ allowExtraDependencies: ["x", "y"] }], - errors: [tooFewParams("require", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIREJS_TOO_FEW_CALLBACK_PARAMS, options: [{ allowExtraDependencies: ["x", "y"] }], - errors: [tooFewParams("requirejs", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIRE_TOO_FEW_CALLBACK_PARAMS_WITH_ERRBACK, options: [{ allowExtraDependencies: ["x", "y"] }], - errors: [tooFewParams("require", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIREJS_TOO_FEW_CALLBACK_PARAMS_WITH_ERRBACK, options: [{ allowExtraDependencies: ["x", "y"] }], - errors: [tooFewParams("requirejs", 2, 1)] + errors: [tooFewParams(2, 1)] }, { code: fixtures.AMD_REQUIRE_SINGLEDEP_TOO_FEW_CALLBACK_PARAMS, options: [{ allowExtraDependencies: ["x", "y"] }], - errors: [tooFewParams("require", 1, 0)] + errors: [tooFewParams(1, 0)] }, { code: fixtures.AMD_REQUIREJS_SINGLEDEP_TOO_FEW_CALLBACK_PARAMS, options: [{ allowExtraDependencies: ["x", "y"] }], - errors: [tooFewParams("requirejs", 1, 0)] + errors: [tooFewParams(1, 0)] }, { code: fixtures.AMD_REQUIRE_SINGLEDEP_TOO_FEW_CALLBACK_PARAMS_WITH_ERRBACK, options: [{ allowExtraDependencies: ["x", "y"] }], - errors: [tooFewParams("require", 1, 0)] + errors: [tooFewParams(1, 0)] }, { code: fixtures.AMD_REQUIREJS_SINGLEDEP_TOO_FEW_CALLBACK_PARAMS_WITH_ERRBACK, options: [{ allowExtraDependencies: ["x", "y"] }], - errors: [tooFewParams("requirejs", 1, 0)] + errors: [tooFewParams(1, 0)] } ] diff --git a/tests/lib/rules/enforce-define.js b/tests/lib/rules/enforce-define.js index febdfd2..a939406 100644 --- a/tests/lib/rules/enforce-define.js +++ b/tests/lib/rules/enforce-define.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `enforce-define` rule - * @author Casey Visco + * @file Tests for `enforce-define` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,32 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/enforce-define"); +// ----------------------------------------------------------------------------- +// Fixtures +// ----------------------------------------------------------------------------- + +const UNWRAPPED_FILE_NO_EXPRESSIONSTATEMENT = ` + var foo = 'foo'; + + function bar() { + return foo; + } +`; + +const UNWRAPPED_FILE = ` + var foo = 'foo'; + + function bar() { + return foo; + } + + window.bar = bar; +`; + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "File must be wrapped in a `define` call", type: "Program" @@ -41,7 +67,7 @@ testRule("enforce-define", rule, { // All of the invalid cases should work if we ignore the file { - code: fixtures.UNWRAPPED_FILE, + code: UNWRAPPED_FILE, filename: "main.js", options: [ "main.js" ] }, @@ -63,19 +89,19 @@ testRule("enforce-define", rule, { // Ignore should work with full path { - code: fixtures.UNWRAPPED_FILE, + code: UNWRAPPED_FILE, filename: "path/to/main.js", options: [ [ "main.js" ] ] }, // Ignore should support multiple filenames { - code: fixtures.UNWRAPPED_FILE, + code: UNWRAPPED_FILE, filename: "main.js", options: [ [ "main.js", "index.js" ] ] }, { - code: fixtures.UNWRAPPED_FILE, + code: UNWRAPPED_FILE, filename: "index.js", options: [ [ "main.js", "index.js" ] ] } @@ -83,11 +109,11 @@ testRule("enforce-define", rule, { invalid: [ { - code: fixtures.UNWRAPPED_FILE, + code: UNWRAPPED_FILE, errors: [ERROR] }, { - code: fixtures.UNWRAPPED_FILE_NO_EXPRESSIONSTATEMENT, + code: UNWRAPPED_FILE_NO_EXPRESSIONSTATEMENT, errors: [ERROR] }, { @@ -103,25 +129,25 @@ testRule("enforce-define", rule, { errors: [ERROR] }, { - code: fixtures.UNWRAPPED_FILE, + code: UNWRAPPED_FILE, filename: "foo.js", args: [ 1, "main.js" ], errors: [ERROR] }, { - code: fixtures.UNWRAPPED_FILE, + code: UNWRAPPED_FILE, filename: "foo.js", args: [ 1, [ "main.js", "index.js" ] ], errors: [ERROR] }, { - code: fixtures.UNWRAPPED_FILE, + code: UNWRAPPED_FILE, filename: "path/to/foo.js", args: [ 1, "main.js" ], errors: [ERROR] }, { - code: fixtures.UNWRAPPED_FILE, + code: UNWRAPPED_FILE, filename: "path/to/main.js/foo.js", args: [ 1, "main.js" ], errors: [ERROR] diff --git a/tests/lib/rules/no-amd-define.js b/tests/lib/rules/no-amd-define.js index b492446..46edfc4 100644 --- a/tests/lib/rules/no-amd-define.js +++ b/tests/lib/rules/no-amd-define.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-amd-define` rule - * @author Casey Visco + * @file Tests for `no-amd-define` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-amd-define"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "AMD form of `define` is not allowed.", type: "CallExpression" diff --git a/tests/lib/rules/no-assign-exports.js b/tests/lib/rules/no-assign-exports.js index f791631..55388a4 100644 --- a/tests/lib/rules/no-assign-exports.js +++ b/tests/lib/rules/no-assign-exports.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-assign-exports` rule - * @author Casey Visco + * @file Tests for `no-assign-exports` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-assign-exports"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "Invalid assignment to `exports`.", type: "AssignmentExpression" diff --git a/tests/lib/rules/no-assign-require.js b/tests/lib/rules/no-assign-require.js index d8e0ad8..b2655aa 100644 --- a/tests/lib/rules/no-assign-require.js +++ b/tests/lib/rules/no-assign-require.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-assign-require` rule - * @author Casey Visco + * @file Tests for `no-assign-require` rule + * @author Casey Visco */ "use strict"; diff --git a/tests/lib/rules/no-commonjs-exports.js b/tests/lib/rules/no-commonjs-exports.js index bcede7b..62d468e 100644 --- a/tests/lib/rules/no-commonjs-exports.js +++ b/tests/lib/rules/no-commonjs-exports.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-commonjs-exports` rule - * @author Casey Visco + * @file Tests for `no-commonjs-exports` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-commonjs-exports"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "Unexpected use of `exports` in module definition.", type: "AssignmentExpression" diff --git a/tests/lib/rules/no-commonjs-module-exports.js b/tests/lib/rules/no-commonjs-module-exports.js index eda8050..ed18ff4 100644 --- a/tests/lib/rules/no-commonjs-module-exports.js +++ b/tests/lib/rules/no-commonjs-module-exports.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-commonjs-module-exports` rule - * @author Casey Visco + * @file Tests for `no-commonjs-module-exports` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-commonjs-module-exports"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "Unexpected use of `module.exports` in module definition.", type: "AssignmentExpression" diff --git a/tests/lib/rules/no-commonjs-return.js b/tests/lib/rules/no-commonjs-return.js index f69c72b..b3b4be6 100644 --- a/tests/lib/rules/no-commonjs-return.js +++ b/tests/lib/rules/no-commonjs-return.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-commonjs-return` rule - * @author Casey Visco + * @file Tests for `no-commonjs-return` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-commonjs-return"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "Unexpected `return` in module definition.", type: "ReturnStatement" diff --git a/tests/lib/rules/no-commonjs-wrapper.js b/tests/lib/rules/no-commonjs-wrapper.js index 263071b..680c8b8 100644 --- a/tests/lib/rules/no-commonjs-wrapper.js +++ b/tests/lib/rules/no-commonjs-wrapper.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-commonjs-wrapper` rule - * @author Casey Visco + * @file Tests for `no-commonjs-wrapper` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-commonjs-wrapper"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "Simplified CommonJS Wrapper form of `define` is not allowed", type: "CallExpression" diff --git a/tests/lib/rules/no-conditional-require.js b/tests/lib/rules/no-conditional-require.js index b5ceda3..6bb0b82 100644 --- a/tests/lib/rules/no-conditional-require.js +++ b/tests/lib/rules/no-conditional-require.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-conditional-require` rule - * @author Casey Visco + * @file Tests for `no-conditional-require` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-conditional-require"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "Conditional `require` calls are not allowed.", type: "CallExpression" diff --git a/tests/lib/rules/no-dynamic-require.js b/tests/lib/rules/no-dynamic-require.js index f565fcc..0d4cea7 100644 --- a/tests/lib/rules/no-dynamic-require.js +++ b/tests/lib/rules/no-dynamic-require.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-dynamic-require` rule - * @author Casey Visco + * @file Tests for `no-dynamic-require` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-dynamic-require"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "Dynamic `require` calls are not allowed.", type: "CallExpression" diff --git a/tests/lib/rules/no-function-define.js b/tests/lib/rules/no-function-define.js index 168cd70..9b92a3e 100644 --- a/tests/lib/rules/no-function-define.js +++ b/tests/lib/rules/no-function-define.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-function-define` rule - * @author Casey Visco + * @file Tests for `no-function-define` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-function-define"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "Simple function form of `define` is not allowed", type: "CallExpression" diff --git a/tests/lib/rules/no-invalid-define.js b/tests/lib/rules/no-invalid-define.js index d9c414c..20dca2b 100644 --- a/tests/lib/rules/no-invalid-define.js +++ b/tests/lib/rules/no-invalid-define.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-invalid-define` rule - * @author Casey Visco + * @file Tests for `no-invalid-define` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-invalid-define"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "Invalid module definition", type: "CallExpression" diff --git a/tests/lib/rules/no-invalid-require.js b/tests/lib/rules/no-invalid-require.js index 477079f..419afce 100644 --- a/tests/lib/rules/no-invalid-require.js +++ b/tests/lib/rules/no-invalid-require.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-invalid-require` rule - * @author Casey Visco + * @file Tests for `no-invalid-require` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-invalid-require"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "Invalid arguments provided to `require` call.", type: "CallExpression" diff --git a/tests/lib/rules/no-js-extension.js b/tests/lib/rules/no-js-extension.js index 571c95d..5b12df1 100644 --- a/tests/lib/rules/no-js-extension.js +++ b/tests/lib/rules/no-js-extension.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-js-extension` rule - * @author Casey Visco + * @file Tests for `no-js-extension` rule + * @author Casey Visco */ "use strict"; diff --git a/tests/lib/rules/no-multiple-define.js b/tests/lib/rules/no-multiple-define.js index be854b4..d354e2d 100644 --- a/tests/lib/rules/no-multiple-define.js +++ b/tests/lib/rules/no-multiple-define.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-multiple-define` rule - * @author Casey Visco + * @file Tests for `no-multiple-define` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,22 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-multiple-define"); +// ----------------------------------------------------------------------------- +// Fixtures +// ----------------------------------------------------------------------------- + +const MULTIPLE_DEFINE_ONE_CALL = ` + if (typeof define === "function") { + define(function () { + return { foo: 'bar' }; + }); + } +`; + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + testRule("no-multiple-define", rule, { valid: [ @@ -21,7 +37,7 @@ testRule("no-multiple-define", rule, { fixtures.AMD_EMPTY_DEFINE, fixtures.NAMED_AMD_DEFINE, fixtures.NAMED_AMD_EMPTY_DEFINE, - fixtures.MULTIPLE_DEFINE_ONE_CALL + MULTIPLE_DEFINE_ONE_CALL ], invalid: [ diff --git a/tests/lib/rules/no-named-define.js b/tests/lib/rules/no-named-define.js index 896f2df..ededb95 100644 --- a/tests/lib/rules/no-named-define.js +++ b/tests/lib/rules/no-named-define.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-named-define` rule - * @author Casey Visco + * @file Tests for `no-named-define` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-named-define"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "Named module form of `define` is not allowed", type: "CallExpression" diff --git a/tests/lib/rules/no-object-define.js b/tests/lib/rules/no-object-define.js index 8a9b9a8..b65d3cb 100644 --- a/tests/lib/rules/no-object-define.js +++ b/tests/lib/rules/no-object-define.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-object-define` rule - * @author Casey Visco + * @file Tests for `no-object-define` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-object-define"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ERROR = { message: "Simple Name/Value Pairs form of `define` is not allowed", type: "CallExpression" diff --git a/tests/lib/rules/no-require-tourl.js b/tests/lib/rules/no-require-tourl.js index 922b149..acfb9f9 100644 --- a/tests/lib/rules/no-require-tourl.js +++ b/tests/lib/rules/no-require-tourl.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `no-require-tourl` rule - * @author Casey Visco + * @file Tests for `no-require-tourl` rule + * @author Casey Visco */ "use strict"; @@ -10,6 +10,39 @@ const util = require("util"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/no-require-tourl"); +// ----------------------------------------------------------------------------- +// Fixtures +// ----------------------------------------------------------------------------- + + +const REQUIREJS_NAME_TO_URL = ` + define(['require'], function (require) { + var idUrl = requirejs.nameToUrl('id'); + }); +`; + +const REQUIREJS_TO_URL = ` + define(['require'], function (require) { + var cssUrl = requirejs.toUrl('./style.css'); + }); +`; + +const REQUIRE_NAME_TO_URL = ` + define(['require'], function (require) { + var idUrl = require.nameToUrl('id'); + }); +`; + +const REQUIRE_TO_URL = ` + define(['require'], function (require) { + var cssUrl = require.toUrl('./style.css'); + }); +`; + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const MESSAGE = "Use of `require.%s` is not allowed."; testRule("no-require-tourl", rule, { @@ -24,28 +57,28 @@ testRule("no-require-tourl", rule, { invalid: [ { - code: fixtures.REQUIRE_TO_URL, + code: REQUIRE_TO_URL, errors: [{ message: util.format(MESSAGE, "toUrl"), type: "CallExpression" }] }, { - code: fixtures.REQUIREJS_TO_URL, + code: REQUIREJS_TO_URL, errors: [{ message: util.format(MESSAGE, "toUrl"), type: "CallExpression" }] }, { - code: fixtures.REQUIRE_NAME_TO_URL, + code: REQUIRE_NAME_TO_URL, errors: [{ message: util.format(MESSAGE, "nameToUrl"), type: "CallExpression" }] }, { - code: fixtures.REQUIREJS_NAME_TO_URL, + code: REQUIREJS_NAME_TO_URL, errors: [{ message: util.format(MESSAGE, "nameToUrl"), type: "CallExpression" diff --git a/tests/lib/rules/one-dependency-per-line.js b/tests/lib/rules/one-dependency-per-line.js index 7a66274..2ebc836 100644 --- a/tests/lib/rules/one-dependency-per-line.js +++ b/tests/lib/rules/one-dependency-per-line.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `one-dependency-per-line` rule - * @author Casey Visco + * @file Tests for `one-dependency-per-line` rule + * @author Casey Visco */ "use strict"; @@ -9,6 +9,10 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/one-dependency-per-line"); +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + const ALWAYS_PATHS_ERROR = { message: "Only one dependency path is permitted per line.", type: "ArrayExpression" diff --git a/tests/lib/rules/sort-amd-paths.js b/tests/lib/rules/sort-amd-paths.js index 43a68af..179d510 100644 --- a/tests/lib/rules/sort-amd-paths.js +++ b/tests/lib/rules/sort-amd-paths.js @@ -1,6 +1,6 @@ /** - * @fileoverview Tests for `sort-amd-paths` rule - * @author Ondřej Brejla + * @file Tests for `sort-amd-paths` rule + * @author Ondřej Brejla */ "use strict"; @@ -9,272 +9,420 @@ const testRule = require("../../rule-tester"); const fixtures = require("../../fixtures"); const rule = require("../../../lib/rules/sort-amd-paths"); -function makeErrorMessage(expectedPath) { - return { - message: "Required paths are not in alphabetical order (expected '" + expectedPath + "').", - type: "Literal" - }; -} +// ----------------------------------------------------------------------------- +// Fixtures +// ----------------------------------------------------------------------------- + +const BASENAME_CAPITAL = ` + define([ + 'aaa/bbb/Xxx', + 'aaa/bbb/ccc/ddd' + ], function (ccc, aaa) { + /* ... */ + }); +`; + +const NO_PATH = ` + define(function () { + /* ... */ + }); +`; + + +const ONE_PATH_IN_ARRAY = ` + define([ + 'foo/bar/baz' + ], function (baz) { + /* ... */ + }); +`; + +const MORE_PATHS_IN_ARRAY = ` + define([ + 'base.dt/js/DesignerUtils', + 'components.dt/js/deleters/DeletersRegistry', + 'core/js/api/Listenable', + 'core/js/api/utils/KeyConstants', + 'pages.dt/js/api/Pages', + 'pages.dt/js/api/ViewGeneratorModes' + ], function (aaa, ccc, ddd, bbb, yyy) { + /* ... */ + }); +`; + +const FULLPATH_INVALID = ` + define([ + 'aaa/bbb/xxx', + 'aaa/bbb/ccc/ddd' + ], function (ccc, aaa) { + /* ... */ + }); +`; + +const DIRNAME_WRONG_ORDER = ` + define([ + 'aaa/bbb/ccc/ddd/aaa', + 'aaa/bbb/ccc/xxx' + ], function (ccc, aaa) { + /* ... */ + }); +`; + +const IGNORED_PATHS = ` + define([ + 'aaa/bbb/xxx', + 'aaa/bbb/yyy', + 'aaa/bbb/zzz', + // following lines should be ignored + 'aaa/bbb/aaa', + 'aaa/bbb/bbb' + ], function (xxx, yyy, zzz) { + /* ... */ + }); +`; + +const SLASH_PUNC_VALID = ` + define([ + 'foo/bar/baz/Bat', + 'foo-bar/baz/Bat', + 'foo.bar/baz/Bat' + ], function (bat1, bat2, bat3) { + /* ... */ + }); +`; + +const PLUGIN_PRESERVE = ` + define([ + 'aaa/aaa/aaa', + 'bbb!fff/fff/fff', + 'ccc/ccc/ccc', + 'ggg/ggg/ggg', + 'hhh!ddd/ddd/ddd' + ], function (aaa, fff, ccc, ggg, ddd) { + /* ... */ + }); +`; + +const FULLPATH_VALID = ` + define([ + 'aaa/bbb/ccc/ddd', + 'aaa/bbb/xxx' + ], function (ccc, aaa) { + /* ... */ + }); +`; + +const BASENAME_VALID_ORDER = ` + define([ + 'xxx/aaa', + 'aaa/xxx' + ], function (ccc, aaa) { + /* ... */ + }); +`; + +const BASENAME_PLUGIN_PRESERVE_IGNORE = ` + define([ + 'awhat/ever1/aaa', + 'cwhat/ever3/ccc', + 'hhh!dwhat/ever5/ddd', + 'bbb!fwhat/ever2/fff', + 'gwhat/ever4/ggg' + ], function (aaa, ccc, ddd, fff, ggg) { + /* ... */ + }); +`; + +const PLUGIN_IGNORE = ` + define([ + 'aaa/aaa/aaa', + 'ccc/ccc/ccc', + 'bbb!fff/fff/fff1', + 'bbb!fff/fff/fff2', + 'ggg/ggg/ggg' + ], function (aaa, ccc, fff1, fff2, ggg) { + /* ... */ + }); +`; + +const PLUGIN_FIRST = ` + define([ + 'bbb!fff/fff/fff1', + 'bbb!fff/fff/fff2', + 'aaa/aaa/aaa', + 'ccc/ccc/ccc', + 'ggg/ggg/ggg' + ], function (fff1, fff2, aaa, ccc, ggg) { + /* ... */ + }); +`; + +const BASENAME_PLUGIN_FIRST = ` + define([ + 'hhh!dwhat/ever5/ddd', + 'bbb!fwhat/ever2/fff', + 'awhat/ever1/aaa', + 'cwhat/ever3/ccc', + 'gwhat/ever4/ggg' + ], function (ddd, fff, aaa, ccc, ggg) { + /* ... */ + }); +`; + +const PLUGIN_LAST = ` + define([ + 'aaa/aaa/aaa', + 'ccc/ccc/ccc', + 'ggg/ggg/ggg', + 'bbb!fff/fff/fff1', + 'bbb!fff/fff/fff2' + ], function (aaa, ccc, ggg, fff1, fff2) { + /* ... */ + }); +`; + +const BASENAME_PLUGIN_LAST = ` + define([ + 'awhat/ever1/aaa', + 'cwhat/ever3/ccc', + 'gwhat/ever4/ggg', + 'hhh!dwhat/ever5/ddd', + 'bbb!fwhat/ever2/fff' + ], function (aaa, ccc, ggg, ddd, fff) { + /* ... */ + }); +`; + +const INVALID_ORDER = ` + define([ + 'aaa/bbb/ccc', + 'aaa/bbb/Aaa' + ], function (ccc, aaa) { + /* ... */ + }); +`; + +const SLASH_PUNC_INVALID = ` + define([ + 'foo.bar/baz/Bat', + 'foo/bar/baz/Bat', + 'foo-bar/baz/Bat' + ], function (bat1, bat2, bat3) { + /* ... */ + }); +`; + +const FIRST_LONGER_INVALID = ` + define([ + 'foo/bar/baz/Batttt', + 'foo/bar/baz/Bat' + ], function (bat1, bat2) { + /* ... */ + }); +`; + +const BASENAME_INVALID_ORDER = ` + define([ + 'aaa/xxx', + 'xxx/aaa' + ], function (ccc, aaa) { + /* ... */ + }); +`; + +const BASENAME_IDENTICAL = ` + define([ + 'aaa/xxx', + 'bbb/xxx', + 'ccc/xxx' + ], function (ccc, aaa) { + /* ... */ + }); +`; + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +const errorMsg = (expected) => ({ + message: `Required paths are not in alphabetical order (expected '${expected}').`, + type: "Literal" +}); testRule("sort-amd-paths", rule, { valid: [ - // valid `define` - - fixtures.ALPHABETICAL_PATHS_NO_PATH, fixtures.AMD_EMPTY_DEFINE, - fixtures.ALPHABETICAL_PATHS_ONE_PATH_IN_ARRAY, - fixtures.ALPHABETICAL_PATHS_MORE_PATHS_IN_ARRAY, - fixtures.ALPHABETICAL_PATHS_BASENAME_CAPITAL, - fixtures.ALPHABETICAL_PATHS_FULLPATH_INVALID, - fixtures.ALPHABETICAL_PATHS_IGNORED_PATHS, - fixtures.ALPHABETICAL_PATHS_SLASH_PUNC_VALID, - fixtures.ALPHABETICAL_PATHS_PLUGIN_PRESERVE, - - // valid `define` with { "compare": "dirname-basename" } - - { - code: fixtures.ALPHABETICAL_PATHS_NO_PATH, - options: [{ "compare": "dirname-basename" }] - }, - + NO_PATH, + ONE_PATH_IN_ARRAY, + MORE_PATHS_IN_ARRAY, + BASENAME_CAPITAL, + FULLPATH_INVALID, + IGNORED_PATHS, + SLASH_PUNC_VALID, + PLUGIN_PRESERVE, { code: fixtures.AMD_EMPTY_DEFINE, - options: [{ "compare": "dirname-basename" }] + options: [{ compare: "dirname-basename" }] }, - { - code: fixtures.ALPHABETICAL_PATHS_ONE_PATH_IN_ARRAY, - options: [{ "compare": "dirname-basename" }] + code: NO_PATH, + options: [{ compare: "dirname-basename" }] }, - { - code: fixtures.ALPHABETICAL_PATHS_MORE_PATHS_IN_ARRAY, - options: [{ "compare": "dirname-basename" }] + code: ONE_PATH_IN_ARRAY, + options: [{ compare: "dirname-basename" }] }, - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_CAPITAL, - options: [{ "compare": "dirname-basename" }] + code: MORE_PATHS_IN_ARRAY, + options: [{ compare: "dirname-basename" }] }, - { - code: fixtures.ALPHABETICAL_PATHS_FULLPATH_INVALID, - options: [{ "compare": "dirname-basename" }] + code: BASENAME_CAPITAL, + options: [{ compare: "dirname-basename" }] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_PRESERVE, - options: [{ "compare": "dirname-basename" }] + code: FULLPATH_INVALID, + options: [{ compare: "dirname-basename" }] }, - - // valid `define` with { "compare": "dirname-basename", "ignoreCase": true } - { - code: fixtures.ALPHABETICAL_PATHS_NO_PATH, - options: [{ "compare": "dirname-basename", "ignoreCase": true }] + code: PLUGIN_PRESERVE, + options: [{ compare: "dirname-basename" }] }, - { code: fixtures.AMD_EMPTY_DEFINE, - options: [{ "compare": "dirname-basename", "ignoreCase": true }] + options: [{ compare: "dirname-basename", ignoreCase: true }] }, - { - code: fixtures.ALPHABETICAL_PATHS_ONE_PATH_IN_ARRAY, - options: [{ "compare": "dirname-basename", "ignoreCase": true }] + code: NO_PATH, + options: [{ compare: "dirname-basename", ignoreCase: true }] }, - { - code: fixtures.ALPHABETICAL_PATHS_MORE_PATHS_IN_ARRAY, - options: [{ "compare": "dirname-basename", "ignoreCase": true }] + code: ONE_PATH_IN_ARRAY, + options: [{ compare: "dirname-basename", ignoreCase: true }] }, - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_CAPITAL, - options: [{ "compare": "dirname-basename", "ignoreCase": true }] + code: MORE_PATHS_IN_ARRAY, + options: [{ compare: "dirname-basename", ignoreCase: true }] }, - { - code: fixtures.ALPHABETICAL_PATHS_FULLPATH_INVALID, - options: [{ "compare": "dirname-basename", "ignoreCase": true }] + code: BASENAME_CAPITAL, + options: [{ compare: "dirname-basename", ignoreCase: true }] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_PRESERVE, - options: [{ "compare": "dirname-basename", "ignoreCase": true }] + code: FULLPATH_INVALID, + options: [{ compare: "dirname-basename", ignoreCase: true }] }, - - // valid `define` with { "compare": "dirname-basename", "ignoreCase": false } - { - code: fixtures.ALPHABETICAL_PATHS_NO_PATH, - options: [{ "compare": "dirname-basename", "ignoreCase": false }] + code: PLUGIN_PRESERVE, + options: [{ compare: "dirname-basename", ignoreCase: true }] }, - { code: fixtures.AMD_EMPTY_DEFINE, - options: [{ "compare": "dirname-basename", "ignoreCase": false }] + options: [{ compare: "dirname-basename", ignoreCase: false }] }, - { - code: fixtures.ALPHABETICAL_PATHS_ONE_PATH_IN_ARRAY, - options: [{ "compare": "dirname-basename", "ignoreCase": false }] + code: NO_PATH, + options: [{ compare: "dirname-basename", ignoreCase: false }] }, - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_CAPITAL, - options: [{ "compare": "dirname-basename", "ignoreCase": false }] + code: ONE_PATH_IN_ARRAY, + options: [{ compare: "dirname-basename", ignoreCase: false }] }, - { - code: fixtures.ALPHABETICAL_PATHS_FULLPATH_INVALID, - options: [{ "compare": "dirname-basename", "ignoreCase": false }] + code: BASENAME_CAPITAL, + options: [{ compare: "dirname-basename", ignoreCase: false }] }, - - // valid `define` with { "compare": "fullpath" } - { - code: fixtures.ALPHABETICAL_PATHS_FULLPATH_VALID, - options: [{ "compare": "fullpath" }] + code: FULLPATH_INVALID, + options: [{ compare: "dirname-basename", ignoreCase: false }] }, - - // valid `define` with { "compare": "fullpath", "ignoreCase": true } - { - code: fixtures.ALPHABETICAL_PATHS_FULLPATH_VALID, - options: [{ "compare": "fullpath", "ignoreCase": true }] + code: FULLPATH_VALID, + options: [{ compare: "fullpath" }] }, - - // valid `define` with { "compare": "fullpath", "ignoreCase": false } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_CAPITAL, - options: [{ "compare": "fullpath", "ignoreCase": false }] + code: FULLPATH_VALID, + options: [{ compare: "fullpath", ignoreCase: true }] }, - { - code: fixtures.ALPHABETICAL_PATHS_FULLPATH_VALID, - options: [{ "compare": "fullpath", "ignoreCase": false }] + code: BASENAME_CAPITAL, + options: [{ compare: "fullpath", ignoreCase: false }] }, - - // valid `define` with { "compare": "basename" } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_VALID_ORDER, - options: [{ "compare": "basename" }] + code: FULLPATH_VALID, + options: [{ compare: "fullpath", ignoreCase: false }] }, - - // valid `define` with { "compare": "basename", "ignoreCase": true } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_VALID_ORDER, - options: [{ "compare": "basename", "ignoreCase": true }] + code: BASENAME_VALID_ORDER, + options: [{ compare: "basename" }] }, - - // valid `define` with { "compare": "basename", "ignoreCase": false } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_VALID_ORDER, - options: [{ "compare": "basename", "ignoreCase": false }] + code: BASENAME_IDENTICAL, + options: [{ compare: "basename" }] }, - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_CAPITAL, - options: [{ "compare": "basename", "ignoreCase": false }] + code: BASENAME_VALID_ORDER, + options: [{ compare: "basename", ignoreCase: true }] }, - - // valid `define` with { "compare": "dirname-basename", "sortPlugins": "preserve" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_PRESERVE, - options: [{ "compare": "dirname-basename", "sortPlugins": "preserve" }] + code: BASENAME_VALID_ORDER, + options: [{ compare: "basename", ignoreCase: false }] }, - - // valid `define` with { "compare": "fullpath", "sortPlugins": "preserve" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_PRESERVE, - options: [{ "compare": "fullpath", "sortPlugins": "preserve" }] + code: BASENAME_CAPITAL, + options: [{ compare: "basename", ignoreCase: false }] }, - - // valid `define` with { "compare": "basename", "sortPlugins": "preserve" } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_PLUGIN_PRESERVE_IGNORE, - options: [{ "compare": "basename", "sortPlugins": "preserve" }] + code: PLUGIN_PRESERVE, + options: [{ compare: "dirname-basename", sortPlugins: "preserve" }] }, - - // valid `define` with { "compare": "dirname-basename", "sortPlugins": "ignore" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_IGNORE, - options: [{ "compare": "dirname-basename", "sortPlugins": "ignore" }] + code: PLUGIN_PRESERVE, + options: [{ compare: "fullpath", sortPlugins: "preserve" }] }, - - // valid `define` with { "compare": "fullpath", "sortPlugins": "ignore" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_IGNORE, - options: [{ "compare": "fullpath", "sortPlugins": "ignore" }] + code: BASENAME_PLUGIN_PRESERVE_IGNORE, + options: [{ compare: "basename", sortPlugins: "preserve" }] }, - - // valid `define` with { "compare": "basename", "sortPlugins": "ignore" } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_PLUGIN_PRESERVE_IGNORE, - options: [{ "compare": "basename", "sortPlugins": "ignore" }] + code: PLUGIN_IGNORE, + options: [{ compare: "dirname-basename", sortPlugins: "ignore" }] }, - - // valid `define` with { "compare": "dirname-basename", "sortPlugins": "first" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_FIRST, - options: [{ "compare": "dirname-basename", "sortPlugins": "first" }] + code: PLUGIN_IGNORE, + options: [{ compare: "fullpath", sortPlugins: "ignore" }] }, - - // valid `define` with { "compare": "fullpath", "sortPlugins": "first" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_FIRST, - options: [{ "compare": "fullpath", "sortPlugins": "first" }] + code: BASENAME_PLUGIN_PRESERVE_IGNORE, + options: [{ compare: "basename", sortPlugins: "ignore" }] }, - - // valid `define` with { "compare": "basename", "sortPlugins": "first" } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_PLUGIN_FIRST, - options: [{ "compare": "basename", "sortPlugins": "first" }] + code: PLUGIN_FIRST, + options: [{ compare: "dirname-basename", sortPlugins: "first" }] }, - - // valid `define` with { "compare": "dirname-basename", "sortPlugins": "last" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_LAST, - options: [{ "compare": "dirname-basename", "sortPlugins": "last" }] + code: PLUGIN_FIRST, + options: [{ compare: "fullpath", sortPlugins: "first" }] }, - - // valid `define` with { "compare": "fullpath", "sortPlugins": "last" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_LAST, - options: [{ "compare": "fullpath", "sortPlugins": "last" }] + code: BASENAME_PLUGIN_FIRST, + options: [{ compare: "basename", sortPlugins: "first" }] }, - - // valid `define` with { "compare": "basename", "sortPlugins": "last" } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_PLUGIN_LAST, - options: [{ "compare": "basename", "sortPlugins": "last" }] + code: PLUGIN_LAST, + options: [{ compare: "dirname-basename", sortPlugins: "last" }] + }, + { + code: PLUGIN_LAST, + options: [{ compare: "fullpath", sortPlugins: "last" }] + }, + { + code: BASENAME_PLUGIN_LAST, + options: [{ compare: "basename", sortPlugins: "last" }] }, - - // valid common require - fixtures.AMD_REQUIRE, fixtures.AMD_REQUIREJS, - - // valid non-AMD modules - fixtures.CJS_WITH_RETURN, fixtures.CJS_WITH_EXPORTS, fixtures.CJS_WITH_MODULE_EXPORTS, @@ -285,349 +433,259 @@ testRule("sort-amd-paths", rule, { ], invalid: [ - // invalid `define` - { - code: fixtures.ALPHABETICAL_PATHS_INVALID_ORDER, - errors: [makeErrorMessage("aaa/bbb/Aaa")] + code: INVALID_ORDER, + errors: [errorMsg("aaa/bbb/Aaa")] }, - { - code: fixtures.ALPHABETICAL_PATHS_SLASH_PUNC_INVALID, - errors: [makeErrorMessage("foo/bar/baz/Bat")] + code: SLASH_PUNC_INVALID, + errors: [errorMsg("foo/bar/baz/Bat")] }, - { - code: fixtures.ALPHABETICAL_PATHS_FIRST_LONGER_INVALID, - errors: [makeErrorMessage("foo/bar/baz/Bat")] + code: FIRST_LONGER_INVALID, + errors: [errorMsg("foo/bar/baz/Bat")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_IGNORE, - errors: [makeErrorMessage("bbb!fff/fff/fff1")] + code: PLUGIN_IGNORE, + errors: [errorMsg("bbb!fff/fff/fff1")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_FIRST, - errors: [makeErrorMessage("aaa/aaa/aaa")] + code: PLUGIN_FIRST, + errors: [errorMsg("aaa/aaa/aaa")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_LAST, - errors: [makeErrorMessage("bbb!fff/fff/fff1")] + code: PLUGIN_LAST, + errors: [errorMsg("bbb!fff/fff/fff1")] }, - - // invalid `define` with { "compare": "dirname-basename" } - { - code: fixtures.ALPHABETICAL_PATHS_INVALID_ORDER, - options: [{ "compare": "dirname-basename" }], - errors: [makeErrorMessage("aaa/bbb/Aaa")] + code: INVALID_ORDER, + options: [{ compare: "dirname-basename" }], + errors: [errorMsg("aaa/bbb/Aaa")] }, - - // invalid `define` with { "compare": "dirname-basename", "ignoreCase": true } - { - code: fixtures.ALPHABETICAL_PATHS_INVALID_ORDER, - options: [{ "compare": "dirname-basename", "ignoreCase": true }], - errors: [makeErrorMessage("aaa/bbb/Aaa")] + code: DIRNAME_WRONG_ORDER, + options: [{ compare: "dirname-basename" }], + errors: [errorMsg("aaa/bbb/ccc/xxx")] }, - - // invalid `define` with { "compare": "dirname-basename", "ignoreCase": false } - { - code: fixtures.ALPHABETICAL_PATHS_INVALID_ORDER, - options: [{ "compare": "dirname-basename", "ignoreCase": false }], - errors: [makeErrorMessage("aaa/bbb/Aaa")] + code: INVALID_ORDER, + options: [{ compare: "dirname-basename", ignoreCase: true }], + errors: [errorMsg("aaa/bbb/Aaa")] }, - - // invalid `define` with { "compare": "fullpath" } - { - code: fixtures.ALPHABETICAL_PATHS_FULLPATH_INVALID, - options: [{ "compare": "fullpath" }], - errors: [makeErrorMessage("aaa/bbb/ccc/ddd")] + code: INVALID_ORDER, + options: [{ compare: "dirname-basename", ignoreCase: false }], + errors: [errorMsg("aaa/bbb/Aaa")] }, - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_CAPITAL, - options: [{ "compare": "fullpath" }], - errors: [makeErrorMessage("aaa/bbb/ccc/ddd")] + code: FULLPATH_INVALID, + options: [{ compare: "fullpath" }], + errors: [errorMsg("aaa/bbb/ccc/ddd")] }, - - // invalid `define` with { "compare": "fullpath", "ignoreCase": true } - { - code: fixtures.ALPHABETICAL_PATHS_FULLPATH_INVALID, - options: [{ "compare": "fullpath", "ignoreCase": true }], - errors: [makeErrorMessage("aaa/bbb/ccc/ddd")] + code: BASENAME_CAPITAL, + options: [{ compare: "fullpath" }], + errors: [errorMsg("aaa/bbb/ccc/ddd")] }, - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_CAPITAL, - options: [{ "compare": "fullpath", "ignoreCase": true }], - errors: [makeErrorMessage("aaa/bbb/ccc/ddd")] + code: FULLPATH_INVALID, + options: [{ compare: "fullpath", ignoreCase: true }], + errors: [errorMsg("aaa/bbb/ccc/ddd")] }, - - // invalid `define` with { "compare": "fullpath", "ignoreCase": false } - { - code: fixtures.ALPHABETICAL_PATHS_FULLPATH_INVALID, - options: [{ "compare": "fullpath", "ignoreCase": false }], - errors: [makeErrorMessage("aaa/bbb/ccc/ddd")] + code: BASENAME_CAPITAL, + options: [{ compare: "fullpath", ignoreCase: true }], + errors: [errorMsg("aaa/bbb/ccc/ddd")] }, - { - code: fixtures.ALPHABETICAL_PATHS_INVALID_ORDER, - options: [{ "compare": "fullpath", "ignoreCase": false }], - errors: [makeErrorMessage("aaa/bbb/Aaa")] + code: FULLPATH_INVALID, + options: [{ compare: "fullpath", ignoreCase: false }], + errors: [errorMsg("aaa/bbb/ccc/ddd")] }, - - // invalid `define` with { "compare": "basename" } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_INVALID_ORDER, - errors: [makeErrorMessage("xxx/aaa")], - options: [{ "compare": "basename" }] + code: INVALID_ORDER, + options: [{ compare: "fullpath", ignoreCase: false }], + errors: [errorMsg("aaa/bbb/Aaa")] }, - - // invalid `define` with { "compare": "basename", "ignoreCase": true } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_INVALID_ORDER, - errors: [makeErrorMessage("xxx/aaa")], - options: [{ "compare": "basename", "ignoreCase": true }] + code: BASENAME_INVALID_ORDER, + errors: [errorMsg("xxx/aaa")], + options: [{ compare: "basename" }] }, - - // invalid `define` with { "compare": "basename", "ignoreCase": false } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_INVALID_ORDER, - errors: [makeErrorMessage("xxx/aaa")], - options: [{ "compare": "basename", "ignoreCase": false }] + code: BASENAME_INVALID_ORDER, + errors: [errorMsg("xxx/aaa")], + options: [{ compare: "basename", ignoreCase: true }] }, - { - code: fixtures.ALPHABETICAL_PATHS_INVALID_ORDER, - options: [{ "compare": "basename", "ignoreCase": false }], - errors: [makeErrorMessage("aaa/bbb/Aaa")] + code: BASENAME_INVALID_ORDER, + errors: [errorMsg("xxx/aaa")], + options: [{ compare: "basename", ignoreCase: false }] }, - - // invalid `define` with { "compare": "dirname-basename", "sortPlugins": "preserve" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_IGNORE, - options: [{ "compare": "dirname-basename", "sortPlugins": "preserve" }], - errors: [makeErrorMessage("bbb!fff/fff/fff1")] + code: INVALID_ORDER, + options: [{ compare: "basename", ignoreCase: false }], + errors: [errorMsg("aaa/bbb/Aaa")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_FIRST, - options: [{ "compare": "dirname-basename", "sortPlugins": "preserve" }], - errors: [makeErrorMessage("aaa/aaa/aaa")] + code: PLUGIN_IGNORE, + options: [{ compare: "dirname-basename", sortPlugins: "preserve" }], + errors: [errorMsg("bbb!fff/fff/fff1")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_LAST, - options: [{ "compare": "dirname-basename", "sortPlugins": "preserve" }], - errors: [makeErrorMessage("bbb!fff/fff/fff1")] + code: PLUGIN_FIRST, + options: [{ compare: "dirname-basename", sortPlugins: "preserve" }], + errors: [errorMsg("aaa/aaa/aaa")] }, - - // invalid `define` with { "compare": "fullpath", "sortPlugins": "preserve" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_IGNORE, - options: [{ "compare": "fullpath", "sortPlugins": "preserve" }], - errors: [makeErrorMessage("bbb!fff/fff/fff1")] + code: PLUGIN_LAST, + options: [{ compare: "dirname-basename", sortPlugins: "preserve" }], + errors: [errorMsg("bbb!fff/fff/fff1")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_FIRST, - options: [{ "compare": "fullpath", "sortPlugins": "preserve" }], - errors: [makeErrorMessage("aaa/aaa/aaa")] + code: PLUGIN_IGNORE, + options: [{ compare: "fullpath", sortPlugins: "preserve" }], + errors: [errorMsg("bbb!fff/fff/fff1")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_LAST, - options: [{ "compare": "fullpath", "sortPlugins": "preserve" }], - errors: [makeErrorMessage("bbb!fff/fff/fff1")] + code: PLUGIN_FIRST, + options: [{ compare: "fullpath", sortPlugins: "preserve" }], + errors: [errorMsg("aaa/aaa/aaa")] }, - - // invalid `define` with { "compare": "basename", "sortPlugins": "preserve" } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_PLUGIN_FIRST, - options: [{ "compare": "basename", "sortPlugins": "preserve" }], - errors: [makeErrorMessage("awhat/ever1/aaa")] + code: PLUGIN_LAST, + options: [{ compare: "fullpath", sortPlugins: "preserve" }], + errors: [errorMsg("bbb!fff/fff/fff1")] }, - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_PLUGIN_LAST, - options: [{ "compare": "basename", "sortPlugins": "preserve" }], - errors: [makeErrorMessage("hhh!dwhat/ever5/ddd")] + code: BASENAME_PLUGIN_FIRST, + options: [{ compare: "basename", sortPlugins: "preserve" }], + errors: [errorMsg("awhat/ever1/aaa")] }, - - // invalid `define` with { "compare": "dirname-basename", "sortPlugins": "ignore" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_PRESERVE, - options: [{ "compare": "dirname-basename", "sortPlugins": "ignore" }], - errors: [makeErrorMessage("ccc/ccc/ccc")] + code: BASENAME_PLUGIN_LAST, + options: [{ compare: "basename", sortPlugins: "preserve" }], + errors: [errorMsg("hhh!dwhat/ever5/ddd")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_FIRST, - options: [{ "compare": "dirname-basename", "sortPlugins": "ignore" }], - errors: [makeErrorMessage("aaa/aaa/aaa")] + code: PLUGIN_PRESERVE, + options: [{ compare: "dirname-basename", sortPlugins: "ignore" }], + errors: [errorMsg("ccc/ccc/ccc")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_LAST, - options: [{ "compare": "dirname-basename", "sortPlugins": "ignore" }], - errors: [makeErrorMessage("bbb!fff/fff/fff1")] + code: PLUGIN_FIRST, + options: [{ compare: "dirname-basename", sortPlugins: "ignore" }], + errors: [errorMsg("aaa/aaa/aaa")] }, - - // invalid `define` with { "compare": "fullpath", "sortPlugins": "ignore" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_PRESERVE, - options: [{ "compare": "fullpath", "sortPlugins": "ignore" }], - errors: [makeErrorMessage("ccc/ccc/ccc")] + code: PLUGIN_LAST, + options: [{ compare: "dirname-basename", sortPlugins: "ignore" }], + errors: [errorMsg("bbb!fff/fff/fff1")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_FIRST, - options: [{ "compare": "fullpath", "sortPlugins": "ignore" }], - errors: [makeErrorMessage("aaa/aaa/aaa")] + code: PLUGIN_PRESERVE, + options: [{ compare: "fullpath", sortPlugins: "ignore" }], + errors: [errorMsg("ccc/ccc/ccc")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_LAST, - options: [{ "compare": "fullpath", "sortPlugins": "ignore" }], - errors: [makeErrorMessage("bbb!fff/fff/fff1")] + code: PLUGIN_FIRST, + options: [{ compare: "fullpath", sortPlugins: "ignore" }], + errors: [errorMsg("aaa/aaa/aaa")] }, - - // invalid `define` with { "compare": "basename", "sortPlugins": "ignore" } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_PLUGIN_FIRST, - options: [{ "compare": "basename", "sortPlugins": "ignore" }], - errors: [makeErrorMessage("awhat/ever1/aaa")] + code: PLUGIN_LAST, + options: [{ compare: "fullpath", sortPlugins: "ignore" }], + errors: [errorMsg("bbb!fff/fff/fff1")] }, - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_PLUGIN_LAST, - options: [{ "compare": "basename", "sortPlugins": "ignore" }], - errors: [makeErrorMessage("hhh!dwhat/ever5/ddd")] + code: BASENAME_PLUGIN_FIRST, + options: [{ compare: "basename", sortPlugins: "ignore" }], + errors: [errorMsg("awhat/ever1/aaa")] }, - - // invalid `define` with { "compare": "dirname-basename", "sortPlugins": "first" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_PRESERVE, - options: [{ "compare": "dirname-basename", "sortPlugins": "first" }], - errors: [makeErrorMessage("bbb!fff/fff/fff")] + code: BASENAME_PLUGIN_LAST, + options: [{ compare: "basename", sortPlugins: "ignore" }], + errors: [errorMsg("hhh!dwhat/ever5/ddd")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_IGNORE, - options: [{ "compare": "dirname-basename", "sortPlugins": "first" }], - errors: [makeErrorMessage("bbb!fff/fff/fff1")] + code: PLUGIN_PRESERVE, + options: [{ compare: "dirname-basename", sortPlugins: "first" }], + errors: [errorMsg("bbb!fff/fff/fff")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_LAST, - options: [{ "compare": "dirname-basename", "sortPlugins": "first" }], - errors: [makeErrorMessage("bbb!fff/fff/fff1")] + code: PLUGIN_IGNORE, + options: [{ compare: "dirname-basename", sortPlugins: "first" }], + errors: [errorMsg("bbb!fff/fff/fff1")] }, - - // invalid `define` with { "compare": "fullpath", "sortPlugins": "first" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_PRESERVE, - options: [{ "compare": "fullpath", "sortPlugins": "first" }], - errors: [makeErrorMessage("bbb!fff/fff/fff")] + code: PLUGIN_LAST, + options: [{ compare: "dirname-basename", sortPlugins: "first" }], + errors: [errorMsg("bbb!fff/fff/fff1")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_IGNORE, - options: [{ "compare": "fullpath", "sortPlugins": "first" }], - errors: [makeErrorMessage("bbb!fff/fff/fff1")] + code: PLUGIN_PRESERVE, + options: [{ compare: "fullpath", sortPlugins: "first" }], + errors: [errorMsg("bbb!fff/fff/fff")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_LAST, - options: [{ "compare": "fullpath", "sortPlugins": "first" }], - errors: [makeErrorMessage("bbb!fff/fff/fff1")] + code: PLUGIN_IGNORE, + options: [{ compare: "fullpath", sortPlugins: "first" }], + errors: [errorMsg("bbb!fff/fff/fff1")] }, - - // invalid `define` with { "compare": "basename", "sortPlugins": "first" } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_PLUGIN_PRESERVE_IGNORE, - options: [{ "compare": "basename", "sortPlugins": "first" }], - errors: [makeErrorMessage("hhh!dwhat/ever5/ddd")] + code: PLUGIN_LAST, + options: [{ compare: "fullpath", sortPlugins: "first" }], + errors: [errorMsg("bbb!fff/fff/fff1")] }, - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_PLUGIN_LAST, - options: [{ "compare": "basename", "sortPlugins": "first" }], - errors: [makeErrorMessage("hhh!dwhat/ever5/ddd")] + code: BASENAME_PLUGIN_PRESERVE_IGNORE, + options: [{ compare: "basename", sortPlugins: "first" }], + errors: [errorMsg("hhh!dwhat/ever5/ddd")] }, - - // invalid `define` with { "compare": "dirname-basename", "sortPlugins": "last" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_PRESERVE, - options: [{ "compare": "dirname-basename", "sortPlugins": "last" }], - errors: [makeErrorMessage("ccc/ccc/ccc")] + code: BASENAME_PLUGIN_LAST, + options: [{ compare: "basename", sortPlugins: "first" }], + errors: [errorMsg("hhh!dwhat/ever5/ddd")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_IGNORE, - options: [{ "compare": "dirname-basename", "sortPlugins": "last" }], - errors: [makeErrorMessage("ggg/ggg/ggg")] + code: PLUGIN_PRESERVE, + options: [{ compare: "dirname-basename", sortPlugins: "last" }], + errors: [errorMsg("ccc/ccc/ccc")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_FIRST, - options: [{ "compare": "dirname-basename", "sortPlugins": "last" }], - errors: [makeErrorMessage("aaa/aaa/aaa")] + code: PLUGIN_IGNORE, + options: [{ compare: "dirname-basename", sortPlugins: "last" }], + errors: [errorMsg("ggg/ggg/ggg")] }, - - // invalid `define` with { "compare": "fullpath", "sortPlugins": "last" } - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_PRESERVE, - options: [{ "compare": "fullpath", "sortPlugins": "last" }], - errors: [makeErrorMessage("ccc/ccc/ccc")] + code: PLUGIN_FIRST, + options: [{ compare: "dirname-basename", sortPlugins: "last" }], + errors: [errorMsg("aaa/aaa/aaa")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_IGNORE, - options: [{ "compare": "fullpath", "sortPlugins": "last" }], - errors: [makeErrorMessage("ggg/ggg/ggg")] + code: PLUGIN_PRESERVE, + options: [{ compare: "fullpath", sortPlugins: "last" }], + errors: [errorMsg("ccc/ccc/ccc")] }, - { - code: fixtures.ALPHABETICAL_PATHS_PLUGIN_FIRST, - options: [{ "compare": "fullpath", "sortPlugins": "last" }], - errors: [makeErrorMessage("aaa/aaa/aaa")] + code: PLUGIN_IGNORE, + options: [{ compare: "fullpath", sortPlugins: "last" }], + errors: [errorMsg("ggg/ggg/ggg")] }, - - // invalid `define` with { "compare": "basename", "sortPlugins": "last" } - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_PLUGIN_PRESERVE_IGNORE, - options: [{ "compare": "basename", "sortPlugins": "last" }], - errors: [makeErrorMessage("gwhat/ever4/ggg")] + code: PLUGIN_FIRST, + options: [{ compare: "fullpath", sortPlugins: "last" }], + errors: [errorMsg("aaa/aaa/aaa")] + }, + { + code: BASENAME_PLUGIN_PRESERVE_IGNORE, + options: [{ compare: "basename", sortPlugins: "last" }], + errors: [errorMsg("gwhat/ever4/ggg")] }, - { - code: fixtures.ALPHABETICAL_PATHS_BASENAME_PLUGIN_FIRST, - options: [{ "compare": "basename", "sortPlugins": "last" }], - errors: [makeErrorMessage("awhat/ever1/aaa")] + code: BASENAME_PLUGIN_FIRST, + options: [{ compare: "basename", sortPlugins: "last" }], + errors: [errorMsg("awhat/ever1/aaa")] } ] - }); diff --git a/tests/lib/utils/ast.js b/tests/lib/utils/ast.js index 9e5f452..f1879a6 100644 --- a/tests/lib/utils/ast.js +++ b/tests/lib/utils/ast.js @@ -1,3 +1,8 @@ +/** + * @file Tests for AST helper functions + * @author Casey Visco + */ + "use strict"; const assert = require("assert"); @@ -421,29 +426,45 @@ describe("ast.hasCallback", function () { }); describe("ast.ancestor", function () { - const sampleNode = { - parent: { - parent: { - parent: { - type: "bar" - }, - type: "foo" - } - } - }; + const a = { type: "Program" }; + const b = { type: "FunctionDeclaration", parent: a }; + const c = { type: "BlockStatement", parent: b }; + const d = { type: "ReturnStatement", parent: c }; it("should return `true` if an ancestor satisfies the predicate", function () { - const actual = ast.ancestor((node) => node.type === "foo", sampleNode); + const actual = ast.ancestor((node) => node.type === "FunctionDeclaration", d); const expected = true; assert.equal(actual, expected); }); it("should return `false` if no ancestor satisfies the predicate", function () { - const actual = ast.ancestor((node) => node.type === "baz", sampleNode); + const actual = ast.ancestor((node) => node.type === "VariableDeclaration", d); const expected = false; assert.equal(actual, expected); }); }); + +describe("ast.nearest", function () { + const a = { type: "Program" }; + const b = { type: "FunctionDeclaration", parent: a }; + const c = { type: "BlockStatement", parent: b }; + const d = { type: "ReturnStatement", parent: c }; + + it("should return found node if an ancestor satisfies the predicate", function () { + const actual = ast.nearest((node) => node.type === "FunctionDeclaration", d); + const expected = b; + + assert.equal(actual, expected); + }); + + it("should return `undefined` if no ancestor satisfies the predicate", function () { + const actual = ast.nearest((node) => node.type === "VariableDeclaration", d); + const expected = undefined; + + assert.equal(actual, expected); + }); + +});