Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make checkswitch handle regular switches #4162

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 64 additions & 0 deletions tools/checkswitch/checkswitch.go
Expand Up @@ -18,6 +18,7 @@ package main
import (
"fmt"
"go/ast"
"go/token"

"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/singlechecker"
Expand Down Expand Up @@ -146,6 +147,69 @@ func run(pass *analysis.Pass) (any, error) {
}
}
}
// Handle *ast.SwitchStmt, which represents a switch statement in Go.
case *ast.SwitchStmt:
// Declare a variable to hold the name of the case.
var name string

// Iterate over each case in the switch statement.
for _, el := range n.Body.List {
// If a case has no expressions (i.e., it's a default case), skip to the next case.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually have default cases? I think we have linter enabled to check that switches are exhaustive. Maybe that tool should double-check that too.

if len(el.(*ast.CaseClause).List) < 1 {
continue
}

// Get the first expression from the case clause.
firstTypeCase := el.(*ast.CaseClause).List[0]

// Check if the first expression is a basic literal of type string.
switch firstTypeCase := firstTypeCase.(type) {
case *ast.BasicLit:
if firstTypeCase.Kind == token.STRING {
// If it is, assign the value of the literal to name.
name = firstTypeCase.Value
}
}

// Check if the name is in the orderTypes map.
idxSl, ok := orderTypes[name]
if ok && (idxSl < idx) {
// If it is, and the current index is less than the previous index,
// report that the current case should go before the last case.
pass.Reportf(n.Pos(), "%s should go before %s in the switch", name, lastName)
}

// Update the index and last name to the current index and name.
idx, lastName = idxSl, name

// If a case has more than one expression (e.g., 'case "tag1", "tag2":'),
// check the order of each expression.
if len(el.(*ast.CaseClause).List) > 1 {
// Initialize subindex and sublastname with current index and name.
subidx, sublastName := idx, lastName
for i := 0; i < len(el.(*ast.CaseClause).List); i++ {
cs := el.(*ast.CaseClause).List[i]
switch cs := cs.(type) {
case *ast.BasicLit:
if cs.Kind == token.STRING {
// If it is, assign the value of the literal to name.
name = cs.Value
}
}

// Check if the name is in the orderTypes map.
subidxSl, ok := orderTypes[name]
if ok && (subidxSl < subidx) {
// If it is, and the current subindex is less than the previous subindex,
// report that the current case should go before the last case.
pass.Reportf(n.Pos(), "%s should go before %s in the switch", name, sublastName)
}

// Update the subindex and sublastname to the current subindex and name.
subidx, sublastName = subidxSl, name
}
}
}
}

return true
Expand Down
37 changes: 37 additions & 0 deletions tools/checkswitch/testdata/testdata.go
Expand Up @@ -57,3 +57,40 @@ func testIncorrectMultiple(v any) {
case float64, int64, int32:
}
}

func testCorrectOrderSwitch() {
tag := "tag1"
switch tag {
case "tag1":
case "tag2":
}
}

func testSkipFirstOrderSwitch() {
tag := "tag1"
switch tag {
case "tag2":
case "tag1":
}
}

func testMultipleCasesSwitch() {
tag := "tag1"
switch tag {
case "tag2", "tag3":
case "tag1":
}
}

func testNoCasesSwitch() {
tag := "tag1"
switch tag {
}
}

func testOneCaseSwitch() {
tag := "tag1"
switch tag {
case "tag1":
}
}