API
Recommended
The recommended rules, how selector keys are resolved, and the styling prop setting.
@plumeria/eslint-plugin is the ESLint plugin for Plumeria.
Installation
pnpm i -D @plumeria/eslint-pluginUsage
import plumeria from '@plumeria/eslint-plugin';
export default [plumeria.configs.recommended];no-invalid-selector, validate-pseudos, and validate-at-rules also check selectors that are not written as string literals. A computed key is followed back to where it is declared, the same way under ESLint and plumeria-lint, with no type information involved:
import * as css from '@plumeria/core';
import { breakpoints } from './tokens';
const hover = ':hover' as const;
export const styles = css.create({
link: {
[hover]: { color: 'teal' },
[breakpoints.md]: { fontSize: 18 },
},
});A key resolves when it is a const holding a string, a member of a const object or a css.createStatic object, or an import of one of these. as const, satisfies, and type annotations make no difference. A key the rules cannot resolve, such as a let or a function parameter, is skipped.
sort-properties reads a computed key the same way, and sorts one that holds an at-rule with the other at-rules, after the pseudo-classes. no-combinator checks a css.createStatic value only when it starts with : or [, the ones that become a selector when used as a key, so a shadow or a font stack is not reported.
Rules
Rules: recommended
'@plumeria/custom-props-require-import': 'error''@plumeria/expand-border-shorthands': 'warn''@plumeria/no-combinator': 'error''@plumeria/no-destructure': 'error''@plumeria/no-inline-object': 'error''@plumeria/no-inner-call': 'error''@plumeria/no-invalid-selector': 'error''@plumeria/no-mixed-styling-props': 'error''@plumeria/no-mutable-binding': 'warn''@plumeria/no-order-dependent-overlap': 'warn''@plumeria/no-style-prop-relay': 'error''@plumeria/no-unknown-css-properties': 'error''@plumeria/no-unused-keys': 'warn''@plumeria/no-unresolved-composition': 'warn''@plumeria/sort-properties': 'warn''@plumeria/format-properties': 'warn''@plumeria/validate-values': 'warn''@plumeria/validate-pseudos': 'warn''@plumeria/validate-at-rules': 'warn'
When using recommended, all rules are turned on.
Two more rules ship with the plugin and stay off until you ask for them. They do not report a defect; they remove the room for one. See Optional rules.
no-unresolved-composition
Warns when a css.use() result is combined with other class names using +,
a template literal, or an inline array's .join(), or passed to a function such
as clsx or css.use() itself. Enabled by recommended as warn:
'@plumeria/no-unresolved-composition': 'warn'// Warns: merge separate css.use() calls.
`${css.use(styles.base)} ${css.use(styles.active)}`
css.use(styles.base) + ' ' + (active ? css.use(styles.active) : '')
// Warns: pass styles, not css.use() results, to one call.
clsx(css.use(styles.base), className)
css.use(css.use(styles.base), styles.active)
// Warns: rewrite external classes with css.create().
css.use(styles.base) + ' external'
[css.use(styles.base), 'external'].join(' ')
// Compose everything in one call; nested arrays are allowed.
css.use(styles.base, [active && styles.active, styles.external])
// Warns: function keys cannot go through css.use().
css.use(styles.palette(color))
// Apply them to classStyle instead.
<div classStyle={styles.palette(color)} />When a css.use() result is a function argument, directly, inside an array or
spread, or in a conditional branch, the rule reports that the styles should be
passed to one css.use() call instead; this is reported before the other two.
When two or more css.use() results are combined, the rule reports that they
should be merged into one call; this is reported first, even if external classes
are also present. When a css.use() result is combined with external class
names, it lists them as written in the source and reports that their precedence is left to the CSS cascade and that they
should be rewritten as styles with css.create() and passed into the same
css.use() call. It offers no automatic fix. Whitespace-only
additions are allowed. Imports and their aliases are resolved with scope, so
unrelated or shadowed use functions are ignored. Detection covers calls within
these expressions, including conditional branches, and follows results through
local variables, += assignments, arrays joined through a variable, and object
properties. It does not follow values returned from functions or imported from
other modules.
no-style-prop-relay
Reports a style prop that a component receives but never applies to
classStyle or css.use(), and a received style passed through classStyle to
a component that declares it, even from another file and even when the same
style is applied to an element as well. The build stops on both. Enabled by
recommended as error:
'@plumeria/no-style-prop-relay': 'error'// Errors: headerStyle is only passed on.
const Layout = ({ headerStyle }: { headerStyle?: css.Style }) => (
<Header headerStyle={headerStyle} />
);
// Errors: Header declares classStyle.
const Layout = ({ classStyle }: { classStyle?: css.Style }) => (
<div classStyle={classStyle}>
<Header classStyle={classStyle} />
</div>
);
// Apply it on an element this component renders.
const Layout = ({ headerStyle }: { headerStyle?: css.Style }) => (
<header classStyle={[styles.base, headerStyle]} />
);
// A component the build never scans declares nothing, so a style composes onto it.
const OpenButton = ({ classStyle }: { classStyle?: css.Style }) => (
<Dialog.Trigger classStyle={[styles.base, classStyle]} />
);Configuring the styling prop
custom-props-require-import, no-inline-object, no-mixed-styling-props, no-style-prop-relay and no-unresolved-composition need to know which JSX prop carries styles. It is classStyle unless you changed it, so most projects configure nothing.
If you did rename it — via styleProp on withPlumeria or on the unplugin options — tell the plugin the same name. The lint the bundler plugins run in the build is given that name already. plumeria-lint takes it with --style-prop, or reads it from the settings below when ESLint is installed. ESLint cannot read it from your bundler config, so set it once in settings and every rule picks it up:
import plumeria from '@plumeria/eslint-plugin';
export default [
plumeria.configs.recommended,
{
settings: {
plumeria: { styleProp: 'sx' },
},
},
];A single rule can override that if you need it to:
{
rules: {
'@plumeria/no-mixed-styling-props': ['error', { styleProp: 'sx' }],
},
}The name must match whatever the loader or unplugin was given. If they disagree, the lint rules report against a prop the compiler never transforms.