honnef.co/go/tools@v0.5.0-0.dev.0.20240520180541-dcae280a5e87/analysis/facts/deprecated/deprecated.go (about)

     1  package deprecated
     2  
     3  import (
     4  	"go/ast"
     5  	"go/token"
     6  	"go/types"
     7  	"reflect"
     8  	"strings"
     9  
    10  	"golang.org/x/tools/go/analysis"
    11  )
    12  
    13  type IsDeprecated struct{ Msg string }
    14  
    15  func (*IsDeprecated) AFact()           {}
    16  func (d *IsDeprecated) String() string { return "Deprecated: " + d.Msg }
    17  
    18  type Result struct {
    19  	Objects  map[types.Object]*IsDeprecated
    20  	Packages map[*types.Package]*IsDeprecated
    21  }
    22  
    23  var Analyzer = &analysis.Analyzer{
    24  	Name:       "fact_deprecated",
    25  	Doc:        "Mark deprecated objects",
    26  	Run:        deprecated,
    27  	FactTypes:  []analysis.Fact{(*IsDeprecated)(nil)},
    28  	ResultType: reflect.TypeOf(Result{}),
    29  }
    30  
    31  func deprecated(pass *analysis.Pass) (interface{}, error) {
    32  	var names []*ast.Ident
    33  
    34  	extractDeprecatedMessage := func(docs []*ast.CommentGroup) string {
    35  		for _, doc := range docs {
    36  			if doc == nil {
    37  				continue
    38  			}
    39  			parts := strings.Split(doc.Text(), "\n\n")
    40  			for _, part := range parts {
    41  				if !strings.HasPrefix(part, "Deprecated: ") {
    42  					continue
    43  				}
    44  				alt := part[len("Deprecated: "):]
    45  				alt = strings.Replace(alt, "\n", " ", -1)
    46  				return alt
    47  			}
    48  		}
    49  		return ""
    50  	}
    51  
    52  	doDocs := func(names []*ast.Ident, docs []*ast.CommentGroup) {
    53  		alt := extractDeprecatedMessage(docs)
    54  		if alt == "" {
    55  			return
    56  		}
    57  
    58  		for _, name := range names {
    59  			obj := pass.TypesInfo.ObjectOf(name)
    60  			pass.ExportObjectFact(obj, &IsDeprecated{alt})
    61  		}
    62  	}
    63  
    64  	var docs []*ast.CommentGroup
    65  	for _, f := range pass.Files {
    66  		docs = append(docs, f.Doc)
    67  	}
    68  	if alt := extractDeprecatedMessage(docs); alt != "" {
    69  		// Don't mark package syscall as deprecated, even though
    70  		// it is. A lot of people still use it for simple
    71  		// constants like SIGKILL, and I am not comfortable
    72  		// telling them to use x/sys for that.
    73  		if pass.Pkg.Path() != "syscall" {
    74  			pass.ExportPackageFact(&IsDeprecated{alt})
    75  		}
    76  	}
    77  
    78  	docs = docs[:0]
    79  	for _, f := range pass.Files {
    80  		fn := func(node ast.Node) bool {
    81  			if node == nil {
    82  				return true
    83  			}
    84  			var ret bool
    85  			switch node := node.(type) {
    86  			case *ast.GenDecl:
    87  				switch node.Tok {
    88  				case token.TYPE, token.CONST, token.VAR:
    89  					docs = append(docs, node.Doc)
    90  					for i := range node.Specs {
    91  						switch n := node.Specs[i].(type) {
    92  						case *ast.ValueSpec:
    93  							names = append(names, n.Names...)
    94  						case *ast.TypeSpec:
    95  							names = append(names, n.Name)
    96  						}
    97  					}
    98  					ret = true
    99  				default:
   100  					return false
   101  				}
   102  			case *ast.FuncDecl:
   103  				docs = append(docs, node.Doc)
   104  				names = []*ast.Ident{node.Name}
   105  				ret = false
   106  			case *ast.TypeSpec:
   107  				docs = append(docs, node.Doc)
   108  				names = []*ast.Ident{node.Name}
   109  				ret = true
   110  			case *ast.ValueSpec:
   111  				docs = append(docs, node.Doc)
   112  				names = node.Names
   113  				ret = false
   114  			case *ast.File:
   115  				return true
   116  			case *ast.StructType:
   117  				for _, field := range node.Fields.List {
   118  					doDocs(field.Names, []*ast.CommentGroup{field.Doc})
   119  				}
   120  				return false
   121  			case *ast.InterfaceType:
   122  				for _, field := range node.Methods.List {
   123  					doDocs(field.Names, []*ast.CommentGroup{field.Doc})
   124  				}
   125  				return false
   126  			default:
   127  				return false
   128  			}
   129  			if len(names) == 0 || len(docs) == 0 {
   130  				return ret
   131  			}
   132  			doDocs(names, docs)
   133  
   134  			docs = docs[:0]
   135  			names = nil
   136  			return ret
   137  		}
   138  		ast.Inspect(f, fn)
   139  	}
   140  
   141  	out := Result{
   142  		Objects:  map[types.Object]*IsDeprecated{},
   143  		Packages: map[*types.Package]*IsDeprecated{},
   144  	}
   145  
   146  	for _, fact := range pass.AllObjectFacts() {
   147  		out.Objects[fact.Object] = fact.Fact.(*IsDeprecated)
   148  	}
   149  	for _, fact := range pass.AllPackageFacts() {
   150  		out.Packages[fact.Package] = fact.Fact.(*IsDeprecated)
   151  	}
   152  
   153  	return out, nil
   154  }