Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/omitzero.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 modernize
     6  
     7  import (
     8  	"go/ast"
     9  	"go/types"
    10  	"reflect"
    11  	"strconv"
    12  	"strings"
    13  	"sync"
    14  
    15  	"golang.org/x/tools/go/analysis"
    16  	"golang.org/x/tools/go/analysis/passes/inspect"
    17  	"golang.org/x/tools/internal/analysis/analyzerutil"
    18  	"golang.org/x/tools/internal/astutil"
    19  	"golang.org/x/tools/internal/versions"
    20  )
    21  
    22  var OmitZeroAnalyzer = &analysis.Analyzer{
    23  	Name:     "omitzero",
    24  	Doc:      analyzerutil.MustExtractDoc(doc, "omitzero"),
    25  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    26  	Run:      omitzero,
    27  	URL:      "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#omitzero",
    28  }
    29  
    30  // The omitzero pass searches for instances of "omitempty" in a json field tag on a
    31  // struct. Since "omitfilesUsingGoVersions not have any effect when applied to a struct field,
    32  // it suggests either deleting "omitempty" or replacing it with "omitzero", which
    33  // correctly excludes structs from a json encoding.
    34  func omitzero(pass *analysis.Pass) (any, error) {
    35  	// usesKubebuilder reports whether "+kubebuilder:" appears in
    36  	// any comment in the package, since it has its own
    37  	// interpretation of what omitzero means; see go.dev/issue/76649.
    38  	// It is computed once, on demand.
    39  	usesKubebuilder := sync.OnceValue[bool](func() bool {
    40  		for _, file := range pass.Files {
    41  			for _, comment := range file.Comments {
    42  				if strings.Contains(comment.Text(), "+kubebuilder:") {
    43  					return true
    44  				}
    45  			}
    46  		}
    47  		return false
    48  	})
    49  
    50  	checkField := func(field *ast.Field) {
    51  		typ := pass.TypesInfo.TypeOf(field.Type)
    52  		_, ok := typ.Underlying().(*types.Struct)
    53  		if !ok {
    54  			// Not a struct
    55  			return
    56  		}
    57  		tag := field.Tag
    58  		if tag == nil {
    59  			// No tag to check
    60  			return
    61  		}
    62  		// The omitempty tag may be used by other packages besides json, but we should only modify its use with json
    63  		tagconv, _ := strconv.Unquote(tag.Value)
    64  		match := omitemptyRegex.FindStringSubmatchIndex(tagconv)
    65  		if match == nil {
    66  			// No omitempty in json tag
    67  			return
    68  		}
    69  		omitEmpty, err := astutil.RangeInStringLiteral(field.Tag, match[2], match[3])
    70  		if err != nil {
    71  			return
    72  		}
    73  		var remove analysis.Range = omitEmpty
    74  
    75  		jsonTag := reflect.StructTag(tagconv).Get("json")
    76  		if jsonTag == ",omitempty" {
    77  			// Remove the entire struct tag if json is the only package used
    78  			if match[1]-match[0] == len(tagconv) {
    79  				remove = field.Tag
    80  			} else {
    81  				// Remove the json tag if omitempty is the only field
    82  				remove, err = astutil.RangeInStringLiteral(field.Tag, match[0], match[1])
    83  				if err != nil {
    84  					return
    85  				}
    86  			}
    87  		}
    88  
    89  		// Don't offer a fix if the package seems to use kubebuilder,
    90  		// as it has its own intepretation of "omitzero" tags.
    91  		// https://book.kubebuilder.io/reference/markers.html
    92  		if usesKubebuilder() {
    93  			return
    94  		}
    95  
    96  		pass.Report(analysis.Diagnostic{
    97  			Pos:     field.Tag.Pos(),
    98  			End:     field.Tag.End(),
    99  			Message: "Omitempty has no effect on nested struct fields",
   100  			SuggestedFixes: []analysis.SuggestedFix{
   101  				{
   102  					Message: "Remove redundant omitempty tag",
   103  					TextEdits: []analysis.TextEdit{
   104  						{
   105  							Pos: remove.Pos(),
   106  							End: remove.End(),
   107  						},
   108  					},
   109  				},
   110  				{
   111  					Message: "Replace omitempty with omitzero (behavior change)",
   112  					TextEdits: []analysis.TextEdit{
   113  						{
   114  							Pos:     omitEmpty.Pos(),
   115  							End:     omitEmpty.End(),
   116  							NewText: []byte(",omitzero"),
   117  						},
   118  					},
   119  				},
   120  			}})
   121  	}
   122  
   123  	for curFile := range filesUsingGoVersion(pass, versions.Go1_24) {
   124  		for curStruct := range curFile.Preorder((*ast.StructType)(nil)) {
   125  			for _, curField := range curStruct.Node().(*ast.StructType).Fields.List {
   126  				checkField(curField)
   127  			}
   128  		}
   129  	}
   130  
   131  	return nil, nil
   132  }
   133  

View as plain text