github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/lintcmd/format.go (about)

     1  package lintcmd
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"go/token"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"text/tabwriter"
    11  
    12  	"github.com/amarpal/go-tools/analysis/lint"
    13  )
    14  
    15  func shortPath(path string) string {
    16  	cwd, err := os.Getwd()
    17  	if err != nil {
    18  		return path
    19  	}
    20  	if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
    21  		return rel
    22  	}
    23  	return path
    24  }
    25  
    26  func relativePositionString(pos token.Position) string {
    27  	s := shortPath(pos.Filename)
    28  	if pos.IsValid() {
    29  		if s != "" {
    30  			s += ":"
    31  		}
    32  		s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
    33  	}
    34  	if s == "" {
    35  		s = "-"
    36  	}
    37  	return s
    38  }
    39  
    40  type statter interface {
    41  	Stats(total, errors, warnings, ignored int)
    42  }
    43  
    44  type formatter interface {
    45  	Format(checks []*lint.Analyzer, diagnostics []diagnostic)
    46  }
    47  
    48  type textFormatter struct {
    49  	W io.Writer
    50  }
    51  
    52  func (o textFormatter) Format(_ []*lint.Analyzer, ps []diagnostic) {
    53  	for _, p := range ps {
    54  		fmt.Fprintf(o.W, "%s: %s\n", relativePositionString(p.Position), p.String())
    55  		for _, r := range p.Related {
    56  			fmt.Fprintf(o.W, "\t%s: %s\n", relativePositionString(r.Position), r.Message)
    57  		}
    58  	}
    59  }
    60  
    61  type nullFormatter struct{}
    62  
    63  func (nullFormatter) Format([]*lint.Analyzer, []diagnostic) {}
    64  
    65  type jsonFormatter struct {
    66  	W io.Writer
    67  }
    68  
    69  func (o jsonFormatter) Format(_ []*lint.Analyzer, ps []diagnostic) {
    70  	type location struct {
    71  		File   string `json:"file"`
    72  		Line   int    `json:"line"`
    73  		Column int    `json:"column"`
    74  	}
    75  	type related struct {
    76  		Location location `json:"location"`
    77  		End      location `json:"end"`
    78  		Message  string   `json:"message"`
    79  	}
    80  
    81  	enc := json.NewEncoder(o.W)
    82  	for _, p := range ps {
    83  		jp := struct {
    84  			Code     string    `json:"code"`
    85  			Severity string    `json:"severity,omitempty"`
    86  			Location location  `json:"location"`
    87  			End      location  `json:"end"`
    88  			Message  string    `json:"message"`
    89  			Related  []related `json:"related,omitempty"`
    90  		}{
    91  			Code:     p.Category,
    92  			Severity: p.Severity.String(),
    93  			Location: location{
    94  				File:   p.Position.Filename,
    95  				Line:   p.Position.Line,
    96  				Column: p.Position.Column,
    97  			},
    98  			End: location{
    99  				File:   p.End.Filename,
   100  				Line:   p.End.Line,
   101  				Column: p.End.Column,
   102  			},
   103  			Message: p.Message,
   104  		}
   105  		for _, r := range p.Related {
   106  			jp.Related = append(jp.Related, related{
   107  				Location: location{
   108  					File:   r.Position.Filename,
   109  					Line:   r.Position.Line,
   110  					Column: r.Position.Column,
   111  				},
   112  				End: location{
   113  					File:   r.End.Filename,
   114  					Line:   r.End.Line,
   115  					Column: r.End.Column,
   116  				},
   117  				Message: r.Message,
   118  			})
   119  		}
   120  		_ = enc.Encode(jp)
   121  	}
   122  }
   123  
   124  type stylishFormatter struct {
   125  	W io.Writer
   126  
   127  	prevFile string
   128  	tw       *tabwriter.Writer
   129  }
   130  
   131  func (o *stylishFormatter) Format(_ []*lint.Analyzer, ps []diagnostic) {
   132  	for _, p := range ps {
   133  		pos := p.Position
   134  		if pos.Filename == "" {
   135  			pos.Filename = "-"
   136  		}
   137  
   138  		if pos.Filename != o.prevFile {
   139  			if o.prevFile != "" {
   140  				o.tw.Flush()
   141  				fmt.Fprintln(o.W)
   142  			}
   143  			fmt.Fprintln(o.W, pos.Filename)
   144  			o.prevFile = pos.Filename
   145  			o.tw = tabwriter.NewWriter(o.W, 0, 4, 2, ' ', 0)
   146  		}
   147  		fmt.Fprintf(o.tw, "  (%d, %d)\t%s\t%s\n", pos.Line, pos.Column, p.Category, p.Message)
   148  		for _, r := range p.Related {
   149  			fmt.Fprintf(o.tw, "    (%d, %d)\t\t  %s\n", r.Position.Line, r.Position.Column, r.Message)
   150  		}
   151  	}
   152  }
   153  
   154  func (o *stylishFormatter) Stats(total, errors, warnings, ignored int) {
   155  	if o.tw != nil {
   156  		o.tw.Flush()
   157  		fmt.Fprintln(o.W)
   158  	}
   159  	fmt.Fprintf(o.W, " ✖ %d problems (%d errors, %d warnings, %d ignored)\n",
   160  		total, errors, warnings, ignored)
   161  }