Source file src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/validatefix.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 defines the validation of SuggestedFixes.
     8  
     9  import (
    10  	"cmp"
    11  	"fmt"
    12  	"go/token"
    13  	"slices"
    14  
    15  	"golang.org/x/tools/go/analysis"
    16  )
    17  
    18  // ValidateFixes validates the set of fixes for a single diagnostic.
    19  // Any error indicates a bug in the originating analyzer.
    20  //
    21  // It updates fixes so that fixes[*].End.IsValid().
    22  //
    23  // It may be used as part of an analysis driver implementation.
    24  func ValidateFixes(fset *token.FileSet, a *analysis.Analyzer, fixes []analysis.SuggestedFix) error {
    25  	fixMessages := make(map[string]bool)
    26  	for i := range fixes {
    27  		fix := &fixes[i]
    28  		if fixMessages[fix.Message] {
    29  			return fmt.Errorf("analyzer %q suggests two fixes with same Message (%s)", a.Name, fix.Message)
    30  		}
    31  		fixMessages[fix.Message] = true
    32  		if err := validateFix(fset, fix); err != nil {
    33  			return fmt.Errorf("analyzer %q suggests invalid fix (%s): %v", a.Name, fix.Message, err)
    34  		}
    35  	}
    36  	return nil
    37  }
    38  
    39  // validateFix validates a single fix.
    40  // Any error indicates a bug in the originating analyzer.
    41  //
    42  // It updates fix so that fix.End.IsValid().
    43  func validateFix(fset *token.FileSet, fix *analysis.SuggestedFix) error {
    44  
    45  	// Stably sort edits by Pos. This ordering puts insertions
    46  	// (end = start) before deletions (end > start) at the same
    47  	// point, but uses a stable sort to preserve the order of
    48  	// multiple insertions at the same point.
    49  	slices.SortStableFunc(fix.TextEdits, func(x, y analysis.TextEdit) int {
    50  		if sign := cmp.Compare(x.Pos, y.Pos); sign != 0 {
    51  			return sign
    52  		}
    53  		return cmp.Compare(x.End, y.End)
    54  	})
    55  
    56  	var prev *analysis.TextEdit
    57  	for i := range fix.TextEdits {
    58  		edit := &fix.TextEdits[i]
    59  
    60  		// Validate edit individually.
    61  		start := edit.Pos
    62  		file := fset.File(start)
    63  		if file == nil {
    64  			return fmt.Errorf("no token.File for TextEdit.Pos (%v)", edit.Pos)
    65  		}
    66  		fileEnd := token.Pos(file.Base() + file.Size())
    67  		if end := edit.End; end.IsValid() {
    68  			if end < start {
    69  				return fmt.Errorf("TextEdit.Pos (%v) > TextEdit.End (%v)", edit.Pos, edit.End)
    70  			}
    71  			endFile := fset.File(end)
    72  			if endFile != file && end < fileEnd+10 {
    73  				// Relax the checks below in the special case when the end position
    74  				// is only slightly beyond EOF, as happens when End is computed
    75  				// (as in ast.{Struct,Interface}Type) rather than based on
    76  				// actual token positions. In such cases, truncate end to EOF.
    77  				//
    78  				// This is a workaround for #71659; see:
    79  				// https://github.com/golang/go/issues/71659#issuecomment-2651606031
    80  				// A better fix would be more faithful recording of token
    81  				// positions (or their absence) in the AST.
    82  				edit.End = fileEnd
    83  				continue
    84  			}
    85  			if endFile == nil {
    86  				return fmt.Errorf("no token.File for TextEdit.End (%v; File(start).FileEnd is %d)", end, file.Base()+file.Size())
    87  			}
    88  			if endFile != file {
    89  				return fmt.Errorf("edit #%d spans files (%v and %v)",
    90  					i, file.Position(edit.Pos), endFile.Position(edit.End))
    91  			}
    92  		} else {
    93  			edit.End = start // update the SuggestedFix
    94  		}
    95  		if eof := fileEnd; edit.End > eof {
    96  			return fmt.Errorf("end is (%v) beyond end of file (%v)", edit.End, eof)
    97  		}
    98  
    99  		// Validate the sequence of edits:
   100  		// properly ordered, no overlapping deletions
   101  		if prev != nil && edit.Pos < prev.End {
   102  			xpos := fset.Position(prev.Pos)
   103  			xend := fset.Position(prev.End)
   104  			ypos := fset.Position(edit.Pos)
   105  			yend := fset.Position(edit.End)
   106  			return fmt.Errorf("overlapping edits to %s (%d:%d-%d:%d and %d:%d-%d:%d)",
   107  				xpos.Filename,
   108  				xpos.Line, xpos.Column,
   109  				xend.Line, xend.Column,
   110  				ypos.Line, ypos.Column,
   111  				yend.Line, yend.Column,
   112  			)
   113  		}
   114  		prev = edit
   115  	}
   116  
   117  	return nil
   118  }
   119  

View as plain text