Marvin Preuss
d095180eb4
All checks were successful
continuous-integration/drone/push Build is passing
149 lines
3.3 KiB
Go
149 lines
3.3 KiB
Go
package dupl
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
|
|
"github.com/golangci/dupl/job"
|
|
"github.com/golangci/dupl/printer"
|
|
"github.com/golangci/dupl/syntax"
|
|
)
|
|
|
|
const defaultThreshold = 15
|
|
|
|
var (
|
|
paths = []string{"."}
|
|
vendor = flag.Bool("dupl.vendor", false, "")
|
|
verbose = flag.Bool("dupl.verbose", false, "")
|
|
files = flag.Bool("dupl.files", false, "")
|
|
|
|
html = flag.Bool("dupl.html", false, "")
|
|
plumbing = flag.Bool("dupl.plumbing", false, "")
|
|
)
|
|
|
|
const (
|
|
vendorDirPrefix = "vendor" + string(filepath.Separator)
|
|
vendorDirInPath = string(filepath.Separator) + vendorDirPrefix
|
|
)
|
|
|
|
func init() {
|
|
flag.BoolVar(verbose, "dupl.v", false, "alias for -verbose")
|
|
}
|
|
|
|
func Run(files []string, threshold int) ([]printer.Issue, error) {
|
|
fchan := make(chan string, 1024)
|
|
go func() {
|
|
for _, f := range files {
|
|
fchan <- f
|
|
}
|
|
close(fchan)
|
|
}()
|
|
schan := job.Parse(fchan)
|
|
t, data, done := job.BuildTree(schan)
|
|
<-done
|
|
|
|
// finish stream
|
|
t.Update(&syntax.Node{Type: -1})
|
|
|
|
mchan := t.FindDuplOver(threshold)
|
|
duplChan := make(chan syntax.Match)
|
|
go func() {
|
|
for m := range mchan {
|
|
match := syntax.FindSyntaxUnits(*data, m, threshold)
|
|
if len(match.Frags) > 0 {
|
|
duplChan <- match
|
|
}
|
|
}
|
|
close(duplChan)
|
|
}()
|
|
|
|
return makeIssues(duplChan)
|
|
}
|
|
|
|
func makeIssues(duplChan <-chan syntax.Match) ([]printer.Issue, error) {
|
|
groups := make(map[string][][]*syntax.Node)
|
|
for dupl := range duplChan {
|
|
groups[dupl.Hash] = append(groups[dupl.Hash], dupl.Frags...)
|
|
}
|
|
keys := make([]string, 0, len(groups))
|
|
for k := range groups {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
p := printer.NewPlumbing(ioutil.ReadFile)
|
|
|
|
var issues []printer.Issue
|
|
for _, k := range keys {
|
|
uniq := unique(groups[k])
|
|
if len(uniq) > 1 {
|
|
i, err := p.MakeIssues(uniq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
issues = append(issues, i...)
|
|
}
|
|
}
|
|
|
|
return issues, nil
|
|
}
|
|
|
|
func unique(group [][]*syntax.Node) [][]*syntax.Node {
|
|
fileMap := make(map[string]map[int]struct{})
|
|
|
|
var newGroup [][]*syntax.Node
|
|
for _, seq := range group {
|
|
node := seq[0]
|
|
file, ok := fileMap[node.Filename]
|
|
if !ok {
|
|
file = make(map[int]struct{})
|
|
fileMap[node.Filename] = file
|
|
}
|
|
if _, ok := file[node.Pos]; !ok {
|
|
file[node.Pos] = struct{}{}
|
|
newGroup = append(newGroup, seq)
|
|
}
|
|
}
|
|
return newGroup
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Fprintln(os.Stderr, `Usage: dupl [flags] [paths]
|
|
|
|
Paths:
|
|
If the given path is a file, dupl will use it regardless of
|
|
the file extension. If it is a directory, it will recursively
|
|
search for *.go files in that directory.
|
|
|
|
If no path is given, dupl will recursively search for *.go
|
|
files in the current directory.
|
|
|
|
Flags:
|
|
-files
|
|
read file names from stdin one at each line
|
|
-html
|
|
output the results as HTML, including duplicate code fragments
|
|
-plumbing
|
|
plumbing (easy-to-parse) output for consumption by scripts or tools
|
|
-t, -threshold size
|
|
minimum token sequence size as a clone (default 15)
|
|
-vendor
|
|
check files in vendor directory
|
|
-v, -verbose
|
|
explain what is being done
|
|
|
|
Examples:
|
|
dupl -t 100
|
|
Search clones in the current directory of size at least
|
|
100 tokens.
|
|
dupl $(find app/ -name '*_test.go')
|
|
Search for clones in tests in the app directory.
|
|
find app/ -name '*_test.go' |dupl -files
|
|
The same as above.`)
|
|
os.Exit(2)
|
|
}
|