Skip to content

Commit aabaa1d

Browse files
authored
Merge pull request #47 from primer/subcomponent-system-props
Update `no-system-props` to check subcomponents
2 parents bdc64e8 + 4632ec7 commit aabaa1d

File tree

5 files changed

+50
-14
lines changed

5 files changed

+50
-14
lines changed

.changeset/brave-hats-add.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"eslint-plugin-primer-react": patch
3+
---
4+
5+
- Update the no-system-props rule to properly check subcomponents for system props
6+
- Adds valid sucomponent props to the allowlist

src/rules/__tests__/no-system-props.test.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ ruleTester.run('no-system-props', rule, {
2020
`import {ProgressBar} from '@primer/react'; <ProgressBar bg="howdy" />`,
2121
`import {Button} from '@primer/react'; <Button {...someExpression()} />`,
2222
`import {Button} from '@primer/react'; <Button variant="large" />`,
23-
`import {Button} from '@primer/react'; <Button size="large" />`
23+
`import {Button} from '@primer/react'; <Button size="large" />`,
24+
`import {ActionMenu} from '@primer/react'; <ActionMenu.Overlay width="large" />`
2425
],
2526
invalid: [
2627
{
@@ -166,6 +167,16 @@ ruleTester.run('no-system-props', rule, {
166167
data: {propNames: 'width', componentName: 'Foo'}
167168
}
168169
]
170+
},
171+
{
172+
code: `import {Button} from '@primer/react'; <Button.Counter width={200} />`,
173+
output: `import {Button} from '@primer/react'; <Button.Counter sx={{width: 200}} />`,
174+
errors: [
175+
{
176+
messageId: 'noSystemProps',
177+
data: {propNames: 'width', componentName: 'Button.Counter'}
178+
}
179+
]
169180
}
170181
]
171182
})

src/rules/direct-slot-children.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const {isPrimerComponent} = require('../utils/is-primer-component')
2+
const {getJSXOpeningElementName} = require('../utils/get-jsx-opening-element-name')
23
const {last} = require('lodash')
34

45
const slotParentToChildMap = {
@@ -84,12 +85,3 @@ module.exports = {
8485
}
8586
}
8687
}
87-
88-
// Convert JSXOpeningElement name to string
89-
function getJSXOpeningElementName(jsxNode) {
90-
if (jsxNode.name.type === 'JSXIdentifier') {
91-
return jsxNode.name.name
92-
} else if (jsxNode.name.type === 'JSXMemberExpression') {
93-
return `${jsxNode.name.object.name}.${jsxNode.name.property.name}`
94-
}
95-
}

src/rules/no-system-props.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const {isPrimerComponent} = require('../utils/is-primer-component')
2+
const {getJSXOpeningElementName} = require('../utils/get-jsx-opening-element-name')
23
const {pick} = require('@styled-system/props')
34
const {some, last} = require('lodash')
45

@@ -12,18 +13,31 @@ const utilityComponents = new Set(['Box', 'Text'])
1213

1314
// Components for which we allow a set of prop names
1415
const excludedComponentProps = new Map([
16+
['ActionMenu.Overlay', new Set(['width', 'height', 'maxHeight', 'position', 'top', 'right', 'bottom', 'left'])],
17+
['Autocomplete.Overlay', new Set(['width', 'height', 'maxHeight', 'position', 'top', 'right', 'bottom', 'left'])],
1518
['AnchoredOverlay', new Set(['width', 'height'])],
1619
['Avatar', new Set(['size'])],
1720
['AvatarToken', new Set(['size'])],
1821
['CircleOcticon', new Set(['size'])],
1922
['Dialog', new Set(['width', 'height'])],
2023
['IssueLabelToken', new Set(['size'])],
24+
['Overlay', new Set(['width', 'height', 'maxHeight', 'position', 'top', 'right', 'bottom', 'left'])],
2125
['ProgressBar', new Set(['bg'])],
2226
['Spinner', new Set(['size'])],
27+
['SplitPageLayout.Header', new Set(['padding'])],
28+
['SplitPageLayout.Footer', new Set(['padding'])],
29+
['SplitPageLayout.Pane', new Set(['padding', 'position', 'width'])],
30+
['SplitPageLayout.Content', new Set(['padding', 'width'])],
2331
['StyledOcticon', new Set(['size'])],
2432
['PointerBox', new Set(['bg'])],
33+
['TextInput', new Set(['size'])],
34+
['TextInputWithTokens', new Set(['size', 'maxHeight'])],
2535
['Token', new Set(['size'])],
2636
['PageLayout', new Set(['padding'])],
37+
['PageLayout.Header', new Set(['padding'])],
38+
['PageLayout.Footer', new Set(['padding'])],
39+
['PageLayout.Pane', new Set(['padding', 'position', 'width'])],
40+
['PageLayout.Content', new Set(['padding', 'width'])],
2741
['ProgressBar', new Set(['bg'])],
2842
['PointerBox', new Set(['bg'])]
2943
])
@@ -65,7 +79,10 @@ module.exports = {
6579
return {
6680
JSXOpeningElement(jsxNode) {
6781
if (!skipImportCheck && !isPrimerComponent(jsxNode.name, context.getScope(jsxNode))) return
68-
if (excludedComponents.has(jsxNode.name.name)) return
82+
83+
const componentName = getJSXOpeningElementName(jsxNode)
84+
85+
if (excludedComponents.has(componentName)) return
6986

7087
// Create an object mapping from prop name to the AST node for that attribute
7188
const propsByNameObject = jsxNode.attributes.reduce((object, attribute) => {
@@ -80,8 +97,8 @@ module.exports = {
8097
// Create an array of system prop attribute nodes
8198
let systemProps = Object.values(pick(propsByNameObject))
8299

83-
const excludedProps = excludedComponentProps.has(jsxNode.name.name)
84-
? new Set([...alwaysExcludedProps, ...excludedComponentProps.get(jsxNode.name.name)])
100+
const excludedProps = excludedComponentProps.has(componentName)
101+
? new Set([...alwaysExcludedProps, ...excludedComponentProps.get(componentName)])
85102
: alwaysExcludedProps
86103

87104
// Filter out our exceptional props
@@ -94,7 +111,7 @@ module.exports = {
94111
node: jsxNode,
95112
messageId: 'noSystemProps',
96113
data: {
97-
componentName: jsxNode.name.name,
114+
componentName,
98115
propNames: systemProps.map(a => a.name.name).join(', ')
99116
},
100117
fix(fixer) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/** Convert JSXOpeningElement name to string */
2+
function getJSXOpeningElementName(jsxNode) {
3+
if (jsxNode.name.type === 'JSXIdentifier') {
4+
return jsxNode.name.name
5+
} else if (jsxNode.name.type === 'JSXMemberExpression') {
6+
return `${jsxNode.name.object.name}.${jsxNode.name.property.name}`
7+
}
8+
}
9+
10+
exports.getJSXOpeningElementName = getJSXOpeningElementName

0 commit comments

Comments
 (0)