@slippy-lint/slippy
Version:
A simple but powerful linter for Solidity
264 lines • 10.6 kB
JavaScript
import { assertNonterminalNode, NonterminalKind, TerminalKind, } from "@nomicfoundation/slang/cst";
import * as z from "zod";
import { ImportDirective, PragmaDirective, SourceUnitMember, } from "@nomicfoundation/slang/ast";
const Schema = z
.object({
ignorePattern: z.string().optional(),
})
.default({});
export const NoUnusedVars = {
name: "no-unused-vars",
recommended: true,
parseConfig: (config) => Schema.parse(config),
create: function (config) {
return new NoUnusedVarsRule(this.name, config);
},
};
class NoUnusedVarsRule {
constructor(name, config) {
this.name = name;
this.config = config;
this.ignorePattern =
config.ignorePattern !== undefined
? new RegExp(config.ignorePattern)
: undefined;
}
run({ file, unit }) {
const diagnostics = [];
const inheritDocComments = findInheritDocComments(file);
const unusedVars = findUnusedVarsInFile(unit, file, inheritDocComments);
for (const unusedVar of unusedVars) {
if (unusedVar.definition.definiensLocation.isUserFileLocation()) {
if (this.ignorePattern !== undefined &&
this.ignorePattern.test(unusedVar.name)) {
continue;
}
diagnostics.push({
rule: this.name,
sourceId: unusedVar.definition.definiensLocation.fileId,
message: `'${unusedVar.name}' is defined but never used`,
line: unusedVar.textRange.start.line,
column: unusedVar.textRange.start.column,
});
}
}
return diagnostics;
}
}
function findUnusedVarsInFile(unit, file, inheritDocComments) {
const unusedDefinitions = [];
const cursor = file.createTreeCursor();
const unusedPrivateVariables = findUnusedPrivateVariables(unit, cursor.spawn());
unusedDefinitions.push(...unusedPrivateVariables);
const unusedPrivateFunctions = findUnusedPrivateFunctions(unit, cursor.spawn());
unusedDefinitions.push(...unusedPrivateFunctions);
while (cursor.goToNextNonterminalWithKind(NonterminalKind.FunctionDefinition)) {
const unusedDefinitionsInFunction = findUnusedDefinitionsInFunction(unit, cursor.spawn());
unusedDefinitions.push(...unusedDefinitionsInFunction);
}
const unusedImportedNames = findUnusedImportedNames(unit, cursor.spawn(), inheritDocComments);
unusedDefinitions.push(...unusedImportedNames);
return unusedDefinitions;
}
function findUnusedDefinitionsInFunction(unit, functionDefinitionCursor) {
const definitions = [];
assertNonterminalNode(functionDefinitionCursor.node, NonterminalKind.FunctionDefinition);
const hasEmptyBlock = !functionDefinitionCursor
.spawn()
.goToNextNonterminalWithKind(NonterminalKind.Statement);
if (!hasEmptyBlock) {
let parametersCursor = functionDefinitionCursor.spawn();
parametersCursor.goToNextNonterminalWithKind(NonterminalKind.ParametersDeclaration);
parametersCursor = parametersCursor.spawn();
while (parametersCursor.goToNextTerminalWithKind(TerminalKind.Identifier)) {
const definition = unit.bindingGraph.definitionAt(parametersCursor);
if (definition === undefined) {
continue;
}
if (definition.references().length === 0) {
definitions.push({
name: parametersCursor.node.unparse(),
definition,
textRange: parametersCursor.textRange,
});
}
}
}
const declarationsCursor = functionDefinitionCursor.spawn();
while (declarationsCursor.goToNextNonterminalWithKind(NonterminalKind.VariableDeclarationStatement)) {
// ignore the type of the variable
declarationsCursor.goToNextNonterminalWithKind(NonterminalKind.VariableDeclarationType);
declarationsCursor.goToNextSibling();
if (!declarationsCursor.goToNextTerminalWithKind(TerminalKind.Identifier)) {
continue;
}
const definition = unit.bindingGraph.definitionAt(declarationsCursor);
if (definition === undefined) {
continue;
}
if (definition.references().length === 0) {
definitions.push({
name: declarationsCursor.node.unparse(),
textRange: declarationsCursor.textRange,
definition,
});
}
}
return definitions;
}
function findUnusedPrivateVariables(unit, cursor) {
const definitions = [];
while (cursor.goToNextNonterminalWithKind(NonterminalKind.StateVariableDefinition)) {
const variableDefinitionCursor = cursor.spawn();
const isPrivate = variableDefinitionCursor
.spawn()
.goToNextTerminalWithKind(TerminalKind.PrivateKeyword);
if (!isPrivate) {
continue;
}
// ignore the type of the variable
variableDefinitionCursor.goToNextNonterminalWithKind(NonterminalKind.TypeName);
variableDefinitionCursor.goToNextSibling();
if (!variableDefinitionCursor.goToNextTerminalWithKind(TerminalKind.Identifier)) {
continue;
}
const definition = unit.bindingGraph.definitionAt(variableDefinitionCursor);
if (definition === undefined) {
continue;
}
if (definition.references().length === 0) {
definitions.push({
name: variableDefinitionCursor.node.unparse(),
textRange: variableDefinitionCursor.textRange,
definition,
});
}
}
return definitions;
}
function findUnusedPrivateFunctions(unit, cursor) {
const definitions = [];
while (cursor.goToNextNonterminalWithKind(NonterminalKind.FunctionDefinition)) {
const isPrivate = checkIsPrivateFunction(cursor.spawn());
if (!isPrivate) {
continue;
}
const definition = getFunctionNameDefinition(unit, cursor.spawn());
if (definition === undefined) {
continue;
}
if (!definition.nameLocation.isUserFileLocation()) {
continue;
}
const definitionCursor = definition.nameLocation.cursor;
if (definition.references().length === 0) {
definitions.push({
name: definitionCursor.node.unparse(),
textRange: definitionCursor.textRange,
definition,
});
}
}
return definitions;
}
function findUnusedImportedNames(unit, cursor, inheritDocComments) {
const definitions = [];
const fileHasOnlyImports = checkFileHasOnlyImports(cursor.spawn());
if (fileHasOnlyImports) {
return [];
}
while (cursor.goToNextNonterminalWithKinds([
NonterminalKind.PathImport,
NonterminalKind.NamedImport,
NonterminalKind.ImportDeconstructionSymbol,
])) {
let variableDefinitionCursor;
if (cursor.node.kind === NonterminalKind.PathImport ||
cursor.node.kind === NonterminalKind.NamedImport) {
if (!cursor.goToNextNonterminalWithKind(NonterminalKind.ImportAlias)) {
continue;
}
if (!cursor.goToNextTerminalWithKind(TerminalKind.Identifier)) {
continue;
}
variableDefinitionCursor = cursor.spawn();
}
else if (cursor.node.kind === NonterminalKind.ImportDeconstructionSymbol) {
const importAliasCursor = cursor.spawn();
const hasAlias = importAliasCursor.goToNextNonterminalWithKind(NonterminalKind.ImportAlias);
if (hasAlias) {
if (!importAliasCursor.goToNextTerminalWithKind(TerminalKind.Identifier)) {
continue;
}
variableDefinitionCursor = importAliasCursor;
}
else {
if (!cursor.goToNextTerminalWithKind(TerminalKind.Identifier)) {
continue;
}
variableDefinitionCursor = cursor;
}
}
else {
continue;
}
const definition = unit.bindingGraph.definitionAt(variableDefinitionCursor);
if (definition === undefined) {
continue;
}
if (definition.references().length === 0) {
const name = variableDefinitionCursor.node.unparse();
if (!inheritDocComments.includes(name)) {
definitions.push({
name,
textRange: variableDefinitionCursor.textRange,
definition,
});
}
}
}
return definitions;
}
function checkIsPrivateFunction(cursor) {
if (!cursor.goToNextNonterminalWithKind(NonterminalKind.FunctionAttributes))
return false;
cursor = cursor.spawn();
return cursor.goToNextTerminalWithKind(TerminalKind.PrivateKeyword);
}
function getFunctionNameDefinition(unit, functionDefinitionCursor) {
if (!functionDefinitionCursor.goToNextNonterminalWithKind(NonterminalKind.FunctionName)) {
return undefined;
}
if (!functionDefinitionCursor.goToNextTerminalWithKind(TerminalKind.Identifier)) {
return undefined;
}
return unit.bindingGraph.definitionAt(functionDefinitionCursor);
}
function findInheritDocComments(file) {
const diagnostics = [];
const cursor = file.createTreeCursor();
while (cursor.goToNextTerminalWithKinds([
TerminalKind.SingleLineNatSpecComment,
TerminalKind.MultiLineNatSpecComment,
])) {
const commentText = cursor.node.unparse();
const inheritDocMatch = commentText.match(/@inheritdoc\s+([$\w]+)/);
if (inheritDocMatch) {
diagnostics.push(inheritDocMatch[1]);
}
}
return diagnostics;
}
function checkFileHasOnlyImports(cursor) {
while (cursor.goToNextNonterminalWithKind(NonterminalKind.SourceUnitMember)) {
assertNonterminalNode(cursor.node);
const sourceUnitMember = new SourceUnitMember(cursor.node);
const isPragma = sourceUnitMember.variant instanceof PragmaDirective;
const isImport = sourceUnitMember.variant instanceof ImportDirective;
if (!isPragma && !isImport) {
return false;
}
}
return true;
}
//# sourceMappingURL=no-unused-vars.js.map