Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var NoArrayIndexKeyRule = rule.Rule{ Name: "react/no-array-index-key", Run: func(ctx rule.RuleContext, options any) rule.RuleListeners { pragma := reactutil.GetReactPragma(ctx.Settings) // indexParamNames is a stack of identifier names introduced as the // index parameter of an enclosing tracked iterator callback. An // identifier reference whose text matches any name on the stack is // treated as the array index. Mirrors upstream's flat array — pushed // on iterator-call enter, popped on exit. The stack is intentionally // a flat name list (not a depth counter): nested iterators with the // SAME index name (`foo.map((a,i)=>bar.map((c,i)=>...))`) push the // name twice, and inner functions defined inside the callback do // NOT shadow it — both behaviors mirror upstream byte-for-byte. var indexParamNames []string isArrayIndex := func(node *ast.Node) bool { if node == nil { return false } node = ast.SkipParentheses(node) if node == nil || node.Kind != ast.KindIdentifier { return false } name := node.AsIdentifier().Text for _, n := range indexParamNames { if n == name { return true } } return false } isUsingReactChildren := func(call *ast.CallExpression) bool { if call == nil || call.Expression == nil { return false } callee := ast.SkipParentheses(call.Expression) if callee == nil || callee.Kind != ast.KindPropertyAccessExpression { return false } pa := callee.AsPropertyAccessExpression() name := pa.Name() if name == nil || name.Kind != ast.KindIdentifier { return false } method := name.AsIdentifier().Text if method != "map" && method != "forEach" { return false } obj := ast.SkipParentheses(pa.Expression) if obj == nil { return false } if obj.Kind == ast.KindIdentifier && obj.AsIdentifier().Text == "Children" { return true } if obj.Kind == ast.KindPropertyAccessExpression { inner := obj.AsPropertyAccessExpression() innerObj := ast.SkipParentheses(inner.Expression) return innerObj != nil && innerObj.Kind == ast.KindIdentifier && innerObj.AsIdentifier().Text == pragma } return false } getMapIndexParamName := func(call *ast.CallExpression) string { if call == nil || call.Expression == nil { return "" } callee := ast.SkipParentheses(call.Expression) if callee == nil || callee.Kind != ast.KindPropertyAccessExpression { return "" } pa := callee.AsPropertyAccessExpression() name := pa.Name() if name == nil || name.Kind != ast.KindIdentifier { return "" } pos, ok := indexParamPositions[name.AsIdentifier().Text] if !ok { return "" } if call.Arguments == nil { return "" } args := call.Arguments.Nodes argIdx := 0 if isUsingReactChildren(call) { argIdx = 1 } if argIdx >= len(args) { return "" } callback := ast.SkipParentheses(args[argIdx]) if callback == nil || !ast.IsFunctionExpressionOrArrowFunction(callback) { return "" } params := callback.Parameters() if pos >= len(params) { return "" } param := params[pos] if param == nil || param.Kind != ast.KindParameter { return "" } pd := param.AsParameterDeclaration() if pd.DotDotDotToken != nil { return "" } if pd.Initializer != nil { return "" } paramName := pd.Name() if paramName == nil || paramName.Kind != ast.KindIdentifier { return "" } return paramName.AsIdentifier().Text } report := func(node *ast.Node) { ctx.ReportNode(node, rule.RuleMessage{ Id: "noArrayIndex", Description: noArrayIndexMessage, }) } // collectIdentifiersFromBinary recursively flattens a chain of // BinaryExpressions into the bare identifiers they reference. // Mirrors upstream's `getIdentifiersFromBinaryExpression`: only // Identifier and BinaryExpression are walked — every other shape // (ConditionalExpression, CallExpression, MemberExpression, …) // is opaque, matching upstream's selective recursion. var collectIdentifiersFromBinary func(node *ast.Node) []*ast.Node collectIdentifiersFromBinary = func(node *ast.Node) []*ast.Node { if node == nil { return nil } node = ast.SkipParentheses(node) if node == nil { return nil } if node.Kind == ast.KindIdentifier { return []*ast.Node{node} } if node.Kind == ast.KindBinaryExpression { be := node.AsBinaryExpression() return append(collectIdentifiersFromBinary(be.Left), collectIdentifiersFromBinary(be.Right)...) } return nil } checkPropValue := func(node *ast.Node) { if node == nil { return } node = ast.SkipParentheses(node) if node == nil { return } if isArrayIndex(node) { report(node) return } if node.Kind == ast.KindTemplateExpression { te := node.AsTemplateExpression() if te.TemplateSpans != nil { for _, span := range te.TemplateSpans.Nodes { if span.Kind != ast.KindTemplateSpan { continue } if isArrayIndex(span.AsTemplateSpan().Expression) { report(node) } } } return } if node.Kind == ast.KindBinaryExpression { for _, id := range collectIdentifiersFromBinary(node) { if isArrayIndex(id) { report(node) } } return } if node.Kind == ast.KindCallExpression { if ast.IsOptionalChain(node) { return } call := node.AsCallExpression() ce := ast.SkipParentheses(call.Expression) if ce != nil && ce.Kind == ast.KindPropertyAccessExpression { if ast.IsOptionalChain(ce) { return } pa := ce.AsPropertyAccessExpression() prop := pa.Name() if isArrayIndex(pa.Expression) && prop != nil && prop.Kind == ast.KindIdentifier && prop.AsIdentifier().Text == "toString" { report(node) return } } if ce != nil && ce.Kind == ast.KindIdentifier && ce.AsIdentifier().Text == "String" { if call.Arguments != nil && len(call.Arguments.Nodes) > 0 { firstArg := call.Arguments.Nodes[0] if isArrayIndex(firstArg) { report(ast.SkipParentheses(firstArg)) } } } } } keyMatchesUpstreamIdentifier := func(keyName *ast.Node) bool { if keyName == nil { return false } if keyName.Kind == ast.KindComputedPropertyName { inner := keyName.AsComputedPropertyName().Expression return inner != nil && inner.Kind == ast.KindIdentifier && inner.AsIdentifier().Text == "key" } return keyName.Kind == ast.KindIdentifier && keyName.AsIdentifier().Text == "key" } checkObjectKeyProp := func(props *ast.Node) { props = ast.SkipParentheses(props) if props == nil || props.Kind != ast.KindObjectLiteralExpression { return } obj := props.AsObjectLiteralExpression() if obj.Properties == nil { return } for _, prop := range obj.Properties.Nodes { switch prop.Kind { case ast.KindPropertyAssignment: pa := prop.AsPropertyAssignment() if !keyMatchesUpstreamIdentifier(pa.Name()) { continue } if pa.Initializer != nil { checkPropValue(pa.Initializer) } case ast.KindShorthandPropertyAssignment: spa := prop.AsShorthandPropertyAssignment() nameNode := spa.Name() if !keyMatchesUpstreamIdentifier(nameNode) { continue } checkPropValue(nameNode) } } } isCreateCloneElement := func(callee *ast.Node) bool { if callee == nil { return false } callee = ast.SkipParentheses(callee) if callee == nil { return false } if callee.Kind == ast.KindPropertyAccessExpression { pa := callee.AsPropertyAccessExpression() name := pa.Name() if name == nil || name.Kind != ast.KindIdentifier { return false } txt := name.AsIdentifier().Text if txt != "createElement" && txt != "cloneElement" { return false } obj := ast.SkipParentheses(pa.Expression) return obj != nil && obj.Kind == ast.KindIdentifier && obj.AsIdentifier().Text == pragma } if callee.Kind == ast.KindIdentifier { return isImportSpecifierFromReact(ctx, callee) } return false } return rule.RuleListeners{ ast.KindCallExpression: func(node *ast.Node) { call := node.AsCallExpression() if isCreateCloneElement(call.Expression) && call.Arguments != nil && len(call.Arguments.Nodes) > 1 { if len(indexParamNames) == 0 { return } checkObjectKeyProp(call.Arguments.Nodes[1]) return } if name := getMapIndexParamName(call); name != "" { indexParamNames = append(indexParamNames, name) } }, rule.ListenerOnExit(ast.KindCallExpression): func(node *ast.Node) { if name := getMapIndexParamName(node.AsCallExpression()); name != "" && len(indexParamNames) > 0 { indexParamNames = indexParamNames[:len(indexParamNames)-1] } }, ast.KindJsxAttribute: func(node *ast.Node) { attr := node.AsJsxAttribute() name := attr.Name() if name == nil || name.Kind != ast.KindIdentifier || name.AsIdentifier().Text != "key" { return } if len(indexParamNames) == 0 { return } init := attr.Initializer if init == nil || init.Kind != ast.KindJsxExpression { return } expr := init.AsJsxExpression().Expression if expr == nil { return } checkPropValue(expr) }, } }, }
Functions ¶
This section is empty.
Types ¶
This section is empty.
Click to show internal directories.
Click to hide internal directories.