no_implicit_coercion

package
v0.5.3 Latest Latest
Warning

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

Go to latest
Published: May 14, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var NoImplicitCoercionRule = rule.Rule{
	Name: "no-implicit-coercion",
	Run: func(ctx rule.RuleContext, rawOptions any) rule.RuleListeners {
		opts := parseOptions(rawOptions)

		report := func(node *ast.Node, recommendation string, shouldSuggest, shouldFix bool) {
			msg := rule.RuleMessage{
				Id:          "implicitCoercion",
				Description: "Unexpected implicit coercion encountered. Use `" + recommendation + "` instead.",
			}
			replacement := recommendation

			start := utils.TrimNodeTextRange(ctx.SourceFile, node).Pos()
			if utils.NeedsLeadingSpaceForReplacement(ctx.SourceFile.Text(), start, recommendation) {
				replacement = " " + recommendation
			}

			if shouldFix {
				ctx.ReportNodeWithFixes(node, msg, rule.RuleFixReplace(ctx.SourceFile, node, replacement))
				return
			}
			if shouldSuggest {
				ctx.ReportNodeWithSuggestions(node, msg, rule.RuleSuggestion{
					Message: rule.RuleMessage{
						Id:          "useRecommendation",
						Description: "Use `" + recommendation + "` instead.",
					},
					FixesArr: []rule.RuleFix{rule.RuleFixReplace(ctx.SourceFile, node, replacement)},
				})
				return
			}
			ctx.ReportNode(node, msg)
		}

		checkUnary := func(node *ast.Node) {
			pue := node.AsPrefixUnaryExpression()
			if pue == nil || pue.Operand == nil {
				return
			}

			if opts.boolean && !opts.allow.Has("!!") && isDoubleLogicalNegating(pue) {
				innerPue := ast.SkipParentheses(pue.Operand).AsPrefixUnaryExpression()
				target := ast.SkipParentheses(innerPue.Operand)
				recommendation := "Boolean(" + utils.TrimmedNodeText(ctx.SourceFile, target) + ")"
				shouldFix := !utils.IsShadowed(node, "Boolean")
				report(node, recommendation, true, shouldFix)
			}

			if opts.boolean && !opts.allow.Has("~") && isBinaryNegatingOfIndexOf(pue) {
				callNode := ast.SkipParentheses(pue.Operand)
				comparison := "!== -1"
				if ast.IsOptionalChain(callNode) {
					comparison = ">= 0"
				}
				recommendation := utils.TrimmedNodeText(ctx.SourceFile, callNode) + " " + comparison
				report(node, recommendation, false, false)
			}

			if opts.number && !opts.allow.Has("+") && pue.Operator == ast.KindPlusToken {
				operand := ast.SkipParentheses(pue.Operand)
				if !isNumeric(operand) {
					recommendation := "Number(" + utils.TrimmedNodeText(ctx.SourceFile, operand) + ")"
					report(node, recommendation, true, false)
				}
			}

			if opts.number && !opts.allow.Has("- -") && pue.Operator == ast.KindMinusToken {
				inner := ast.SkipParentheses(pue.Operand)
				if inner != nil && inner.Kind == ast.KindPrefixUnaryExpression {
					innerPue := inner.AsPrefixUnaryExpression()
					operand := ast.SkipParentheses(innerPue.Operand)
					if innerPue.Operator == ast.KindMinusToken && !isNumeric(operand) {
						recommendation := "Number(" + utils.TrimmedNodeText(ctx.SourceFile, operand) + ")"
						report(node, recommendation, true, false)
					}
				}
			}
		}

		checkBinary := func(node *ast.Node) {
			bin := node.AsBinaryExpression()
			if bin == nil || bin.OperatorToken == nil {
				return
			}

			switch bin.OperatorToken.Kind {
			case ast.KindAsteriskToken:

				if opts.number && !opts.allow.Has("*") && isMultiplyByOne(bin) && !isMultiplyByFractionOfOne(node) {
					if operand := getNonNumericOperand(bin); operand != nil {
						recommendation := "Number(" + utils.TrimmedNodeText(ctx.SourceFile, operand) + ")"
						report(node, recommendation, true, false)
					}
				}
			case ast.KindMinusToken:

				if opts.number && !opts.allow.Has("-") {
					left := ast.SkipParentheses(bin.Left)
					if isLiteralZero(ast.SkipParentheses(bin.Right)) && !isNumeric(left) {
						recommendation := "Number(" + utils.TrimmedNodeText(ctx.SourceFile, left) + ")"
						report(node, recommendation, true, false)
					}
				}
			case ast.KindPlusToken:

				if opts.str && !opts.allow.Has("+") && isConcatWithEmptyString(bin) {
					operand := ast.SkipParentheses(getNonEmptyOperand(bin))
					recommendation := "String(" + utils.TrimmedNodeText(ctx.SourceFile, operand) + ")"
					report(node, recommendation, true, false)
				}
			case ast.KindPlusEqualsToken:

				if opts.str && !opts.allow.Has("+") && isEmptyString(ast.SkipParentheses(bin.Right)) {
					leftText := utils.TrimmedNodeText(ctx.SourceFile, ast.SkipParentheses(bin.Left))
					recommendation := leftText + " = String(" + leftText + ")"
					report(node, recommendation, true, false)
				}
			}
		}

		checkTemplate := func(node *ast.Node) {
			if !opts.disallowTemplateShorthand {
				return
			}

			if node.Parent != nil && node.Parent.Kind == ast.KindTaggedTemplateExpression {
				return
			}
			te := node.AsTemplateExpression()
			if te == nil || te.Head == nil || te.TemplateSpans == nil {
				return
			}

			if len(te.TemplateSpans.Nodes) != 1 || te.Head.Text() != "" {
				return
			}
			span := te.TemplateSpans.Nodes[0].AsTemplateSpan()
			if span == nil || span.Literal == nil || span.Expression == nil || span.Literal.Text() != "" {
				return
			}

			expr := ast.SkipParentheses(span.Expression)
			if isStringType(expr) {
				return
			}
			recommendation := "String(" + utils.TrimmedNodeText(ctx.SourceFile, expr) + ")"
			report(node, recommendation, true, false)
		}

		return rule.RuleListeners{
			ast.KindPrefixUnaryExpression: checkUnary,

			rule.ListenerOnExit(ast.KindBinaryExpression): checkBinary,
			ast.KindTemplateExpression:                    checkTemplate,
		}
	},
}

NoImplicitCoercionRule disallows shorthand type conversions, suggesting explicit `Boolean()` / `Number()` / `String()` calls instead. https://eslint.org/docs/latest/rules/no-implicit-coercion

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

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