Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect unused function #6

Merged
merged 2 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions internal/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ func (e *Engine) Run(filename string) ([]Issue, error) {
}
filtered = append(filtered, unnecessaryElseIssues...)

unusedFunc, err := e.detectUnusedFunctions(tempFile)
if err != nil {
return nil, fmt.Errorf("error detecting unused functions: %w", err)
}
filtered = append(filtered, unusedFunc...)

// map issues back to .gno file if necessary
if strings.HasSuffix(filename, ".gno") {
for i := range filtered {
Expand Down
102 changes: 0 additions & 102 deletions internal/lint_test.go

This file was deleted.

49 changes: 49 additions & 0 deletions internal/rule_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"go/token"
)

// detectUnnecessaryElse detects unnecessary else blocks.
// This rule considers an else block unnecessary if the if block ends with a return statement.
// In such cases, the else block can be removed and the code can be flattened to improve readability.
func (e *Engine) detectUnnecessaryElse(filename string) ([]Issue, error) {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
Expand Down Expand Up @@ -42,3 +45,49 @@ func (e *Engine) detectUnnecessaryElse(filename string) ([]Issue, error) {

return issues, nil
}

// detectUnusedFunctions detects functions that are declared but never used.
// This rule reports all unused functions except for the following cases:
// 1. The main function: It's considered "used" as it's the entry point of the program.
// 2. The init function: It's used for package initialization and runs without explicit calls.
// 3. Exported functions: Functions starting with a capital letter are excluded as they might be used in other packages.
//
// This rule helps in code cleanup and improves maintainability.
func (e *Engine) detectUnusedFunctions(filename string) ([]Issue, error) {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
if err != nil {
return nil, err
}

declaredFuncs := make(map[string]*ast.FuncDecl)
calledFuncs := make(map[string]bool)

ast.Inspect(node, func(n ast.Node) bool {
switch x := n.(type) {
case *ast.FuncDecl:
declaredFuncs[x.Name.Name] = x
case *ast.CallExpr:
if ident, ok := x.Fun.(*ast.Ident); ok {
calledFuncs[ident.Name] = true
}
}
return true
})

var issues []Issue
for funcName, funcDecl := range declaredFuncs {
if !calledFuncs[funcName] && funcName != "main" && funcName != "init" && !ast.IsExported(funcName) {
issue := Issue{
Rule: "unused-function",
Filename: filename,
Start: fset.Position(funcDecl.Pos()),
End: fset.Position(funcDecl.End()),
Message: "function " + funcName + " is declared but not used",
}
issues = append(issues, issue)
}
}

return issues, nil
}
105 changes: 105 additions & 0 deletions internal/rule_set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,108 @@ func example2() int {
})
}
}

func TestDetectUnusedFunctions(t *testing.T) {
tests := []struct {
name string
code string
expected int
}{
{
name: "No unused functions",
code: `
package main

func main() {
helper()
}

func helper() {
println("do something")
}`,
expected: 0,
},
{
name: "One unused function",
code: `
package main

func main() {
println("1")
}

func unused() {
println("do something")
}`,
expected: 1,
},
{
name: "Multiple unused functions",
code: `
package main

func main() {
used()
}

func used() {
// this function is called
}

func unused1() {
// this function is never called
}

func unused2() {
// this function is also never called
}`,
expected: 2,
},
// {
// name: "Unused method",
// code: `
// package main

// type MyStruct struct{}

// func (m MyStruct) Used() {
// // this method is used
// }

// func (m MyStruct) Unused() {
// // this method is never used
// }

// func main() {
// m := MyStruct{}
// m.Used()
// }`,
// expected: 1,
// },
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "lint-test")
require.NoError(t, err)
defer os.RemoveAll(tmpDir)

tmpfile := filepath.Join(tmpDir, "test.go")
err = os.WriteFile(tmpfile, []byte(tt.code), 0o644)
require.NoError(t, err)

engine := &Engine{}
issues, err := engine.detectUnusedFunctions(tmpfile)
require.NoError(t, err)

assert.Equal(t, tt.expected, len(issues), "Number of detected unused functions doesn't match expected")

if len(issues) > 0 {
for _, issue := range issues {
assert.Equal(t, "unused-function", issue.Rule)
assert.Contains(t, issue.Message, "function", "is declared but not used")
}
}
})
}
}
9 changes: 6 additions & 3 deletions testdata/main.gno
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package main

import "fmt"
import "strings"

func foo(x bool) int {
func foo(x, y bool) int {
if x {
return 1
} else {
return 2
if y {
return 2
}
return 3
}
}

Expand Down
Loading