sa4019.go raw
1 package sa4019
2
3 import (
4 "fmt"
5 "go/ast"
6 "sort"
7 "strings"
8
9 "honnef.co/go/tools/analysis/facts/generated"
10 "honnef.co/go/tools/analysis/lint"
11 "honnef.co/go/tools/analysis/report"
12 "honnef.co/go/tools/go/ast/astutil"
13
14 "golang.org/x/tools/go/analysis"
15 )
16
17 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
18 Analyzer: &analysis.Analyzer{
19 Name: "SA4019",
20 Run: run,
21 Requires: []*analysis.Analyzer{generated.Analyzer},
22 },
23 Doc: &lint.RawDocumentation{
24 Title: `Multiple, identical build constraints in the same file`,
25 Since: "2017.1",
26 Severity: lint.SeverityWarning,
27 MergeIf: lint.MergeIfAny,
28 },
29 })
30
31 var Analyzer = SCAnalyzer.Analyzer
32
33 func buildTagsIdentical(s1, s2 []string) bool {
34 if len(s1) != len(s2) {
35 return false
36 }
37 s1s := make([]string, len(s1))
38 copy(s1s, s1)
39 sort.Strings(s1s)
40 s2s := make([]string, len(s2))
41 copy(s2s, s2)
42 sort.Strings(s2s)
43 for i, s := range s1s {
44 if s != s2s[i] {
45 return false
46 }
47 }
48 return true
49 }
50
51 func run(pass *analysis.Pass) (interface{}, error) {
52 for _, f := range pass.Files {
53 constraints := buildTags(f)
54 for i, constraint1 := range constraints {
55 for j, constraint2 := range constraints {
56 if i >= j {
57 continue
58 }
59 if buildTagsIdentical(constraint1, constraint2) {
60 msg := fmt.Sprintf("identical build constraints %q and %q",
61 strings.Join(constraint1, " "),
62 strings.Join(constraint2, " "))
63 report.Report(pass, f, msg, report.FilterGenerated(), report.ShortRange())
64 }
65 }
66 }
67 }
68 return nil, nil
69 }
70
71 func buildTags(f *ast.File) [][]string {
72 var out [][]string
73 for _, line := range strings.Split(astutil.Preamble(f), "\n") {
74 if !strings.HasPrefix(line, "+build ") {
75 continue
76 }
77 line = strings.TrimSpace(strings.TrimPrefix(line, "+build "))
78 fields := strings.Fields(line)
79 out = append(out, fields)
80 }
81 return out
82 }
83