Path: blob/main/tools/agentlint/internal/findcomponents/findcomponents.go
4096 views
// Package findcomponents exposes an Analyzer which ensures that created Flow1// components are imported by a registry package.2package findcomponents34import (5"fmt"6"go/ast"7"go/types"89"golang.org/x/tools/go/analysis"10"golang.org/x/tools/go/packages"11)1213var Analyzer = &analysis.Analyzer{14Name: "findcomponents",15Doc: "ensure Flow components are imported",16Run: run,17}1819var (20componentPattern = "./component/..."21checkPackage = "github.com/grafana/agent/component/all"22)2324func init() {25Analyzer.Flags.StringVar(&componentPattern, "components", componentPattern, "Pattern where components are defined")26Analyzer.Flags.StringVar(&checkPackage, "import-package", checkPackage, "Package that should import components")27}2829func run(p *analysis.Pass) (interface{}, error) {30// Our linter works as follows:31//32// 1. Retrieve the list of direct imports of the package we are performing33// analysis on.34// 2. Find component packages across the module as defined by the -components35// flag.36// 3. Report a diagnostic for any component package which is not being37// imported.38//39// This linter should only be run against a single package to check for40// imports. The import-package flag is checked and all other packages are41// ignored.4243if p.Pkg.Path() != checkPackage {44return nil, nil45}4647imports := make(map[string]struct{})48for _, dep := range p.Pkg.Imports() {49imports[dep.Path()] = struct{}{}50}5152componentPackages, err := findComponentPackages(componentPattern)53if err != nil {54return nil, err55}56for componentPackage := range componentPackages {57if _, imported := imports[componentPackage]; !imported {58p.Report(analysis.Diagnostic{59Pos: p.Files[0].Pos(),60Message: fmt.Sprintf("package does not import component %s", componentPackage),61})62}63}6465return nil, nil66}6768// findComponentPackages returns a map of discovered packages which declare69// Flow components. The pattern argument controls the full list of patterns70// which are searched (e.g., "./..." or "./component/...").71func findComponentPackages(pattern string) (map[string]struct{}, error) {72pkgs, err := packages.Load(&packages.Config{73Mode: packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo,74}, "pattern="+pattern)75if err != nil {76return nil, err77}7879componentPackages := map[string]struct{}{}8081for _, pkg := range pkgs {82for _, file := range pkg.Syntax {83if declaresComponent(pkg, file) {84componentPackages[pkg.ID] = struct{}{}85}86}87}8889return componentPackages, nil90}9192// declaresComponent inspects a file to see if it has something matching the93// following:94//95// func init() {96// component.Register(component.Registration{ ... })97// }98func declaresComponent(pkg *packages.Package, file *ast.File) bool {99// Look for an init function in the file.100for _, decl := range file.Decls {101funcDecl, ok := decl.(*ast.FuncDecl)102if !ok {103continue104}105106if funcDecl.Name.Name != "init" || funcDecl.Recv != nil {107continue108}109110var foundComponentDecl bool111112// Given an init function, check to see if there's a function call to113// component.Register.114ast.Inspect(funcDecl.Body, func(n ast.Node) bool {115call, ok := n.(*ast.CallExpr)116if !ok {117return true118}119120sel, ok := call.Fun.(*ast.SelectorExpr)121if !ok {122return true123}124125ident, ok := sel.X.(*ast.Ident)126if !ok {127return true128}129130// Check to see if the ident refers to131// github.com/grafana/agent/component.132if pkgName, ok := pkg.TypesInfo.Uses[ident].(*types.PkgName); ok {133if pkgName.Imported().Path() == "github.com/grafana/agent/component" &&134sel.Sel.Name == "Register" {135136foundComponentDecl = true137return false138}139}140141return true142})143144if foundComponentDecl {145return true146}147}148149return false150}151152153