Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/internal/gofixdirective/gofixdirective.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 gofixdirective searches for and validates go:fix directives. The
     6  // go/analysis/passes/inline package uses findgofix to perform inlining.
     7  // The go/analysis/passes/gofix package uses findgofix to check for problems
     8  // with go:fix directives.
     9  //
    10  // gofixdirective is separate from gofix to avoid depending on refactor/inline,
    11  // which is large.
    12  package gofixdirective
    13  
    14  // This package is tested by go/analysis/passes/inline.
    15  
    16  import (
    17  	"go/ast"
    18  	"go/token"
    19  	"go/types"
    20  
    21  	"golang.org/x/tools/go/analysis"
    22  	"golang.org/x/tools/go/ast/inspector"
    23  	internalastutil "golang.org/x/tools/internal/astutil"
    24  )
    25  
    26  // A Handler handles language entities with go:fix directives.
    27  type Handler interface {
    28  	HandleFunc(*ast.FuncDecl)
    29  	HandleAlias(*ast.TypeSpec)
    30  	HandleConst(name, rhs *ast.Ident)
    31  }
    32  
    33  // Find finds functions and constants annotated with an appropriate "//go:fix"
    34  // comment (the syntax proposed by #32816), and calls handler methods for each one.
    35  // h may be nil.
    36  func Find(pass *analysis.Pass, root inspector.Cursor, h Handler) {
    37  	for cur := range root.Preorder((*ast.FuncDecl)(nil), (*ast.GenDecl)(nil)) {
    38  		switch decl := cur.Node().(type) {
    39  		case *ast.FuncDecl:
    40  			findFunc(decl, h)
    41  
    42  		case *ast.GenDecl:
    43  			if decl.Tok != token.CONST && decl.Tok != token.TYPE {
    44  				continue
    45  			}
    46  			declInline := hasFixInline(decl.Doc)
    47  			// Accept inline directives on the entire decl as well as individual specs.
    48  			for _, spec := range decl.Specs {
    49  				switch spec := spec.(type) {
    50  				case *ast.TypeSpec: // Tok == TYPE
    51  					findAlias(pass, spec, declInline, h)
    52  
    53  				case *ast.ValueSpec: // Tok == CONST
    54  					findConst(pass, spec, declInline, h)
    55  				}
    56  			}
    57  		}
    58  	}
    59  }
    60  
    61  func findFunc(decl *ast.FuncDecl, h Handler) {
    62  	if !hasFixInline(decl.Doc) {
    63  		return
    64  	}
    65  	if h != nil {
    66  		h.HandleFunc(decl)
    67  	}
    68  }
    69  
    70  func findAlias(pass *analysis.Pass, spec *ast.TypeSpec, declInline bool, h Handler) {
    71  	if !declInline && !hasFixInline(spec.Doc) {
    72  		return
    73  	}
    74  	if !spec.Assign.IsValid() {
    75  		pass.Reportf(spec.Pos(), "invalid //go:fix inline directive: not a type alias")
    76  		return
    77  	}
    78  
    79  	// Disallow inlines of type expressions containing array types.
    80  	// Given an array type like [N]int where N is a named constant, go/types provides
    81  	// only the value of the constant as an int64. So inlining A in this code:
    82  	//
    83  	//    const N = 5
    84  	//    type A = [N]int
    85  	//
    86  	// would result in [5]int, breaking the connection with N.
    87  	for n := range ast.Preorder(spec.Type) {
    88  		if ar, ok := n.(*ast.ArrayType); ok && ar.Len != nil {
    89  			// Make an exception when the array length is a literal int.
    90  			if lit, ok := ast.Unparen(ar.Len).(*ast.BasicLit); ok && lit.Kind == token.INT {
    91  				continue
    92  			}
    93  			pass.Reportf(spec.Pos(), "invalid //go:fix inline directive: array types not supported")
    94  			return
    95  		}
    96  	}
    97  	if h != nil {
    98  		h.HandleAlias(spec)
    99  	}
   100  }
   101  
   102  func findConst(pass *analysis.Pass, spec *ast.ValueSpec, declInline bool, h Handler) {
   103  	specInline := hasFixInline(spec.Doc)
   104  	if declInline || specInline {
   105  		for i, nameIdent := range spec.Names {
   106  			if i >= len(spec.Values) {
   107  				// Possible following an iota.
   108  				break
   109  			}
   110  			var rhsIdent *ast.Ident
   111  			switch val := spec.Values[i].(type) {
   112  			case *ast.Ident:
   113  				// Constants defined with the predeclared iota cannot be inlined.
   114  				if pass.TypesInfo.Uses[val] == builtinIota {
   115  					pass.Reportf(val.Pos(), "invalid //go:fix inline directive: const value is iota")
   116  					return
   117  				}
   118  				rhsIdent = val
   119  			case *ast.SelectorExpr:
   120  				rhsIdent = val.Sel
   121  			default:
   122  				pass.Reportf(val.Pos(), "invalid //go:fix inline directive: const value is not the name of another constant")
   123  				return
   124  			}
   125  			if h != nil {
   126  				h.HandleConst(nameIdent, rhsIdent)
   127  			}
   128  		}
   129  	}
   130  }
   131  
   132  // hasFixInline reports the presence of a "//go:fix inline" directive
   133  // in the comments.
   134  func hasFixInline(cg *ast.CommentGroup) bool {
   135  	for _, d := range internalastutil.Directives(cg) {
   136  		if d.Tool == "go" && d.Name == "fix" && d.Args == "inline" {
   137  			return true
   138  		}
   139  	}
   140  	return false
   141  }
   142  
   143  var builtinIota = types.Universe.Lookup("iota")
   144  

View as plain text