Source file src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/print.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package driverutil
     6  
     7  // This file defined output helpers common to all drivers.
     8  
     9  import (
    10  	"cmp"
    11  	"encoding/json"
    12  	"fmt"
    13  	"go/token"
    14  	"io"
    15  	"log"
    16  	"os"
    17  	"strings"
    18  
    19  	"golang.org/x/tools/go/analysis"
    20  )
    21  
    22  // TODO(adonovan): don't accept an io.Writer if we don't report errors.
    23  // Either accept a bytes.Buffer (infallible), or return a []byte.
    24  
    25  // PrintPlain prints a diagnostic in plain text form.
    26  // If contextLines is nonnegative, it also prints the
    27  // offending line plus this many lines of context.
    28  func PrintPlain(out io.Writer, fset *token.FileSet, contextLines int, diag analysis.Diagnostic) {
    29  	print := func(pos, end token.Pos, message string) {
    30  		posn := fset.Position(pos)
    31  		fmt.Fprintf(out, "%s: %s\n", posn, message)
    32  
    33  		// show offending line plus N lines of context.
    34  		if contextLines >= 0 {
    35  			end := fset.Position(end)
    36  			if !end.IsValid() {
    37  				end = posn
    38  			}
    39  			// TODO(adonovan): highlight the portion of the line indicated
    40  			// by pos...end using ASCII art, terminal colors, etc?
    41  			data, _ := os.ReadFile(posn.Filename)
    42  			lines := strings.Split(string(data), "\n")
    43  			for i := posn.Line - contextLines; i <= end.Line+contextLines; i++ {
    44  				if 1 <= i && i <= len(lines) {
    45  					fmt.Fprintf(out, "%d\t%s\n", i, lines[i-1])
    46  				}
    47  			}
    48  		}
    49  	}
    50  
    51  	print(diag.Pos, diag.End, diag.Message)
    52  	for _, rel := range diag.Related {
    53  		print(rel.Pos, rel.End, "\t"+rel.Message)
    54  	}
    55  }
    56  
    57  // A JSONTree is a mapping from package ID to analysis name to result.
    58  // Each result is either a jsonError or a list of JSONDiagnostic.
    59  type JSONTree map[string]map[string]any
    60  
    61  // A TextEdit describes the replacement of a portion of a file.
    62  // Start and End are zero-based half-open indices into the original byte
    63  // sequence of the file, and New is the new text.
    64  type JSONTextEdit struct {
    65  	Filename string `json:"filename"`
    66  	Start    int    `json:"start"`
    67  	End      int    `json:"end"`
    68  	New      string `json:"new"`
    69  }
    70  
    71  // A JSONSuggestedFix describes an edit that should be applied as a whole or not
    72  // at all. It might contain multiple TextEdits/text_edits if the SuggestedFix
    73  // consists of multiple non-contiguous edits.
    74  type JSONSuggestedFix struct {
    75  	Message string         `json:"message"`
    76  	Edits   []JSONTextEdit `json:"edits"`
    77  }
    78  
    79  // A JSONDiagnostic describes the JSON schema of an analysis.Diagnostic.
    80  type JSONDiagnostic struct {
    81  	Category       string                   `json:"category,omitempty"`
    82  	Posn           string                   `json:"posn"` // e.g. "file.go:line:column"
    83  	End            string                   `json:"end"`  // (ditto)
    84  	Message        string                   `json:"message"`
    85  	SuggestedFixes []JSONSuggestedFix       `json:"suggested_fixes,omitempty"`
    86  	Related        []JSONRelatedInformation `json:"related,omitempty"`
    87  }
    88  
    89  // A JSONRelated describes a secondary position and message related to
    90  // a primary diagnostic.
    91  type JSONRelatedInformation struct {
    92  	Posn    string `json:"posn"` // e.g. "file.go:line:column"
    93  	End     string `json:"end"`  // (ditto)
    94  	Message string `json:"message"`
    95  }
    96  
    97  // Add adds the result of analysis 'name' on package 'id'.
    98  // The result is either a list of diagnostics or an error.
    99  func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
   100  	var v any
   101  	if err != nil {
   102  		type jsonError struct {
   103  			Err string `json:"error"`
   104  		}
   105  		v = jsonError{err.Error()}
   106  	} else if len(diags) > 0 {
   107  		diagnostics := make([]JSONDiagnostic, 0, len(diags))
   108  		for _, f := range diags {
   109  			var fixes []JSONSuggestedFix
   110  			for _, fix := range f.SuggestedFixes {
   111  				var edits []JSONTextEdit
   112  				for _, edit := range fix.TextEdits {
   113  					edits = append(edits, JSONTextEdit{
   114  						Filename: fset.Position(edit.Pos).Filename,
   115  						Start:    fset.Position(edit.Pos).Offset,
   116  						End:      fset.Position(edit.End).Offset,
   117  						New:      string(edit.NewText),
   118  					})
   119  				}
   120  				fixes = append(fixes, JSONSuggestedFix{
   121  					Message: fix.Message,
   122  					Edits:   edits,
   123  				})
   124  			}
   125  			var related []JSONRelatedInformation
   126  			for _, r := range f.Related {
   127  				related = append(related, JSONRelatedInformation{
   128  					Posn:    fset.Position(r.Pos).String(),
   129  					End:     fset.Position(cmp.Or(r.End, r.Pos)).String(),
   130  					Message: r.Message,
   131  				})
   132  			}
   133  			jdiag := JSONDiagnostic{
   134  				Category:       f.Category,
   135  				Posn:           fset.Position(f.Pos).String(),
   136  				End:            fset.Position(cmp.Or(f.End, f.Pos)).String(),
   137  				Message:        f.Message,
   138  				SuggestedFixes: fixes,
   139  				Related:        related,
   140  			}
   141  			diagnostics = append(diagnostics, jdiag)
   142  		}
   143  		v = diagnostics
   144  	}
   145  	if v != nil {
   146  		m, ok := tree[id]
   147  		if !ok {
   148  			m = make(map[string]any)
   149  			tree[id] = m
   150  		}
   151  		m[name] = v
   152  	}
   153  }
   154  
   155  func (tree JSONTree) Print(out io.Writer) error {
   156  	data, err := json.MarshalIndent(tree, "", "\t")
   157  	if err != nil {
   158  		log.Panicf("internal error: JSON marshaling failed: %v", err)
   159  	}
   160  	_, err = fmt.Fprintf(out, "%s\n", data)
   161  	return err
   162  }
   163  

View as plain text