no_misused_promises

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: 6 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var NoMisusedPromisesRule = rule.CreateRule(rule.Rule{
	Name: "no-misused-promises",
	Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
		opts, ok := options.(NoMisusedPromisesOptions)
		if !ok {
			opts = NoMisusedPromisesOptions{}
		}
		if opts.ChecksConditionals == nil {
			opts.ChecksConditionals = utils.Ref(true)
		}
		if opts.ChecksSpreads == nil {
			opts.ChecksSpreads = utils.Ref(true)
		}
		if opts.ChecksVoidReturn == nil {
			opts.ChecksVoidReturn = utils.Ref(true)
		}
		if opts.ChecksVoidReturnOpts == nil {
			opts.ChecksVoidReturnOpts = utils.Ref(NoMisusedPromisesChecksVoidReturnOptions{})
		}
		if opts.ChecksVoidReturnOpts.Arguments == nil {
			opts.ChecksVoidReturnOpts.Arguments = utils.Ref(true)
		}
		if opts.ChecksVoidReturnOpts.Attributes == nil {
			opts.ChecksVoidReturnOpts.Attributes = utils.Ref(true)
		}
		if opts.ChecksVoidReturnOpts.InheritedMethods == nil {
			opts.ChecksVoidReturnOpts.InheritedMethods = utils.Ref(true)
		}
		if opts.ChecksVoidReturnOpts.Properties == nil {
			opts.ChecksVoidReturnOpts.Properties = utils.Ref(true)
		}
		if opts.ChecksVoidReturnOpts.Returns == nil {
			opts.ChecksVoidReturnOpts.Returns = utils.Ref(true)
		}
		if opts.ChecksVoidReturnOpts.Variables == nil {
			opts.ChecksVoidReturnOpts.Variables = utils.Ref(true)
		}

		anySignatureIsThenableType := func(
			node *ast.Node,
			t *checker.Type,
		) bool {
			return utils.Some(utils.GetCallSignatures(ctx.TypeChecker, t), func(sig *checker.Signature) bool {
				return utils.IsThenableType(ctx.TypeChecker, node, checker.Checker_getReturnTypeOfSignature(ctx.TypeChecker, sig))
			})
		}

		returnsThenable := func(node *ast.Node) bool {
			t := checker.Checker_getApparentType(ctx.TypeChecker, ctx.TypeChecker.GetTypeAtLocation(node))
			return utils.Some(utils.UnionTypeParts(t), func(t *checker.Type) bool {
				return anySignatureIsThenableType(node, t)
			})
		}

		checkArrayPredicates := func(node *ast.Node) {
			parent := node.Parent

			if !ast.IsCallExpression(parent) {
				return
			}

			expr := parent.AsCallExpression()
			arguments := expr.Arguments.Nodes
			if len(arguments) == 0 {
				return
			}

			callback := arguments[0]

			if utils.IsArrayMethodCallWithPredicate(ctx.TypeChecker, expr) && returnsThenable(callback) {
				ctx.ReportNode(callback, buildPredicateMessage())
			}
		}

		isFunctionParam := func(
			param *ast.Symbol,
			node *ast.Node,
		) bool {
			t := checker.Checker_getApparentType(ctx.TypeChecker, ctx.TypeChecker.GetTypeOfSymbolAtLocation(param, node))
			if t == nil {
				return false
			}
			return utils.Some(utils.UnionTypeParts(t), func(t *checker.Type) bool {
				return len(utils.GetCallSignatures(ctx.TypeChecker, t)) != 0
			})
		}

		isAlwaysThenable := func(node *ast.Node) bool {
			t := ctx.TypeChecker.GetTypeAtLocation(node)

			for _, subType := range utils.UnionTypeParts(checker.Checker_getApparentType(ctx.TypeChecker, t)) {
				thenProp := checker.Checker_getPropertyOfType(ctx.TypeChecker, subType, "then")

				if thenProp == nil {
					return false
				}

				thenType := ctx.TypeChecker.GetTypeOfSymbolAtLocation(thenProp, node)
				hasThenableSignature := false
				for _, subType := range utils.UnionTypeParts(thenType) {
					for _, signature := range utils.GetCallSignatures(ctx.TypeChecker, subType) {
						params := checker.Signature_parameters(signature)
						if len(params) != 0 && isFunctionParam(params[0], node) {
							hasThenableSignature = true
						}
					}

					if hasThenableSignature {
						break
					}
				}

				if !hasThenableSignature {
					return false
				}
			}

			return true
		}

		checkedNodes := map[*ast.Node](struct{}){}

		var checkConditional func(
			node *ast.Expression,
			isTestExpr bool,
		)
		checkConditional = func(
			node *ast.Expression,
			isTestExpr bool,
		) {
			if node == nil || ast.IsAssignmentExpression(node, false) {
				return
			}

			if _, ok := checkedNodes[node]; ok {
				return
			}
			checkedNodes[node] = struct{}{}

			node = ast.SkipParentheses(node)

			if ast.IsBinaryExpression(node) && ast.IsLogicalExpression(node) {
				expr := node.AsBinaryExpression()

				if expr.OperatorToken.Kind != ast.KindQuestionQuestionToken || isTestExpr {
					checkConditional(expr.Left, isTestExpr)
				}

				if isTestExpr {
					checkConditional(expr.Right, isTestExpr)
				}
				return
			}

			if isAlwaysThenable(node) {
				ctx.ReportNode(node, buildConditionalMessage())
			}
		}

		getMemberIfExists := func(
			t *checker.Type,
			memberName string,
		) *ast.Symbol {

			symbol := checker.Type_symbol(t)
			if symbol != nil {
				symbol = symbol.Members[memberName]
			}
			if symbol != nil {
				return symbol
			}

			return checker.Checker_getPropertyOfType(ctx.TypeChecker, t, memberName)
		}

		isVoidReturningFunctionType := func(
			node *ast.Node,
			t *checker.Type,
		) bool {
			hadVoidReturn := false
			for _, t := range utils.UnionTypeParts(t) {
				for _, sig := range utils.GetCallSignatures(ctx.TypeChecker, t) {
					returnType := checker.Checker_getReturnTypeOfSignature(ctx.TypeChecker, sig)

					if utils.IsThenableType(ctx.TypeChecker, node, returnType) {
						return false
					}

					hadVoidReturn = hadVoidReturn || utils.IsTypeFlagSet(returnType, checker.TypeFlagsVoid)
				}
			}
			return hadVoidReturn
		}

		checkHeritageTypeForMemberReturningVoid := func(
			nodeMember *ast.Node,
			heritageType *checker.Type,
			memberName string,
		) {
			heritageMember := getMemberIfExists(heritageType, memberName)
			if heritageMember == nil {
				return
			}
			memberType := ctx.TypeChecker.GetTypeOfSymbolAtLocation(
				heritageMember,
				nodeMember,
			)
			if !isVoidReturningFunctionType(nodeMember, memberType) {
				return
			}
			ctx.ReportNode(nodeMember, buildVoidReturnInheritedMethodMessage(ctx.TypeChecker.TypeToString(heritageType)))
		}

		checkJSXAttribute := func(node *ast.JsxAttribute) {
			if node.Initializer == nil || node.Initializer.Kind != ast.KindJsxExpression {
				return
			}
			expressionContainer := node.Initializer.AsJsxExpression()
			expression := expressionContainer.Expression
			contextualType := checker.Checker_getContextualType(ctx.TypeChecker, node.Initializer, checker.ContextFlagsNone)
			if contextualType != nil && isVoidReturningFunctionType(node.Initializer, contextualType) && returnsThenable(expression) {
				ctx.ReportNode(node.Initializer, buildVoidReturnAttributeMessage())
			}
		}

		checkSpread := func(node *ast.Node) {
			if utils.IsThenableType(ctx.TypeChecker, node.Expression(), nil) {
				ctx.ReportNode(node.Expression(), buildSpreadMessage())
			}
		}

		isThenableReturningFunctionType := func(
			node *ast.Node,
			t *checker.Type,
		) bool {
			return utils.Some(utils.UnionTypeParts(t), func(t *checker.Type) bool {
				return anySignatureIsThenableType(node, t)
			})
		}

		var checkThenableOrVoidArgument func(
			node *ast.Expression,
			t *checker.Type,
			index int,
			thenableReturnIndices *[]int,
			voidReturnIndices *[]int,
		)
		checkThenableOrVoidArgument = func(
			node *ast.Expression,
			t *checker.Type,
			index int,
			thenableReturnIndices *[]int,
			voidReturnIndices *[]int,
		) {
			if isThenableReturningFunctionType(node.Expression(), t) {
				(*thenableReturnIndices) = append(*thenableReturnIndices, index)
			} else if isVoidReturningFunctionType(node.Expression(), t) &&

				!slices.Contains(*thenableReturnIndices, index) {

				(*voidReturnIndices) = append(*voidReturnIndices, index)
			}
			contextualType := checker.Checker_getContextualTypeForArgumentAtIndex(ctx.TypeChecker, node, index)

			if contextualType != t {
				checkThenableOrVoidArgument(
					node,
					contextualType,
					index,
					thenableReturnIndices,
					voidReturnIndices,
				)
			}
		}

		voidFunctionArguments := func(
			node *ast.Expression,
		) []int {

			if node.Arguments() == nil {
				return []int{}
			}
			thenableReturnIndices := []int{}
			voidReturnIndices := []int{}
			t := ctx.TypeChecker.GetTypeAtLocation(node.Expression())

			for _, subType := range utils.UnionTypeParts(t) {
				// Standard function calls and `new` have two different types of signatures
				var signatures []*checker.Signature
				if ast.IsCallExpression(node) {
					signatures = utils.GetCallSignatures(ctx.TypeChecker, subType)
				} else {
					signatures = utils.GetConstructSignatures(ctx.TypeChecker, subType)
				}
				for _, signature := range signatures {
					for index, parameter := range checker.Signature_parameters(signature) {
						decl := parameter.ValueDeclaration
						t := ctx.TypeChecker.GetTypeOfSymbolAtLocation(parameter, node.Expression())

						if decl != nil && utils.IsRestParameterDeclaration(decl) {
							if checker.Checker_isArrayType(ctx.TypeChecker, t) {

								t = checker.Checker_getTypeArguments(ctx.TypeChecker, t)[0]
								for i := index; i < len(node.Arguments()); i++ {
									checkThenableOrVoidArgument(
										node,
										t,
										i,
										&thenableReturnIndices,
										&voidReturnIndices,
									)
								}
							} else if checker.IsTupleType(t) {

								typeArgs := checker.Checker_getTypeArguments(ctx.TypeChecker, t)
								for i := index; i < len(node.Arguments()) && i-index < len(typeArgs); i++ {
									checkThenableOrVoidArgument(
										node,
										typeArgs[i-index],
										i,
										&thenableReturnIndices,
										&voidReturnIndices,
									)
								}
							}
						} else {
							checkThenableOrVoidArgument(
								node,
								t,
								index,
								&thenableReturnIndices,
								&voidReturnIndices,
							)
						}
					}
				}
			}

			for _, index := range thenableReturnIndices {
				at := slices.Index(voidReturnIndices, index)
				if at >= 0 {
					voidReturnIndices = slices.Delete(voidReturnIndices, at, at+1)
				}
			}

			return voidReturnIndices
		}

		checkArguments := func(
			node *ast.Expression,
		) {
			voidArgs := voidFunctionArguments(node)
			if len(voidArgs) == 0 {
				return
			}

			for index, argument := range node.Arguments() {
				if !slices.Contains(voidArgs, index) {
					continue
				}
				if returnsThenable(argument) {
					ctx.ReportNode(argument, buildVoidReturnArgumentMessage())
				}
			}
		}

		checkClassLikeOrInterfaceNode := func(
			node *ast.Node,
		) {
			heritageClauses := utils.GetHeritageClauses(node)
			if heritageClauses == nil || len(heritageClauses.Nodes) == 0 {
				return
			}

			heritageTypes := utils.Flatten(utils.Map(heritageClauses.Nodes, func(h *ast.Node) []*checker.Type {
				return utils.Map(h.AsHeritageClause().Types.Nodes, func(n *ast.Node) *checker.Type {
					return ctx.TypeChecker.GetTypeAtLocation(n)
				})
			}))

			for _, nodeMember := range node.Members() {
				if nodeMember.Name() == nil {

					continue
				}
				if !ast.IsIdentifier(nodeMember.Name()) && !ast.IsPrivateIdentifier(nodeMember.Name()) && !ast.IsStringLiteral(nodeMember.Name()) && !ast.IsNumericLiteral(nodeMember.Name()) && !ast.IsBigIntLiteral(nodeMember.Name()) {
					continue
				}
				memberName := nodeMember.Name().Text()
				if ast.IsStatic(nodeMember) {
					continue
				}
				if !returnsThenable(nodeMember) {
					continue
				}
				for _, heritageType := range heritageTypes {
					checkHeritageTypeForMemberReturningVoid(
						nodeMember,
						heritageType,
						memberName,
					)
				}
			}
		}

		checkProperty := func(node *ast.Node) {
			if ast.IsPropertyAssignment(node) {
				property := node.AsPropertyAssignment()
				contextualType := checker.Checker_getContextualType(ctx.TypeChecker, property.Initializer, checker.ContextFlagsNone)

				if contextualType != nil && isVoidReturningFunctionType(
					property.Initializer,
					contextualType,
				) && returnsThenable(property.Initializer) {
					if ast.IsFunctionLike(property.Initializer) {
						returnType := property.Initializer.Type()
						if returnType != nil {
							ctx.ReportNode(returnType, buildVoidReturnPropertyMessage())
						} else {
							ctx.ReportNode(

								property.Initializer,
								buildVoidReturnPropertyMessage(),
							)
						}
					} else {
						ctx.ReportNode(property.Initializer, buildVoidReturnPropertyMessage())
					}
				}
			} else if ast.IsShorthandPropertyAssignment(node) {
				contextualType := checker.Checker_getContextualType(ctx.TypeChecker, node.Name(), checker.ContextFlagsNone)
				if contextualType != nil &&
					isVoidReturningFunctionType(node.Name(), contextualType) &&
					returnsThenable(node.Name()) {
					ctx.ReportNode(node.Name(), buildVoidReturnPropertyMessage())
				}
			} else if ast.IsMethodDeclaration(node) {
				if ast.IsComputedPropertyName(node.Name()) {
					return
				}
				obj := node.Parent

				if !ast.IsObjectLiteralExpression(obj) {
					return
				}

				if !returnsThenable(node) {
					return
				}
				objType := checker.Checker_getContextualType(ctx.TypeChecker, obj, checker.ContextFlagsNone)
				if objType == nil {
					return
				}
				propertySymbol := checker.Checker_getPropertyOfType(ctx.TypeChecker, objType, node.Name().Text())
				if propertySymbol == nil {
					return
				}

				contextualType := ctx.TypeChecker.GetTypeOfSymbolAtLocation(
					propertySymbol,
					node.Name(),
				)

				if isVoidReturningFunctionType(node.Name(), contextualType) {

					if ast.IsMethodDeclaration(node) {

					}

					if node.Type() != nil {
						ctx.ReportNode(node.Type(), buildVoidReturnPropertyMessage())
					} else {
						ctx.ReportNode(

							node,
							buildVoidReturnPropertyMessage(),
						)
					}
				}
			}
		}

		isPossiblyFunctionType := func(node *ast.Node) bool {
			switch node.Kind {
			case ast.KindConditionalType,
				ast.KindConstructorType,
				ast.KindFunctionType,
				ast.KindImportType,
				ast.KindIndexedAccessType,
				ast.KindInferType,
				ast.KindIntersectionType,
				ast.KindQualifiedName,
				ast.KindThisType,
				ast.KindTypeOperator,
				ast.KindTypeQuery,
				ast.KindTypeReference,
				ast.KindUnionType:
				return true

			case ast.KindTypeLiteral:
				return utils.Some(node.AsTypeLiteralNode().Members.Nodes, func(member *ast.Node) bool {
					return member.Kind == ast.KindCallSignature || member.Kind == ast.KindConstructSignature
				})

			case ast.KindAbstractKeyword,
				ast.KindAnyKeyword,
				ast.KindArrayType,
				ast.KindAsyncKeyword,
				ast.KindBigIntKeyword,
				ast.KindBooleanKeyword,
				ast.KindDeclareKeyword,
				ast.KindExportKeyword,
				ast.KindIntrinsicKeyword,
				ast.KindLiteralType,
				ast.KindMappedType,
				ast.KindNamedTupleMember,
				ast.KindNeverKeyword,
				ast.KindNullKeyword,
				ast.KindNumberKeyword,
				ast.KindObjectKeyword,
				ast.KindOptionalType,
				ast.KindPrivateKeyword,
				ast.KindProtectedKeyword,
				ast.KindPublicKeyword,
				ast.KindReadonlyKeyword,
				ast.KindRestType,
				ast.KindStaticKeyword,
				ast.KindStringKeyword,
				ast.KindSymbolKeyword,
				ast.KindTemplateLiteralType,
				ast.KindTupleType,
				ast.KindTypePredicate,
				ast.KindUndefinedKeyword,
				ast.KindUnknownKeyword,
				ast.KindVoidKeyword:
				return false
			}
			return false
		}

		checkReturnStatement := func(node *ast.ReturnStatement) {
			if node.Expression == nil {
				return
			}

			functionNode := (func() *ast.Node {
				current := node.Parent
				for current != nil && !ast.IsFunctionLike(current) {
					current = current.Parent
				}
				if current == nil {
					panic("missing parent function")
				}
				return current
			})()

			if functionNode.Type() != nil && !isPossiblyFunctionType(functionNode.Type()) {
				return
			}

			contextualType := checker.Checker_getContextualType(ctx.TypeChecker, node.Expression, checker.ContextFlagsNone)
			if contextualType != nil &&
				isVoidReturningFunctionType(
					node.Expression,
					contextualType,
				) && returnsThenable(node.Expression) {
				ctx.ReportNode(node.Expression, buildVoidReturnReturnValueMessage())
			}
		}

		checkAssignment := func(node *ast.BinaryExpression) {
			varType := ctx.TypeChecker.GetTypeAtLocation(node.Left)
			if !isVoidReturningFunctionType(node.Left, varType) {
				return
			}

			if returnsThenable(node.Right) {
				ctx.ReportNode(node.Right, buildVoidReturnVariableMessage())
			}
		}

		checkVariableDeclaration := func(node *ast.VariableDeclaration) {
			if node.Initializer == nil ||
				node.Type == nil {
				return
			}

			if !isPossiblyFunctionType(node.Type) {
				return
			}

			varType := ctx.TypeChecker.GetTypeAtLocation(node.Name())
			if !isVoidReturningFunctionType(node.Initializer, varType) {
				return
			}

			if returnsThenable(node.Initializer) {
				ctx.ReportNode(node.Initializer, buildVoidReturnVariableMessage())
			}
		}

		listeners := rule.RuleListeners{
			ast.KindBinaryExpression: func(node *ast.Node) {
				if *opts.ChecksConditionals {
					checkConditional(node, false)
				}

				if *opts.ChecksVoidReturn && *opts.ChecksVoidReturnOpts.Variables && ast.IsAssignmentExpression(node, false) {
					checkAssignment(node.AsBinaryExpression())
				}
			},
		}
		if *opts.ChecksConditionals {
			listeners[ast.KindPropertyAccessExpression] = checkArrayPredicates
			listeners[ast.KindElementAccessExpression] = checkArrayPredicates

			listeners[ast.KindPrefixUnaryExpression] = func(node *ast.Node) {
				expr := node.AsPrefixUnaryExpression()
				if expr.Operator == ast.KindExclamationToken {
					checkConditional(expr.Operand, true)
				}
			}

			listeners[ast.KindConditionalExpression] = func(node *ast.Node) { checkConditional(node.AsConditionalExpression().Condition, true) }
			listeners[ast.KindForStatement] = func(node *ast.Node) { checkConditional(node.AsForStatement().Condition, true) }
			listeners[ast.KindDoStatement] = func(node *ast.Node) { checkConditional(node.Expression(), true) }
			listeners[ast.KindWhileStatement] = func(node *ast.Node) { checkConditional(node.Expression(), true) }
			listeners[ast.KindIfStatement] = func(node *ast.Node) { checkConditional(node.Expression(), true) }
		}

		if *opts.ChecksVoidReturn {
			if *opts.ChecksVoidReturnOpts.Arguments {
				listeners[ast.KindCallExpression] = checkArguments
				listeners[ast.KindNewExpression] = checkArguments
			}
			if *opts.ChecksVoidReturnOpts.Attributes {
				listeners[ast.KindJsxAttribute] = func(node *ast.Node) { checkJSXAttribute(node.AsJsxAttribute()) }
			}
			if *opts.ChecksVoidReturnOpts.InheritedMethods {
				listeners[ast.KindClassDeclaration] = checkClassLikeOrInterfaceNode
				listeners[ast.KindClassExpression] = checkClassLikeOrInterfaceNode
				listeners[ast.KindInterfaceDeclaration] = checkClassLikeOrInterfaceNode
			}
			if *opts.ChecksVoidReturnOpts.Properties {
				listeners[ast.KindPropertyAssignment] = checkProperty
				listeners[ast.KindMethodDeclaration] = checkProperty
				listeners[ast.KindShorthandPropertyAssignment] = checkProperty
			}
			if *opts.ChecksVoidReturnOpts.Returns {
				listeners[ast.KindReturnStatement] = func(node *ast.Node) { checkReturnStatement(node.AsReturnStatement()) }
			}
			if *opts.ChecksVoidReturnOpts.Variables {
				listeners[ast.KindVariableDeclaration] = func(node *ast.Node) { checkVariableDeclaration(node.AsVariableDeclaration()) }
			}

		}
		if *opts.ChecksSpreads {
			listeners[ast.KindSpreadElement] = checkSpread
			listeners[ast.KindSpreadAssignment] = checkSpread
		}

		return listeners

	},
})

Functions

This section is empty.

Types

type NoMisusedPromisesChecksVoidReturnOptions

type NoMisusedPromisesChecksVoidReturnOptions struct {
	Arguments        *bool
	Attributes       *bool
	InheritedMethods *bool
	Properties       *bool
	Returns          *bool
	Variables        *bool
}

type NoMisusedPromisesOptions

type NoMisusedPromisesOptions struct {
	ChecksConditionals   *bool
	ChecksSpreads        *bool
	ChecksVoidReturn     *bool
	ChecksVoidReturnOpts *NoMisusedPromisesChecksVoidReturnOptions
}

Jump to

Keyboard shortcuts

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