@@ -18,9 +18,15 @@ type JSXIdentifier = {
1818 + name : string ,
1919} ;
2020
21+ type JSXExpressionContainer = {
22+ + type : 'JSXExpressionContainer' ,
23+ + expression : Node | { + type : 'JSXEmptyExpression' } ,
24+ } ;
25+
2126type JSXAttribute = {
2227 + type : 'JSXAttribute' ,
2328 + name : JSXIdentifier | { + type : string } ,
29+ + value ? : Node | JSXExpressionContainer | null ,
2430} ;
2531
2632type JSXSpreadAttribute = {
@@ -30,15 +36,22 @@ type JSXSpreadAttribute = {
3036
3137type JSXOpeningElement = {
3238 + type : 'JSXOpeningElement' ,
39+ + name : JSXIdentifier | { + type : string } ,
3340 + attributes : $ReadOnlyArray < JSXAttribute | JSXSpreadAttribute > ,
3441} ;
3542
43+ const STYLEX_PROPS_CONFLICTING_PROPS_MESSAGE =
44+ 'The `{{propName}}` prop should not be used when spreading `stylex.props()` to avoid conflicts.' ;
45+
46+ const STYLEX_SHORTHAND_CONFLICTING_PROPS_MESSAGE =
47+ 'The `{{propName}}` prop should not be used with the `{{sxPropName}}` StyleX prop to avoid conflicts.' ;
48+
3649const stylexNoConflictingProps = {
3750 meta : {
3851 type : 'problem' ,
3952 docs : {
4053 description :
41- 'Disallow using `className` or `style` props on elements that spread `stylex.props()`' ,
54+ 'Disallow using `className` or `style` props on elements that spread `stylex.props()` or use StyleX JSX shorthand ' ,
4255 category : 'Best Practices' ,
4356 recommended : true ,
4457 } ,
@@ -62,14 +75,20 @@ const stylexNoConflictingProps = {
6275 } ,
6376 default : [ 'stylex' , '@stylexjs/stylex' ] ,
6477 } ,
78+ sxPropName : {
79+ oneOf : [ { type : 'string' } , { enum : [ false ] } ] ,
80+ default : 'sx' ,
81+ } ,
6582 } ,
6683 additionalProperties : false ,
6784 } ,
6885 ] ,
6986 } ,
7087 create ( context : Rule . RuleContext ) : { ... } {
71- const { validImports : importsToLookFor = [ 'stylex' , '@stylexjs/stylex' ] } =
72- context . options [ 0 ] || { } ;
88+ const {
89+ validImports : importsToLookFor = [ 'stylex' , '@stylexjs/stylex' ] ,
90+ sxPropName = 'sx' ,
91+ } = context . options [ 0 ] || { } ;
7392
7493 const importTracker = createImportTracker ( importsToLookFor ) ;
7594
@@ -85,6 +104,30 @@ const stylexNoConflictingProps = {
85104 ) ;
86105 }
87106
107+ function isLowercaseHostElement ( node : JSXOpeningElement ) : boolean {
108+ return (
109+ node . name . type === 'JSXIdentifier' &&
110+ typeof node . name . name === 'string' &&
111+ node . name . name [ 0 ] === node . name . name [ 0 ] . toLowerCase ( )
112+ ) ;
113+ }
114+
115+ function hasStylexShorthandProp ( node : JSXOpeningElement ) : boolean {
116+ return (
117+ typeof sxPropName === 'string' &&
118+ isLowercaseHostElement ( node ) &&
119+ node . attributes . some (
120+ ( attr ) =>
121+ attr . type === 'JSXAttribute' &&
122+ attr . name . type === 'JSXIdentifier' &&
123+ attr . name . name === sxPropName &&
124+ attr . value != null &&
125+ attr . value . type === 'JSXExpressionContainer' &&
126+ attr . value . expression . type !== 'JSXEmptyExpression' ,
127+ )
128+ ) ;
129+ }
130+
88131 return {
89132 ImportDeclaration : importTracker . ImportDeclaration ,
90133
@@ -96,10 +139,16 @@ const stylexNoConflictingProps = {
96139 isStylexPropsCallee ( attr . argument . callee ) ,
97140 ) ;
98141
99- if ( ! hasStylexPropsSpread ) {
142+ const hasStylexShorthand = hasStylexShorthandProp ( node ) ;
143+
144+ if ( ! hasStylexPropsSpread && ! hasStylexShorthand ) {
100145 return ;
101146 }
102147
148+ const message = hasStylexShorthand
149+ ? STYLEX_SHORTHAND_CONFLICTING_PROPS_MESSAGE
150+ : STYLEX_PROPS_CONFLICTING_PROPS_MESSAGE ;
151+
103152 for ( const attr of node . attributes ) {
104153 if (
105154 attr . type === 'JSXAttribute' &&
@@ -109,9 +158,8 @@ const stylexNoConflictingProps = {
109158 context . report ( {
110159 // $FlowFixMe[incompatible-type]
111160 node : attr ,
112- message :
113- 'The `{{propName}}` prop should not be used when spreading `stylex.props()` to avoid conflicts.' ,
114- data : { propName : attr . name . name } ,
161+ message,
162+ data : { propName : attr . name . name , sxPropName } ,
115163 } ) ;
116164 } else if (
117165 attr . type === 'JSXSpreadAttribute' &&
@@ -127,9 +175,8 @@ const stylexNoConflictingProps = {
127175 context . report ( {
128176 // $FlowFixMe[incompatible-type]
129177 node : prop ,
130- message :
131- 'The `{{propName}}` prop should not be used when spreading `stylex.props()` to avoid conflicts.' ,
132- data : { propName : prop . key . name } ,
178+ message,
179+ data : { propName : prop . key . name , sxPropName } ,
133180 } ) ;
134181 }
135182 }
0 commit comments