switchchain

package
v0.0.0-...-d9a4191 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 19, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package switchchain detects if/else-if/else chains that should be switch statements, and rewrites the ones it can.

It is the deliberate complement of package deepifelse, which flattens a chain into guard clauses. Flattening is the right move only when every branch hands control away: that is what makes the `else` after it redundant. deepifelse therefore refuses a chain whose branches fall through ("a branch does not hand control away, so the else after it cannot be dropped"), and refuses one whose `if` headers declare variables ("run fix_if_init_assignments first"). Those two refusals cover most of the chains that were never guard clauses in the first place: a dispatch on one value, or a run of type assertions. Those are switch statements, and this analyzer converts them. A chain the flattener can already rewrite is not reported here at all, so the two fixers never compete for the same code.

Converting is structurally safer than flattening, for two reasons that between them delete most of the machinery deepifelse needs:

  • A case clause is its own implicit block, so every branch body keeps the scope it already had. Nothing is dedented, so there is no name collision to check, no "does the surrounding code still read this" analysis, and no scope bookkeeping threaded through the walk.
  • gofmt puts `case` labels at the switch's own indentation, so a branch body ends up at exactly the depth it started at. No branch is re-indented.

What is left is a question about meaning rather than layout: does the switch evaluate the same things, in the same order, as the chain did. Cases are tried top to bottom and their values left to right, which is precisely the chain's own short-circuit order, so the answer is yes for everything the conversion carries over verbatim. The only expression at risk is one the conversion *hoists* — the tag of a tagged switch, read once instead of once per comparison, and the operand of a type switch, asserted once instead of once per link. Those two are checked where they are known, in planTagged and planTypeSwitch. A tagless switch hoists nothing and needs no such check at all.

A convertible chain nested inside a branch body is left alone: the body is carried over verbatim, so the inner chain survives untouched and becomes visible to the walker on a later run, once the outer chain is a switch. That keeps every conversion to a single, non-overlapping text edit.

Index

Constants

View Source
const (
	// KindTagged is `switch x { case k: }` — every link compares one expression
	// against a value.
	KindTagged = "tagged"
	// KindTagless is `switch { case cond: }` — the general form, which needs
	// nothing of the conditions but that they are booleans.
	KindTagless = "tagless"
	// KindTypeSwitch is `switch v := x.(type) { case T: }` — every link is a
	// comma-ok type assertion tested for success.
	KindTypeSwitch = "type_switch"
)

The switch forms a chain can map onto.

Variables

View Source
var Analyzer = &analysis.Analyzer{
	Name:     "switchchain",
	Doc:      "detects if-else chains that should be switch statements",
	Run:      makeRun(defaultConfig()),
	Requires: []*analysis.Analyzer{filedata.Analyzer},
}

Functions

func NewAnalyzer

func NewAnalyzer(opts ...Option) *analysis.Analyzer

NewAnalyzer creates a configured analyzer.

Types

type Option

type Option func(*config)

Option configures the analyzer.

func WithKinds

func WithKinds(kinds ...string) Option

WithKinds restricts reporting to a subset of the switch forms. Passing no kinds leaves all three enabled.

func WithMinBranches

func WithMinBranches(n int) Option

WithMinBranches sets how many case clauses the resulting switch must have before a chain is worth reporting.

type Result

type Result struct {
	File     string `json:"file"`
	Line     int    `json:"line"`
	Column   int    `json:"column"`
	Function string `json:"function_name"`

	// Kind is the switch form this chain maps onto: "tagged", "tagless" or
	// "type_switch". It is set even when Fixable is false, so a caller can see
	// what was refused.
	Kind string `json:"kind"`
	// Branches counts the links plus the final else, i.e. how many case
	// clauses the switch would have.
	Branches   int      `json:"branches"`
	Tag        string   `json:"tag,omitempty"`
	Cases      []string `json:"cases,omitempty"`
	HasDefault bool     `json:"has_default"`

	Fixable bool   `json:"fixable"`
	Reason  string `json:"reason,omitempty"`
}

Result is the typed result returned for MCP consumption.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL