diff --git a/docs/src/extend/code-path-analysis.md b/docs/src/extend/code-path-analysis.md index 6cb7d2ac571..424f7163c50 100644 --- a/docs/src/extend/code-path-analysis.md +++ b/docs/src/extend/code-path-analysis.md @@ -259,7 +259,8 @@ Please use a map of information instead. ```js function hasCb(node, context) { if (node.type.indexOf("Function") !== -1) { - return context.getDeclaredVariables(node).some(function(v) { + const sourceCode = context.getSourceCode(); + return sourceCode.getDeclaredVariables(node).some(function(v) { return v.type === "Parameter" && v.name === "cb"; }); } diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 11ccc02563a..01fed35977a 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -117,9 +117,9 @@ The `context` object contains additional functionality that is helpful for rules Additionally, the `context` object has the following methods: -* `getAncestors()` - returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. +* `getAncestors()` - (**Deprecated:** Use `SourceCode#getAncestors(node)` instead.) returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. * `getCwd()` - returns the `cwd` passed to [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered as the current working directory. -* `getDeclaredVariables(node)` - returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. +* `getDeclaredVariables(node)` - (**Deprecated:** Use `SourceCode#getDeclaredVariables(node)` instead.) returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. * If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned, in addition to variables for the function parameters. @@ -131,7 +131,7 @@ Additionally, the `context` object has the following methods: * Otherwise, if the node does not declare any variables, an empty array is returned. * `getFilename()` - returns the filename associated with the source. * `getPhysicalFilename()` - when linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `—stdin-filename` or `` if not specified. -* `getScope()` - (**Deprecated: Use `SourceCode.getScope(node)` instead.**) returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. +* `getScope()` - (**Deprecated:** Use `SourceCode#getScope(node)` instead.) returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. * `getSourceCode()` - returns a [`SourceCode`](#contextgetsourcecode) object that you can use to work with the source that was passed to ESLint. * `markVariableAsUsed(name)` - marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. * `report(descriptor)` - reports a problem in the code (see the [dedicated section](#contextreport)). diff --git a/lib/linter/linter.js b/lib/linter/linter.js index 3b099004d2b..0bfbb161bc8 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -905,22 +905,6 @@ function createRuleListeners(rule, ruleContext) { } } -/** - * Gets all the ancestors of a given node - * @param {ASTNode} node The node - * @returns {ASTNode[]} All the ancestor nodes in the AST, not including the provided node, starting - * from the root node and going inwards to the parent node. - */ -function getAncestors(node) { - const ancestorsStartingAtParent = []; - - for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) { - ancestorsStartingAtParent.push(ancestor); - } - - return ancestorsStartingAtParent.reverse(); -} - // methods that exist on SourceCode object const DEPRECATED_SOURCECODE_PASSTHROUGHS = { getSource: "getText", @@ -996,8 +980,8 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserName, languageO Object.assign( Object.create(BASE_TRAVERSAL_CONTEXT), { - getAncestors: () => getAncestors(currentNode), - getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager), + getAncestors: () => sourceCode.getAncestors(currentNode), + getDeclaredVariables: node => sourceCode.getDeclaredVariables(node), getCwd: () => cwd, getFilename: () => filename, getPhysicalFilename: () => physicalFilename || filename, diff --git a/lib/rules/block-scoped-var.js b/lib/rules/block-scoped-var.js index 731d06d0f3b..68611fb595d 100644 --- a/lib/rules/block-scoped-var.js +++ b/lib/rules/block-scoped-var.js @@ -28,6 +28,7 @@ module.exports = { create(context) { let stack = []; + const sourceCode = context.getSourceCode(); /** * Makes a block scope. @@ -83,7 +84,7 @@ module.exports = { } // Gets declared variables, and checks its references. - const variables = context.getDeclaredVariables(node); + const variables = sourceCode.getDeclaredVariables(node); for (let i = 0; i < variables.length; ++i) { diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js index 910e8b6e583..3bed89855d7 100644 --- a/lib/rules/camelcase.js +++ b/lib/rules/camelcase.js @@ -296,7 +296,7 @@ module.exports = { "ClassExpression", "CatchClause" ]](node) { - for (const variable of context.getDeclaredVariables(node)) { + for (const variable of sourceCode.getDeclaredVariables(node)) { if (isGoodName(variable.name)) { continue; } @@ -346,7 +346,7 @@ module.exports = { // Report camelcase in import -------------------------------------- ImportDeclaration(node) { - for (const variable of context.getDeclaredVariables(node)) { + for (const variable of sourceCode.getDeclaredVariables(node)) { if (isGoodName(variable.name)) { continue; } diff --git a/lib/rules/func-names.js b/lib/rules/func-names.js index ee4664592f2..e42b81d3ed2 100644 --- a/lib/rules/func-names.js +++ b/lib/rules/func-names.js @@ -159,7 +159,7 @@ module.exports = { function handleFunction(node) { // Skip recursive functions. - const nameVar = context.getDeclaredVariables(node)[0]; + const nameVar = sourceCode.getDeclaredVariables(node)[0]; if (isFunctionName(nameVar) && nameVar.references.length > 0) { return; diff --git a/lib/rules/global-require.js b/lib/rules/global-require.js index c8a5fc309a1..5c0a6483663 100644 --- a/lib/rules/global-require.js +++ b/lib/rules/global-require.js @@ -78,7 +78,7 @@ module.exports = { const currentScope = sourceCode.getScope(node); if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) { - const isGoodRequire = context.getAncestors().every(parent => ACCEPTABLE_PARENTS.has(parent.type)); + const isGoodRequire = sourceCode.getAncestors(node).every(parent => ACCEPTABLE_PARENTS.has(parent.type)); if (!isGoodRequire) { context.report({ node, messageId: "unexpected" }); diff --git a/lib/rules/no-class-assign.js b/lib/rules/no-class-assign.js index 32e48e21188..1129a5628ad 100644 --- a/lib/rules/no-class-assign.js +++ b/lib/rules/no-class-assign.js @@ -31,6 +31,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Finds and reports references that are non initializer and writable. * @param {Variable} variable A variable to check. @@ -49,7 +51,7 @@ module.exports = { * @returns {void} */ function checkForClass(node) { - context.getDeclaredVariables(node).forEach(checkVariable); + sourceCode.getDeclaredVariables(node).forEach(checkVariable); } return { diff --git a/lib/rules/no-const-assign.js b/lib/rules/no-const-assign.js index 55e40c8849f..58011b99448 100644 --- a/lib/rules/no-const-assign.js +++ b/lib/rules/no-const-assign.js @@ -31,6 +31,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Finds and reports references that are non initializer and writable. * @param {Variable} variable A variable to check. @@ -45,7 +47,7 @@ module.exports = { return { VariableDeclaration(node) { if (node.kind === "const") { - context.getDeclaredVariables(node).forEach(checkVariable); + sourceCode.getDeclaredVariables(node).forEach(checkVariable); } } }; diff --git a/lib/rules/no-dupe-args.js b/lib/rules/no-dupe-args.js index faf253793ec..116efd93724 100644 --- a/lib/rules/no-dupe-args.js +++ b/lib/rules/no-dupe-args.js @@ -29,6 +29,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- @@ -49,7 +51,7 @@ module.exports = { * @private */ function checkParams(node) { - const variables = context.getDeclaredVariables(node); + const variables = sourceCode.getDeclaredVariables(node); for (let i = 0; i < variables.length; ++i) { const variable = variables[i]; diff --git a/lib/rules/no-ex-assign.js b/lib/rules/no-ex-assign.js index 4c77b1128ad..af46510ed45 100644 --- a/lib/rules/no-ex-assign.js +++ b/lib/rules/no-ex-assign.js @@ -31,6 +31,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Finds and reports references that are non initializer and writable. * @param {Variable} variable A variable to check. @@ -44,7 +46,7 @@ module.exports = { return { CatchClause(node) { - context.getDeclaredVariables(node).forEach(checkVariable); + sourceCode.getDeclaredVariables(node).forEach(checkVariable); } }; diff --git a/lib/rules/no-func-assign.js b/lib/rules/no-func-assign.js index 2c8fa6a8e08..6e33b03b88d 100644 --- a/lib/rules/no-func-assign.js +++ b/lib/rules/no-func-assign.js @@ -31,6 +31,8 @@ module.exports = { create(context) { + const sourceCode = context.getSourceCode(); + /** * Reports a reference if is non initializer and writable. * @param {References} references Collection of reference to check. @@ -65,7 +67,7 @@ module.exports = { * @returns {void} */ function checkForFunction(node) { - context.getDeclaredVariables(node).forEach(checkVariable); + sourceCode.getDeclaredVariables(node).forEach(checkVariable); } return { diff --git a/lib/rules/no-import-assign.js b/lib/rules/no-import-assign.js index dcfebaf631b..2c616b49fc8 100644 --- a/lib/rules/no-import-assign.js +++ b/lib/rules/no-import-assign.js @@ -200,7 +200,7 @@ module.exports = { ImportDeclaration(node) { const scope = sourceCode.getScope(node); - for (const variable of context.getDeclaredVariables(node)) { + for (const variable of sourceCode.getDeclaredVariables(node)) { const shouldCheckMembers = variable.defs.some( d => d.node.type === "ImportNamespaceSpecifier" ); diff --git a/lib/rules/no-lone-blocks.js b/lib/rules/no-lone-blocks.js index 23f581d3fc6..416bbd499c0 100644 --- a/lib/rules/no-lone-blocks.js +++ b/lib/rules/no-lone-blocks.js @@ -68,14 +68,15 @@ module.exports = { /** * Checks the enclosing block of the current node for block-level bindings, * and "marks it" as valid if any. + * @param {ASTNode} node The current node to check. * @returns {void} */ - function markLoneBlock() { + function markLoneBlock(node) { if (loneBlocks.length === 0) { return; } - const block = context.getAncestors().pop(); + const block = sourceCode.getAncestors(node).pop(); if (loneBlocks[loneBlocks.length - 1] === block) { loneBlocks.pop(); @@ -117,13 +118,13 @@ module.exports = { ruleDef.VariableDeclaration = function(node) { if (node.kind === "let" || node.kind === "const") { - markLoneBlock(); + markLoneBlock(node); } }; ruleDef.FunctionDeclaration = function(node) { if (sourceCode.getScope(node).isStrict) { - markLoneBlock(); + markLoneBlock(node); } }; diff --git a/lib/rules/no-lonely-if.js b/lib/rules/no-lonely-if.js index 0774b9fa30f..4abaa6753ba 100644 --- a/lib/rules/no-lonely-if.js +++ b/lib/rules/no-lonely-if.js @@ -32,7 +32,7 @@ module.exports = { return { IfStatement(node) { - const ancestors = context.getAncestors(), + const ancestors = sourceCode.getAncestors(node), parent = ancestors.pop(), grandparent = ancestors.pop(); diff --git a/lib/rules/no-param-reassign.js b/lib/rules/no-param-reassign.js index f89435c8675..f7ab0e1e1bf 100644 --- a/lib/rules/no-param-reassign.js +++ b/lib/rules/no-param-reassign.js @@ -70,6 +70,7 @@ module.exports = { const props = context.options[0] && context.options[0].props; const ignoredPropertyAssignmentsFor = context.options[0] && context.options[0].ignorePropertyModificationsFor || []; const ignoredPropertyAssignmentsForRegex = context.options[0] && context.options[0].ignorePropertyModificationsForRegex || []; + const sourceCode = context.getSourceCode(); /** * Checks whether or not the reference modifies properties of its variable. @@ -214,7 +215,7 @@ module.exports = { * @returns {void} */ function checkForFunction(node) { - context.getDeclaredVariables(node).forEach(checkVariable); + sourceCode.getDeclaredVariables(node).forEach(checkVariable); } return { diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index d201e3b03a6..6fd247e981c 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -99,6 +99,7 @@ module.exports = { const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports); const restrictDefaultExports = context.options[0] && context.options[0].restrictDefaultExports; + const sourceCode = context.getSourceCode(); /** * Checks and reports given exported name. @@ -176,7 +177,7 @@ module.exports = { if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") { checkExportedName(declaration.id); } else if (declaration.type === "VariableDeclaration") { - context.getDeclaredVariables(declaration) + sourceCode.getDeclaredVariables(declaration) .map(v => v.defs.find(d => d.parent === declaration)) .map(d => d.name) // Identifier nodes .forEach(checkExportedName); diff --git a/lib/rules/no-shadow-restricted-names.js b/lib/rules/no-shadow-restricted-names.js index a7d6d00f164..377d450cc77 100644 --- a/lib/rules/no-shadow-restricted-names.js +++ b/lib/rules/no-shadow-restricted-names.js @@ -43,10 +43,11 @@ module.exports = { const RESTRICTED = new Set(["undefined", "NaN", "Infinity", "arguments", "eval"]); + const sourceCode = context.getSourceCode(); return { "VariableDeclaration, :function, CatchClause"(node) { - for (const variable of context.getDeclaredVariables(node)) { + for (const variable of sourceCode.getDeclaredVariables(node)) { if (variable.defs.length > 0 && RESTRICTED.has(variable.name) && !safelyShadowsUndefined(variable)) { context.report({ node: variable.defs[0].name, diff --git a/lib/rules/no-underscore-dangle.js b/lib/rules/no-underscore-dangle.js index 692637920b9..55201f5bd3e 100644 --- a/lib/rules/no-underscore-dangle.js +++ b/lib/rules/no-underscore-dangle.js @@ -84,6 +84,7 @@ module.exports = { const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true; const allowInArrayDestructuring = typeof options.allowInArrayDestructuring !== "undefined" ? options.allowInArrayDestructuring : true; const allowInObjectDestructuring = typeof options.allowInObjectDestructuring !== "undefined" ? options.allowInObjectDestructuring : true; + const sourceCode = context.getSourceCode(); //------------------------------------------------------------------------- // Helpers @@ -213,7 +214,7 @@ module.exports = { * @private */ function checkForDanglingUnderscoreInVariableExpression(node) { - context.getDeclaredVariables(node).forEach(variable => { + sourceCode.getDeclaredVariables(node).forEach(variable => { const definition = variable.defs.find(def => def.node === node); const identifierNode = definition.name; const identifier = identifierNode.name; diff --git a/lib/rules/no-unused-expressions.js b/lib/rules/no-unused-expressions.js index d34d5844d97..156d8b6f606 100644 --- a/lib/rules/no-unused-expressions.js +++ b/lib/rules/no-unused-expressions.js @@ -70,7 +70,8 @@ module.exports = { allowShortCircuit = config.allowShortCircuit || false, allowTernary = config.allowTernary || false, allowTaggedTemplates = config.allowTaggedTemplates || false, - enforceForJSX = config.enforceForJSX || false; + enforceForJSX = config.enforceForJSX || false, + sourceCode = context.getSourceCode(); /** * Has AST suggesting a directive. @@ -180,7 +181,7 @@ module.exports = { return { ExpressionStatement(node) { - if (Checker.isDisallowed(node.expression) && !isDirective(node, context.getAncestors())) { + if (Checker.isDisallowed(node.expression) && !isDirective(node, sourceCode.getAncestors(node))) { context.report({ node, messageId: "unusedExpression" }); } } diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js index 79f972f4b04..aec02dadac4 100644 --- a/lib/rules/no-unused-vars.js +++ b/lib/rules/no-unused-vars.js @@ -555,7 +555,7 @@ module.exports = { */ function isAfterLastUsedArg(variable) { const def = variable.defs[0]; - const params = context.getDeclaredVariables(def.node); + const params = sourceCode.getDeclaredVariables(def.node); const posteriorParams = params.slice(params.indexOf(variable) + 1); // If any used parameters occur after this parameter, do not report. diff --git a/lib/rules/no-var.js b/lib/rules/no-var.js index 80ff8f1a027..01316db35c4 100644 --- a/lib/rules/no-var.js +++ b/lib/rules/no-var.js @@ -210,7 +210,7 @@ module.exports = { if (!declarator.init) { return false; } - const variables = context.getDeclaredVariables(declarator); + const variables = sourceCode.getDeclaredVariables(declarator); return variables.some(hasReferenceInTDZ(declarator.init)); } @@ -268,7 +268,7 @@ module.exports = { * @returns {boolean} `true` if it can fix the node. */ function canFix(node) { - const variables = context.getDeclaredVariables(node); + const variables = sourceCode.getDeclaredVariables(node); const scopeNode = getScopeNode(node); if (node.parent.type === "SwitchCase" || diff --git a/lib/rules/prefer-arrow-callback.js b/lib/rules/prefer-arrow-callback.js index 68c630e0a98..1c270189140 100644 --- a/lib/rules/prefer-arrow-callback.js +++ b/lib/rules/prefer-arrow-callback.js @@ -263,7 +263,7 @@ module.exports = { } // Skip recursive functions. - const nameVar = context.getDeclaredVariables(node)[0]; + const nameVar = sourceCode.getDeclaredVariables(node)[0]; if (isFunctionName(nameVar) && nameVar.references.length > 0) { return; diff --git a/lib/rules/prefer-const.js b/lib/rules/prefer-const.js index e3d2db7aeb9..2d8cab83292 100644 --- a/lib/rules/prefer-const.js +++ b/lib/rules/prefer-const.js @@ -493,7 +493,7 @@ module.exports = { VariableDeclaration(node) { if (node.kind === "let" && !isInitOfForStatement(node)) { - variables.push(...context.getDeclaredVariables(node)); + variables.push(...sourceCode.getDeclaredVariables(node)); } } }; diff --git a/lib/rules/prefer-promise-reject-errors.js b/lib/rules/prefer-promise-reject-errors.js index bd7bdcbf5b7..391ba1aa158 100644 --- a/lib/rules/prefer-promise-reject-errors.js +++ b/lib/rules/prefer-promise-reject-errors.js @@ -41,6 +41,7 @@ module.exports = { create(context) { const ALLOW_EMPTY_REJECT = context.options.length && context.options[0].allowEmptyReject; + const sourceCode = context.getSourceCode(); //---------------------------------------------------------------------- // Helpers @@ -100,7 +101,7 @@ module.exports = { node.arguments.length && astUtils.isFunction(node.arguments[0]) && node.arguments[0].params.length > 1 && node.arguments[0].params[1].type === "Identifier" ) { - context.getDeclaredVariables(node.arguments[0]) + sourceCode.getDeclaredVariables(node.arguments[0]) /* * Find the first variable that matches the second parameter's name. diff --git a/lib/rules/valid-typeof.js b/lib/rules/valid-typeof.js index 6ba5d466118..3562e653c08 100644 --- a/lib/rules/valid-typeof.js +++ b/lib/rules/valid-typeof.js @@ -83,7 +83,7 @@ module.exports = { UnaryExpression(node) { if (isTypeofExpression(node)) { - const parent = context.getAncestors().pop(); + const parent = sourceCode.getAncestors(node).pop(); if (parent.type === "BinaryExpression" && OPERATORS.has(parent.operator)) { const sibling = parent.left === node ? parent.right : parent.left; diff --git a/lib/rules/wrap-regex.js b/lib/rules/wrap-regex.js index b24d36025ed..10c388e8f07 100644 --- a/lib/rules/wrap-regex.js +++ b/lib/rules/wrap-regex.js @@ -40,7 +40,7 @@ module.exports = { if (nodeType === "RegularExpression") { const beforeToken = sourceCode.getTokenBefore(node); const afterToken = sourceCode.getTokenAfter(node); - const ancestors = context.getAncestors(); + const ancestors = sourceCode.getAncestors(node); const grandparent = ancestors[ancestors.length - 1]; if (grandparent.type === "MemberExpression" && grandparent.object === node && diff --git a/lib/rules/yoda.js b/lib/rules/yoda.js index eb9a32ad5c3..f3acece47a7 100644 --- a/lib/rules/yoda.js +++ b/lib/rules/yoda.js @@ -343,7 +343,7 @@ module.exports = { ) && !(!isEqualityOperator(node.operator) && onlyEquality) && isComparisonOperator(node.operator) && - !(exceptRange && isRangeTest(context.getAncestors().pop())) + !(exceptRange && isRangeTest(sourceCode.getAncestors(node).pop())) ) { context.report({ node, diff --git a/lib/source-code/source-code.js b/lib/source-code/source-code.js index 7e1630cc73b..abaefa89994 100644 --- a/lib/source-code/source-code.js +++ b/lib/source-code/source-code.js @@ -14,6 +14,12 @@ const astUtils = require("../shared/ast-utils"), Traverser = require("../shared/traverser"); +//------------------------------------------------------------------------------ +// Type Definitions +//------------------------------------------------------------------------------ + +/** @typedef {import("eslint-scope").Variable} Variable */ + //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ @@ -639,6 +645,42 @@ class SourceCode extends TokenStore { return this.scopeManager.scopes[0]; } + /** + * Gets all of the declared variables in the scope associated + * with `node`. This is a convenience method that passes through + * to the same method on the `scopeManager`. + * @param {ASTNode} node The node from which to retrieve the scope to check. + * @returns {Array} An array of variable nodes representing + * the declared variables in the scope associated with `node`. + */ + getDeclaredVariables(node) { + return this.scopeManager.getDeclaredVariables(node); + } + + /* eslint-disable class-methods-use-this -- node is owned by SourceCode */ + /** + * Gets all the ancestors of a given node + * @param {ASTNode} node The node + * @returns {Array} All the ancestor nodes in the AST, not including the provided node, starting + * from the root node at index 0 and going inwards to the parent node. + * @throws {TypeError} When `node` is missing. + */ + getAncestors(node) { + + if (!node) { + throw new TypeError("Missing required argument: node."); + } + + const ancestorsStartingAtParent = []; + + for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) { + ancestorsStartingAtParent.push(ancestor); + } + + return ancestorsStartingAtParent.reverse(); + } + /* eslint-enable class-methods-use-this -- node is owned by SourceCode */ + } module.exports = SourceCode; diff --git a/tests/lib/source-code/source-code.js b/tests/lib/source-code/source-code.js index d65045ad384..976b59d9844 100644 --- a/tests/lib/source-code/source-code.js +++ b/tests/lib/source-code/source-code.js @@ -29,6 +29,7 @@ const DEFAULT_CONFIG = { loc: true }; const linter = new Linter(); +const flatLinter = new Linter({ configType: "flat" }); const AST = espree.parse("let foo = bar;", DEFAULT_CONFIG), TEST_CODE = "var answer = 6 * 7;", SHEBANG_TEST_CODE = `#!/usr/bin/env node\n${TEST_CODE}`; @@ -3283,4 +3284,343 @@ describe("SourceCode", () => { assert.strictEqual(scope.references[1].resolved, scope.variables[0]); }); }); + + describe("getAncestors()", () => { + const code = TEST_CODE; + const filename = "foo.js"; + + it("should retrieve all ancestors when used", () => { + + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const sourceCode = context.getSourceCode(); + const ancestors = sourceCode.getAncestors(node); + + assert.strictEqual(ancestors.length, 3); + }); + return { BinaryExpression: spy }; + } + } + } + } + }, + rules: { "test/checker": "error" } + }; + + flatLinter.verify(code, config, filename, true); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + + it("should retrieve empty ancestors for root node", () => { + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const sourceCode = context.getSourceCode(); + const ancestors = sourceCode.getAncestors(node); + + assert.strictEqual(ancestors.length, 0); + }); + + return { Program: spy }; + } + } + } + } + }, + rules: { "test/checker": "error" } + }; + + flatLinter.verify(code, config); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + + it("should throw an error when the argument is missing", () => { + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(() => { + const sourceCode = context.getSourceCode(); + + assert.throws(() => { + sourceCode.getAncestors(); + }, /Missing required argument: node/u); + + }); + + return { Program: spy }; + } + } + } + } + }, + rules: { "test/checker": "error" } + }; + + flatLinter.verify(code, config); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + }); + + + describe("getDeclaredVariables(node)", () => { + + /** + * Assert `sourceCode.getDeclaredVariables(node)` is valid. + * @param {string} code A code to check. + * @param {string} type A type string of ASTNode. This method checks variables on the node of the type. + * @param {Array>} expectedNamesList An array of expected variable names. The expected variable names is an array of string. + * @returns {void} + */ + function verify(code, type, expectedNamesList) { + linter.defineRules({ + test: { + create(context) { + + const sourceCode = context.getSourceCode(); + + /** + * Assert `sourceCode.getDeclaredVariables(node)` is empty. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkEmpty(node) { + assert.strictEqual(0, sourceCode.getDeclaredVariables(node).length); + } + const rule = { + Program: checkEmpty, + EmptyStatement: checkEmpty, + BlockStatement: checkEmpty, + ExpressionStatement: checkEmpty, + LabeledStatement: checkEmpty, + BreakStatement: checkEmpty, + ContinueStatement: checkEmpty, + WithStatement: checkEmpty, + SwitchStatement: checkEmpty, + ReturnStatement: checkEmpty, + ThrowStatement: checkEmpty, + TryStatement: checkEmpty, + WhileStatement: checkEmpty, + DoWhileStatement: checkEmpty, + ForStatement: checkEmpty, + ForInStatement: checkEmpty, + DebuggerStatement: checkEmpty, + ThisExpression: checkEmpty, + ArrayExpression: checkEmpty, + ObjectExpression: checkEmpty, + Property: checkEmpty, + SequenceExpression: checkEmpty, + UnaryExpression: checkEmpty, + BinaryExpression: checkEmpty, + AssignmentExpression: checkEmpty, + UpdateExpression: checkEmpty, + LogicalExpression: checkEmpty, + ConditionalExpression: checkEmpty, + CallExpression: checkEmpty, + NewExpression: checkEmpty, + MemberExpression: checkEmpty, + SwitchCase: checkEmpty, + Identifier: checkEmpty, + Literal: checkEmpty, + ForOfStatement: checkEmpty, + ArrowFunctionExpression: checkEmpty, + YieldExpression: checkEmpty, + TemplateLiteral: checkEmpty, + TaggedTemplateExpression: checkEmpty, + TemplateElement: checkEmpty, + ObjectPattern: checkEmpty, + ArrayPattern: checkEmpty, + RestElement: checkEmpty, + AssignmentPattern: checkEmpty, + ClassBody: checkEmpty, + MethodDefinition: checkEmpty, + MetaProperty: checkEmpty + }; + + rule[type] = function(node) { + const expectedNames = expectedNamesList.shift(); + const variables = sourceCode.getDeclaredVariables(node); + + assert(Array.isArray(expectedNames)); + assert(Array.isArray(variables)); + assert.strictEqual(expectedNames.length, variables.length); + for (let i = variables.length - 1; i >= 0; i--) { + assert.strictEqual(expectedNames[i], variables[i].name); + } + }; + return rule; + } + } + }); + linter.verify(code, { + rules: { test: 2 }, + parserOptions: { + ecmaVersion: 6, + sourceType: "module" + } + }); + + // Check all expected names are asserted. + assert.strictEqual(0, expectedNamesList.length); + } + + it("VariableDeclaration", () => { + const code = "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n "; + const namesList = [ + ["a", "b", "c"], + ["d", "e", "f"], + ["g", "h", "i", "j", "k"], + ["l"] + ]; + + verify(code, "VariableDeclaration", namesList); + }); + + it("VariableDeclaration (on for-in/of loop)", () => { + + // TDZ scope is created here, so tests to exclude those. + const code = "\n for (var {a, x: [b], y: {c = 0}} in foo) {\n let g;\n }\n for (let {d, x: [e], y: {f = 0}} of foo) {\n let h;\n }\n "; + const namesList = [ + ["a", "b", "c"], + ["g"], + ["d", "e", "f"], + ["h"] + ]; + + verify(code, "VariableDeclaration", namesList); + }); + + it("VariableDeclarator", () => { + + // TDZ scope is created here, so tests to exclude those. + const code = "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n "; + const namesList = [ + ["a", "b", "c"], + ["d", "e", "f"], + ["g", "h", "i"], + ["j", "k"], + ["l"] + ]; + + verify(code, "VariableDeclarator", namesList); + }); + + it("FunctionDeclaration", () => { + const code = "\n function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n }\n function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n }\n "; + const namesList = [ + ["foo", "a", "b", "c", "d", "e"], + ["bar", "f", "g", "h", "i", "j"] + ]; + + verify(code, "FunctionDeclaration", namesList); + }); + + it("FunctionExpression", () => { + const code = "\n (function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n });\n (function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n });\n "; + const namesList = [ + ["foo", "a", "b", "c", "d", "e"], + ["bar", "f", "g", "h", "i", "j"], + ["q"] + ]; + + verify(code, "FunctionExpression", namesList); + }); + + it("ArrowFunctionExpression", () => { + const code = "\n (({a, x: [b], y: {c = 0}}, [d, e]) => {\n let z;\n });\n (({f, x: [g], y: {h = 0}}, [i, j]) => {\n let z;\n });\n "; + const namesList = [ + ["a", "b", "c", "d", "e"], + ["f", "g", "h", "i", "j"] + ]; + + verify(code, "ArrowFunctionExpression", namesList); + }); + + it("ClassDeclaration", () => { + const code = "\n class A { foo(x) { let y; } }\n class B { foo(x) { let y; } }\n "; + const namesList = [ + ["A", "A"], // outer scope's and inner scope's. + ["B", "B"] + ]; + + verify(code, "ClassDeclaration", namesList); + }); + + it("ClassExpression", () => { + const code = "\n (class A { foo(x) { let y; } });\n (class B { foo(x) { let y; } });\n "; + const namesList = [ + ["A"], + ["B"] + ]; + + verify(code, "ClassExpression", namesList); + }); + + it("CatchClause", () => { + const code = "\n try {} catch ({a, b}) {\n let x;\n try {} catch ({c, d}) {\n let y;\n }\n }\n "; + const namesList = [ + ["a", "b"], + ["c", "d"] + ]; + + verify(code, "CatchClause", namesList); + }); + + it("ImportDeclaration", () => { + const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; + const namesList = [ + [], + ["a"], + ["b", "c", "d"] + ]; + + verify(code, "ImportDeclaration", namesList); + }); + + it("ImportSpecifier", () => { + const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; + const namesList = [ + ["c"], + ["d"] + ]; + + verify(code, "ImportSpecifier", namesList); + }); + + it("ImportDefaultSpecifier", () => { + const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; + const namesList = [ + ["b"] + ]; + + verify(code, "ImportDefaultSpecifier", namesList); + }); + + it("ImportNamespaceSpecifier", () => { + const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; + const namesList = [ + ["a"] + ]; + + verify(code, "ImportNamespaceSpecifier", namesList); + }); + }); + });