@cloudai-design/eslint-plugin
v0.1.2
Published
CloudAI UI 设计体系护栏 ESLint 插件(S 层确定性规则:禁硬编码颜色 / 禁串 Tailwind 版本 / 禁任意值圆角间距,token 名录随 Figma 生成)
Downloads
171
Readme
@cloudai-design/eslint-plugin
CloudAI UI 设计体系的 S 层「确定性」护栏 ESLint 插件。skill / DESIGN.md 负责「教 AI 怎么写」, 本插件负责「写错就红 + 给改法」,是设计规范里最强、可机器判定的一层约束。
规则
| 规则 | 抓什么 |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| @cloudai-design/no-hardcoded-color | 任意值颜色(bg-[#...]、shadow-[..rgba(..)])、默认调色板刻度色(bg-red-500)、内联 style 颜色,以及引用了不存在的 var(--x) |
| @cloudai-design/no-arbitrary-radius-spacing | 任意值圆角 / 间距 / 字号(rounded-[10px] / p-[13px] / text-[14px]),不拦布局类(w-[42px] 等) |
| @cloudai-design/no-tailwind-version-mismatch | 与项目 Tailwind 大版本不符的语法(v3 项目禁 @theme / @import "tailwindcss";v4 项目禁 @tailwind base) |
| @cloudai-design/classname-must-be-transformable | 类名字符串写在 prefix 转换改不到的位置(模块常量、Record 映射表、数组、模板串、JSX 外的 cn()) |
每条规则的「判真伪 / 怎么改」说明见 prompts/<rule>.md(与规则代码解耦)。
classname-must-be-transformable 只对配了 Tailwind prefix 的业务是硬需求,但它拦的写法在
任何项目里都更难维护,所以进 recommended-*。判据是启发式(单词字符串一律放过,避免打到
zod enum),精确判据是本仓库的 pnpm check:prefix-safe。
用法(经典 .eslintrc)
// .eslintrc.cjs
module.exports = {
plugins: ['@cloudai-design'],
// 项目用 Tailwind v3(本体系交付默认):
extends: ['plugin:@cloudai-design/recommended-tailwind3'],
rules: {
// 报错文案要指名本品牌的 token / 圆角取值,就把 brand 告诉它:
'@cloudai-design/no-hardcoded-color': ['error', { brand: 'aidbs' }],
'@cloudai-design/no-arbitrary-radius-spacing': [
'error',
{ brand: 'aidbs' },
],
},
};插件名
@cloudai-design/eslint-plugin在 eslint 配置里简写为@cloudai-design。
没有 recommended,只有 recommended-tailwind3 / recommended-tailwind4:两者唯一的差别是
no-tailwind-version-mismatch 的方向。config 名把 tailwind 写全而不是 recommended-v3,
因为它出现在业务的 .eslintrc 里,-v3 会被读成「插件的第 3 个大版本」。
token 名录是生成物
data/<brand>.json 由 tools/emitters/lint-data.ts 从 token IR 发射(别手改):
{
"cssVars": ["--accent", "--brand", "--brand-gradient", "…"], // 判定 var(--x) 是否为本体系变量
"classKeys": ["accent", "brand", "…"], // 可写成工具类的键
"radius": {
"root": "0.625rem",
"scale": { "lg": ["10px", "0.625rem"], "md": ["8px", "0.5rem"] },
},
}名录让规则能分辨两种长得很像的写法,不配名录的规则两边都会判错:
bg-[hsl(var(--brand))]引用的是真 token,合法,不报。bg-[hsl(var(--brnad))]把变量名拼错了,浏览器里颜色直接失效,照样报。
改了 Figma 的 token,重跑 pnpm tokens:emit 名录就同步;规则代码不动。allow 选项保留,
但只服务「确无对应 token 的一次性颜色」,不再承担名录的职责。
不配 brand 时读 data/index.json(所有 brand 的并集)——宁可放宽到「任一 brand 有这个 token」,
也不在插件里猜业务用的是哪个品牌。brand 写错会当场报错并列出可选值,不会静默回落。
radius 的收益是报错能指名 token:aidbs 的 --radius = 0.625rem,于是 rounded-[10px]
的报错直接告诉你「这就是 rounded-lg」。间距 / 字号沿用 Tailwind 默认刻度、不是品牌 token,
所以无 data 可喂,报错只给方向。
已知缺口(诚实标注)
no-tailwind-version-mismatch基于 ESLint,只能看到 JS/JSX/TS:CSS-in-JS 模板串与 className 里的信号能拦,但独立.css文件里的@import "tailwindcss"/@theme {}拦不到 ——那属于 CSS 维度,需 stylelint(或将来 eslint 9 flat config +@eslint/css)兜底。- 模板串的插值部分(
`bg-${color}`)是运行时值,静态判定不了,本插件有意不猜。 classname-must-be-transformable不查「属性里的第二个cn()」这类细节(上游 transform 只取 第一个),也不验证类名真的是 utility;单词类名一律放过。这些缺口由check:prefix-safe的精确比对兜住。- v3 侧唯一足够确定的信号是
@tailwind base|components|utilities——@config、theme()、 JS 插件在 v4 里仍受支持,拿它们当版本信号会误报,故不拦。
测试
pnpm --filter @cloudai-design/eslint-plugin test基于 eslint RuleTester,另有一组契约断言:config 名、config 里引用的规则是否存在、
tailwind 方向是否配对、brand 写错是否报错。这些错规则用例发现不了,但会让业务的
extends 直接失效。
与 react-doctor 的关系
本包只自建规则(标准 ESLint AST 规则),不自建扫描 / 评分引擎。未来 react-doctor 稳定后, 以「合并 eslint config」方式吸收同一份规则,迁移≈配置连线;prompt(独立 markdown)与度量 (从 eslint JSON 输出派生)均与引擎解耦,不会白干。
为什么是 JS 而不是 TS
本包由业务仓的 eslint 加载,Node 版本不可控,所以发布入口一律 JS、engines.node 保守到
>=18(同 @cloudai-design/cli)。规则逻辑是正则匹配与 context.report,没有可建模的领域类型,
上 TS 只换来一个构建步骤。
规则归属的一条取舍
bg-[oklch(...)] 归 no-hardcoded-color,不归 no-tailwind-version-mismatch。oklch 是合法
CSS 颜色函数,v3 也能写,它是硬编码颜色而不是版本信号。挂在版本规则上会让 v4 项目失去
这层保护。
