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  }