astro-ghostcms/.pnpm-store/v3/files/e8/ef8a1b4e243f6e6ab574d4ce01f...

561 lines
23 KiB
Plaintext
Raw Normal View History

2024-02-14 19:45:06 +00:00
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _UnusedVarsVisitor_scopeManager;
Object.defineProperty(exports, "__esModule", { value: true });
exports.collectUnusedVariables = void 0;
const scope_manager_1 = require("@typescript-eslint/scope-manager");
const utils_1 = require("@typescript-eslint/utils");
class UnusedVarsVisitor extends scope_manager_1.Visitor {
// readonly #unusedVariables = new Set<TSESLint.Scope.Variable>();
constructor(context) {
super({
visitChildrenEvenIfSelectorExists: true,
});
_UnusedVarsVisitor_scopeManager.set(this, void 0);
//#endregion HELPERS
//#region VISITORS
// NOTE - This is a simple visitor - meaning it does not support selectors
this.ClassDeclaration = this.visitClass;
this.ClassExpression = this.visitClass;
this.FunctionDeclaration = this.visitFunction;
this.FunctionExpression = this.visitFunction;
this.MethodDefinition = this.visitSetter;
this.Property = this.visitSetter;
this.TSCallSignatureDeclaration = this.visitFunctionTypeSignature;
this.TSConstructorType = this.visitFunctionTypeSignature;
this.TSConstructSignatureDeclaration = this.visitFunctionTypeSignature;
this.TSDeclareFunction = this.visitFunctionTypeSignature;
this.TSEmptyBodyFunctionExpression = this.visitFunctionTypeSignature;
this.TSFunctionType = this.visitFunctionTypeSignature;
this.TSMethodSignature = this.visitFunctionTypeSignature;
__classPrivateFieldSet(this, _UnusedVarsVisitor_scopeManager, utils_1.ESLintUtils.nullThrows(context.sourceCode.scopeManager, 'Missing required scope manager'), "f");
}
static collectUnusedVariables(context) {
const program = context.sourceCode.ast;
const cached = this.RESULTS_CACHE.get(program);
if (cached) {
return cached;
}
const visitor = new this(context);
visitor.visit(program);
const unusedVars = visitor.collectUnusedVariables(visitor.getScope(program));
this.RESULTS_CACHE.set(program, unusedVars);
return unusedVars;
}
collectUnusedVariables(scope, unusedVariables = new Set()) {
for (const variable of scope.variables) {
if (
// skip function expression names,
scope.functionExpressionScope ||
// variables marked with markVariableAsUsed(),
variable.eslintUsed ||
// implicit lib variables (from @typescript-eslint/scope-manager),
variable instanceof scope_manager_1.ImplicitLibVariable ||
// basic exported variables
isExported(variable) ||
// variables implicitly exported via a merged declaration
isMergableExported(variable) ||
// used variables
isUsedVariable(variable)) {
continue;
}
unusedVariables.add(variable);
}
for (const childScope of scope.childScopes) {
this.collectUnusedVariables(childScope, unusedVariables);
}
return unusedVariables;
}
//#region HELPERS
getScope(currentNode) {
// On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope.
const inner = currentNode.type !== utils_1.AST_NODE_TYPES.Program;
let node = currentNode;
while (node) {
const scope = __classPrivateFieldGet(this, _UnusedVarsVisitor_scopeManager, "f").acquire(node, inner);
if (scope) {
if (scope.type === scope_manager_1.ScopeType.functionExpressionName) {
return scope.childScopes[0];
}
return scope;
}
node = node.parent;
}
return __classPrivateFieldGet(this, _UnusedVarsVisitor_scopeManager, "f").scopes[0];
}
markVariableAsUsed(variableOrIdentifierOrName, parent) {
if (typeof variableOrIdentifierOrName !== 'string' &&
!('type' in variableOrIdentifierOrName)) {
variableOrIdentifierOrName.eslintUsed = true;
return;
}
let name;
let node;
if (typeof variableOrIdentifierOrName === 'string') {
name = variableOrIdentifierOrName;
node = parent;
}
else {
name = variableOrIdentifierOrName.name;
node = variableOrIdentifierOrName;
}
let currentScope = this.getScope(node);
while (currentScope) {
const variable = currentScope.variables.find(scopeVar => scopeVar.name === name);
if (variable) {
variable.eslintUsed = true;
return;
}
currentScope = currentScope.upper;
}
}
visitClass(node) {
// skip a variable of class itself name in the class scope
const scope = this.getScope(node);
for (const variable of scope.variables) {
if (variable.identifiers[0] === scope.block.id) {
this.markVariableAsUsed(variable);
return;
}
}
}
visitFunction(node) {
const scope = this.getScope(node);
// skip implicit "arguments" variable
const variable = scope.set.get('arguments');
if (variable?.defs.length === 0) {
this.markVariableAsUsed(variable);
}
}
visitFunctionTypeSignature(node) {
// function type signature params create variables because they can be referenced within the signature,
// but they obviously aren't unused variables for the purposes of this rule.
for (const param of node.params) {
this.visitPattern(param, name => {
this.markVariableAsUsed(name);
});
}
}
visitSetter(node) {
if (node.kind === 'set') {
// ignore setter parameters because they're syntactically required to exist
for (const param of node.value.params) {
this.visitPattern(param, id => {
this.markVariableAsUsed(id);
});
}
}
}
ForInStatement(node) {
/**
* (Brad Zacher): I hate that this has to exist.
* But it is required for compat with the base ESLint rule.
*
* In 2015, ESLint decided to add an exception for these two specific cases
* ```
* for (var key in object) return;
*
* var key;
* for (key in object) return;
* ```
*
* I disagree with it, but what are you going to do...
*
* https://github.com/eslint/eslint/issues/2342
*/
let idOrVariable;
if (node.left.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
const variable = __classPrivateFieldGet(this, _UnusedVarsVisitor_scopeManager, "f").getDeclaredVariables(node.left).at(0);
if (!variable) {
return;
}
idOrVariable = variable;
}
if (node.left.type === utils_1.AST_NODE_TYPES.Identifier) {
idOrVariable = node.left;
}
if (idOrVariable == null) {
return;
}
let body = node.body;
if (node.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
if (node.body.body.length !== 1) {
return;
}
body = node.body.body[0];
}
if (body.type !== utils_1.AST_NODE_TYPES.ReturnStatement) {
return;
}
this.markVariableAsUsed(idOrVariable);
}
Identifier(node) {
const scope = this.getScope(node);
if (scope.type === utils_1.TSESLint.Scope.ScopeType.function &&
node.name === 'this') {
// this parameters should always be considered used as they're pseudo-parameters
if ('params' in scope.block && scope.block.params.includes(node)) {
this.markVariableAsUsed(node);
}
}
}
TSEnumDeclaration(node) {
// enum members create variables because they can be referenced within the enum,
// but they obviously aren't unused variables for the purposes of this rule.
const scope = this.getScope(node);
for (const variable of scope.variables) {
this.markVariableAsUsed(variable);
}
}
TSMappedType(node) {
// mapped types create a variable for their type name, but it's not necessary to reference it,
// so we shouldn't consider it as unused for the purpose of this rule.
this.markVariableAsUsed(node.typeParameter.name);
}
TSModuleDeclaration(node) {
// -- global augmentation can be in any file, and they do not need exports
if (node.global) {
this.markVariableAsUsed('global', node.parent);
}
}
TSParameterProperty(node) {
let identifier = null;
switch (node.parameter.type) {
case utils_1.AST_NODE_TYPES.AssignmentPattern:
if (node.parameter.left.type === utils_1.AST_NODE_TYPES.Identifier) {
identifier = node.parameter.left;
}
break;
case utils_1.AST_NODE_TYPES.Identifier:
identifier = node.parameter;
break;
}
if (identifier) {
this.markVariableAsUsed(identifier);
}
}
}
_UnusedVarsVisitor_scopeManager = new WeakMap();
UnusedVarsVisitor.RESULTS_CACHE = new WeakMap();
//#region private helpers
/**
* Checks the position of given nodes.
* @param inner A node which is expected as inside.
* @param outer A node which is expected as outside.
* @returns `true` if the `inner` node exists in the `outer` node.
*/
function isInside(inner, outer) {
return inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1];
}
/**
* Determine if an identifier is referencing an enclosing name.
* This only applies to declarations that create their own scope (modules, functions, classes)
* @param ref The reference to check.
* @param nodes The candidate function nodes.
* @returns True if it's a self-reference, false if not.
*/
function isSelfReference(ref, nodes) {
let scope = ref.from;
while (scope) {
if (nodes.has(scope.block)) {
return true;
}
scope = scope.upper;
}
return false;
}
const MERGABLE_TYPES = new Set([
utils_1.AST_NODE_TYPES.TSInterfaceDeclaration,
utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration,
utils_1.AST_NODE_TYPES.TSModuleDeclaration,
utils_1.AST_NODE_TYPES.ClassDeclaration,
utils_1.AST_NODE_TYPES.FunctionDeclaration,
]);
/**
* Determine if the variable is directly exported
* @param variable the variable to check
*/
function isMergableExported(variable) {
// If all of the merged things are of the same type, TS will error if not all of them are exported - so we only need to find one
for (const def of variable.defs) {
// parameters can never be exported.
// their `node` prop points to the function decl, which can be exported
// so we need to special case them
if (def.type === utils_1.TSESLint.Scope.DefinitionType.Parameter) {
continue;
}
if ((MERGABLE_TYPES.has(def.node.type) &&
def.node.parent?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration) ||
def.node.parent?.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) {
return true;
}
}
return false;
}
/**
* Determines if a given variable is being exported from a module.
* @param variable eslint-scope variable object.
* @returns True if the variable is exported, false if not.
*/
function isExported(variable) {
return variable.defs.some(definition => {
let node = definition.node;
if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
node = node.parent;
}
else if (definition.type === utils_1.TSESLint.Scope.DefinitionType.Parameter) {
return false;
}
return node.parent.type.indexOf('Export') === 0;
});
}
const LOGICAL_ASSIGNMENT_OPERATORS = new Set(['&&=', '||=', '??=']);
/**
* Determines if the variable is used.
* @param variable The variable to check.
* @returns True if the variable is used
*/
function isUsedVariable(variable) {
/**
* Gets a list of function definitions for a specified variable.
* @param variable eslint-scope variable object.
* @returns Function nodes.
*/
function getFunctionDefinitions(variable) {
const functionDefinitions = new Set();
variable.defs.forEach(def => {
// FunctionDeclarations
if (def.type === utils_1.TSESLint.Scope.DefinitionType.FunctionName) {
functionDefinitions.add(def.node);
}
// FunctionExpressions
if (def.type === utils_1.TSESLint.Scope.DefinitionType.Variable &&
(def.node.init?.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
def.node.init?.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
functionDefinitions.add(def.node.init);
}
});
return functionDefinitions;
}
function getTypeDeclarations(variable) {
const nodes = new Set();
variable.defs.forEach(def => {
if (def.node.type === utils_1.AST_NODE_TYPES.TSInterfaceDeclaration ||
def.node.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration) {
nodes.add(def.node);
}
});
return nodes;
}
function getModuleDeclarations(variable) {
const nodes = new Set();
variable.defs.forEach(def => {
if (def.node.type === utils_1.AST_NODE_TYPES.TSModuleDeclaration) {
nodes.add(def.node);
}
});
return nodes;
}
/**
* Checks if the ref is contained within one of the given nodes
*/
function isInsideOneOf(ref, nodes) {
for (const node of nodes) {
if (isInside(ref.identifier, node)) {
return true;
}
}
return false;
}
/**
* If a given reference is left-hand side of an assignment, this gets
* the right-hand side node of the assignment.
*
* In the following cases, this returns null.
*
* - The reference is not the LHS of an assignment expression.
* - The reference is inside of a loop.
* - The reference is inside of a function scope which is different from
* the declaration.
* @param ref A reference to check.
* @param prevRhsNode The previous RHS node.
* @returns The RHS node or null.
*/
function getRhsNode(ref, prevRhsNode) {
/**
* Checks whether the given node is in a loop or not.
* @param node The node to check.
* @returns `true` if the node is in a loop.
*/
function isInLoop(node) {
let currentNode = node;
while (currentNode) {
if (utils_1.ASTUtils.isFunction(currentNode)) {
break;
}
if (utils_1.ASTUtils.isLoop(currentNode)) {
return true;
}
currentNode = currentNode.parent;
}
return false;
}
const id = ref.identifier;
const parent = id.parent;
const grandparent = parent.parent;
const refScope = ref.from.variableScope;
const varScope = ref.resolved.scope.variableScope;
const canBeUsedLater = refScope !== varScope || isInLoop(id);
/*
* Inherits the previous node if this reference is in the node.
* This is for `a = a + a`-like code.
*/
if (prevRhsNode && isInside(id, prevRhsNode)) {
return prevRhsNode;
}
if (parent.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
grandparent.type === utils_1.AST_NODE_TYPES.ExpressionStatement &&
id === parent.left &&
!canBeUsedLater) {
return parent.right;
}
return null;
}
/**
* Checks whether a given reference is a read to update itself or not.
* @param ref A reference to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns The reference is a read to update itself.
*/
function isReadForItself(ref, rhsNode) {
/**
* Checks whether a given Identifier node exists inside of a function node which can be used later.
*
* "can be used later" means:
* - the function is assigned to a variable.
* - the function is bound to a property and the object can be used later.
* - the function is bound as an argument of a function call.
*
* If a reference exists in a function which can be used later, the reference is read when the function is called.
* @param id An Identifier node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if the `id` node exists inside of a function node which can be used later.
*/
function isInsideOfStorableFunction(id, rhsNode) {
/**
* Finds a function node from ancestors of a node.
* @param node A start node to find.
* @returns A found function node.
*/
function getUpperFunction(node) {
let currentNode = node;
while (currentNode) {
if (utils_1.ASTUtils.isFunction(currentNode)) {
return currentNode;
}
currentNode = currentNode.parent;
}
return null;
}
/**
* Checks whether a given function node is stored to somewhere or not.
* If the function node is stored, the function can be used later.
* @param funcNode A function node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if under the following conditions:
* - the funcNode is assigned to a variable.
* - the funcNode is bound as an argument of a function call.
* - the function is bound to a property and the object satisfies above conditions.
*/
function isStorableFunction(funcNode, rhsNode) {
let node = funcNode;
let parent = funcNode.parent;
while (parent && isInside(parent, rhsNode)) {
switch (parent.type) {
case utils_1.AST_NODE_TYPES.SequenceExpression:
if (parent.expressions[parent.expressions.length - 1] !== node) {
return false;
}
break;
case utils_1.AST_NODE_TYPES.CallExpression:
case utils_1.AST_NODE_TYPES.NewExpression:
return parent.callee !== node;
case utils_1.AST_NODE_TYPES.AssignmentExpression:
case utils_1.AST_NODE_TYPES.TaggedTemplateExpression:
case utils_1.AST_NODE_TYPES.YieldExpression:
return true;
default:
if (parent.type.endsWith('Statement') ||
parent.type.endsWith('Declaration')) {
/*
* If it encountered statements, this is a complex pattern.
* Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
*/
return true;
}
}
node = parent;
parent = parent.parent;
}
return false;
}
const funcNode = getUpperFunction(id);
return (!!funcNode &&
isInside(funcNode, rhsNode) &&
isStorableFunction(funcNode, rhsNode));
}
const id = ref.identifier;
const parent = id.parent;
const grandparent = parent.parent;
return (ref.isRead() && // in RHS of an assignment for itself. e.g. `a = a + 1`
// self update. e.g. `a += 1`, `a++`
((parent.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
!LOGICAL_ASSIGNMENT_OPERATORS.has(parent.operator) &&
grandparent.type === utils_1.AST_NODE_TYPES.ExpressionStatement &&
parent.left === id) ||
(parent.type === utils_1.AST_NODE_TYPES.UpdateExpression &&
grandparent.type === utils_1.AST_NODE_TYPES.ExpressionStatement) ||
(!!rhsNode &&
isInside(id, rhsNode) &&
!isInsideOfStorableFunction(id, rhsNode))));
}
const functionNodes = getFunctionDefinitions(variable);
const isFunctionDefinition = functionNodes.size > 0;
const typeDeclNodes = getTypeDeclarations(variable);
const isTypeDecl = typeDeclNodes.size > 0;
const moduleDeclNodes = getModuleDeclarations(variable);
const isModuleDecl = moduleDeclNodes.size > 0;
let rhsNode = null;
return variable.references.some(ref => {
const forItself = isReadForItself(ref, rhsNode);
rhsNode = getRhsNode(ref, rhsNode);
return (ref.isRead() &&
!forItself &&
!(isFunctionDefinition && isSelfReference(ref, functionNodes)) &&
!(isTypeDecl && isInsideOneOf(ref, typeDeclNodes)) &&
!(isModuleDecl && isSelfReference(ref, moduleDeclNodes)));
});
}
//#endregion private helpers
/**
* Collects the set of unused variables for a given context.
*
* Due to complexity, this does not take into consideration:
* - variables within declaration files
* - variables within ambient module declarations
*/
function collectUnusedVariables(context) {
return UnusedVarsVisitor.collectUnusedVariables(context);
}
exports.collectUnusedVariables = collectUnusedVariables;
//# sourceMappingURL=collectUnusedVariables.js.map