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