jsx_key

package
v0.17.0 Latest Latest
Warning

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

Go to latest
Published: Apr 23, 2026 License: MIT, MIT Imports: 4 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var JsxKeyRule = rule.Rule{
	Name: "react/jsx-key",
	Run: func(ctx rule.RuleContext, rawOptions any) rule.RuleListeners {
		opts := parseOptions(rawOptions)
		reactPragma := reactutil.GetReactPragma(ctx.Settings)
		fragmentPragma := reactutil.GetReactFragmentPragma(ctx.Settings)

		missingIterKeyUsePragDesc := `Missing "key" prop for element in iterator. Shorthand fragment syntax does not support providing keys. Use ` + reactPragma + `.` + fragmentPragma + ` instead`
		missingArrayKeyUsePragDesc := `Missing "key" prop for element in array. Shorthand fragment syntax does not support providing keys. Use ` + reactPragma + `.` + fragmentPragma + ` instead`

		isWithinChildrenToArray := false

		reportMissingIterKey := func(node *ast.Node) {
			ctx.ReportNode(node, rule.RuleMessage{
				Id:          "missingIterKey",
				Description: `Missing "key" prop for element in iterator`,
			})
		}
		reportMissingIterKeyUsePrag := func(node *ast.Node) {
			ctx.ReportNode(node, rule.RuleMessage{
				Id:          "missingIterKeyUsePrag",
				Description: missingIterKeyUsePragDesc,
			})
		}
		reportMissingArrayKey := func(node *ast.Node) {
			ctx.ReportNode(node, rule.RuleMessage{
				Id:          "missingArrayKey",
				Description: `Missing "key" prop for element in array`,
			})
		}
		reportMissingArrayKeyUsePrag := func(node *ast.Node) {
			ctx.ReportNode(node, rule.RuleMessage{
				Id:          "missingArrayKeyUsePrag",
				Description: missingArrayKeyUsePragDesc,
			})
		}
		reportKeyBeforeSpread := func(node *ast.Node) {
			ctx.ReportNode(node, rule.RuleMessage{
				Id:          "keyBeforeSpread",
				Description: "`key` prop must be placed before any `{...spread}, to avoid conflicting with React\u2019s new JSX transform: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html`",
			})
		}
		reportNonUniqueKeys := func(node *ast.Node) {
			ctx.ReportNode(node, rule.RuleMessage{
				Id:          "nonUniqueKeys",
				Description: "`key` prop must be unique",
			})
		}

		checkIteratorElement := func(node *ast.Node) {
			if node == nil {
				return
			}
			if isJsxElementLike(node) {
				attrs := getJsxAttributeProps(node)
				if !hasKeyAttribute(attrs) {
					reportMissingIterKey(node)
					return
				}
				if opts.checkKeyMustBeforeSpread && isKeyAfterSpread(attrs) {
					reportKeyBeforeSpread(node)
				}
				return
			}
			if opts.checkFragmentShorthand && ast.IsJsxFragment(node) {
				reportMissingIterKeyUsePrag(node)
			}
		}

		peelToJsxCandidates := func(expr *ast.Node) {
			expr = ast.SkipParentheses(expr)
			if expr == nil {
				return
			}
			if ast.IsConditionalExpression(expr) {
				ce := expr.AsConditionalExpression()
				if ce.WhenTrue != nil {
					t := ast.SkipParentheses(ce.WhenTrue)
					if isJsxNode(t) {
						checkIteratorElement(t)
					}
				}
				if ce.WhenFalse != nil {
					f := ast.SkipParentheses(ce.WhenFalse)
					if isJsxNode(f) {
						checkIteratorElement(f)
					}
				}
				return
			}
			if ast.IsLogicalOrCoalescingBinaryExpression(expr) {
				right := expr.AsBinaryExpression().Right
				if right != nil {
					r := ast.SkipParentheses(right)
					if isJsxNode(r) {
						checkIteratorElement(r)
					}
				}
				return
			}
			checkIteratorElement(expr)
		}

		checkArrowBodyJSX := func(fn *ast.Node) {
			if !ast.IsArrowFunction(fn) {
				return
			}
			body := fn.AsArrowFunction().Body
			if body == nil {
				return
			}
			peelToJsxCandidates(body)
		}

		checkFunctionsBlockStatement := func(fn *ast.Node) {
			if !ast.IsFunctionExpressionOrArrowFunction(fn) {
				return
			}
			var body *ast.Node
			if ast.IsArrowFunction(fn) {
				body = fn.AsArrowFunction().Body
			} else {
				body = fn.AsFunctionExpression().Body
			}
			if body == nil || !ast.IsBlock(body) {
				return
			}
			var returns []*ast.Node
			returns = collectReturnStatements(body, returns)
			for _, rs := range returns {
				arg := rs.AsReturnStatement().Expression
				if arg == nil {
					continue
				}
				peelToJsxCandidates(arg)
			}
		}

		processMapOrFromCallback := func(fn *ast.Node) {
			fn = ast.SkipParentheses(fn)
			if !ast.IsFunctionExpressionOrArrowFunction(fn) {
				return
			}
			checkArrowBodyJSX(fn)
			checkFunctionsBlockStatement(fn)
		}

		processArrayLikeSiblings := func(elements []*ast.Node, parentNode *ast.Node, inArray bool) {
			if len(elements) == 0 {
				return
			}
			var jsxEls []*ast.Node
			for _, el := range elements {
				if isJsxElementLike(el) {
					jsxEls = append(jsxEls, el)
				}
			}
			if len(jsxEls) == 0 {
				return
			}
			var keysByText map[string][]*ast.Node
			if opts.warnOnDuplicates {
				keysByText = map[string][]*ast.Node{}
			}
			for _, el := range jsxEls {
				attrs := getJsxAttributeProps(el)
				keyAttrs := getKeyAttributes(attrs)
				if len(keyAttrs) == 0 {
					if inArray {
						reportMissingArrayKey(el)
					}
					continue
				}
				for _, k := range keyAttrs {
					if opts.warnOnDuplicates {
						text := keyValueText(ctx.SourceFile, k)
						keysByText[text] = append(keysByText[text], k)
					}
					if opts.checkKeyMustBeforeSpread && isKeyAfterSpread(attrs) {

						reportKeyBeforeSpread(parentNode)
					}
				}
			}
			for _, group := range keysByText {
				if len(group) <= 1 {
					continue
				}
				for _, attr := range group {
					reportNonUniqueKeys(attr)
				}
			}
		}

		return rule.RuleListeners{
			ast.KindCallExpression: func(node *ast.Node) {
				call := node.AsCallExpression()
				if isChildrenToArrayCall(call, reactPragma) {
					isWithinChildrenToArray = true
					return
				}
				if isWithinChildrenToArray {
					return
				}
				switch calleePropertyName(call) {
				case "map":
					if call.Arguments == nil || len(call.Arguments.Nodes) == 0 {
						return
					}
					processMapOrFromCallback(call.Arguments.Nodes[0])
				case "from":
					if call.Arguments == nil || len(call.Arguments.Nodes) < 2 {
						return
					}
					processMapOrFromCallback(call.Arguments.Nodes[1])
				}
			},
			rule.ListenerOnExit(ast.KindCallExpression): func(node *ast.Node) {
				if isChildrenToArrayCall(node.AsCallExpression(), reactPragma) {
					isWithinChildrenToArray = false
				}
			},
			ast.KindArrayLiteralExpression: func(node *ast.Node) {
				if isWithinChildrenToArray {
					return
				}
				arr := node.AsArrayLiteralExpression()
				if arr.Elements == nil {
					return
				}
				processArrayLikeSiblings(arr.Elements.Nodes, node, true)
			},
			ast.KindJsxElement: func(node *ast.Node) {
				if isWithinChildrenToArray {
					return
				}
				jsxEl := node.AsJsxElement()
				if jsxEl.Children == nil {
					return
				}
				processArrayLikeSiblings(jsxEl.Children.Nodes, node, false)
			},
			ast.KindJsxFragment: func(node *ast.Node) {
				if isWithinChildrenToArray || !opts.checkFragmentShorthand {
					return
				}
				parent := node.Parent
				for parent != nil && ast.IsParenthesizedExpression(parent) {
					parent = parent.Parent
				}
				if parent != nil && ast.IsArrayLiteralExpression(parent) {
					reportMissingArrayKeyUsePrag(node)
				}
			},
		}
	},
}

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