restrict_plus_operands

package
v0.0.0-...-4178710 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 2025 License: MIT Imports: 6 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var RestrictPlusOperandsRule = rule.Rule{
	Name: "restrict-plus-operands",
	Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
		opts, ok := options.(RestrictPlusOperandsOptions)
		if !ok {
			opts = RestrictPlusOperandsOptions{}
		}
		if opts.AllowAny == nil {
			opts.AllowAny = utils.Ref(true)
		}
		if opts.AllowBoolean == nil {
			opts.AllowBoolean = utils.Ref(true)
		}
		if opts.AllowNullish == nil {
			opts.AllowNullish = utils.Ref(true)
		}
		if opts.AllowNumberAndString == nil {
			opts.AllowNumberAndString = utils.Ref(true)
		}
		if opts.AllowRegExp == nil {
			opts.AllowRegExp = utils.Ref(true)
		}
		if opts.SkipCompoundAssignments == nil {
			opts.SkipCompoundAssignments = utils.Ref(false)
		}

		stringLikes := make([]string, 0, 5)
		if *opts.AllowAny {
			stringLikes = append(stringLikes, "`any`")
		}
		if *opts.AllowBoolean {
			stringLikes = append(stringLikes, "`boolean`")
		}
		if *opts.AllowNullish {
			stringLikes = append(stringLikes, "`null`")
		}
		if *opts.AllowRegExp {
			stringLikes = append(stringLikes, "`RegExp`")
		}
		if *opts.AllowNullish {
			stringLikes = append(stringLikes, "`undefined`")
		}
		var stringLike string
		switch len(stringLikes) {
		case 0:
			stringLike = "string"
		case 1:
			stringLike = "string, allowing a string + " + stringLikes[0]
		default:
			stringLike = "string, allowing a string + any of: " + strings.Join(stringLikes, ", ")
		}

		getTypeConstrained := func(node *ast.Node) *checker.Type {
			return checker.Checker_getBaseTypeOfLiteralType(ctx.TypeChecker, utils.GetConstrainedTypeAtLocation(ctx.TypeChecker, node))
		}

		globalRegexpType := checker.Checker_globalRegExpType(ctx.TypeChecker)

		invalidFlags := checker.TypeFlagsESSymbolLike |
			checker.TypeFlagsNever |
			checker.TypeFlagsUnknown
		if !*opts.AllowAny {
			invalidFlags |= checker.TypeFlagsAny
		}
		if !*opts.AllowBoolean {
			invalidFlags |= checker.TypeFlagsBooleanLike
		}
		if !*opts.AllowNullish {
			invalidFlags |= checker.TypeFlagsNullable
		}

		checkInvalidPlusOperand := func(baseNode *ast.Node, baseType, otherType *checker.Type) (checker.TypeFlags, bool) {
			foundRegexp := false

			var flags checker.TypeFlags

			reported := false
			for _, part := range utils.UnionTypeParts(baseType) {
				flags |= checker.Type_flags(part)
				if reported {
					continue
				}
				if utils.IsTypeFlagSet(part, invalidFlags) {
					ctx.ReportNode(baseNode, buildInvalidMessage(stringLike, ctx.TypeChecker.TypeToString(baseType)))
					reported = true
					continue
				}

				if part == globalRegexpType {
					if *opts.AllowRegExp && !utils.IsTypeFlagSet(otherType, checker.TypeFlagsNumberLike) {
						continue
					}
				} else if (*opts.AllowAny || !utils.IsTypeAnyType(part)) && !utils.Every(utils.IntersectionTypeParts(part), utils.IsObjectType) {
					continue
				}
				foundRegexp = true
			}

			if !reported && foundRegexp {
				ctx.ReportNode(baseNode, buildInvalidMessage(stringLike, ctx.TypeChecker.TypeToString(globalRegexpType)))
				return flags, true
			}

			return flags, reported
		}

		checkPlusOperands := func(
			node *ast.BinaryExpression,
		) {
			leftType := getTypeConstrained(node.Left)
			rightType := getTypeConstrained(node.Right)

			if leftType == rightType &&
				utils.IsTypeFlagSet(
					leftType,
					checker.TypeFlagsBigIntLike|
						checker.TypeFlagsNumberLike|
						checker.TypeFlagsStringLike,
				) {
				return
			}

			leftTypeFlags, leftInvalid := checkInvalidPlusOperand(node.Left, leftType, rightType)
			rightTypeFlags, rightInvalid := checkInvalidPlusOperand(node.Right, rightType, leftType)
			if leftInvalid || rightInvalid {
				return
			}

			checkMismatchedPlusOperands := func(baseTypeFlags, otherTypeFlags checker.TypeFlags) bool {
				if !*opts.AllowNumberAndString &&
					baseTypeFlags&checker.TypeFlagsStringLike != 0 &&
					otherTypeFlags&(checker.TypeFlagsNumberLike|checker.TypeFlagsBigIntLike) != 0 {
					ctx.ReportNode(&node.Node, buildMismatchedMessage(stringLike, ctx.TypeChecker.TypeToString(leftType), ctx.TypeChecker.TypeToString(rightType)))
					return true
				}

				if baseTypeFlags&checker.TypeFlagsNumberLike != 0 && otherTypeFlags&checker.TypeFlagsBigIntLike != 0 {
					ctx.ReportNode(&node.Node, buildBigintAndNumberMessage(ctx.TypeChecker.TypeToString(leftType), ctx.TypeChecker.TypeToString(rightType)))
					return true
				}

				return false
			}

			if checkMismatchedPlusOperands(leftTypeFlags, rightTypeFlags) {
				return
			}
			checkMismatchedPlusOperands(rightTypeFlags, leftTypeFlags)
		}

		return rule.RuleListeners{
			ast.KindBinaryExpression: func(node *ast.Node) {
				expr := node.AsBinaryExpression()
				if expr.OperatorToken.Kind == ast.KindPlusToken || (!*opts.SkipCompoundAssignments && expr.OperatorToken.Kind == ast.KindPlusEqualsToken) {
					checkPlusOperands(expr)
				}
			},
		}
	},
}

Functions

This section is empty.

Types

type RestrictPlusOperandsOptions

type RestrictPlusOperandsOptions struct {
	AllowAny                *bool
	AllowBoolean            *bool
	AllowNullish            *bool
	AllowNumberAndString    *bool
	AllowRegExp             *bool
	SkipCompoundAssignments *bool
}

Jump to

Keyboard shortcuts

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