Skip to content

Commit

Permalink
feat: Move getDeclaredVariables and getAncestors to SourceCode (#17059)
Browse files Browse the repository at this point in the history
* feat: Move getDeclaredVariables and getAncestors to SourceCode

Refs #16999

* Update rules to use sourceCode.getAncestors()

* Add caching to sourceCode.getAncestors()

* Update docs

* Fix getAncestors caching

* Update tests/lib/source-code/source-code.js

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

* Update tests/lib/source-code/source-code.js

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

* Update tests/lib/source-code/source-code.js

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>

* Restore deprecated custom rules docs

* Update comments in source-code.js

* Check for missing argument in getAncestors()

* Check for missing argument in getAncestors()

---------

Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
  • Loading branch information
nzakas and mdjermanovic committed Apr 7, 2023
1 parent 0fd6bb2 commit a1d561d
Show file tree
Hide file tree
Showing 30 changed files with 438 additions and 53 deletions.
3 changes: 2 additions & 1 deletion docs/src/extend/code-path-analysis.md
Expand Up @@ -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";
});
}
Expand Down
6 changes: 3 additions & 3 deletions docs/src/extend/custom-rules.md
Expand Up @@ -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.
Expand All @@ -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 `<text>` 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)).
Expand Down
20 changes: 2 additions & 18 deletions lib/linter/linter.js
Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion lib/rules/block-scoped-var.js
Expand Up @@ -28,6 +28,7 @@ module.exports = {

create(context) {
let stack = [];
const sourceCode = context.getSourceCode();

/**
* Makes a block scope.
Expand Down Expand Up @@ -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) {

Expand Down
4 changes: 2 additions & 2 deletions lib/rules/camelcase.js
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/func-names.js
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/global-require.js
Expand Up @@ -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" });
Expand Down
4 changes: 3 additions & 1 deletion lib/rules/no-class-assign.js
Expand Up @@ -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.
Expand All @@ -49,7 +51,7 @@ module.exports = {
* @returns {void}
*/
function checkForClass(node) {
context.getDeclaredVariables(node).forEach(checkVariable);
sourceCode.getDeclaredVariables(node).forEach(checkVariable);
}

return {
Expand Down
4 changes: 3 additions & 1 deletion lib/rules/no-const-assign.js
Expand Up @@ -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.
Expand All @@ -45,7 +47,7 @@ module.exports = {
return {
VariableDeclaration(node) {
if (node.kind === "const") {
context.getDeclaredVariables(node).forEach(checkVariable);
sourceCode.getDeclaredVariables(node).forEach(checkVariable);
}
}
};
Expand Down
4 changes: 3 additions & 1 deletion lib/rules/no-dupe-args.js
Expand Up @@ -29,6 +29,8 @@ module.exports = {

create(context) {

const sourceCode = context.getSourceCode();

//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
Expand All @@ -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];
Expand Down
4 changes: 3 additions & 1 deletion lib/rules/no-ex-assign.js
Expand Up @@ -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.
Expand All @@ -44,7 +46,7 @@ module.exports = {

return {
CatchClause(node) {
context.getDeclaredVariables(node).forEach(checkVariable);
sourceCode.getDeclaredVariables(node).forEach(checkVariable);
}
};

Expand Down
4 changes: 3 additions & 1 deletion lib/rules/no-func-assign.js
Expand Up @@ -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.
Expand Down Expand Up @@ -65,7 +67,7 @@ module.exports = {
* @returns {void}
*/
function checkForFunction(node) {
context.getDeclaredVariables(node).forEach(checkVariable);
sourceCode.getDeclaredVariables(node).forEach(checkVariable);
}

return {
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-import-assign.js
Expand Up @@ -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"
);
Expand Down
9 changes: 5 additions & 4 deletions lib/rules/no-lone-blocks.js
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
};

Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-lonely-if.js
Expand Up @@ -32,7 +32,7 @@ module.exports = {

return {
IfStatement(node) {
const ancestors = context.getAncestors(),
const ancestors = sourceCode.getAncestors(node),
parent = ancestors.pop(),
grandparent = ancestors.pop();

Expand Down
3 changes: 2 additions & 1 deletion lib/rules/no-param-reassign.js
Expand Up @@ -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.
Expand Down Expand Up @@ -214,7 +215,7 @@ module.exports = {
* @returns {void}
*/
function checkForFunction(node) {
context.getDeclaredVariables(node).forEach(checkVariable);
sourceCode.getDeclaredVariables(node).forEach(checkVariable);
}

return {
Expand Down
3 changes: 2 additions & 1 deletion lib/rules/no-restricted-exports.js
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion lib/rules/no-shadow-restricted-names.js
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion lib/rules/no-underscore-dangle.js
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions lib/rules/no-unused-expressions.js
Expand Up @@ -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.
Expand Down Expand Up @@ -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" });
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-unused-vars.js
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions lib/rules/no-var.js
Expand Up @@ -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));
}
Expand Down Expand Up @@ -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" ||
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/prefer-arrow-callback.js
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/prefer-const.js
Expand Up @@ -493,7 +493,7 @@ module.exports = {

VariableDeclaration(node) {
if (node.kind === "let" && !isInitOfForStatement(node)) {
variables.push(...context.getDeclaredVariables(node));
variables.push(...sourceCode.getDeclaredVariables(node));
}
}
};
Expand Down

0 comments on commit a1d561d

Please sign in to comment.