Skip to content

Commit 825cda1

Browse files
authored
Merge branch 'main' into camchenry/prefer-onselect-rule
2 parents bc963a2 + ab8269f commit 825cda1

File tree

2 files changed

+26
-11
lines changed

2 files changed

+26
-11
lines changed

src/rules/__tests__/no-unnecessary-components.test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ ruleTester.run('unnecessary-components', rule, {
6565
filename,
6666
},
6767
]),
68+
{
69+
name: `Text with weight prop`,
70+
code: `${prcImport}${jsx(`<Text weight='medium'>Hello World</Text>`)}`,
71+
filename,
72+
},
73+
{
74+
name: `Text with size prop`,
75+
code: `${prcImport}${jsx(`<Text size='small'>Hello World</Text>`)}`,
76+
filename,
77+
},
6878
],
6979
invalid: Object.entries(components).flatMap(([component, {messageId, replacement}]) => [
7080
{

src/rules/no-unnecessary-components.js

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ const components = {
1212
replacement: 'div',
1313
messageId: 'unecessaryBox',
1414
message: 'Prefer plain HTML elements over `Box` when not using `sx` for styling.',
15+
allowedProps: new Set(['sx']), // + styled-system props
1516
},
1617
Text: {
1718
replacement: 'span',
1819
messageId: 'unecessarySpan',
1920
message: 'Prefer plain HTML elements over `Text` when not using `sx` for styling.',
21+
allowedProps: new Set(['sx', 'size', 'weight']), // + styled-system props
2022
},
2123
}
2224

@@ -68,33 +70,36 @@ const rule = ESLintUtils.RuleCreator.withoutDocs({
6870
const isPrimer = skipImportCheck || isPrimerComponent(name, context.sourceCode.getScope(openingElement))
6971
if (!isPrimer) return
7072

71-
// Validate the attributes and ensure an `sx` prop is present or spreaded in
73+
/** @param {string} name */
74+
const isAllowedProp = name => componentConfig.allowedProps.has(name) || isStyledSystemProp(name)
75+
76+
// Validate the attributes and ensure an allowed prop is present or spreaded in
7277
/** @type {typeof attributes[number] | undefined | null} */
7378
let asProp = undefined
7479
for (const attribute of attributes) {
75-
// If there is a spread type, check if the type of the spreaded value has an `sx` property
80+
// If there is a spread type, check if the type of the spreaded value has an allowed property
7681
if (attribute.type === 'JSXSpreadAttribute') {
7782
const services = ESLintUtils.getParserServices(context)
7883
const typeChecker = services.program.getTypeChecker()
7984

8085
const spreadType = services.getTypeAtLocation(attribute.argument)
81-
if (typeChecker.getPropertyOfType(spreadType, 'sx') !== undefined) return
8286

83-
// Check if the spread type has a string index signature - this could hide an `sx` property
87+
// Check if the spread type has a string index signature - this could hide an allowed property
8488
if (typeChecker.getIndexTypeOfType(spreadType, IndexKind.String) !== undefined) return
8589

90+
const spreadPropNames = typeChecker.getPropertiesOfType(spreadType).map(prop => prop.getName())
91+
92+
// If an allowed prop gets spread in, this is a valid use of the component
93+
if (spreadPropNames.some(isAllowedProp)) return
94+
8695
// If there is an `as` inside the spread object, we can't autofix reliably
87-
if (typeChecker.getPropertyOfType(spreadType, 'as') !== undefined) asProp = null
96+
if (spreadPropNames.includes('as')) asProp = null
8897

8998
continue
9099
}
91100

92-
// Has sx prop, so should keep using this component
93-
if (
94-
attribute.name.type === 'JSXIdentifier' &&
95-
(attribute.name.name === 'sx' || isStyledSystemProp(attribute.name.name))
96-
)
97-
return
101+
// Has an allowed prop, so should keep using this component
102+
if (attribute.name.type === 'JSXIdentifier' && isAllowedProp(attribute.name.name)) return
98103

99104
// If there is an `as` prop we will need to account for that when autofixing
100105
if (attribute.name.type === 'JSXIdentifier' && attribute.name.name === 'as') asProp = attribute

0 commit comments

Comments
 (0)