Skip to content

Commit 9eb2ecd

Browse files
authored
Merge pull request #45 from primer/slot-fixes
More direct-slot-children fixes
2 parents f5e5287 + a39e34d commit 9eb2ecd

File tree

3 files changed

+53
-11
lines changed

3 files changed

+53
-11
lines changed

.changeset/smooth-chefs-hammer.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"eslint-plugin-primer-react": patch
3+
---
4+
5+
More `direct-slot-children` fixes:
6+
- Fix bug related self-closing JSX tags
7+
- Allow slot children to accept multiple parents (ex: `ActionList.Item` or `ActionList.LinkItem`)
8+
- Add `SplitPageLayout` and `NavList` to the slot map
9+
- Ignore `MarkdownEditor` because it's still a draft

src/rules/__tests__/direct-slot-children.test.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ ruleTester.run('direct-slot-children', rule, {
1717
`import {PageLayout} from '@primer/react'; <PageLayout><div><PageLayout.Pane>Header</PageLayout.Pane></div></PageLayout>`,
1818
`import {PageLayout} from '@primer/react'; <PageLayout>{true ? <PageLayout.Header>Header</PageLayout.Header> : null}</PageLayout>`,
1919
`import {PageLayout} from './PageLayout'; <PageLayout.Header>Header</PageLayout.Header>`,
20-
{
21-
code: `import {Foo} from './Foo'; <Foo><div><Foo.Bar></Foo.Bar></div></Foo>`,
22-
options: [{skipImportCheck: true}]
23-
}
20+
`import {FormControl, Radio} from '@primer/react'; <FormControl><Radio value="one" /><FormControl.Label>Choice one</FormControl.Label></FormControl>`,
21+
`import {ActionList} from '@primer/react';
22+
<ActionList.Item><ActionList.LeadingVisual> <Avatar src="https://github.com/mona.png" /></ActionList.LeadingVisual>mona<ActionList.Description>Monalisa Octocat</ActionList.Description></ActionList.Item>`,
23+
`import {ActionList} from '@primer/react';
24+
<ActionList.LinkItem><ActionList.LeadingVisual></ActionList.LeadingVisual>mona<ActionList.Description>Monalisa Octocat</ActionList.Description></ActionList.LinkItem>`,
25+
{code: `import {Foo} from './Foo'; <Foo><div><Foo.Bar></Foo.Bar></div></Foo>`, options: [{skipImportCheck: true}]}
2426
],
2527
invalid: [
2628
{
@@ -32,6 +34,15 @@ ruleTester.run('direct-slot-children', rule, {
3234
}
3335
]
3436
},
37+
{
38+
code: `import {PageLayout} from '@primer/react'; function Header() { return <PageLayout.Header>Header</PageLayout.Header>; }`,
39+
errors: [
40+
{
41+
messageId: 'directSlotChildren',
42+
data: {childName: 'PageLayout.Header', parentName: 'PageLayout'}
43+
}
44+
]
45+
},
3546
{
3647
code: `import {PageLayout} from '@primer/react/drafts'; <PageLayout.Header>Header</PageLayout.Header>`,
3748
errors: [
@@ -77,6 +88,15 @@ ruleTester.run('direct-slot-children', rule, {
7788
data: {childName: 'PageLayout.Header', parentName: 'PageLayout'}
7889
}
7990
]
91+
},
92+
{
93+
code: `import {ActionList} from '@primer/react'; <ActionList.Item><div><ActionList.LeadingVisual>Visual</ActionList.LeadingVisual></div></ActionList.Item>`,
94+
errors: [
95+
{
96+
messageId: 'directSlotChildren',
97+
data: {childName: 'ActionList.LeadingVisual', parentName: 'ActionList.Item or ActionList.LinkItem'}
98+
}
99+
]
80100
}
81101
]
82102
})

src/rules/direct-slot-children.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,25 @@ const {last} = require('lodash')
33

44
const slotParentToChildMap = {
55
PageLayout: ['PageLayout.Header', 'PageLayout.Footer'],
6+
SplitPageLayout: ['SplitPageLayout.Header', 'SplitPageLayout.Footer'],
67
FormControl: ['FormControl.Label', 'FormControl.Caption', 'FormControl.LeadingVisual', 'FormControl.TrailingVisual'],
7-
MarkdownEditor: ['MarkdownEditor.Toolbar', 'MarkdownEditor.Actions', 'MarkdownEditor.Label'],
88
'ActionList.Item': ['ActionList.LeadingVisual', 'ActionList.TrailingVisual', 'ActionList.Description'],
9+
'ActionList.LinkItem': ['ActionList.LeadingVisual', 'ActionList.TrailingVisual', 'ActionList.Description'],
10+
'NavList.Item': ['NavList.LeadingVisual', 'NavList.TrailingVisual'],
911
'TreeView.Item': ['TreeView.LeadingVisual', 'TreeView.TrailingVisual'],
1012
RadioGroup: ['RadioGroup.Label', 'RadioGroup.Caption', 'RadioGroup.Validation'],
1113
CheckboxGroup: ['CheckboxGroup.Label', 'CheckboxGroup.Caption', 'CheckboxGroup.Validation']
14+
// Ignore `MarkdownEditor` for now because it's still in drafts
15+
// MarkdownEditor: ['MarkdownEditor.Toolbar', 'MarkdownEditor.Actions', 'MarkdownEditor.Label'],
1216
}
1317

1418
const slotChildToParentMap = Object.entries(slotParentToChildMap).reduce((acc, [parent, children]) => {
1519
for (const child of children) {
16-
acc[child] = parent
20+
if (acc[child]) {
21+
acc[child].push(parent)
22+
} else {
23+
acc[child] = [parent]
24+
}
1725
}
1826
return acc
1927
}, {})
@@ -50,19 +58,24 @@ module.exports = {
5058
(skipImportCheck || isPrimerComponent(jsxNode.name, context.getScope(jsxNode))) &&
5159
slotChildToParentMap[name]
5260
) {
53-
const expectedParentName = slotChildToParentMap[name]
61+
const expectedParentNames = slotChildToParentMap[name]
5462
const parent = last(stack)
55-
if (parent !== expectedParentName) {
63+
if (!expectedParentNames.includes(parent)) {
5664
context.report({
5765
node: jsxNode,
5866
messageId: 'directSlotChildren',
59-
data: {childName: name, parentName: expectedParentName}
67+
data: {
68+
childName: name,
69+
parentName: expectedParentNames.length > 1 ? expectedParentNames.join(' or ') : expectedParentNames[0]
70+
}
6071
})
6172
}
6273
}
6374

64-
// Push the current element onto the stack
65-
stack.push(name)
75+
// If tag is not self-closing, push it onto the stack
76+
if (!jsxNode.selfClosing) {
77+
stack.push(name)
78+
}
6679
},
6780
JSXClosingElement() {
6881
// Pop the current element off the stack

0 commit comments

Comments
 (0)