@putout/plugin-remove-unused-variables
Version:
🐊Putout plugin adds ability to find and remove unused variables
591 lines (453 loc) • 18.2 kB
JavaScript
import {operator, types} from 'putout';
import jsx from './jsx.js';
import typescript from './typescript.js';
import {createExportNamedDeclaration} from './visitors/export-named-declaration.js';
import {
traverseObjectExpression,
processObjectPattern,
traverseArrayExpression,
traverseAssignmentExpression,
traverseTemplateLiteral,
} from './traverse.js';
const {assign} = Object;
const {isKeyword} = operator;
const {
isAssignmentPattern,
isClassDeclaration,
isIdentifier,
isSpreadElement,
isObjectPattern,
isObjectExpression,
isFunctionDeclaration,
isArrayExpression,
isRestElement,
} = types;
export default ({use, declare, addParams}) => {
const traverseObj = traverseObjectExpression(use);
const processObj = processObjectPattern({
use,
declare,
});
const traverseAssign = traverseAssignmentExpression({
use,
declare,
});
const traverseTmpl = traverseTemplateLiteral(use);
const traverseArray = traverseArrayExpression(use);
return {
'ObjectExpression'(path) {
traverseObj(path.get('properties'));
},
SequenceExpression(path) {
for (const exprPath of path.get('expressions')) {
if (exprPath.isIdentifier())
use(exprPath, exprPath.node.name);
}
},
'AwaitExpression|YieldExpression|SpreadElement'(path) {
const {argument} = path.node;
if (isIdentifier(argument))
use(path, argument.name);
},
CatchClause(path) {
const param = path.get('param');
if (param.isObjectPattern())
return processObj(param.get('properties'));
if (!param.isIdentifier())
return;
const {name} = param.node;
declare(param, name);
const binding = path.scope.getOwnBinding(name);
if (binding.referenced)
use(param, name);
},
Decorator(path) {
const expressionPath = path.get('expression');
if (!expressionPath.isIdentifier())
return;
const {name} = expressionPath.node;
use(expressionPath, name);
},
RestElement(path) {
const {argument} = path.node;
/* istanbul ignore else */
if (isIdentifier(argument))
declare(path, argument.name);
},
VariableDeclarator(path) {
const {node} = path;
const {init} = node;
const idPath = path.get('id');
const isForIn = path.parentPath.parentPath.isForInStatement();
const isTSModule = path.parentPath.parentPath.isTSModuleBlock();
/* istanbul ignore else */
if (isIdentifier(node.id)) {
if (!isKeyword(node.id.name)) {
declare(path, node.id.name);
if (isForIn || isTSModule)
use(path, node.id.name);
}
} else if (isObjectPattern(node.id)) {
idPath.traverse({
ObjectProperty(propPath) {
if (isAssignmentPattern(propPath.node.value))
traverseAssign(propPath.get('value'));
if (propPath.node.computed && isIdentifier(propPath.node.key))
use(propPath.get('key'), propPath.node.key.name);
if (!isIdentifier(propPath.node.value))
return;
const {properties} = node.id;
const isOne = properties.length === 1;
const nodePath = isOne ? path : propPath;
const {name} = propPath.node.value;
declare(nodePath, name);
if (isRestElement(propPath.parentPath.node.properties.at(-1)))
use(nodePath, name);
},
});
} else if (idPath.isArrayPattern()) {
const elements = idPath.get('elements');
for (const elPath of elements) {
if (elPath.isObjectPattern()) {
processObj(elPath.get('properties'));
continue;
}
if (elPath.isAssignmentPattern()) {
const leftPath = elPath.get('left');
const rightPath = elPath.get('right');
declare(leftPath, elPath.node.left.name);
use(rightPath, elPath.node.right.name);
assign(leftPath, {
remove: removeArrayPatternElement(elPath),
});
}
if (elPath.isIdentifier()) {
assign(elPath, {
remove: removeArrayPatternElement(elPath),
});
declare(elPath, elPath.node.name);
}
}
}
const initPath = path.get('init');
if (isIdentifier(init))
use(path, init.name);
else if (isArrayExpression(init))
traverseArray(initPath.get('elements'));
},
'ClassProperty'(path) {
const valuePath = path.get('value');
if (valuePath.isIdentifier())
use(valuePath, valuePath.node.name);
},
'ClassDeclaration|ClassExpression'(path) {
const {node} = path;
const {id, superClass} = node;
if (superClass)
use(path, superClass.name);
if (id)
declare(path, id.name);
if (id && path.isClassExpression())
use(path, id.name);
},
AssignmentExpression(path) {
const leftPath = path.get('left');
const rightPath = path.get('right');
if (leftPath.isIdentifier())
use(leftPath, leftPath.node.name);
if (rightPath.isIdentifier())
use(rightPath, rightPath.node.name);
},
'ArrayExpression'(path) {
const {elements} = path.node;
for (const el of elements) {
if (isIdentifier(el)) {
use(path, el.name);
continue;
}
if (isSpreadElement(el)) {
use(path, el.argument.name);
continue;
}
}
},
ConditionalExpression(path) {
const {
test,
consequent,
alternate,
} = path.node;
const alternatePath = path.get('alternate');
const consequentPath = path.get('consequent');
if (isIdentifier(test))
use(path, test.name);
if (isIdentifier(consequent))
use(path, consequent.name);
if (isIdentifier(alternate))
use(path, alternate.name);
if (alternatePath.isFunction() && alternate.id)
use(alternatePath, alternate.id.name);
if (consequentPath.isFunction() && consequent.id)
use(consequentPath, consequent.id.name);
},
DoWhileStatement(path) {
const testPath = path.get('test');
if (testPath.isIdentifier())
use(testPath, testPath.node.name);
},
TemplateLiteral(path) {
traverseTmpl(path, path.node.expressions);
},
LogicalExpression(path) {
const {left, right} = path.node;
if (isIdentifier(left))
use(path, left.name);
if (isIdentifier(right))
use(path, right.name);
},
MemberExpression(path) {
const {
property,
object,
computed,
} = path.node;
if (isIdentifier(object))
use(path, object.name);
if (computed && isIdentifier(property))
use(path, property.name);
},
OptionalMemberExpression(path) {
const {
object,
property,
computed,
} = path.node;
if (isIdentifier(object))
use(path, object.name);
if (computed && isIdentifier(property))
use(path, property.name);
},
NewExpression(path) {
const calleePath = path.get('callee');
const {node} = path;
if (calleePath.isIdentifier())
use(path, node.callee.name);
else if (calleePath.isFunction())
use(calleePath, calleePath.node.id.name);
const argPaths = path.get('arguments');
for (const {node} of argPaths) {
if (isIdentifier(node)) {
use(path, node.name);
continue;
}
}
},
TaggedTemplateExpression(path) {
const {tag} = path.node;
/* istanbul ignore else */
if (isIdentifier(tag))
use(path, tag.name);
},
UnaryExpression(path) {
const {argument} = path.node;
/* istanbul ignore else */
if (isIdentifier(argument))
use(path, argument.name);
},
UpdateExpression(path) {
const {argument} = path.node;
/* istanbul ignore else */
if (isIdentifier(argument))
use(path, argument.name);
},
ThrowStatement(path) {
const {argument} = path.node;
if (isIdentifier(argument))
use(path, argument.name);
},
IfStatement(path) {
const {node} = path;
const {test} = node;
if (isIdentifier(test))
return use(path, test.name);
},
ForInStatement(path) {
const {node} = path;
const {left, right} = node;
if (isIdentifier(left))
use(path, left.name);
/* istanbul ignore else */
if (isIdentifier(right))
use(path, right.name);
},
ForOfStatement(path) {
const {node} = path;
const {left, right} = node;
if (isIdentifier(right))
use(path, right.name);
if (isIdentifier(left))
use(path, left.name);
else
path.get('left').traverse({
Identifier(leftPath) {
declare(path, leftPath.node.name);
},
});
path.get('left').traverse({
Identifier(path) {
use(path, path.node.name);
},
});
},
ExpressionStatement(path) {
const {node} = path;
if (isIdentifier(node.expression))
use(path, node.expression.name);
},
SwitchStatement(path) {
const {node} = path;
if (isIdentifier(node.discriminant))
use(path, node.discriminant.name);
for (const {test} of node.cases) {
if (isIdentifier(test))
use(path, test.name);
}
},
ReturnStatement(path) {
const {node} = path;
const {argument} = node;
const argumentPath = path.get('argument');
if (argumentPath.isIdentifier())
return use(path, argument.name);
if (argumentPath.isFunction() && argument.id)
return use(argumentPath, argument.id.name);
},
ObjectMethod(path) {
const {params} = path.node;
const paramsPaths = path.get('params');
for (const paramPath of paramsPaths) {
const {node} = paramPath;
/* istanbul ignore else */
if (isIdentifier(node)) {
declare(paramPath, node.name);
continue;
}
}
addParams({
path,
params,
});
},
'CallExpression|OptionalCallExpression'(path) {
const {node} = path;
const {callee} = node;
if (isIdentifier(callee))
use(path, node.callee.name);
path.traverse({
SpreadElement(path) {
const {argument} = path.node;
if (isIdentifier(argument))
use(path, argument.name);
},
Identifier(path) {
if (node.arguments.includes(path.node))
use(path, path.node.name);
},
});
},
BinaryExpression(path) {
const {left, right} = path.node;
if (isIdentifier(left))
use(path, left.name);
if (isIdentifier(right))
use(path, right.name);
},
ImportExpression(path) {
const {source} = path.node;
const {name} = source;
if (isIdentifier(source, {name}))
use(path, name);
},
ImportDeclaration(path) {
const specifierPaths = path.get('specifiers');
for (const specPath of specifierPaths) {
const {local} = specPath.node;
/* istanbul ignore else */
if (isIdentifier(local))
declare(specPath, local.name);
}
},
ExportDefaultDeclaration(path) {
const declarationPath = path.get('declaration');
const {declaration} = path.node;
const {id} = declaration;
if (id && isFunctionDeclaration(declaration))
return use(path, declaration.id.name);
if (isIdentifier(declaration))
return use(path, declaration.name);
if (id && isClassDeclaration(declaration))
return use(path, declaration.id.name);
if (isObjectExpression(declaration))
return traverseObj(declarationPath.get('properties'));
},
ExportNamedDeclaration: createExportNamedDeclaration({
use,
}),
Function(path) {
const {node, parentPath} = path;
const {
id,
body,
params,
} = node;
const paramsPaths = path.get('params');
if (id) {
declare(path, node.id.name);
if (!parentPath.isBlock() && !parentPath.isProgram())
use(path, node.id.name);
}
const paramsCount = paramsPaths.length;
for (const [i, paramPath] of paramsPaths.entries()) {
const {node} = paramPath;
if (isIdentifier(node)) {
declare(paramPath, node.name);
if (!i && paramsCount > 1)
use(paramPath, node.name);
continue;
}
if (isAssignmentPattern(node)) {
traverseAssign(paramPath);
continue;
}
/* istanbul ignore else */
if (isObjectPattern(node)) {
processObj(paramPath.get('properties'));
continue;
}
}
// ArrowFunction only
if (isIdentifier(body))
use(path, body.name);
addParams({
path,
params,
});
},
...jsx(use),
...typescript({
use,
declare,
}),
};
};
const removeArrayPatternElement = (elPath) => {
const {remove} = elPath;
return () => {
const el = elPath.node;
const {elements} = elPath.parentPath.node;
const n = elements.length - 1;
const i = elements.indexOf(el);
if (i === n)
return remove.call(elPath);
};
};