gitee.com/wgliang/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/copycheck/output/text.go (about) 1 package output 2 3 import ( 4 "fmt" 5 "io" 6 "log" 7 "sort" 8 9 "github.com/360EntSecGroup-Skylar/goreporter/linters/copycheck/syntax" 10 ) 11 12 type FileReader interface { 13 ReadFile(node *syntax.Node) ([]byte, error) 14 } 15 16 type Printer interface { 17 Print(dups [][]*syntax.Node) 18 SPrint(dups [][]*syntax.Node) []string 19 Finish() 20 } 21 22 type TextPrinter struct { 23 writer io.Writer 24 freader FileReader 25 cnt int 26 } 27 28 func NewTextPrinter(w io.Writer, fr FileReader) *TextPrinter { 29 return &TextPrinter{ 30 writer: w, 31 freader: fr, 32 } 33 } 34 35 func (p *TextPrinter) Print(dups [][]*syntax.Node) { 36 p.cnt++ 37 fmt.Fprintf(p.writer, "found %d clones:\n", len(dups)) 38 clones := p.prepareClonesInfo(dups) 39 sort.Sort(byNameAndLine(clones)) 40 for _, cl := range clones { 41 fmt.Fprintf(p.writer, " %s:%d,%d\n", cl.filename, cl.lineStart, cl.lineEnd) 42 } 43 } 44 45 func (p *TextPrinter) SPrint(dups [][]*syntax.Node) (copys []string) { 46 p.cnt++ 47 clones := p.prepareClonesInfo(dups) 48 sort.Sort(byNameAndLine(clones)) 49 for _, cl := range clones { 50 copys = append(copys, fmt.Sprintf("%s:%d,%d\n", cl.filename, cl.lineStart, cl.lineEnd)) 51 } 52 return copys 53 } 54 55 func (p *TextPrinter) prepareClonesInfo(dups [][]*syntax.Node) []clone { 56 clones := make([]clone, len(dups)) 57 for i, dup := range dups { 58 cnt := len(dup) 59 if cnt == 0 { 60 log.Fatal("zero length dup") 61 } 62 nstart := dup[0] 63 nend := dup[cnt-1] 64 65 file, err := p.freader.ReadFile(nstart) 66 if err != nil { 67 log.Fatal(err) 68 } 69 70 cl := clone{filename: nstart.Filename} 71 cl.lineStart, cl.lineEnd = blockLines(file, nstart.Pos, nend.End) 72 clones[i] = cl 73 } 74 return clones 75 } 76 77 func (p *TextPrinter) Finish() { 78 fmt.Fprintf(p.writer, "\nFound total %d clone groups.\n", p.cnt) 79 } 80 81 func blockLines(file []byte, from, to int) (int, int) { 82 line := 1 83 lineStart, lineEnd := 0, 0 84 for offset, b := range file { 85 if b == '\n' { 86 line++ 87 } 88 if offset == from { 89 lineStart = line 90 } 91 if offset == to-1 { 92 lineEnd = line 93 break 94 } 95 } 96 return lineStart, lineEnd 97 } 98 99 type clone struct { 100 filename string 101 lineStart int 102 lineEnd int 103 fragment []byte 104 } 105 106 type byNameAndLine []clone 107 108 func (c byNameAndLine) Len() int { return len(c) } 109 110 func (c byNameAndLine) Swap(i, j int) { c[i], c[j] = c[j], c[i] } 111 112 func (c byNameAndLine) Less(i, j int) bool { 113 if c[i].filename == c[j].filename { 114 return c[i].lineStart < c[j].lineStart 115 } 116 return c[i].filename < c[j].filename 117 }