Skip to content

[eslint] no-conflicting-props rule - #1381

Merged
mellyeliu merged 5 commits into
facebook:mainfrom
henryqdineen:hqd-no-conflicting-props
Dec 21, 2025
Merged

mellyeliu merged 5 commits into
facebook:mainfrom
henryqdineen:hqd-no-conflicting-props

Conversation

@henryqdineen

@henryqdineen henryqdineen commented Dec 7, 2025

Copy link
Copy Markdown
Collaborator

What changed / motivation ?

Never apply className and style properties alongside a {...stylex.props(...)} spread

I saw the above recommendation in the draft LLM context PR (#1345) and I thought it made sense as an ESLint rule.

This PR adds a new no-conflicting-props ESLint rule which disallows usage of className and style props on the same JSX element as a {...stylex.props(...)} spread, both before and after the spread. I don't have a great sense for how often people might run into this footgun but it seemed easy enough to statically analyze and imakes sense as an ESLint rule.

I considered that we might want to have the rule disallow JSX spreads after stylex.props (e.g <div {...stylex.props(...)} {...rest}> in case the rest has className or style but I did not include that in this PR.

Notes

  • The estree types that are used in this and other ESLint rules don't have JSX types so I had to roll my own. I considered adding a dev dependency on hermes-estree, which seems to have a more complete set of types but I noticed that the JSXAttribute.name type was not accurate (or at least consistent with @babel/types and ast-types).

Pre-flight checklist

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Dec 7, 2025
@henryqdineen
henryqdineen force-pushed the hqd-no-conflicting-props branch from cd89e15 to 441c624 Compare December 7, 2025 07:12

@mellyeliu mellyeliu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite useful to add within OSS - we have an internal version of this rule already (we are behind in consolidating all our lint rules!).

Before we merge, let's add in documentation within the README.md as a minimum

Comment thread packages/@stylexjs/eslint-plugin/src/stylex-no-conflicting-props.js Outdated
},
],
invalid: [
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a test that checks for a conflict via another spread?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are you thinking by "conflict via another spread"? With the current implementation these would be false negatives:

<div {...stylex.props(styles.main)} {...{ className: 'foo' }} />
<div {...stylex.props(styles.main)} {...objThatHasClassName} />

The first example just seems unlikely and the second one seems difficult to implement without deeper analysis and/or type awareness. The simplest thing I can think of is to enforce that the stylex.props spread is the last spread so it overwrites previous conflicting props.

Let me know what you were thinking. Thanks!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline spread, the latter is probably overkill. We've seen some ugly jsx before (though uncommon!) and it's an easy check.

We shouldn't enforce stylex.props being last, since the goal of this guidance is to avoid multiple sources of truth for className and style altogether.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't enforce stylex.props being last

It might make sense to enforce that it is the last spread?


Separately, I was thinking of an enhancement to Babel to check for cases like:

<div {...props} {...stylex.props} />

And logging an error in dev if props contains className or style?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I pushed a commit in e6fa4b8 to handle the inline spread (e.g {...{className: 'foo'}). I considered handling stuff like {...{...{className: 'foo'}} but it seemed like an extreme edge case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might make sense to enforce that it is the last spread?

Personally, would rather just disallow the pattern altogether than patch over the problem to have StyleX overwrite things when we don't know the author's intent. But no strong opinion.

Separately, I was thinking of an enhancement to Babel

Yeah good call! We should add more compiler validation in general.

@mellyeliu mellyeliu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy with this, good to go aside from Flow

@henryqdineen

Copy link
Copy Markdown
Collaborator Author

Happy with this, good to go aside from Flow

Thanks. I fixed flow in b335eb0

@mellyeliu
mellyeliu merged commit a3dcf5c into facebook:main Dec 21, 2025
6 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants