Plugins@plumeria/eslint-plugin

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

Terminal
pnpm i -D @plumeria/eslint-plugin

Usage

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.

Edit on GitHub

On this page