Skip to content
This repository was archived by the owner on Jun 28, 2022. It is now read-only.

Commit be103eb

Browse files
Glyphackjpeach
andauthored
Add linters (#2)
* Add linters Signed-off-by: Shayegan Hooshyari <[email protected]> Co-authored-by: James Peach <[email protected]>
1 parent 042b810 commit be103eb

File tree

6 files changed

+486
-1
lines changed

6 files changed

+486
-1
lines changed

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,51 @@
11
# lint
2+
23
Go code linters for Project Contour.
4+
5+
## running
6+
```bash
7+
$ go build -o contour-lint .
8+
$ go vet -vetttool $(which contour-lint) ./...
9+
```
10+
11+
## linters
12+
13+
### importalias
14+
15+
consider import path: github.com/projectcontour/x/y/z/v\*
16+
17+
1. the alias name should be subset of `x[optional]_y[optional]_z[optional]_v*` where optional means it can be present or not.
18+
2. one of `x` or `y` or `z` must be present in alias name.
19+
3. If version exists in path, must be specified.
20+
4. words like `apis` should be `api` in import alias
21+
22+
#### Example
23+
24+
**Valid imports**
25+
26+
```go
27+
import meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
import api_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
import api_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
import envoy_api_v2_auth "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth"
31+
import envoy_config_filter_http_ext_authz_v2 "github.com/envoyproxy/go-control-plane/envoy/config/filter/http/ext_authz/v2"
32+
import contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1"
33+
import contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1"
34+
import kingpin_v2 "gopkg.in/alecthomas/kingpin.v2"
35+
import serviceapis_v1alpha1 "sigs.k8s.io/service-apis/api/v1alpha1"
36+
```
37+
38+
**Invalid imports**
39+
40+
```go
41+
import v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
42+
import meta "k8s.io/apimachinery/pkg/apis/meta/v1"
43+
import meta_api_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
44+
import api_meta "k8s.io/apimachinery/pkg/apis/meta/v1"
45+
```
46+
47+
### messagefmt
48+
linter for log message formatting.
49+
50+
- that logrus log messages are not capitalized
51+
- that kingpin command help is capitalized

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
module github.com/projectcontour/lint
22

33
go 1.15
4+
5+
require golang.org/x/tools v0.0.0-20201211025543-abf6a1d87e11

go.sum

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
2+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
3+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
4+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
5+
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
6+
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
7+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
8+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
9+
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
10+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
11+
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
12+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
13+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
14+
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
15+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
16+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
17+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
18+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
19+
golang.org/x/tools v0.0.0-20201211025543-abf6a1d87e11 h1:9j/upNXDRpADUw2RpUfJ7E7GHtfhDih62kX6JM8vs2c=
20+
golang.org/x/tools v0.0.0-20201211025543-abf6a1d87e11/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
21+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
22+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
23+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
24+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

main.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@
1313

1414
package main
1515

16+
import (
17+
"github.com/projectcontour/lint/pkg/analysis/importalias"
18+
"github.com/projectcontour/lint/pkg/analysis/messagefmt"
19+
"golang.org/x/tools/go/analysis/unitchecker"
20+
)
21+
1622
func main() {
17-
// TODO: register linters with singlechecker.Main().
23+
unitchecker.Main(
24+
messagefmt.Analyzer,
25+
importalias.Analyzer,
26+
)
1827
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Copyright Project Contour Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package importalias
15+
16+
import (
17+
"fmt"
18+
"go/ast"
19+
"regexp"
20+
"strings"
21+
22+
"golang.org/x/tools/go/analysis"
23+
"golang.org/x/tools/go/analysis/passes/inspect"
24+
"golang.org/x/tools/go/ast/inspector"
25+
)
26+
27+
// Analyzer for import aliases
28+
var Analyzer = &analysis.Analyzer{
29+
Name: "importalias",
30+
Doc: "Checks import aliases have consistent names",
31+
Run: run,
32+
Requires: []*analysis.Analyzer{inspect.Analyzer},
33+
}
34+
35+
func run(pass *analysis.Pass) (interface{}, error) {
36+
inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
37+
nodeFilter := []ast.Node{ // filter needed nodes: visit only them
38+
(*ast.ImportSpec)(nil),
39+
}
40+
41+
inspector.Preorder(nodeFilter, func(node ast.Node) {
42+
importStmt := node.(*ast.ImportSpec)
43+
44+
if importStmt.Name == nil {
45+
return
46+
}
47+
alias := importStmt.Name.Name
48+
if alias == "" {
49+
return
50+
}
51+
aliasSlice := strings.Split(alias, "_")
52+
path := strings.ReplaceAll(importStmt.Path.Value, "\"", "")
53+
// replace all separators with `/` for normalization
54+
path = strings.ReplaceAll(path, "_", "/")
55+
path = strings.ReplaceAll(path, ".", "/")
56+
path = strings.ReplaceAll(path, "-", "")
57+
// omit the domain name in path
58+
pathSlice := strings.Split(path, "/")[1:]
59+
60+
if !checkVersion(aliasSlice[len(aliasSlice)-1], pathSlice) {
61+
applicableAlias := getAliasFix(pathSlice)
62+
_, versionIndex := packageVersion(pathSlice)
63+
pass.Report(
64+
analysis.Diagnostic{
65+
Pos: node.Pos(),
66+
Message: fmt.Sprintf("version %q not specified in alias %q for import path %q", pathSlice[versionIndex], alias, path),
67+
SuggestedFixes: []analysis.SuggestedFix{
68+
{
69+
Message: fmt.Sprintf("should replace %q with %q", alias, applicableAlias),
70+
TextEdits: []analysis.TextEdit{
71+
{
72+
Pos: importStmt.Pos(),
73+
End: importStmt.Name.End(),
74+
NewText: []byte(applicableAlias),
75+
},
76+
},
77+
},
78+
},
79+
},
80+
)
81+
return
82+
}
83+
if error := checkAliasName(aliasSlice, pathSlice, pass); error != nil {
84+
applicableAlias := getAliasFix(pathSlice)
85+
pass.Report(
86+
analysis.Diagnostic{
87+
Pos: node.Pos(),
88+
Message: error.Error(),
89+
SuggestedFixes: []analysis.SuggestedFix{
90+
{
91+
Message: fmt.Sprintf("should replace %q with %q", alias, applicableAlias),
92+
TextEdits: []analysis.TextEdit{
93+
{
94+
Pos: importStmt.Pos(),
95+
End: importStmt.Name.End(),
96+
NewText: []byte(applicableAlias),
97+
},
98+
},
99+
},
100+
},
101+
},
102+
)
103+
return
104+
}
105+
})
106+
return nil, nil
107+
}
108+
109+
// checkVersion checks that if package name starts with `v` it's included in alias name
110+
func checkVersion(aliasLastWord string, pathSlice []string) bool {
111+
versionExists, versionPos := packageVersion(pathSlice)
112+
if !versionExists {
113+
return true
114+
}
115+
return aliasLastWord == pathSlice[versionPos]
116+
117+
}
118+
119+
// checkAliasName check consistency in alias name
120+
func checkAliasName(aliasSlice []string, pathSlice []string, pass *analysis.Pass) error {
121+
lastUsedWordIndex := -1
122+
for _, name := range aliasSlice {
123+
// we don't check version rule here
124+
if strings.HasPrefix(name, "v") || name == "" {
125+
continue
126+
}
127+
usedWordIndex := searchString(pathSlice, name)
128+
129+
if usedWordIndex == len(pathSlice) {
130+
return fmt.Errorf("alias %q does not contain any words from import path %q", strings.Join(aliasSlice, "_"), strings.Join(pathSlice, "/"))
131+
}
132+
133+
if usedWordIndex <= lastUsedWordIndex {
134+
return fmt.Errorf("alias %q does not match word order from import path %q", strings.Join(aliasSlice, "_"), strings.Join(pathSlice, "/"))
135+
}
136+
137+
lastUsedWordIndex = usedWordIndex
138+
}
139+
140+
if lastUsedWordIndex == -1 {
141+
return fmt.Errorf("alias %q uses words that are not in path %q", strings.Join(aliasSlice, "_"), strings.Join(pathSlice, "/"))
142+
}
143+
144+
return nil
145+
}
146+
147+
func getAliasFix(pathSlice []string) string {
148+
versionExists, versionPos := packageVersion(pathSlice)
149+
if !versionExists {
150+
return pathSlice[len(pathSlice)-1]
151+
}
152+
if versionPos == len(pathSlice)-1 {
153+
applicableAlias := pathSlice[len(pathSlice)-2] + "_" + pathSlice[versionPos]
154+
return applicableAlias
155+
}
156+
157+
applicableAlias := pathSlice[len(pathSlice)-1] + "_" + pathSlice[versionPos]
158+
return applicableAlias
159+
}
160+
161+
// packageVersion returns if some version specification exists in import path and it's position
162+
func packageVersion(pathSlice []string) (bool, int) {
163+
for pos, value := range pathSlice {
164+
r, _ := regexp.Compile("^v[0-9]+$")
165+
if r.MatchString(value) {
166+
return true, pos
167+
}
168+
}
169+
return false, 0
170+
}
171+
172+
func searchString(slice []string, word string) int {
173+
for pos, value := range slice {
174+
r, _ := regexp.Compile(word + "(s)?")
175+
if r.MatchString(value) {
176+
return pos
177+
}
178+
}
179+
return len(slice)
180+
}

0 commit comments

Comments
 (0)