deprecated.go raw

   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  }
 155