Skip to content

Commit

Permalink
feat: remove examples/foo.go and add fixer.go for enhanced functionality
Browse files Browse the repository at this point in the history
- **Why remove examples/foo.go?** The functions within were likely deemed unnecessary or have been refactored into a more comprehensive solution.

- **Why add fixer.go?** Introduces a utility for modifying Go source files, possibly to automate code refactoring or corrections, enhancing the development workflow.

refactor(main.go): streamline flag parsing and function removal logic
feat(main.go): add JSON schema support for dead code analysis
feat(schema.go): introduce JSON schema for deadcode output analysis
feat(testdata): add test files and examples for dead code analysis

feat(testdata/stringer.go): add new myString struct implementing fmt.Stringer

This commit introduces a new file `stringer.go` within the `testdata` directory, which defines a `myString` struct. This struct implements the `fmt.Stringer` interface, ensuring that instances of `myString` can be easily converted to a string using the `String()` method. This addition aims to enhance the codebase by providing a clear and idiomatic way to convert `myString` instances to strings, following Go's convention of using the `Stringer` interface for string representations of types.
  • Loading branch information
yyoshiki41 committed May 5, 2024
1 parent d484a9b commit 9ea8391
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 64 deletions.
5 changes: 0 additions & 5 deletions examples/foo.go

This file was deleted.

84 changes: 84 additions & 0 deletions fixer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package main

import (
"bufio"
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"os"
)

func fix(filename, functionName string) error {
start, end, err := lookup(filename, functionName)
if err != nil {
return err
}

if err := rewrite(filename, start, end); err != nil {
return err
}
return nil
}

func lookup(filename, functionName string) (int, int, error) {
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
if err != nil {
return 0, 0, err
}

var start, end int
ast.Inspect(node, func(n ast.Node) bool {
funcDecl, ok := n.(*ast.FuncDecl)
if ok && funcDecl.Name.Name == functionName {
start = fset.Position(funcDecl.Pos()).Line
end = fset.Position(funcDecl.End()).Line
return false
}
return true
})
if start == 0 || end == 0 {
return 0, 0, fmt.Errorf("func %s not found in %s", functionName, filename)
}
return start, end, nil
}

func rewrite(filename string, start, end int) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()

buf := new(bytes.Buffer)
scanner := bufio.NewScanner(file)
for l := 1; scanner.Scan(); {
if l < start || l > end {
if _, err := buf.Write(append(scanner.Bytes(), '\n')); err != nil {
return err
}
}
l++
}
if err := scanner.Err(); err != nil {
return err
}

// run gofmt
b, err := format.Source(buf.Bytes())
if err != nil {
return err
}
// write to file
info, err := file.Stat()
if err != nil {
return err
}
if err := os.WriteFile(filename, b, info.Mode()); err != nil {
return err
}
return nil
}
96 changes: 37 additions & 59 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,81 +1,59 @@
package main

import (
"bufio"
"bytes"
"encoding/json"
"flag"
"go/ast"
"go/format"
"go/parser"
"go/token"
"io"
"log"
"os"
)

// flags
var (
fileName, functionName string
jsonFlag = flag.String("json", "", "JSON file generated by deadcode")
fileFlag = flag.String("file", "", "File to remove function from")
functionFlag = flag.String("function", "", "Function to remove")
)

func main() {
flag.StringVar(&fileName, "f", "", "File to remove function from (shorthand)")
flag.StringVar(&fileName, "file", "", "File to remove function from")
flag.StringVar(&functionName, "fn", "", "File to remove function from (shorthand)")
flag.StringVar(&functionName, "function", "", "Function to remove")
flag.Parse()

if fileName == "" || functionName == "" {
flag.Usage()
os.Exit(1)
}

fset := token.NewFileSet()
node, err := parser.ParseFile(fset, fileName, nil, parser.ParseComments)
if err != nil {
log.Fatalf("Failed to parse file: %v", err)
}

var start, end int
ast.Inspect(node, func(n ast.Node) bool {
fn, ok := n.(*ast.FuncDecl)
if ok && fn.Name.Name == functionName {
start = fset.Position(fn.Pos()).Line
end = fset.Position(fn.End()).Line
return false
// run specific fixer
if f, fn := *fileFlag, *functionFlag; f != "" && fn != "" {
if err := fix(f, fn); err != nil {
log.Fatal(err)
}
return true
})

file, err := os.Open(fileName)
if err != nil {
log.Fatalf("Failed to open file: %v", err)
}
defer file.Close()

buf := new(bytes.Buffer)
scanner := bufio.NewScanner(file)
for l := 1; scanner.Scan(); {
if l < start || l > end {
if _, err := buf.Write(append(scanner.Bytes(), '\n')); err != nil {
log.Fatalf("Failed to write to buffer: %v", err)
}
return
}
// run fixer by reading JSON
var r io.Reader
switch v := *jsonFlag; v {
case "", "-":
r = os.Stdin
default:
f, err := os.Open(v)
if err != nil {
log.Fatalf("failed to open file %s: %v", v, err)
}
l++
defer f.Close()
r = f
}
if err := scanner.Err(); err != nil {
log.Fatalf("Failed to read file: %v", err)
if err := run(r); err != nil {
log.Fatal(err)
}
}

// run gofmt
b, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalf("Failed to format source: %v", err)
func run(r io.Reader) error {
packages := []Package{}
if err := json.NewDecoder(r).Decode(&packages); err != nil {
return err
}
// write to file
info, err := file.Stat()
if err != nil {
log.Fatalf("Failed to get file info: %v", err)
}
if err := os.WriteFile(fileName, b, info.Mode()); err != nil {
log.Fatalf("Failed to write to file: %v", err)
for _, p := range packages {
for _, f := range p.Funcs {
if err := fix(f.Position.File, f.Name); err != nil {
return err
}
}
}
return nil
}
28 changes: 28 additions & 0 deletions schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

// This file contains the JSON schema for the deadcode output.
// https://pkg.go.dev/golang.org/x/tools@v0.20.0/cmd/deadcode#hdr-JSON_schema

type Package struct {
Name string // declared name
Path string // full import path
Funcs []Function // list of dead functions within it
}

type Function struct {
Name string // name (sans package qualifier)
Position Position // file/line/column of function declaration
Generated bool // function is declared in a generated .go file
}

type Edge struct {
Initial string // initial entrypoint (main or init); first edge only
Kind string // = static | dynamic
Position Position // file/line/column of call site
Callee string // target of the call
}

type Position struct {
File string // name of file
Line, Col int // line and byte index, both 1-based
}
10 changes: 10 additions & 0 deletions testdata/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

func main() {
Reachable()
}

func init() {
s := myString{Value: "hello"}
s.Reachable()
}
7 changes: 7 additions & 0 deletions testdata/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "testing"

func TestReachableByTest(t *testing.T) {
ReachableByTest()
}
15 changes: 15 additions & 0 deletions testdata/reachable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

import "fmt"

func Reachable() {
fmt.Println("reachable")
}

func Unreachable() {
fmt.Println("unreachable")
}

func ReachableByTest() {
fmt.Println("reachableByTest")
}
17 changes: 17 additions & 0 deletions testdata/stringer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import "fmt"

var _ fmt.Stringer = myString{}

type myString struct {
Value string
}

func (s myString) String() string {
return s.Value
}

func (s myString) Reachable() {
return
}

0 comments on commit 9ea8391

Please sign in to comment.