src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

deprecated.go (3573B)


      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.TypeFor[Result](),
     29 }
     30 
     31 func deprecated(pass *analysis.Pass) (any, 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.SplitSeq(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 }