package facts import ( "go/ast" "go/token" "go/types" "reflect" "strings" "golang.org/x/tools/go/analysis" ) type IsDeprecated struct{ Msg string } func (*IsDeprecated) AFact() {} func (d *IsDeprecated) String() string { return "Deprecated: " + d.Msg } type DeprecatedResult struct { Objects map[types.Object]*IsDeprecated Packages map[*types.Package]*IsDeprecated } var Deprecated = &analysis.Analyzer{ Name: "fact_deprecated", Doc: "Mark deprecated objects", Run: deprecated, FactTypes: []analysis.Fact{(*IsDeprecated)(nil)}, ResultType: reflect.TypeOf(DeprecatedResult{}), } func deprecated(pass *analysis.Pass) (interface{}, error) { var names []*ast.Ident extractDeprecatedMessage := func(docs []*ast.CommentGroup) string { for _, doc := range docs { if doc == nil { continue } parts := strings.Split(doc.Text(), "\n\n") last := parts[len(parts)-1] if !strings.HasPrefix(last, "Deprecated: ") { continue } alt := last[len("Deprecated: "):] alt = strings.Replace(alt, "\n", " ", -1) return alt } return "" } doDocs := func(names []*ast.Ident, docs []*ast.CommentGroup) { alt := extractDeprecatedMessage(docs) if alt == "" { return } for _, name := range names { obj := pass.TypesInfo.ObjectOf(name) pass.ExportObjectFact(obj, &IsDeprecated{alt}) } } var docs []*ast.CommentGroup for _, f := range pass.Files { docs = append(docs, f.Doc) } if alt := extractDeprecatedMessage(docs); alt != "" { // Don't mark package syscall as deprecated, even though // it is. A lot of people still use it for simple // constants like SIGKILL, and I am not comfortable // telling them to use x/sys for that. if pass.Pkg.Path() != "syscall" { pass.ExportPackageFact(&IsDeprecated{alt}) } } docs = docs[:0] for _, f := range pass.Files { fn := func(node ast.Node) bool { if node == nil { return true } var ret bool switch node := node.(type) { case *ast.GenDecl: switch node.Tok { case token.TYPE, token.CONST, token.VAR: docs = append(docs, node.Doc) return true default: return false } case *ast.FuncDecl: docs = append(docs, node.Doc) names = []*ast.Ident{node.Name} ret = false case *ast.TypeSpec: docs = append(docs, node.Doc) names = []*ast.Ident{node.Name} ret = true case *ast.ValueSpec: docs = append(docs, node.Doc) names = node.Names ret = false case *ast.File: return true case *ast.StructType: for _, field := range node.Fields.List { doDocs(field.Names, []*ast.CommentGroup{field.Doc}) } return false case *ast.InterfaceType: for _, field := range node.Methods.List { doDocs(field.Names, []*ast.CommentGroup{field.Doc}) } return false default: return false } if len(names) == 0 || len(docs) == 0 { return ret } doDocs(names, docs) docs = docs[:0] names = nil return ret } ast.Inspect(f, fn) } out := DeprecatedResult{ Objects: map[types.Object]*IsDeprecated{}, Packages: map[*types.Package]*IsDeprecated{}, } for _, fact := range pass.AllObjectFacts() { out.Objects[fact.Object] = fact.Fact.(*IsDeprecated) } for _, fact := range pass.AllPackageFacts() { out.Packages[fact.Package] = fact.Fact.(*IsDeprecated) } return out, nil }