no_floating_promises

package
v0.1.4 Latest Latest
Warning

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

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

Documentation

Index

Constants

This section is empty.

Variables

View Source
var NoFloatingPromisesRule = rule.CreateRule(rule.Rule{
	Name: "no-floating-promises",
	Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
		opts, ok := options.(NoFloatingPromisesOptions)
		if !ok {

			opts = NoFloatingPromisesOptions{
				AllowForKnownSafeCalls:          []utils.TypeOrValueSpecifier{},
				AllowForKnownSafeCallsInline:    []string{},
				AllowForKnownSafePromises:       []utils.TypeOrValueSpecifier{},
				AllowForKnownSafePromisesInline: []string{},
			}

			options_array, _ := options.([]interface{})

			if len(options_array) > 0 {
				optsJSON, err := json.Marshal(options_array[0])
				if err == nil {
					json.Unmarshal(optsJSON, &opts)
				}

			}

		}
		if opts.CheckThenables == nil {
			opts.CheckThenables = utils.Ref(false)
		}
		if opts.IgnoreIIFE == nil {
			opts.IgnoreIIFE = utils.Ref(false)
		}
		if opts.IgnoreVoid == nil {
			opts.IgnoreVoid = utils.Ref(true)
		}
		isHigherPrecedenceThanUnary := func(node *ast.Node) bool {
			operator := ast.KindUnknown
			if ast.IsBinaryExpression(node) {
				operator = node.AsBinaryExpression().OperatorToken.Kind
			}
			nodePrecedence := ast.GetOperatorPrecedence(node.Kind, operator, ast.OperatorPrecedenceFlagsNone)
			return nodePrecedence > ast.OperatorPrecedenceUnary
		}

		addAwait := func(
			expression *ast.Expression,
			node *ast.ExpressionStatement,
		) []rule.RuleFix {
			if ast.IsVoidExpression(expression) {
				voidTokenRange := scanner.GetRangeOfTokenAtPosition(ctx.SourceFile, expression.Pos())
				return []rule.RuleFix{rule.RuleFixReplaceRange(voidTokenRange, "await")}
			}
			if isHigherPrecedenceThanUnary(node.Expression) {
				return []rule.RuleFix{rule.RuleFixInsertBefore(ctx.SourceFile, &node.Node, "await ")}
			}
			return []rule.RuleFix{
				rule.RuleFixInsertBefore(ctx.SourceFile, &node.Node, "await ("),
				rule.RuleFixInsertAfter(expression, ")"),
			}
		}
		hasMatchingSignature := func(
			t *checker.Type,
			matcher func(signature *checker.Signature) bool,
		) bool {
			for _, part := range utils.UnionTypeParts(t) {
				if utils.Some(utils.GetCallSignatures(ctx.TypeChecker, part), matcher) {
					return true
				}
			}

			return false
		}

		isFunctionParam := func(
			param *ast.Symbol,
			node *ast.Node,
		) bool {
			t := checker.Checker_getApparentType(ctx.TypeChecker, ctx.TypeChecker.GetTypeOfSymbolAtLocation(param, node))

			for _, part := range utils.UnionTypeParts(t) {
				if len(utils.GetCallSignatures(ctx.TypeChecker, part)) != 0 {
					return true
				}
			}
			return false
		}
		isPromiseLike := func(node *ast.Node, t *checker.Type) bool {
			if t == nil {
				t = ctx.TypeChecker.GetTypeAtLocation(node)
			}

			if utils.TypeMatchesSomeSpecifier(
				t,
				opts.AllowForKnownSafePromises,
				opts.AllowForKnownSafePromisesInline,
				ctx.Program,
			) {
				return false
			}

			typeParts := utils.UnionTypeParts(checker.Checker_getApparentType(ctx.TypeChecker, t))
			if utils.Some(typeParts, func(typePart *checker.Type) bool {
				return utils.IsPromiseLike(ctx.Program, ctx.TypeChecker, typePart)
			}) {
				return true
			}

			if !*opts.CheckThenables {
				return false
			}

			for _, typePart := range typeParts {
				then := checker.Checker_getPropertyOfType(ctx.TypeChecker, typePart, "then")
				if then == nil {
					continue
				}

				thenType := ctx.TypeChecker.GetTypeOfSymbolAtLocation(then, node)
				if hasMatchingSignature(
					thenType,
					func(signature *checker.Signature) bool {
						params := checker.Signature_parameters(signature)
						return len(params) >= 2 && isFunctionParam(params[0], node) && isFunctionParam(params[1], node)
					}) {
					return true
				}
			}
			return false
		}
		isPromiseArray := func(node *ast.Node) bool {
			t := ctx.TypeChecker.GetTypeAtLocation(node)
			for _, typePart := range utils.UnionTypeParts(t) {
				apparent := checker.Checker_getApparentType(ctx.TypeChecker, typePart)

				if checker.Checker_isArrayType(ctx.TypeChecker, apparent) {
					arrayType := checker.Checker_getTypeArguments(ctx.TypeChecker, apparent)[0]
					if isPromiseLike(node, arrayType) {
						return true
					}
				}

				if checker.IsTupleType(apparent) {
					for _, tupleElementType := range checker.Checker_getTypeArguments(ctx.TypeChecker, apparent) {
						if isPromiseLike(node, tupleElementType) {
							return true
						}
					}
				}
			}
			return false
		}

		isKnownSafePromiseReturn := func(node *ast.Node) bool {
			if !ast.IsCallExpression(node) {
				return false
			}

			t := ctx.TypeChecker.GetTypeAtLocation(node.AsCallExpression().Expression)

			return utils.TypeMatchesSomeSpecifier(
				t,
				opts.AllowForKnownSafeCalls,
				opts.AllowForKnownSafeCallsInline,
				ctx.Program,
			)
		}

		isAsyncIife := func(node *ast.ExpressionStatement) bool {
			if !ast.IsCallExpression(node.Expression) {
				return false
			}

			callee := ast.SkipParentheses(node.Expression.AsCallExpression().Expression)

			return ast.IsArrowFunction(callee) || ast.IsFunctionExpression(callee)
		}

		isValidRejectionHandler := func(rejectionHandler *ast.Node) bool {
			return len(utils.GetCallSignatures(ctx.TypeChecker, ctx.TypeChecker.GetTypeAtLocation(rejectionHandler))) > 0
		}

		var isUnhandledPromise func(
			node *ast.Node,
		) (
			bool,
			bool,
			bool,
		)
		isUnhandledPromise = func(
			node *ast.Node,
		) (
			bool,
			bool,
			bool,
		) {
			if ast.IsAssignmentExpression(node, false) {
				return false, false, false
			}

			if ast.IsCommaExpression(node) {
				expr := node.AsBinaryExpression()

				isUnhandled, nonFunctionHandler, promiseArray := isUnhandledPromise(expr.Left)
				if isUnhandled {
					return isUnhandled, nonFunctionHandler, promiseArray
				}
				return isUnhandledPromise(expr.Right)
			}

			if !*opts.IgnoreVoid && ast.IsVoidExpression(node) {

				return isUnhandledPromise(node.Expression())
			}

			if isPromiseArray(node) {
				return true, false, true
			}

			if ast.IsAwaitExpression(node) {

				return false, false, false
			}

			if !isPromiseLike(node, nil) {
				return false, false, false
			}

			if ast.IsCallExpression(node) {

				callExpr := node.AsCallExpression()
				callee := callExpr.Expression
				if ast.IsAccessExpression(callee) {

					methodName, _ := checker.Checker_getAccessedPropertyName(ctx.TypeChecker, callee)

					if methodName == "catch" && len(callExpr.Arguments.Nodes) >= 1 {
						if isValidRejectionHandler(callExpr.Arguments.Nodes[0]) {
							return false, false, false
						}
						return true, true, false
					}
					if methodName == "then" && len(callExpr.Arguments.Nodes) >= 2 {
						if isValidRejectionHandler(callExpr.Arguments.Nodes[1]) {
							return false, false, false
						}
						return true, true, false
					}

					if methodName == "finally" {
						return isUnhandledPromise(callee.Expression())
					}
				}

				return true, false, false
			}

			if node.Kind == ast.KindConditionalExpression {
				expr := node.AsConditionalExpression()

				isUnhandled, nonFunctionHandler, promiseArray := isUnhandledPromise(expr.WhenFalse)
				if isUnhandled {
					return isUnhandled, nonFunctionHandler, promiseArray
				}
				return isUnhandledPromise(expr.WhenTrue)
			}

			if ast.IsLogicalOrCoalescingBinaryExpression(node) {
				expr := node.AsBinaryExpression()
				isUnhandled, nonFunctionHandler, promiseArray := isUnhandledPromise(expr.Left)
				if isUnhandled {
					return isUnhandled, nonFunctionHandler, promiseArray
				}
				return isUnhandledPromise(expr.Right)
			}

			return true, false, false
		}

		return rule.RuleListeners{
			ast.KindExpressionStatement: func(node *ast.Node) {
				exprStatement := node.AsExpressionStatement()

				if *opts.IgnoreIIFE && isAsyncIife(exprStatement) {
					return
				}

				expression := ast.SkipParentheses(exprStatement.Expression)

				if isKnownSafePromiseReturn(expression) {
					return
				}

				isUnhandled, nonFunctionHandler, promiseArray := isUnhandledPromise(expression)

				if !isUnhandled {
					return
				}
				if promiseArray {
					var msg rule.RuleMessage
					if *opts.IgnoreVoid {
						msg = buildFloatingPromiseArrayVoidMessage()
					} else {
						msg = buildFloatingPromiseArrayMessage()
					}
					ctx.ReportNode(node, msg)
				} else if *opts.IgnoreVoid {
					var msg rule.RuleMessage
					if nonFunctionHandler {
						msg = buildFloatingUselessRejectionHandlerVoidMessage()
					} else {
						msg = buildFloatingVoidMessage()
					}

					ctx.ReportNodeWithSuggestions(node, msg, rule.RuleSuggestion{
						Message: buildFloatingFixVoidMessage(),
						FixesArr: (func() []rule.RuleFix {
							if isHigherPrecedenceThanUnary(exprStatement.Expression) {
								return []rule.RuleFix{rule.RuleFixInsertBefore(ctx.SourceFile, node, "void ")}
							}
							return []rule.RuleFix{
								rule.RuleFixInsertBefore(ctx.SourceFile, node, "void ("),
								rule.RuleFixInsertAfter(expression, ")"),
							}
						})(),
					}, rule.RuleSuggestion{
						Message:  buildFloatingFixAwaitMessage(),
						FixesArr: addAwait(expression, exprStatement),
					})
				} else {
					var msg rule.RuleMessage
					if nonFunctionHandler {
						msg = buildFloatingUselessRejectionHandlerMessage()
					} else {
						msg = buildFloatingMessage()
					}
					ctx.ReportNodeWithSuggestions(node, msg, rule.RuleSuggestion{
						Message:  buildFloatingFixAwaitMessage(),
						FixesArr: addAwait(expression, exprStatement),
					})
				}
			},
		}
	},
})

Functions

This section is empty.

Types

type NoFloatingPromisesOptions

type NoFloatingPromisesOptions struct {
	AllowForKnownSafeCalls          []utils.TypeOrValueSpecifier `json:"allowForKnownSafeCalls"`
	AllowForKnownSafeCallsInline    []string                     `json:"allowForKnownSafeCallsInline"`
	AllowForKnownSafePromises       []utils.TypeOrValueSpecifier `json:"allowForKnownSafePromises"`
	AllowForKnownSafePromisesInline []string                     `json:"allowForKnownSafePromisesInline"`
	CheckThenables                  *bool                        `json:"checkThenables"`
	IgnoreIIFE                      *bool                        `json:"ignoreIIFE"`
	IgnoreVoid                      *bool                        `json:"ignoreVoid"`
}

Jump to

Keyboard shortcuts

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