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)
 | 
						|
}
 |