github.com/cheshirekow/buildtools@v0.0.0-20200224190056-5d637702fe81/buildifier/utils/diagnostics.go (about) 1 package utils 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "github.com/cheshirekow/buildtools/build" 7 "github.com/cheshirekow/buildtools/warn" 8 "sort" 9 "strings" 10 ) 11 12 // Diagnostics contains diagnostic information returned by formatter and linter 13 type Diagnostics struct { 14 Success bool `json:"success"` // overall success (whether all files are formatted properly and have no warnings) 15 Files []*FileDiagnostics `json:"files"` // diagnostics per file 16 } 17 18 // Format formats a Diagnostics object either as plain text or as json 19 func (d *Diagnostics) Format(format string, verbose bool) string { 20 switch format { 21 case "text", "": 22 var output strings.Builder 23 for _, f := range d.Files { 24 for _, w := range f.Warnings { 25 formatString := "%s:%d: %s: %s (%s)\n" 26 if !w.Actionable { 27 formatString = "%s:%d: %s: %s [%s]\n" 28 } 29 output.WriteString(fmt.Sprintf(formatString, 30 f.Filename, 31 w.Start.Line, 32 w.Category, 33 w.Message, 34 w.URL)) 35 } 36 if !f.Formatted { 37 rewrites := []string{} 38 for category, count := range f.Rewrites { 39 if count > 0 { 40 rewrites = append(rewrites, category) 41 } 42 } 43 log := "" 44 if len(rewrites) > 0 { 45 sort.Strings(rewrites) 46 log = " " + strings.Join(rewrites, " ") 47 } 48 output.WriteString(fmt.Sprintf("%s # reformat%s\n", f.Filename, log)) 49 } 50 } 51 return output.String() 52 case "json": 53 var result []byte 54 if verbose { 55 result, _ = json.MarshalIndent(*d, "", " ") 56 } else { 57 result, _ = json.Marshal(*d) 58 } 59 return string(result) + "\n" 60 } 61 return "" 62 } 63 64 // FileDiagnostics contains diagnostics information for a file 65 type FileDiagnostics struct { 66 Filename string `json:"filename"` 67 Formatted bool `json:"formatted"` 68 Valid bool `json:"valid"` 69 Warnings []*warning `json:"warnings"` 70 Rewrites map[string]int `json:"rewrites,omitempty"` 71 } 72 73 // SetRewrites adds information about rewrites to the diagnostics 74 func (fd *FileDiagnostics) SetRewrites(categories map[string]int) { 75 for category, count := range categories { 76 if count > 0 { 77 fd.Rewrites[category] = count 78 } 79 } 80 } 81 82 type warning struct { 83 Start position `json:"start"` 84 End position `json:"end"` 85 Category string `json:"category"` 86 Actionable bool `json:"actionable"` 87 Message string `json:"message"` 88 URL string `json:"url"` 89 } 90 91 type position struct { 92 Line int `json:"line"` 93 Column int `json:"column"` 94 } 95 96 // NewDiagnostics returns a new Diagnostics object 97 func NewDiagnostics(fileDiagnostics ...*FileDiagnostics) *Diagnostics { 98 diagnostics := &Diagnostics{ 99 Success: true, 100 Files: fileDiagnostics, 101 } 102 for _, file := range diagnostics.Files { 103 if !file.Formatted || len(file.Warnings) > 0 { 104 diagnostics.Success = false 105 break 106 } 107 } 108 return diagnostics 109 } 110 111 // NewFileDiagnostics returns a new FileDiagnostics object 112 func NewFileDiagnostics(filename string, warnings []*warn.Finding) *FileDiagnostics { 113 fileDiagnostics := FileDiagnostics{ 114 Filename: filename, 115 Formatted: true, 116 Valid: true, 117 Warnings: []*warning{}, 118 Rewrites: map[string]int{}, 119 } 120 121 for _, w := range warnings { 122 fileDiagnostics.Warnings = append(fileDiagnostics.Warnings, &warning{ 123 Start: makePosition(w.Start), 124 End: makePosition(w.End), 125 Category: w.Category, 126 Actionable: w.Actionable, 127 Message: w.Message, 128 URL: w.URL, 129 }) 130 } 131 132 return &fileDiagnostics 133 } 134 135 // InvalidFileDiagnostics returns a new FileDiagnostics object for an invalid file 136 func InvalidFileDiagnostics(filename string) *FileDiagnostics { 137 fileDiagnostics := &FileDiagnostics{ 138 Filename: filename, 139 Formatted: false, 140 Valid: false, 141 Warnings: []*warning{}, 142 Rewrites: map[string]int{}, 143 } 144 if filename == "" { 145 fileDiagnostics.Filename = "<stdin>" 146 } 147 return fileDiagnostics 148 } 149 150 func makePosition(p build.Position) position { 151 return position{ 152 Line: p.Line, 153 Column: p.LineRune, 154 } 155 }