-
Notifications
You must be signed in to change notification settings - Fork 89
/
findcomponents.go
151 lines (126 loc) · 3.76 KB
/
findcomponents.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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// Package findcomponents exposes an Analyzer which ensures that created
// components are imported by a registry package.
package findcomponents
import (
"fmt"
"go/ast"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/packages"
)
var Analyzer = &analysis.Analyzer{
Name: "findcomponents",
Doc: "ensure components are imported",
Run: run,
}
var (
componentPattern = "./internal/component/..."
checkPackage = "github.com/grafana/alloy/internal/component/all"
)
func init() {
Analyzer.Flags.StringVar(&componentPattern, "components", componentPattern, "Pattern where components are defined")
Analyzer.Flags.StringVar(&checkPackage, "import-package", checkPackage, "Package that should import components")
}
func run(p *analysis.Pass) (interface{}, error) {
// Our linter works as follows:
//
// 1. Retrieve the list of direct imports of the package we are performing
// analysis on.
// 2. Find component packages across the module as defined by the -components
// flag.
// 3. Report a diagnostic for any component package which is not being
// imported.
//
// This linter should only be run against a single package to check for
// imports. The import-package flag is checked and all other packages are
// ignored.
if p.Pkg.Path() != checkPackage {
return nil, nil
}
imports := make(map[string]struct{})
for _, dep := range p.Pkg.Imports() {
imports[dep.Path()] = struct{}{}
}
componentPackages, err := findComponentPackages(componentPattern)
if err != nil {
return nil, err
}
for componentPackage := range componentPackages {
if _, imported := imports[componentPackage]; !imported {
p.Report(analysis.Diagnostic{
Pos: p.Files[0].Pos(),
Message: fmt.Sprintf("package does not import component %s", componentPackage),
})
}
}
return nil, nil
}
// findComponentPackages returns a map of discovered packages which declare
// components. The pattern argument controls the full list of patterns which
// are searched (e.g., "./..." or "./component/...").
func findComponentPackages(pattern string) (map[string]struct{}, error) {
pkgs, err := packages.Load(&packages.Config{
Mode: packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo,
}, "pattern="+pattern)
if err != nil {
return nil, err
}
componentPackages := map[string]struct{}{}
for _, pkg := range pkgs {
for _, file := range pkg.Syntax {
if declaresComponent(pkg, file) {
componentPackages[pkg.ID] = struct{}{}
}
}
}
return componentPackages, nil
}
// declaresComponent inspects a file to see if it has something matching the
// following:
//
// func init() {
// component.Register(component.Registration{ ... })
// }
func declaresComponent(pkg *packages.Package, file *ast.File) bool {
// Look for an init function in the file.
for _, decl := range file.Decls {
funcDecl, ok := decl.(*ast.FuncDecl)
if !ok {
continue
}
if funcDecl.Name.Name != "init" || funcDecl.Recv != nil {
continue
}
var foundComponentDecl bool
// Given an init function, check to see if there's a function call to
// component.Register.
ast.Inspect(funcDecl.Body, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok {
return true
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
ident, ok := sel.X.(*ast.Ident)
if !ok {
return true
}
// Check to see if the ident refers to
// github.com/grafana/alloy/internal/component.
if pkgName, ok := pkg.TypesInfo.Uses[ident].(*types.PkgName); ok {
if pkgName.Imported().Path() == "github.com/grafana/alloy/internal/component" &&
sel.Sel.Name == "Register" {
foundComponentDecl = true
return false
}
}
return true
})
if foundComponentDecl {
return true
}
}
return false
}