github.com/vanstinator/golangci-lint@v0.0.0-20240223191551-cc572f00d9d1/pkg/printers/teamcity.go (about) 1 package printers 2 3 import ( 4 "fmt" 5 "io" 6 "strings" 7 "unicode/utf8" 8 9 "github.com/vanstinator/golangci-lint/pkg/result" 10 ) 11 12 // Field limits. 13 const ( 14 smallLimit = 255 15 largeLimit = 4000 16 ) 17 18 // TeamCity printer for TeamCity format. 19 type TeamCity struct { 20 w io.Writer 21 escaper *strings.Replacer 22 } 23 24 // NewTeamCity output format outputs issues according to TeamCity service message format. 25 func NewTeamCity(w io.Writer) *TeamCity { 26 return &TeamCity{ 27 w: w, 28 // https://www.jetbrains.com/help/teamcity/service-messages.html#Escaped+Values 29 escaper: strings.NewReplacer( 30 "'", "|'", 31 "\n", "|n", 32 "\r", "|r", 33 "|", "||", 34 "[", "|[", 35 "]", "|]", 36 ), 37 } 38 } 39 40 func (p *TeamCity) Print(issues []result.Issue) error { 41 uniqLinters := map[string]struct{}{} 42 43 for i := range issues { 44 issue := issues[i] 45 46 _, ok := uniqLinters[issue.FromLinter] 47 if !ok { 48 inspectionType := InspectionType{ 49 id: issue.FromLinter, 50 name: issue.FromLinter, 51 description: issue.FromLinter, 52 category: "Golangci-lint reports", 53 } 54 55 _, err := inspectionType.Print(p.w, p.escaper) 56 if err != nil { 57 return err 58 } 59 60 uniqLinters[issue.FromLinter] = struct{}{} 61 } 62 63 instance := InspectionInstance{ 64 typeID: issue.FromLinter, 65 message: issue.Text, 66 file: issue.FilePath(), 67 line: issue.Line(), 68 severity: issue.Severity, 69 } 70 71 _, err := instance.Print(p.w, p.escaper) 72 if err != nil { 73 return err 74 } 75 } 76 77 return nil 78 } 79 80 // InspectionType is the unique description of the conducted inspection. Each specific warning or 81 // an error in code (inspection instance) has an inspection type. 82 // https://www.jetbrains.com/help/teamcity/service-messages.html#Inspection+Type 83 type InspectionType struct { 84 id string // (mandatory) limited by 255 characters. 85 name string // (mandatory) limited by 255 characters. 86 description string // (mandatory) limited by 255 characters. 87 category string // (mandatory) limited by 4000 characters. 88 } 89 90 func (i InspectionType) Print(w io.Writer, escaper *strings.Replacer) (int, error) { 91 return fmt.Fprintf(w, "##teamcity[InspectionType id='%s' name='%s' description='%s' category='%s']\n", 92 limit(i.id, smallLimit), limit(i.name, smallLimit), limit(escaper.Replace(i.description), largeLimit), limit(i.category, smallLimit)) 93 } 94 95 // InspectionInstance reports a specific defect, warning, error message. 96 // Includes location, description, and various optional and custom attributes. 97 // https://www.jetbrains.com/help/teamcity/service-messages.html#Inspection+Instance 98 type InspectionInstance struct { 99 typeID string // (mandatory) limited by 255 characters. 100 message string // (optional) limited by 4000 characters. 101 file string // (mandatory) file path limited by 4000 characters. 102 line int // (optional) line of the file. 103 severity string // (optional) any linter severity. 104 } 105 106 func (i InspectionInstance) Print(w io.Writer, replacer *strings.Replacer) (int, error) { 107 return fmt.Fprintf(w, "##teamcity[inspection typeId='%s' message='%s' file='%s' line='%d' SEVERITY='%s']\n", 108 limit(i.typeID, smallLimit), 109 limit(replacer.Replace(i.message), largeLimit), 110 limit(i.file, largeLimit), 111 i.line, strings.ToUpper(i.severity)) 112 } 113 114 func limit(s string, max int) string { 115 var size, count int 116 for i := 0; i < max && count < len(s); i++ { 117 _, size = utf8.DecodeRuneInString(s[count:]) 118 count += size 119 } 120 121 return s[:count] 122 }