Skip to content

[eslint-plugin] Fix no-unused false positive for styles exported via export specifier - #1787

Open
trofim-samusev wants to merge 1 commit into
facebook:mainfrom
trofim-samusev:fix/no-unused-export-specifier
Open

[eslint-plugin] Fix no-unused false positive for styles exported via export specifier#1787
trofim-samusev wants to merge 1 commit into
facebook:mainfrom
trofim-samusev:fix/no-unused-export-specifier

Conversation

@trofim-samusev

@trofim-samusev trofim-samusev commented Jul 29, 2026

Copy link
Copy Markdown

Fixes #1786

What changed / motivation ?

@stylexjs/no-unused reports every key of a stylex.create() object as unused when the
object is exported using the specifier form:

import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  main: { color: 'red' },
  secondary: { color: 'blue' },
});

export { styles }; // => "Unused style detected: styles.main" (+ styles.secondary)

The rule already exempts export const styles = stylex.create({...}) and
export default styles, on the basis that an exported styles object may have its keys used
by another module. export { styles }; and export { styles as s }; are the same situation
but were not covered.

The rule is fixable: 'code', so eslint --fix acted on the false positive and deleted
the style keys from the source file
, emptying the create() call in the example above to
stylex.create({\n});.

Cause: the ExportNamedDeclaration visitor only looked at node.declaration. For
export { styles }; that is null and the exported bindings live in node.specifiers[],
so the variable was never removed from stylexProperties and Program:exit reported all of
its keys.

The fix iterates node.specifiers and exempts each local name. Re-exports
(export { styles } from './other-styles') are skipped via the node.source == null guard,
because they export another module's binding and must not exempt a same-named local
variable.

Linked PR/Issues

Fixes # (issue)

Additional Context

Tests added to packages/@stylexjs/eslint-plugin/__tests__/stylex-no-unused-test.js:

  • valid: export { styles };
  • valid: export { styles as sharedStyles };
  • valid: export { theme, styles }; (specifier list with an unrelated binding)
  • valid: export { styles as default };
  • invalid: export { styles } from './other-styles'; — still reports the locally unused key,
    covering the re-export guard

No documentation change: neither the plugin README nor the docs site describes the rule's
export handling.

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 Jul 29, 2026
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
stylex Skipped Skipped Jul 30, 2026 3:13am

Request Review


// Exempt used styles: export const exportStyles = stylex.create({});
ExportNamedDeclaration(node: ExportNamedDeclaration) {
// Exempt used styles: export { exportStyles };

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.

This is an edge case but should we make sure there are no false positives for
export type { styles }; or export { type styles }; by checking the exportKind on the declaration and specifiers?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added support for those cases. Can you please check again when you'll have some time?

…export specifier

`no-unused` only inspected `node.declaration` in its `ExportNamedDeclaration`
visitor, so `export { styles };` (where the exported bindings live in
`node.specifiers`) never exempted the styles object and every key was reported
as unused. Because the rule is fixable, `--fix` then deleted the style keys
from the source.

Exempt specifier exports too, skipping re-exports (`export { styles } from './x'`),
which refer to another module's binding rather than a local variable.
@trofim-samusev
trofim-samusev force-pushed the fix/no-unused-export-specifier branch from b608b0e to ce12c1f Compare July 30, 2026 03:13
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.

[eslint-plugin] no-unused reports every style as unused when the styles object is exported via an export specifier

2 participants