@duro-app/eslint-plugin

中文文档
6.1.0发布于 2026/11/12MITTypeScriptES Module近 30 天下载 7064

ESLint rules enforcing the Duro design system conventions: react-strict-dom html.* elements, deep token imports, token values over raw px/hex, and no deprecated Table parts.

原始使用文档
文档目录9 节
  1. Usage
  2. Rules
  3. no-raw-html-element
  4. no-tokens-barrel-import
  5. no-deprecated-table-parts
  6. no-raw-design-values
  7. no-raw-layer-values
  8. no-raw-breakpoint-query
  9. Token data

@duro-app/eslint-plugin

ESLint rules that enforce the Duro design system conventions mechanically — what CLAUDE.md can only suggest, these rules gate.

Requires ESLint 9 (flat config). The plugin has zero runtime dependencies.

Usage

// eslint.config.js
import duro from '@duro-app/eslint-plugin'
import tseslint from 'typescript-eslint'

export default [
  ...tseslint.configs.recommended,
  duro.configs.recommended,
  {
    rules: {
      // Example: a data grid the design system doesn't cover yet.
      'duro/no-raw-html-element': ['error', {allow: ['table', 'thead', 'tbody', 'tr', 'th', 'td']}],
    },
  },
]

The recommended preset registers the plugin under the duro namespace, so you never have to register it yourself. Don't also register it manually under another key — you'd get two live namespaces and confusing config.

Rules

RulePresetFix
duro/no-raw-html-elementerrorsuggestion
duro/no-tokens-barrel-importerrorautofix
duro/no-deprecated-table-partserrorautofix / suggestion
duro/no-raw-design-valueserrorsuggestion
duro/no-raw-layer-valueswarnsuggestion
duro/no-raw-breakpoint-queryerrorautofix

no-raw-html-element

Raw lowercase JSX intrinsics (<div>, <span>, …) must be react-strict-dom html.* elements. SVG subtrees are exempt (RSD has no SVG primitives). Elements RSD doesn't export (<table>, <canvas>, …) report as non-portable; allow them explicitly per-project via allow. The rewrite is a suggestion, not an autofix, because html.* props are stricter than raw DOM props — a mechanical --fix would trade lint errors for type errors.

Options: {allow?: string[], reportUnsupported?: boolean, htmlModule?: string}

no-tokens-barrel-import

import {colors} from '@duro-app/tokens' breaks the StyleX babel plugin. The autofix splits the barrel import into the right deep imports (@duro-app/tokens/tokens/colors.css, …), preserving aliases and inline type specifiers. Imports with any unmapped specifier report without a fix.

Options: {packages?: string[]}

no-deprecated-table-parts

Flags <Table.Container> (Root owns the container query now; report-only — unwrapping is layout-unsafe to automate) and the no-op isActions prop on <Table.HeaderCell> (autofixed away for literal values; suggestion when the value is an expression). Matches by identifier name (Table, TableCore by default) — configure via tableIdentifiers.

no-raw-design-values

Inside css.create() objects, a raw design value is the finding — anything with a token equivalent, and anything on a tokenised property that is off the scale:

  • colors — a hex / rgb() / hsl() literal anywhere. A palette value suggests its colors.* token; an off-palette one reports without a fix.

  • spacing and radius — a number or 'Npx' on the padding/margin/gap families → spacing.*, on the border-radius family → radii.*. On the scale it suggests the token; off the scale (marginTop: 23) it reports — "not a token" is the point.

  • breakpoints — a px inside a '@media …' / '@container …' condition key. On the scale it suggests the computed key [`@media (min-width: ${breakpoints.md})`] and the import.

  • font size (number, px or rem) → typography.fontSize* or, for the steps typography lacks, typeScale.fontSizeN; font weight 400–700 → typography.fontWeight*; boxShadow → shadows.* on an exact match, otherwise a report; transitionDuration / animationDuration 'Nms' → duration.*; timing functions → easing.*.

  • sizes and border widths (since 5.0, ADR-0027) — a number, 'Npx' or negative on width/height and their min/max, flexBasis, block/inline size; on border*Width, outlineWidth, outlineOffset; and the width inside a border* or outline shorthand string. Each property draws from its own candidates: sizes → sizes.*; border widths → borders.hairline / strong / accent; outline width → borders.focusRing; outline offset → borders.focusOffset / focusOffsetSm. Since 5.2 the size candidates follow the property's axis: a …W / …MinW / …MaxW token is only offered on a width property, a …H / …MinH / …MaxH token only on a height one, and an unsuffixed token on both (flexBasis takes either). One match suggests it (a negative as calc(-1 * …)); several give one suggestion each. When none matches, the message names the nearest role: every same-axis token at the nearest value within ±10% (width: 232 → sizes.gridColSm, sizes.sidebarW, sizes.popoverWSm (240)); when two values are equally near, both are listed, smaller first. Only when nothing is that close does it say to add a token. A calc() or template literal whose static text holds a px is reported too.

  • boxShadow in a template literal (since 5.2) — a px length in its static text (`inset 0 0 0 1px ${colors.border}`) is a raw shadow; use a shadows.* token.

  • micro spacing — an off-scale spacing of 1/2/3/5/6px suggests microSpacing.pxN.

Allowed: 0, relative units (%, vh, fr, em, rem, ch…), keywords (auto, max-content…), identifiers and member expressions (tokens), a calc() of tokens. Skipped on purpose: negatives on spacing properties, 'opacity 150ms'-style transitions, and positions (top, left) and lineHeight. Since 3.0 it ships as error: a raw value is the drift the design system exists to stop.

Shorthands (since 5.7). A spacing or radius string with several words (padding: '4px 8px', gap: '8px 16px') is split on whitespace and every nonzero px word is checked as a single value would be: '4px 8px' gives two reports, '6px 14px' gives rawMicroSpacing and offScaleSpacing. 0, auto, %, rem, em, var(...) and calc(...) words pass. A shorthand report carries no suggestion, because swapping one word would rewrite the whole string; the message names the token for each word.

Inline styles (since 5.7). A style={{ ... }} object literal on any JSX element is read like a css.create style, so a raw colour, font size, weight, measure or px in a shorthand is reported. These reports carry no suggestion, since plain-React code has no .css.ts token imports; the message still names the token. style={styles.x}, arrays, identifiers and spreads are ignored, and so are custom-property keys ('--wb-vw': '12px') and computed keys. Set inlineStyle: false to turn this off.

Options: {factories?: string[], spacingProperties?: string[], radiiProperties?: string[], exemptFiles?: string[], inlineStyle?: boolean} (property lists replace the defaults, they don't merge). inlineStyle defaults to true. exemptFiles takes globs (**, *, ?, {a,b}), matched against the path relative to the working directory and the absolute path; a matching file is not checked. Name only a file whose literals are a technique, not a design value — Duro names its visually-hidden module.

no-raw-layer-values

Since 5.3. Inside css.create() objects:

  • zIndex — a number or numeric string other than 0 and negatives. A layers value suggests its token (zIndex: 1000 → layers.overlay, 1050 → layers.popup); any other value names the layers it sits between, without a fix (zIndex: 16 → between layers.raised (1) and layers.floating (50)). Since 5.4, a value from 2 up to localMax (default 9) is not reported: it orders children inside one component, which is not a layer (1 is layers.raised and keeps its suggestion).
  • backdropFilter / filter — a blur(...) literal. blur(2px) suggests effects.overlayBlur, blur(6px) (5.4) effects.surfaceBlur; another blur reports without a fix.

It ships as warn for the 5.x line, so no consumer's lint turns red on a minor, and becomes error in the next major. It is its own rule, apart from no-raw-design-values, because ESLint sets severity per rule.

Leaving local stacking unreported is this rule's scope, not an exemption from ADR-0027: a z-index is not one of the measures design-system.every-measure-is-a-token makes a token (spacing, radius, size, border width). localMax is capped at 49, below layers.floating (50), so the skip never reaches a layer; {localMax: 0} reports every positive value.

Options: {factories?: string[], exemptFiles?: string[], localMax?: number}

no-raw-breakpoint-query

A media-query string anywhere in a file — useMediaQuery('(min-width: 768px)'), matchMedia(...), a constant — carries a raw breakpoint that no-raw-design-values never sees (it only reads css.create). On the scale the autofix rewrites the literal to `(min-width: ${breakpoints.md})` and adds the import (a css.defineConsts string, so it interpolates to '768px'); off the scale it reports. Skips css.create keys, and skips the fix when a local breakpoints binding would shadow the import.

Token data

The rule data (deep-path map, spacing/radius/breakpoint px values, font sizes and weights, shadows, durations, easings, color palette) is mirrored from @duro-app/tokens as literals so the published plugin stays dependency-free. test/token-drift.test.ts rebuilds every table from the real tokens package and fails CI when they drift.

Only test/ imports @duro-app/tokens; src/ never does. The root prepare script relies on that: it builds this plugin and the tokens package in parallel (pnpm --no-sort). If src/ ever imports @duro-app/tokens, drop --no-sort there, or a fresh install can build the plugin before the tokens it reads.

版本与发布记录

@duro-app/eslint-plugin 共发布 80 个版本,首次发布于 2026/08/12;过去 12 个月发布了 80 个版本。

版本发布日期距今
6.1.0latest2026/11/121 天前
6.0.02026/11/085 天前
5.45.02026/11/0211 天前
5.44.02026/11/0211 天前
5.43.02026/11/0211 天前
5.42.02026/11/0211 天前

关于 @duro-app/eslint-plugin 的常见问题

@duro-app/eslint-plugin 是什么?

ESLint rules enforcing the Duro design system conventions: react-strict-dom html.* elements, deep token imports, token values over raw px/hex, and no deprecated Table parts.

如何安装 @duro-app/eslint-plugin?

在项目目录运行 npm install @duro-app/eslint-plugin,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:eslint(^9.0.0 || ^10.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@duro-app/eslint-plugin 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@duro-app/eslint-plugin 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@duro-app/eslint-plugin 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js ^18.18.0 || ^20.9.0 || >=21.1.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@duro-app/eslint-plugin 可以商用吗?

@duro-app/eslint-plugin 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@duro-app/eslint-plugin 的下载量有多少?

根据 npm 官方下载统计,@duro-app/eslint-plugin 在 2026/10/13 – 2026/11/11 这 30 天内被下载 7,064 次,日均约 235 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@duro-app/eslint-plugin 还在维护吗?

最新版本 6.1.0 发布于 2026/11/12(1 天前);至今共发布 80 个版本,其中过去 12 个月发布了 80 个;首次发布于 2026/08/12。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。