src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

qf1002.go (3441B)


      1 package qf1002
      2 
      3 import (
      4 	"fmt"
      5 	"go/ast"
      6 	"go/token"
      7 	"strings"
      8 
      9 	"honnef.co/go/tools/analysis/code"
     10 	"honnef.co/go/tools/analysis/edit"
     11 	"honnef.co/go/tools/analysis/lint"
     12 	"honnef.co/go/tools/analysis/report"
     13 	"honnef.co/go/tools/go/ast/astutil"
     14 
     15 	"golang.org/x/tools/go/analysis"
     16 	"golang.org/x/tools/go/analysis/passes/inspect"
     17 )
     18 
     19 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     20 	Analyzer: &analysis.Analyzer{
     21 		Name:     "QF1002",
     22 		Run:      run,
     23 		Requires: []*analysis.Analyzer{inspect.Analyzer},
     24 	},
     25 	Doc: &lint.RawDocumentation{
     26 		Title: "Convert untagged switch to tagged switch",
     27 		Text: `
     28 An untagged switch that compares a single variable against a series of
     29 values can be replaced with a tagged switch.`,
     30 		Before: `
     31 switch {
     32 case x == 1 || x == 2, x == 3:
     33     ...
     34 case x == 4:
     35     ...
     36 default:
     37     ...
     38 }`,
     39 
     40 		After: `
     41 switch x {
     42 case 1, 2, 3:
     43     ...
     44 case 4:
     45     ...
     46 default:
     47     ...
     48 }`,
     49 		Since:    "2021.1",
     50 		Severity: lint.SeverityHint,
     51 	},
     52 })
     53 
     54 var Analyzer = SCAnalyzer.Analyzer
     55 
     56 func run(pass *analysis.Pass) (any, error) {
     57 	fn := func(node ast.Node) {
     58 		swtch := node.(*ast.SwitchStmt)
     59 		if swtch.Tag != nil || len(swtch.Body.List) == 0 {
     60 			return
     61 		}
     62 
     63 		pairs := make([][]*ast.BinaryExpr, len(swtch.Body.List))
     64 		for i, stmt := range swtch.Body.List {
     65 			stmt := stmt.(*ast.CaseClause)
     66 			for _, cond := range stmt.List {
     67 				if !findSwitchPairs(pass, cond, &pairs[i]) {
     68 					return
     69 				}
     70 			}
     71 		}
     72 
     73 		var x ast.Expr
     74 		for _, pair := range pairs {
     75 			if len(pair) == 0 {
     76 				continue
     77 			}
     78 			if x == nil {
     79 				x = pair[0].X
     80 			} else {
     81 				if !astutil.Equal(x, pair[0].X) {
     82 					return
     83 				}
     84 			}
     85 		}
     86 		if x == nil {
     87 			// the switch only has a default case
     88 			if len(pairs) > 1 {
     89 				panic("found more than one case clause with no pairs")
     90 			}
     91 			return
     92 		}
     93 
     94 		edits := make([]analysis.TextEdit, 0, len(swtch.Body.List)+1)
     95 		for i, stmt := range swtch.Body.List {
     96 			stmt := stmt.(*ast.CaseClause)
     97 			if stmt.List == nil {
     98 				continue
     99 			}
    100 
    101 			var values []string
    102 			for _, binexpr := range pairs[i] {
    103 				y := binexpr.Y
    104 				if p, ok := y.(*ast.ParenExpr); ok {
    105 					y = p.X
    106 				}
    107 				values = append(values, report.Render(pass, y))
    108 			}
    109 
    110 			edits = append(edits, edit.ReplaceWithString(edit.Range{stmt.List[0].Pos(), stmt.Colon}, strings.Join(values, ", ")))
    111 		}
    112 		pos := swtch.Body.Lbrace
    113 		edits = append(edits, edit.ReplaceWithString(edit.Range{pos, pos}, " "+report.Render(pass, x)))
    114 		report.Report(pass, swtch, fmt.Sprintf("could use tagged switch on %s", report.Render(pass, x)),
    115 			report.Fixes(edit.Fix("Replace with tagged switch", edits...)),
    116 			report.ShortRange())
    117 	}
    118 
    119 	code.Preorder(pass, fn, (*ast.SwitchStmt)(nil))
    120 	return nil, nil
    121 }
    122 
    123 func findSwitchPairs(pass *analysis.Pass, expr ast.Expr, pairs *[]*ast.BinaryExpr) bool {
    124 	binexpr, ok := ast.Unparen(expr).(*ast.BinaryExpr)
    125 	if !ok {
    126 		return false
    127 	}
    128 	switch binexpr.Op {
    129 	case token.EQL:
    130 		if code.MayHaveSideEffects(pass, binexpr.X, nil) || code.MayHaveSideEffects(pass, binexpr.Y, nil) {
    131 			return false
    132 		}
    133 		// syntactic identity should suffice. we do not allow side
    134 		// effects in the case clauses, so there should be no way for
    135 		// values to change.
    136 		if len(*pairs) > 0 && !astutil.Equal(binexpr.X, (*pairs)[0].X) {
    137 			return false
    138 		}
    139 		*pairs = append(*pairs, binexpr)
    140 		return true
    141 	case token.LOR:
    142 		return findSwitchPairs(pass, binexpr.X, pairs) && findSwitchPairs(pass, binexpr.Y, pairs)
    143 	default:
    144 		return false
    145 	}
    146 }