@corndev/eslint-plugin
v1.0.0-beta.4
Published
Corn UI lint rules: props a component does not read, and values outside the ones it accepts
Maintainers
Readme
@corndev/eslint-plugin
Lint rules for code that uses Corn UI's React components. They catch the two mistakes a CSS-first library cannot report for itself, because CSS never errors:
- a prop the component does not read, from another library's vocabulary —
w={260},mt="md",data={…}on a nativeSelect. React passes it to the DOM as a stray attribute, or the component ignores it, and nothing says so. - a value outside the set a prop accepts —
size="xs"on a component whose sizes start atsm. No stylesheet rule matches it, so it renders as the default size, which looks fine and is simply wrong.
Both were found the hard way by a real migration off Mantine: the first run of the check these rules grew from found three live bugs, and its first run against this repository found thirty-three buttons in the documentation's own examples rendering in the wrong colour.
Install
npm install --save-dev @corndev/eslint-pluginIt needs ESLint 8.57 or later, and Corn UI React packages at the same version as the plugin.
Use
With a flat config (eslint.config.js, the default from ESLint 9):
import corn from '@corndev/eslint-plugin';
export default [
corn.configs.recommended,
];With .eslintrc:
{
"extends": ["plugin:@corndev/recommended-legacy"]
}Rules
All four are in recommended.
@corndev/no-foreign-props
Reports a prop from another library's vocabulary on a Corn UI component that does
not declare it. The list is Mantine's — the props a migration most often carries
over by habit: spacing and sizing shorthands (m, mt, p, w, h, miw,
…), style props (c, fw, fz, bg, …), and component props such as data,
searchable, leftSection, styles and classNames.
A prop on the list is fine wherever a component really reads it: label on a
Tab, clearable on a Combobox, leftSection on an Input. The rule checks
each one against that component's declared props, so it only speaks up where the
prop would go nowhere.
Options:
'@corndev/no-foreign-props': ['error', {
props: ['sx'], // more to flag
allow: ['color'], // never flag these
}]@corndev/prop-values
Reports a literal value outside the set a prop accepts:
<Button size="xs" /> // sm | md | lg
<Button variant="ghost" /> // button | icon | unstyled — the colour is `level`It reads "x", {'x'} and {`x`}. A value computed at run time is left
alone: the rule reports what it can prove, never a guess.
It also reports a literal object, array or string passed to a prop typed as a number:
<SimpleGrid cols={{ base: 1, sm: 3 }} /> // Mantine's responsive formThat becomes the string "[object Object]", and the grid silently falls back to one column.
@corndev/no-shadowed-props
Reports a data-* attribute that stands in for a prop the component has:
<Button data-emphasis="quiet" /> // use emphasis="quiet"The component writes data-emphasis itself from its emphasis prop, and an
attribute you pass lands after it — so yours silently wins, and the prop stops
working without a sound. Markup written before a prop existed is where these
come from: one app found 287.
@corndev/no-unbraced-expression
Reports JSX text that is an expression which lost its braces:
<MenuItem>
target.shared ?
<Shared /> : <Private />
</MenuItem>This compiles, and prints "target.shared ?" beside both elements. It is what
moving leftSection={x ? <A /> : <B />} into children produces when the braces
do not come along — one app shipped it for weeks. The rule checks every element,
not only Corn UI's, and stays quiet on prose: it reports text ending in &&,
||, or ? with a space before it (as a ternary is written, and an English
question is not), and only when an element or {…} follows.
How it knows
Every Corn UI React package ships a props.json listing each component's props
and the values of those with a fixed set. It is generated from the same JSDoc as
the package's TypeScript declarations and its documentation, so the three cannot
disagree, and the rules read the copy installed next to the file being linted —
so they always describe the version you are actually using. If an installed
version predates the manifest, the rules say so once per file instead of passing
silently.
In a monorepo that lints source it has not built, point the rules at a manifest directly:
settings: {
corndev: { manifests: { '@corndev/ui-react': manifestObject } },
}Documentation
License
MIT
