generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 7
/
analyzer.go
115 lines (105 loc) · 3.6 KB
/
analyzer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package transitive
import (
"go/ast"
"go/types"
"github.com/alecthomas/types/optional"
sets "github.com/deckarep/golang-set/v2"
"github.com/TBD54566975/ftl/backend/schema"
"github.com/TBD54566975/ftl/go-runtime/schema/common"
"github.com/TBD54566975/golang-tools/go/analysis"
"github.com/TBD54566975/golang-tools/go/analysis/passes/inspect"
"github.com/TBD54566975/golang-tools/go/ast/inspector"
)
// Extractor extracts transitive schema.Decls to the module schema.
//
// This extractor is used to extract schema.Decls that are implicitly included in the schema via other schema.Decls
// but not themselves explicitly annotated.
var Extractor = common.NewExtractor("transitive", (*Fact)(nil), Extract)
type Tag struct{} // Tag uniquely identifies the fact type for this extractor.
type Fact = common.DefaultFact[Tag]
// Extract traverses all schema type root AST nodes and determines if a node has been marked for extraction.
//
// Transitive data decls are marked via "facts", annotating the object which must be extracted to the schema with
// common.NeedsExtraction. This allows us to identify objects for extraction that are not explicitly
// annotated with an FTL directive.
func Extract(pass *analysis.Pass) (interface{}, error) {
needsExtraction := sets.NewSet[types.Object]()
for obj, fact := range common.MergeAllFacts(pass) {
if _, ok := fact.Get().(*common.NeedsExtraction); ok {
needsExtraction.Add(obj)
}
}
for !needsExtraction.IsEmpty() {
extractTransitive(pass, needsExtraction)
needsExtraction = refreshNeedsExtraction(pass)
}
return common.NewExtractorResult(pass), nil
}
func refreshNeedsExtraction(pass *analysis.Pass) sets.Set[types.Object] {
facts := sets.NewSet[types.Object]()
for _, fact := range pass.AllObjectFacts() {
f, ok := fact.Fact.(common.SchemaFact)
if !ok {
continue
}
if _, ok := f.Get().(*common.NeedsExtraction); ok && fact.Object.Pkg().Path() == pass.Pkg.Path() {
facts.Add(fact.Object)
}
}
return facts
}
func extractTransitive(pass *analysis.Pass, needsExtraction sets.Set[types.Object]) {
in := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert
nodeFilter := []ast.Node{
(*ast.TypeSpec)(nil),
(*ast.FuncDecl)(nil),
}
in.Preorder(nodeFilter, func(n ast.Node) {
obj, ok := common.GetObjectForNode(pass.TypesInfo, n).Get()
if !ok {
return
}
if !needsExtraction.Contains(obj) {
return
}
schType, ok := inferDeclType(pass, n, obj).Get()
if !ok {
// if we can't infer the type, try to extract it as data
schType = &schema.Data{}
}
extract, err := common.ExtractFuncForDecl(schType)
if err != nil {
// unmigrated, skip
// temporarily marking as extracted to avoid infinite loop
common.MarkSchemaDecl(pass, obj, nil)
return
}
if decl, ok := extract(pass, n, obj).Get(); ok {
common.MarkSchemaDecl(pass, obj, decl)
} else {
common.MarkFailedExtraction(pass, obj)
}
})
}
func inferDeclType(pass *analysis.Pass, node ast.Node, obj types.Object) optional.Option[schema.Decl] {
if md, ok := common.GetFactForObject[*common.ExtractedMetadata](pass, obj).Get(); ok {
if md.Type != nil {
return optional.Some[schema.Decl](md.Type)
}
}
ts, ok := node.(*ast.TypeSpec)
if !ok {
return optional.None[schema.Decl]()
}
if _, ok := ts.Type.(*ast.InterfaceType); ok {
return optional.Some[schema.Decl](&schema.Enum{})
}
t, ok := common.ExtractTypeForNode(pass, obj, ts.Type, nil).Get()
if !ok {
return optional.None[schema.Decl]()
}
if !common.IsSelfReference(pass, obj, t) {
return optional.Some[schema.Decl](&schema.TypeAlias{})
}
return optional.Some[schema.Decl](&schema.Data{})
}