/
main.go
132 lines (120 loc) · 3.14 KB
/
main.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
// Parses golang code looking for github.com/hashicorp/consul/api.NewClient()
// being used in non-test code. If it finds this, it will error.
// The purpose of this lint is that we actually want to use our internal
// github.com/hashicorp/consul-k8s/consul.NewClient() function because that
// adds the consul-k8s version as a header.
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
)
var (
broken = make(map[string]bool, 0) // Stored in a map for deduplication
exitCode = 0
fset = token.NewFileSet()
consulApiPackage = "github.com/hashicorp/consul/api"
)
func main() {
dir, err := os.Getwd()
if err != nil {
os.Stderr.WriteString(fmt.Sprintf("failed to get cwd: %v", err))
os.Exit(1)
}
err = walkDir(dir)
if err != nil {
os.Stderr.WriteString(err.Error())
os.Exit(1)
}
if len(broken) > 0 {
exitCode = 1
os.Stderr.WriteString("Found code using github.com/hashicorp/consul/api.NewClient()\ninstead of github.com/hashicorp/consul-k8s/consul.NewClient()\nin the following files:\n")
for filePath := range broken {
os.Stderr.WriteString(fmt.Sprintf("- %s\n", filePath))
}
}
os.Exit(exitCode)
}
type visitor struct {
path string
alias string
}
func (v visitor) Visit(n ast.Node) ast.Visitor {
switch node := n.(type) {
case *ast.CallExpr:
function, ok := node.Fun.(*ast.SelectorExpr)
if !ok {
break
}
pkg, ok := function.X.(*ast.Ident)
if !ok {
break
}
if !(pkg.Name == v.alias && function.Sel.Name == "NewClient") {
break
}
broken[v.path] = true
}
return v
}
// imports returns true if file imports pkg and the name of the alias
// used to import.
func imports(file *ast.File, pkgName string) (bool, string) {
var specs []ast.Spec
for _, decl := range file.Decls {
if general, ok := decl.(*ast.GenDecl); ok {
specs = append(specs, general.Specs...)
}
}
for _, spec := range specs {
pkg, ok := spec.(*ast.ImportSpec)
if !ok {
continue
}
path := pkg.Path.Value
// path may have leading/trailing quotes.
path = strings.Trim(path, "\"")
if path == pkgName {
alias := filepath.Base(pkgName)
if pkg.Name != nil {
alias = pkg.Name.Name
}
return true, alias
}
}
return false, ""
}
func walkDir(path string) error {
return filepath.Walk(path, visitFile)
}
func visitFile(path string, f os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("failed to visit '%s', %v", path, err)
}
// consul/consul.go is where we have our re-implementation of NewClient()
// which under the hood calls api.NewClient() so we need to discard that
// path.
if isNonTestFile(path, f) && !strings.Contains(path, "consul/consul.go") {
tree, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
return err
}
// Only process files importing github.com/hashicorp/consul/api.
importsAPI, alias := imports(tree, consulApiPackage)
if importsAPI {
v := visitor{
path: path,
alias: alias,
}
ast.Walk(v, tree)
}
}
return nil
}
func isNonTestFile(path string, f os.FileInfo) bool {
return !f.IsDir() && !strings.Contains(path, "test") && filepath.Ext(path) == ".go"
}