UNPKG

@slippy-lint/slippy

Version:

A simple but powerful linter for Solidity

264 lines 10.6 kB
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