switch_exhaustiveness_check

package
v0.1.8 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2025 License: MIT Imports: 5 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var SwitchExhaustivenessCheckRule = rule.CreateRule(rule.Rule{
	Name: "switch-exhaustiveness-check",
	Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
		opts, ok := options.(SwitchExhaustivenessCheckOptions)
		if !ok {
			opts = SwitchExhaustivenessCheckOptions{}
		}
		if opts.AllowDefaultCaseForExhaustiveSwitch == nil {
			opts.AllowDefaultCaseForExhaustiveSwitch = utils.Ref(true)
		}
		if opts.ConsiderDefaultExhaustiveForUnions == nil {
			opts.ConsiderDefaultExhaustiveForUnions = utils.Ref(false)
		}
		if opts.RequireDefaultForNonUnion == nil {
			opts.RequireDefaultForNonUnion = utils.Ref(false)
		}

		isLiteralLikeType := func(t *checker.Type) bool {
			return utils.IsTypeFlagSet(
				t,
				checker.TypeFlagsLiteral|checker.TypeFlagsUndefined|checker.TypeFlagsNull|checker.TypeFlagsUniqueESSymbol,
			)
		}

		doesTypeContainNonLiteralType := func(t *checker.Type) bool {
			return utils.Some(
				utils.UnionTypeParts(t),
				func(t *checker.Type) bool {
					return utils.Every(
						utils.IntersectionTypeParts(t),
						func(t *checker.Type) bool {
							return !isLiteralLikeType(t)
						},
					)
				},
			)
		}

		getSwitchMetadata := func(node *ast.SwitchStatement) *SwitchMetadata {
			cases := node.CaseBlock.AsCaseBlock().Clauses.Nodes
			defaultCaseIndex := slices.IndexFunc(cases, func(clause *ast.Node) bool {
				return clause.Kind == ast.KindDefaultClause
			})
			var defaultCase *ast.CaseOrDefaultClause
			if defaultCaseIndex > -1 {
				defaultCase = cases[defaultCaseIndex].AsCaseOrDefaultClause()
			}

			discriminantType := utils.GetConstrainedTypeAtLocation(ctx.TypeChecker, node.Expression)

			caseTypes := make([]*checker.Type, 0, len(cases))
			for _, c := range cases {
				if c.Kind == ast.KindDefaultClause {
					continue
				}

				caseTypes = append(caseTypes, utils.GetConstrainedTypeAtLocation(ctx.TypeChecker, c.AsCaseOrDefaultClause().Expression))
			}

			containsNonLiteralType := doesTypeContainNonLiteralType(discriminantType)

			missingLiteralBranchTypes := make([]*checker.Type, 0, 10)
			utils.TypeRecurser(discriminantType, func(t *checker.Type) bool {
				if slices.Contains(caseTypes, t) || !isLiteralLikeType(t) {
					return false
				}

				if slices.ContainsFunc(caseTypes, func(t *checker.Type) bool {
					return utils.IsTypeFlagSet(t, checker.TypeFlagsUndefined)
				}) && utils.IsTypeFlagSet(t, checker.TypeFlagsUndefined) {
					return false
				}

				missingLiteralBranchTypes = append(missingLiteralBranchTypes, t)

				return false
			})

			return &SwitchMetadata{
				ContainsNonLiteralType:    containsNonLiteralType,
				DefaultCase:               defaultCase,
				MissingLiteralBranchTypes: missingLiteralBranchTypes,
			}
		}

		checkSwitchExhaustive := func(node *ast.SwitchStatement, switchMetadata *SwitchMetadata) {

			if *opts.ConsiderDefaultExhaustiveForUnions && switchMetadata.DefaultCase != nil {
				return
			}

			if len(switchMetadata.MissingLiteralBranchTypes) > 0 {

				ctx.ReportNode(node.Expression, buildSwitchIsNotExhaustiveMessage("TODO"))
			}
		}

		checkSwitchUnnecessaryDefaultCase := func(switchMetadata *SwitchMetadata) {
			if *opts.AllowDefaultCaseForExhaustiveSwitch {
				return
			}

			if len(switchMetadata.MissingLiteralBranchTypes) == 0 &&
				switchMetadata.DefaultCase != nil &&
				!switchMetadata.ContainsNonLiteralType {
				ctx.ReportNode(&switchMetadata.DefaultCase.Node, buildDangerousDefaultCaseMessage())
			}
		}
		checkSwitchNoUnionDefaultCase := func(node *ast.SwitchStatement, switchMetadata *SwitchMetadata) {
			if !*opts.RequireDefaultForNonUnion {
				return
			}

			if switchMetadata.ContainsNonLiteralType && switchMetadata.DefaultCase == nil {
				ctx.ReportNode(node.Expression, buildSwitchIsNotExhaustiveMessage("default"))

			}
		}

		return rule.RuleListeners{
			ast.KindSwitchStatement: func(node *ast.Node) {

				stmt := node.AsSwitchStatement()

				metadata := getSwitchMetadata(stmt)
				checkSwitchExhaustive(stmt, metadata)
				checkSwitchUnnecessaryDefaultCase(metadata)
				checkSwitchNoUnionDefaultCase(stmt, metadata)
			},
		}

	},
})

Functions

This section is empty.

Types

type SwitchExhaustivenessCheckOptions

type SwitchExhaustivenessCheckOptions struct {
	AllowDefaultCaseForExhaustiveSwitch *bool
	ConsiderDefaultExhaustiveForUnions  *bool
	DefaultCaseCommentPattern           *string
	RequireDefaultForNonUnion           *bool
}

type SwitchMetadata

type SwitchMetadata struct {
	ContainsNonLiteralType bool
	// nil if there is no default case
	DefaultCase               *ast.CaseOrDefaultClause
	MissingLiteralBranchTypes []*checker.Type
}

Jump to

Keyboard shortcuts

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