Skip to content

Commit

Permalink
Detect unused function (#6)
Browse files Browse the repository at this point in the history
* detect unused function

* fix
  • Loading branch information
notJoon authored Jul 18, 2024
1 parent 06ba205 commit 45363f2
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 3 deletions.
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
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

0 comments on commit 45363f2

Please sign in to comment.