diff --git a/src/rules/__tests__/no-unnecessary-components.test.js b/src/rules/__tests__/no-unnecessary-components.test.js
index 67e327ff..c6dda7d2 100644
--- a/src/rules/__tests__/no-unnecessary-components.test.js
+++ b/src/rules/__tests__/no-unnecessary-components.test.js
@@ -65,6 +65,16 @@ ruleTester.run('unnecessary-components', rule, {
filename,
},
]),
+ {
+ name: `Text with weight prop`,
+ code: `${prcImport}${jsx(`Hello World`)}`,
+ filename,
+ },
+ {
+ name: `Text with size prop`,
+ code: `${prcImport}${jsx(`Hello World`)}`,
+ filename,
+ },
],
invalid: Object.entries(components).flatMap(([component, {messageId, replacement}]) => [
{
diff --git a/src/rules/no-unnecessary-components.js b/src/rules/no-unnecessary-components.js
index c5701d6a..da02b655 100644
--- a/src/rules/no-unnecessary-components.js
+++ b/src/rules/no-unnecessary-components.js
@@ -12,11 +12,13 @@ const components = {
replacement: 'div',
messageId: 'unecessaryBox',
message: 'Prefer plain HTML elements over `Box` when not using `sx` for styling.',
+ allowedProps: new Set(['sx']), // + styled-system props
},
Text: {
replacement: 'span',
messageId: 'unecessarySpan',
message: 'Prefer plain HTML elements over `Text` when not using `sx` for styling.',
+ allowedProps: new Set(['sx', 'size', 'weight']), // + styled-system props
},
}
@@ -68,33 +70,36 @@ const rule = ESLintUtils.RuleCreator.withoutDocs({
const isPrimer = skipImportCheck || isPrimerComponent(name, context.sourceCode.getScope(openingElement))
if (!isPrimer) return
- // Validate the attributes and ensure an `sx` prop is present or spreaded in
+ /** @param {string} name */
+ const isAllowedProp = name => componentConfig.allowedProps.has(name) || isStyledSystemProp(name)
+
+ // Validate the attributes and ensure an allowed prop is present or spreaded in
/** @type {typeof attributes[number] | undefined | null} */
let asProp = undefined
for (const attribute of attributes) {
- // If there is a spread type, check if the type of the spreaded value has an `sx` property
+ // If there is a spread type, check if the type of the spreaded value has an allowed property
if (attribute.type === 'JSXSpreadAttribute') {
const services = ESLintUtils.getParserServices(context)
const typeChecker = services.program.getTypeChecker()
const spreadType = services.getTypeAtLocation(attribute.argument)
- if (typeChecker.getPropertyOfType(spreadType, 'sx') !== undefined) return
- // Check if the spread type has a string index signature - this could hide an `sx` property
+ // Check if the spread type has a string index signature - this could hide an allowed property
if (typeChecker.getIndexTypeOfType(spreadType, IndexKind.String) !== undefined) return
+ const spreadPropNames = typeChecker.getPropertiesOfType(spreadType).map(prop => prop.getName())
+
+ // If an allowed prop gets spread in, this is a valid use of the component
+ if (spreadPropNames.some(isAllowedProp)) return
+
// If there is an `as` inside the spread object, we can't autofix reliably
- if (typeChecker.getPropertyOfType(spreadType, 'as') !== undefined) asProp = null
+ if (spreadPropNames.includes('as')) asProp = null
continue
}
- // Has sx prop, so should keep using this component
- if (
- attribute.name.type === 'JSXIdentifier' &&
- (attribute.name.name === 'sx' || isStyledSystemProp(attribute.name.name))
- )
- return
+ // Has an allowed prop, so should keep using this component
+ if (attribute.name.type === 'JSXIdentifier' && isAllowedProp(attribute.name.name)) return
// If there is an `as` prop we will need to account for that when autofixing
if (attribute.name.type === 'JSXIdentifier' && attribute.name.name === 'as') asProp = attribute