Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go

     1  // Copyright 2024 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 modernize
     6  
     7  import (
     8  	_ "embed"
     9  	"go/ast"
    10  	"go/constant"
    11  	"go/format"
    12  	"go/token"
    13  	"go/types"
    14  	"iter"
    15  	"regexp"
    16  	"strings"
    17  
    18  	"golang.org/x/tools/go/analysis"
    19  	"golang.org/x/tools/go/analysis/passes/inspect"
    20  	"golang.org/x/tools/go/ast/inspector"
    21  	"golang.org/x/tools/internal/analysis/analyzerutil"
    22  	"golang.org/x/tools/internal/refactor"
    23  	"golang.org/x/tools/internal/typesinternal/typeindex"
    24  
    25  	"golang.org/x/tools/internal/moreiters"
    26  	"golang.org/x/tools/internal/packagepath"
    27  	"golang.org/x/tools/internal/stdlib"
    28  	"golang.org/x/tools/internal/typesinternal"
    29  )
    30  
    31  //go:embed doc.go
    32  var doc string
    33  
    34  // Suite lists all modernize analyzers.
    35  var Suite = []*analysis.Analyzer{
    36  	AnyAnalyzer,
    37  	AtomicTypesAnalyzer,
    38  	// AppendClippedAnalyzer, // not nil-preserving!
    39  	// BLoopAnalyzer, // may skew benchmark results, see golang/go#74967
    40  	EmbedLitAnalyzer,
    41  	ErrorsAsTypeAnalyzer,
    42  	// FmtAppendfAnalyzer, // makes code less clear, see golang/go#77581
    43  	ForVarAnalyzer,
    44  	MapsLoopAnalyzer,
    45  	MinMaxAnalyzer,
    46  	NewExprAnalyzer,
    47  	OmitZeroAnalyzer,
    48  	PlusBuildAnalyzer,
    49  	RangeIntAnalyzer,
    50  	ReflectTypeForAnalyzer,
    51  	slicesBackwardAnalyzer,
    52  	SlicesContainsAnalyzer,
    53  	// SlicesDeleteAnalyzer, // not nil-preserving!
    54  	SlicesSortAnalyzer,
    55  	StdIteratorsAnalyzer,
    56  	StringsCutAnalyzer,
    57  	StringsCutPrefixAnalyzer,
    58  	StringsSeqAnalyzer,
    59  	StringsBuilderAnalyzer,
    60  	TestingContextAnalyzer,
    61  	unsafeFuncsAnalyzer,
    62  	WaitGroupGoAnalyzer,
    63  }
    64  
    65  // -- helpers --
    66  
    67  // formatExprs formats a comma-separated list of expressions.
    68  func formatExprs(fset *token.FileSet, exprs []ast.Expr) string {
    69  	var buf strings.Builder
    70  	for i, e := range exprs {
    71  		if i > 0 {
    72  			buf.WriteString(",  ")
    73  		}
    74  		format.Node(&buf, fset, e) // ignore errors
    75  	}
    76  	return buf.String()
    77  }
    78  
    79  // isZeroIntConst reports whether e is an integer whose value is 0.
    80  func isZeroIntConst(info *types.Info, e ast.Expr) bool {
    81  	return isIntLiteral(info, e, 0)
    82  }
    83  
    84  // isIntLiteral reports whether e is an integer with given value.
    85  func isIntLiteral(info *types.Info, e ast.Expr, n int64) bool {
    86  	return info.Types[e].Value == constant.MakeInt64(n)
    87  }
    88  
    89  // isInteger reports whether t is an integer type.
    90  func isInteger(t types.Type) bool {
    91  	basic, ok := t.Underlying().(*types.Basic)
    92  	return ok && basic.Info()&types.IsInteger != 0
    93  }
    94  
    95  // filesUsingGoVersion returns a cursor for each *ast.File in the inspector
    96  // that uses at least the specified version of Go (e.g. "go1.24").
    97  //
    98  // The pass's analyzer must require [inspect.Analyzer].
    99  //
   100  // TODO(adonovan): opt: eliminate this function, instead following the
   101  // approach of [fmtappendf], which uses typeindex and
   102  // [analyzerutil.FileUsesGoVersion]; see "Tip" documented at the
   103  // latter function for motivation.
   104  func filesUsingGoVersion(pass *analysis.Pass, version string) iter.Seq[inspector.Cursor] {
   105  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
   106  
   107  	return func(yield func(inspector.Cursor) bool) {
   108  		for curFile := range inspect.Root().Children() {
   109  			file := curFile.Node().(*ast.File)
   110  			if analyzerutil.FileUsesGoVersion(pass, file, version) && !yield(curFile) {
   111  				break
   112  			}
   113  		}
   114  	}
   115  }
   116  
   117  // within reports whether the current pass is analyzing one of the
   118  // specified standard packages or their dependencies.
   119  func within(pass *analysis.Pass, pkgs ...string) bool {
   120  	path := pass.Pkg.Path()
   121  	return packagepath.IsStdPackage(path) &&
   122  		moreiters.Contains(stdlib.Dependencies(pkgs...), path)
   123  }
   124  
   125  var (
   126  	builtinAny     = types.Universe.Lookup("any")
   127  	builtinAppend  = types.Universe.Lookup("append")
   128  	builtinBool    = types.Universe.Lookup("bool")
   129  	builtinInt     = types.Universe.Lookup("int")
   130  	builtinFalse   = types.Universe.Lookup("false")
   131  	builtinLen     = types.Universe.Lookup("len")
   132  	builtinMake    = types.Universe.Lookup("make")
   133  	builtinNew     = types.Universe.Lookup("new")
   134  	builtinNil     = types.Universe.Lookup("nil")
   135  	builtinString  = types.Universe.Lookup("string")
   136  	builtinTrue    = types.Universe.Lookup("true")
   137  	byteSliceType  = types.NewSlice(types.Typ[types.Byte])
   138  	omitemptyRegex = regexp.MustCompile(`(?:^json| json):"[^"]*(,omitempty)(?:"|,[^"]*")\s?`)
   139  	errorType      = types.Universe.Lookup("error").Type()
   140  )
   141  
   142  // lookup returns the symbol denoted by name at the position of the cursor.
   143  func lookup(info *types.Info, cur inspector.Cursor, name string) types.Object {
   144  	scope := typesinternal.EnclosingScope(info, cur)
   145  	_, obj := scope.LookupParent(name, cur.Node().Pos())
   146  	return obj
   147  }
   148  
   149  func first[T any](x T, _ any) T { return x }
   150  
   151  // freshName returns a fresh name at the given pos and scope based on preferredName.
   152  // It generates a new name using refactor.FreshName only if:
   153  // (a) the preferred name is already defined at definedCur, and
   154  // (b) there are references to it from within usedCur.
   155  // If useAfterPos.IsValid(), the references must be after
   156  // useAfterPos within usedCur in order to warrant a fresh name.
   157  // Otherwise, it returns preferredName, since shadowing is valid in this case.
   158  // (declaredCur and usedCur may be identical in some use cases).
   159  func freshName(info *types.Info, index *typeindex.Index, scope *types.Scope, pos token.Pos, defCur inspector.Cursor, useCur inspector.Cursor, useAfterPos token.Pos, preferredName string) string {
   160  	obj := lookup(info, defCur, preferredName)
   161  	if obj == nil {
   162  		// preferredName has not been declared here.
   163  		return preferredName
   164  	}
   165  	for use := range index.Uses(obj) {
   166  		if useCur.Contains(use) && use.Node().Pos() >= useAfterPos {
   167  			return refactor.FreshName(scope, pos, preferredName)
   168  		}
   169  	}
   170  	// Name is taken but not used in the given block; shadowing is acceptable.
   171  	return preferredName
   172  }
   173  
   174  // isLocal reports whether obj is local to some function.
   175  // Precondition: not a struct field or interface method.
   176  func isLocal(obj types.Object) bool {
   177  	// [... 5=stmt 4=func 3=file 2=pkg 1=universe]
   178  	var depth int
   179  	for scope := obj.Parent(); scope != nil; scope = scope.Parent() {
   180  		depth++
   181  	}
   182  	return depth >= 4
   183  }
   184  
   185  func is[T any](x any) bool {
   186  	_, ok := x.(T)
   187  	return ok
   188  }
   189  
   190  func cond[T any](cond bool, t, f T) T {
   191  	if cond {
   192  		return t
   193  	} else {
   194  		return f
   195  	}
   196  }
   197  

View as plain text