github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa4019/sa4019.go (about)

     1  package sa4019
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/amarpal/go-tools/analysis/facts/generated"
    10  	"github.com/amarpal/go-tools/analysis/lint"
    11  	"github.com/amarpal/go-tools/analysis/report"
    12  	"github.com/amarpal/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.Documentation{
    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  }