noulscorechoice
Jev shadcn lint eval
This checks shadcn-ui/lint (commit 53de86f) with TypeSafe's Jev model (jev-latest). The repo's own eval measures how agents style UI before and after lint feedback. This one asks Jev whether each lint result is right and whether its message tells you what to change.
Source (8 files)
#!/usr/bin/env node
// Turns a shadcn-ui/lint eval run into System One states.
//
// Usage: node build-states.mjs <results.json> [--out states.jsonl]
//
// One "finding" state per condition-A lint finding (the diagnostics the
// agent was fed) and one "task" state per task (A vs C sources). The
// project's tokens and components come from design-system.json, keyed by
// the run's fixture (packages/evals/<fixture>).
import * as fs from "node:fs"
import * as path from "node:path"
import { fileURLToPath } from "node:url"
const here = path.dirname(fileURLToPath(import.meta.url))
const RULES = JSON.parse(fs.readFileSync(path.join(here, "rules.json"), "utf-8")).rules
const POLICY =
"no-restyle allows layout on every component; Card and its Content/Header/Footer/Group/Panel slots also allow spacing. no-arbitrary-values allows layout."
const args = process.argv.slice(2)
const resultsPath = args[0]
if (!resultsPath) {
console.error("Usage: node build-states.mjs <results.json> [--out states.jsonl]")
process.exit(1)
}
const outIdx = args.indexOf("--out")
const outPath = outIdx !== -1 ? args[outIdx + 1] : "states.jsonl"
const read = (file) => {
try {
return fs.readFileSync(file, "utf-8")
} catch {
return ""
}
}
function snippet(source, line, radius = 3) {
const lines = source.split("\n")
const from = Math.max(1, line - radius)
const to = Math.min(lines.length, line + radius)
const out = []
for (let n = from; n <= to; n++) {
const mark = n === line ? ">" : " "
out.push(`${mark}${String(n).padStart(4)} | ${lines[n - 1]}`)
}
return out.join("\n")
}
const DESIGN = JSON.parse(read(path.join(here, "design-system.json")))
function designSystem(fixture) {
const ds = DESIGN.projects[`packages/evals/${fixture}`]
if (!ds) throw new Error(`design-system.json has no entry for packages/evals/${fixture}`)
return { policy: POLICY, fixture: `packages/evals/${fixture}`, ...ds }
}
const data = JSON.parse(read(resultsPath))
const runDir = path.dirname(path.resolve(resultsPath))
const records = []
for (const r of data.results) {
const aDir = path.join(runDir, r.task, "a")
const cDir = path.join(runDir, r.task, "c")
const task = { id: r.task, prompt: r.prompt, file: r.file }
const ds = designSystem(data.fixture ?? "fixture")
const context = {
run: data.runId,
model: data.model,
fixture: data.fixture,
c_redirect: r.c.redirect,
c_violations_after: r.c.violations.length,
c_rounds: r.c.rounds.length,
}
for (const f of r.a.violations) {
// Harness findings (missing file, parse error, agent error) are not lint output.
if (!RULES[f.rule]) continue
records.push({
id: `${r.task}:a:${f.file}:${f.line}:${f.rule}`,
scope: "finding",
state: {
task,
rule: { id: f.rule, ...RULES[f.rule] },
finding: { file: f.file, line: f.line, message: f.message },
snippet: snippet(read(path.join(aDir, f.file)), f.line),
design_system: ds,
},
context: { ...context, condition: "a" },
})
}
records.push({
id: `${r.task}:task`,
scope: "task",
state: {
task,
before: { source: read(path.join(aDir, r.file)) },
after: {
source: read(path.join(cDir, r.file)),
redirect: r.c.redirect,
},
design_system: ds,
},
context,
})
}
fs.writeFileSync(outPath, records.map((x) => JSON.stringify(x)).join("\n") + "\n")
console.log(
`${records.length} states (${records.filter((x) => x.scope === "finding").length} findings) -> ${outPath}`
)
{
"source": "https://github.com/shadcn-ui/lint",
"commit": "53de86f0e7dcc341a9cb45c383a9f2c454d1e958",
"outside": "The file is outside any shadcn project, so there are no theme tokens or design-system components; only palette and literal checks apply.",
"projects": {
"packages/lint/test/fixtures/project": {
"theme": "app/globals.css",
"color_tokens": [
"accent",
"accent-foreground",
"background",
"border",
"card",
"card-foreground",
"destructive",
"foreground",
"input",
"muted",
"muted-foreground",
"popover",
"popover-foreground",
"primary",
"primary-foreground",
"ring",
"secondary",
"secondary-foreground"
],
"components": [
{
"file": "components/ui/button.tsx",
"exports": [
"Button"
],
"variants": [
{
"name": "buttonVariants",
"axes": {
"variant": [
"default",
"outline",
"secondary",
"ghost",
"destructive",
"link"
],
"size": [
"default",
"xs",
"sm",
"lg",
"icon",
"icon-xs",
"icon-sm",
"icon-lg"
]
}
}
]
},
{
"file": "components/ui/card.tsx",
"exports": [
"Card",
"CardAction",
"CardContent",
"CardDescription",
"CardFooter",
"CardHeader",
"CardTitle"
],
"variants": [
{
"name": "Card",
"axes": {
"size": [
"default",
"sm"
]
}
}
]
},
{
"file": "components/ui/field.tsx",
"exports": [
"Field",
"FieldLabel"
],
"variants": [
{
"name": "fieldVariants",
"axes": {
"orientation": [
"vertical",
"horizontal",
"responsive"
]
}
},
{
"name": "fieldLabelVariants",
"axes": {
"variant": [
"default",
"muted"
]
}
}
]
},
{
"file": "components/ui/legacy-chip.js",
"exports": [
"LegacyChip"
],
"variants": []
},
{
"file": "components/ui/tv-chip.tsx",
"exports": [
"TvChip"
],
"variants": [
{
"name": "chip",
"axes": {
"tone": [
"neutral",
"loud"
]
}
},
{
"name": "TvChip",
"axes": {
"tone": [
"neutral",
"loud"
]
}
}
]
}
]
},
"packages/lint/test/fixtures/broken-theme": {
"theme": "app/globals.css",
"color_tokens": [
"primary"
],
"components": []
},
"packages/lint/test/fixtures/prefixed": {
"theme": "app/globals.css",
"color_tokens": [
"primary"
],
"components": []
},
"packages/evals/fixture": {
"theme": "app/globals.css",
"color_tokens": [
"accent",
"accent-foreground",
"background",
"border",
"card",
"card-foreground",
"destructive",
"foreground",
"input",
"muted",
"muted-foreground",
"popover",
"popover-foreground",
"primary",
"primary-foreground",
"ring",
"secondary",
"secondary-foreground"
],
"components": [
{
"file": "components/ui/badge.tsx",
"exports": [
"Badge"
],
"variants": [
{
"name": "badgeVariants",
"axes": {
"variant": [
"default",
"secondary",
"destructive",
"outline",
"ghost",
"link"
]
}
}
]
},
{
"file": "components/ui/button.tsx",
"exports": [
"Button"
],
"variants": [
{
"name": "buttonVariants",
"axes": {
"variant": [
"default",
"outline",
"secondary",
"ghost",
"destructive",
"link"
],
"size": [
"default",
"xs",
"sm",
"lg",
"icon",
"icon-xs",
"icon-sm",
"icon-lg"
]
}
}
]
},
{
"file": "components/ui/card.tsx",
"exports": [
"Card",
"CardAction",
"CardContent",
"CardDescription",
"CardFooter",
"CardHeader",
"CardTitle"
],
"variants": [
{
"name": "Card",
"axes": {
"size": [
"default",
"sm"
]
}
}
]
}
]
},
"packages/evals/fixture-rich": {
"theme": "app/globals.css",
"color_tokens": [
"accent",
"accent-foreground",
"background",
"border",
"card",
"card-foreground",
"chart-1",
"chart-2",
"chart-3",
"chart-4",
"chart-5",
"destructive",
"foreground",
"input",
"muted",
"muted-foreground",
"popover",
"popover-foreground",
"primary",
"primary-foreground",
"ring",
"secondary",
"secondary-foreground",
"sidebar",
"sidebar-accent",
"sidebar-accent-foreground",
"sidebar-border",
"sidebar-foreground",
"sidebar-primary",
"sidebar-primary-foreground",
"sidebar-ring"
],
"components": [
{
"file": "components/ui/avatar.tsx",
"exports": [
"Avatar",
"AvatarBadge",
"AvatarFallback",
"AvatarGroup",
"AvatarGroupCount",
"AvatarImage"
],
"variants": [
{
"name": "Avatar",
"axes": {
"size": [
"default",
"sm",
"lg"
]
}
}
]
},
{
"file": "components/ui/badge.tsx",
"exports": [
"Badge"
],
"variants": [
{
"name": "badgeVariants",
"axes": {
"variant": [
"default",
"secondary",
"destructive",
"outline",
"ghost",
"link"
]
}
}
]
},
{
"file": "components/ui/button.tsx",
"exports": [
"Button"
],
"variants": [
{
"name": "buttonVariants",
"axes": {
"variant": [
"default",
"outline",
"secondary",
"ghost",
"destructive",
"link"
],
"size": [
"default",
"xs",
"sm",
"lg",
"icon",
"icon-xs",
"icon-sm",
"icon-lg"
]
}
}
]
},
{
"file": "components/ui/card.tsx",
"exports": [
"Card",
"CardAction",
"CardContent",
"CardDescription",
"CardFooter",
"CardHeader",
"CardTitle"
],
"variants": [
{
"name": "Card",
"axes": {
"size": [
"default",
"sm"
]
}
}
]
},
{
"file": "components/ui/dropdown-menu.tsx",
"exports": [
"DropdownMenu",
"DropdownMenuCheckboxItem",
"DropdownMenuContent",
"DropdownMenuGroup",
"DropdownMenuItem",
"DropdownMenuLabel",
"DropdownMenuPortal",
"DropdownMenuRadioGroup",
"DropdownMenuRadioItem",
"DropdownMenuSeparator",
"DropdownMenuShortcut",
"DropdownMenuSub",
"DropdownMenuSubContent",
"DropdownMenuSubTrigger",
"DropdownMenuTrigger"
],
"variants": [
{
"name": "DropdownMenuItem",
"axes": {
"variant": [
"default",
"destructive"
]
}
}
]
},
{
"file": "components/ui/input.tsx",
"exports": [
"Input"
],
"variants": []
},
{
"file": "components/ui/label.tsx",
"exports": [
"Label"
],
"variants": []
},
{
"file": "components/ui/select.tsx",
"exports": [
"Select",
"SelectContent",
"SelectGroup",
"SelectItem",
"SelectLabel",
"SelectScrollDownButton",
"SelectScrollUpButton",
"SelectSeparator",
"SelectTrigger",
"SelectValue"
],
"variants": [
{
"name": "SelectTrigger",
"axes": {
"size": [
"sm",
"default"
]
}
}
]
},
{
"file": "components/ui/separator.tsx",
"exports": [
"Separator"
],
"variants": []
},
{
"file": "components/ui/table.tsx",
"exports": [
"Table",
"TableBody",
"TableCaption",
"TableCell",
"TableFooter",
"TableHead",
"TableHeader",
"TableRow"
],
"variants": []
},
{
"file": "components/ui/tabs.tsx",
"exports": [
"Tabs",
"TabsContent",
"TabsList",
"TabsTrigger"
],
"variants": [
{
"name": "tabsListVariants",
"axes": {
"variant": [
"default",
"line"
]
}
}
]
}
]
}
}
}
#!/usr/bin/env node
// Writes design-system.json: the real color tokens and component variants
// of each shadcn-ui/lint fixture, read with @shadcn/lint's project API.
// The eval scripts read this file instead of the checkout, so it is the
// source of truth for what Jev is told about each project.
//
// Usage: node extract-design-system.mjs --repo <built shadcn-lint checkout>
import { execFileSync } from "node:child_process"
import * as fs from "node:fs"
import * as path from "node:path"
import { fileURLToPath, pathToFileURL } from "node:url"
const here = path.dirname(fileURLToPath(import.meta.url))
const args = process.argv.slice(2)
const i = args.indexOf("--repo")
const REPO = i !== -1 ? path.resolve(args[i + 1]) : null
if (!REPO) {
console.error("Usage: node extract-design-system.mjs --repo <checkout>")
process.exit(1)
}
// Fixtures that ship a components.json. packages/evals/fixture-ds has none
// (it passes componentImports instead), so it is not listed.
const FIXTURES = [
"packages/lint/test/fixtures/project",
"packages/lint/test/fixtures/broken-theme",
"packages/lint/test/fixtures/prefixed",
"packages/evals/fixture",
"packages/evals/fixture-rich",
]
const { project } = await import(
pathToFileURL(path.join(REPO, "packages/lint/dist/index.js")).href
)
const rel = (file) => path.relative(REPO, file).replace(/\\/g, "/")
const projects = {}
for (const fixture of FIXTURES) {
const root = path.join(REPO, fixture)
const probe = path.join(root, "app/page.tsx")
const meta = project.projectFor(probe)
const byFile = new Map()
for (const [name, file] of project.componentsFor(probe)?.files ?? []) {
if (!byFile.has(file)) byFile.set(file, [])
byFile.get(file).push(name)
}
const components = [...byFile]
.map(([file, exports]) => ({
file: path.relative(root, file),
exports: exports.sort(),
variants: (project.variantDefinitionsOf(file) ?? [])
.filter((d) => Object.keys(d.axes).length)
.map((d) => ({ name: d.name, axes: d.axes })),
}))
.sort((a, b) => a.file.localeCompare(b.file))
projects[fixture] = {
theme: meta?.cssFile ? path.relative(root, meta.cssFile) : null,
color_tokens: [...(project.colorTokensFor(probe) ?? [])].sort(),
components,
}
}
const out = {
source: "https://github.com/shadcn-ui/lint",
commit: execFileSync("git", ["-C", REPO, "rev-parse", "HEAD"], { encoding: "utf-8" }).trim(),
outside:
"The file is outside any shadcn project, so there are no theme tokens or design-system components; only palette and literal checks apply.",
projects,
}
fs.writeFileSync(path.join(here, "design-system.json"), JSON.stringify(out, null, 2) + "\n")
for (const [k, v] of Object.entries(projects))
console.log(`${k}: ${v.color_tokens.length} tokens, ${v.components.length} component files`)
{
"model": "jev-latest",
"finding": {
"is_violation": {
"type": "noul",
"instructions": "Given `rule` and `design_system`, does the code at `finding.line` in `snippet` actually bypass the design system in the way `rule.checks` describes?",
"criteria": {
"true": "The flagged class, style, or value is off-system: it restyles a component from outside, uses a color or value the theme does not provide, or cannot be read by the linter.",
"false": "The flagged code is on-system or permitted by the policy in `design_system.policy`, so the finding is a false positive."
}
},
"message_actionable": {
"type": "score",
"instructions": "How well does `finding.message` tell a developer what to change at `finding.line` so the code follows `rule.intended_fix`?",
"criteria": [
"The message names a problem but gives no usable direction, or points at the wrong fix.",
"The message gives a direction, but the developer still has to look up which variant, token, or file to use.",
"The message names the concrete variant, size, token, or file to use, so the fix can be made without looking anything up."
]
},
"fix_direction": {
"type": "choice",
"instructions": "Given `task.prompt`, `snippet`, and `design_system`, which change best resolves `finding` while keeping what the task asked for?",
"criteria": {
"use_existing_variant": "An existing variant or size listed in `design_system.components` already gives the requested look.",
"add_variant": "The task explicitly asks for a treatment no existing variant provides, so a new variant belongs in the component file.",
"use_theme_token": "A color, spacing, radius, or text token already declared in `design_system.color_tokens` or the Tailwind scale fits the request.",
"add_theme_token": "The task asks for a specific brand value the theme does not declare, so a new token belongs in the theme file.",
"move_to_parent": "The class is layout and belongs on a wrapping element rather than on the component.",
"fix_spelling": "The class is a typo or a name Tailwind does not generate.",
"not_a_violation": "Nothing needs to change; the finding is wrong."
}
}
},
"case": {
"is_violation": {
"type": "noul",
"instructions": "Under `rule_options` and `design_system`, does `code` contain something `rule.checks` describes as a problem?",
"criteria": {
"true": "At least one class, style, attribute, or value in `code` is off-system in the way `rule.checks` describes and is not exempted by `rule_options`.",
"false": "Everything in `code` is on-system for this rule, or every off-system part is exempted by `rule_options`."
}
}
},
"task": {
"intent_kept": {
"type": "score",
"instructions": "Comparing `before.source` and `after.source`, does the after code still deliver what `task.prompt` asked for? Snapping a stated value to the nearest theme token counts as kept.",
"criteria": [
"A stated color family, emphasis, shape, or element is missing or reversed in the after code.",
"The requested look is partly kept, but one stated requirement is visibly weaker than asked.",
"Every stated requirement is kept, allowing for snapping values to the nearest token."
]
},
"workaround": {
"type": "noul",
"instructions": "Does `after.source` silence or dodge the linter instead of fixing the styling, for example with a disable comment, an inline style, a <style> element, or a class hidden in a variable?"
}
}
}
{"rule":"no-inline-styles","kind":"valid","code":"export const A = () => <div className=\"p-4\" />"}
{"rule":"no-inline-styles","kind":"valid","code":"export const A = ({ w }: { w: string }) => <div style={{ \"--sidebar-width\": w }} />"}
{"rule":"no-inline-styles","kind":"valid","code":"export const A = () => <div style={{ \"--gap\": \"8px\" }} />"}
{"rule":"no-inline-styles","kind":"valid","code":"export const A = ({ style }: { style?: React.CSSProperties }) => <div style={{ \"--w\": 4, ...style } as React.CSSProperties} />"}
{"rule":"no-inline-styles","kind":"valid","code":"const tones = { brand: \"var(--color-brand)\", muted: \"var(--color-muted)\" }\nexport const A = ({ tone }: { tone: keyof typeof tones }) => <div style={{ \"--ring\": tones[tone] }} />"}
{"rule":"no-inline-styles","kind":"invalid","code":"declare function defaults(): { style?: React.CSSProperties }\nexport const A = ({ style }: { style?: React.CSSProperties } = defaults()) => <Box style={style} />","options":[{"contracts":[{"pattern":"^Box$","message":"<Box> takes its style from the theme."}]}],"errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = () => <><style>{\".glow { box-shadow: 0 0 40px rgba(59,130,246,.3) }\"}</style><div className=\"glow\" /></>","errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"const glow = \"rgba(59, 130, 246, 0.15)\"\nexport const A = () => <div style={{ \"--glow\": glow }} />","errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"const glowColorMap = { blue: \"rgba(59, 130, 246, 0.15)\", slate: \"rgba(148, 163, 184, 0.15)\" }\nexport const A = ({ tone }: { tone: \"blue\" | \"slate\" }) => { const c = glowColorMap[tone]; return <div style={{ \"--glow-shadow-color\": c }} /> }","errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = () => <div style={{ backgroundColor: \"#FF6B35\", padding: 13 }} />","errors":2}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = ({ w }: { w: string }) => <div style={{ \"--w\": w, color: \"red\" }} />","errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = () => <div style={{ \"--x\": \"#ff00aa\" }} className=\"bg-(--x)\" />","errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = () => <div style={{ \"--tint\": \"oklch(0.6 0.2 20)\" }} />","errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = ({ s }: { s: React.CSSProperties }) => <div style={s} />","errors":1}
{"rule":"no-restyle","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"mt-4 w-full col-start-2 hidden md:inline-flex\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}]}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = ({ s }: { s: object }) => <div style={{ ...s }} />","errors":1}
{"rule":"no-restyle","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button variant=\"destructive\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-restyle","kind":"valid","code":"export const A = () => <div className=\"bg-red-500 rounded-full\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-restyle","kind":"valid","code":"import { Hero } from \"./hero\"\nexport const A = () => <Hero className=\"bg-muted\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-restyle","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\nexport const A = ({ className }: { className?: string }) => <Button className={cn(\"w-full\", className)}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}]}
{"rule":"no-restyle","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"bg-red-500\">Go</Button>","filename":"/nonexistent/app/page.tsx"}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"mt-4 bg-red-500\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":2}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"bg-red-500 rounded-full\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}],"errors":2}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/widgets\"\nexport const A = () => <Button className=\"text-xs\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}],"errors":1}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"[margin:1rem] [background:red] [border-top-color:red] [text-shadow:0_0_1px_red] [border:1px_solid_red]\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}],"errors":4}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\nexport const A = () => <Button className={cn(\"w-full\", \"shadow-lg\")}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}],"errors":1}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = ({ on }: { on: boolean }) => <Button className={on ? \"bg-accent\" : \"mt-2\"}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}],"errors":1}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"hover:bg-emerald-500\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}],"errors":1}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"p-0\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}],"errors":1}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button classNames={{ root: \"bg-red-500\" }}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}],"errors":1}
{"rule":"require-static-classes","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"mt-4\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"require-static-classes","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\nexport const A = ({ on, className }: { on: boolean; className?: string }) => <Button className={cn(\"mt-4\", on && \"w-full\", className)}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"require-static-classes","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\nexport const A = ({ on }: { on: boolean }) => <Button className={cn({ \"w-full\": on })}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-raw-colors","kind":"valid","code":"export const A = () => <div className=\"bg-primary text-muted-foreground border-border ring-ring/50\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"require-static-classes","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nconst layout = \"mt-4\"\nexport const A = () => <Button className={layout}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-raw-colors","kind":"valid","code":"export const A = () => <div className=\"bg-primary/[0.5] hover:bg-primary/(--o) text-muted-foreground/[0.7]\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"require-static-classes","kind":"valid","code":"export const A = ({ cls }: { cls: string }) => <div className={cls} />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-arbitrary-values","kind":"valid","code":"export const A = () => <div className=\"p-4 rounded-lg border border-border text-sm bg-card\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-raw-colors","kind":"valid","code":"export const A = () => <div className=\"bg-transparent text-white border-black text-current\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"require-static-classes","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = ({ cls }: { cls: string }) => <Button className={cls}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-raw-colors","kind":"valid","code":"export const A = () => <div className=\"text-sm ring-2 border-2 shadow-lg bg-cover bg-none\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-arbitrary-values","kind":"valid","code":"export const A = () => <div className=\"data-[state=open]:flex [&_svg]:size-4\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-raw-colors","kind":"valid","code":"export const A = () => <div className=\"bg-[#333] bg-(--brand)\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"require-static-classes","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = ({ size }: { size: string }) => <Button className={`p-${size}`}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-raw-colors","kind":"valid","code":"export const A = () => <div className=\"bg-amber-100\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["bg-amber-100"]}]}
{"rule":"require-static-classes","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nimport { cn } from \"@/lib/utils\"\nexport const A = ({ extra }: { extra: string }) => <Button className={cn(\"mt-4\", extra)}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-arbitrary-values","kind":"valid","code":"export const A = () => <div className=\"w-[calc(100%-2rem)] max-h-[250px] mt-[13px]\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}]}
{"rule":"no-raw-colors","kind":"valid","code":"export const A = () => <div className=\"bg-brand text-whatever\" />","filename":"/nonexistent/app/page.tsx"}
{"rule":"require-static-classes","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className={makeClasses()}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-arbitrary-values","kind":"valid","code":"export const A = () => <div className=\"w-[320px]\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["w-[320px]"]}]}
{"rule":"no-raw-colors","kind":"invalid","code":"import { cva } from \"class-variance-authority\"\nconst variants = { tone: { hot: \"bg-pink-500\" } }\nexport const v = cva(\"mt-4\", { variants })","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"require-static-classes","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nlet c = \"mt-4\"\nc = \"bg-red-500\"\nexport const A = () => <Button className={c}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-arbitrary-values","kind":"invalid","code":"export const A = () => <div className=\"p-[13px] rounded-[10px] border-[#E4E4E7] text-[13px]\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":4}
{"rule":"no-raw-colors","kind":"invalid","code":"import { cva } from \"class-variance-authority\"\nconst tone = { hot: \"bg-pink-500\" }\nexport const v = cva(\"mt-4\", { variants: { tone } })","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-arbitrary-values","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"p-[3px]\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-raw-colors","kind":"invalid","code":"const c = \"bg-pink-500\"\nexport const A = () => <><div className={c} /><div className={c} /></>","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-arbitrary-values","kind":"invalid","code":"import { cn } from \"@/lib/utils\"\nexport const A = () => <p className={cn(\"md:text-[11px]\")} />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-raw-colors","kind":"invalid","code":"const c = \"bg-pink-500\"\nexport const A = ({ on }: { on: boolean }) => <div className={on ? c : c} />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-arbitrary-values","kind":"invalid","code":"export const A = () => <div className=\"w-[320px] mt-[13px]\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":2}
{"rule":"no-raw-colors","kind":"invalid","code":"export const A = () => <div className=\"bg-olive-500 border-t-red-500 text-shadow-red-500 ring-offset-red-500 mask-linear-from-red-500\" />","filename":"/nonexistent/app/page.tsx","errors":5}
{"rule":"no-arbitrary-values","kind":"invalid","code":"import { cva } from \"class-variance-authority\"\nexport const v = cva(\"bg-[#f0f]\")","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-raw-colors","kind":"invalid","code":"export const A = () => <div className=\"bg-pink-500/[0.5] hover:bg-pink-500/(--o)\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":2}
{"rule":"no-arbitrary-values","kind":"invalid","code":"const c = \"bg-[#ff00aa]\"\nexport const A = () => <div className={c} />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-raw-colors","kind":"invalid","code":"export const A = () => <div className=\"bg-pink-500 text-zinc-400\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":2}
{"rule":"no-raw-colors","kind":"invalid","code":"export const A = () => <div className=\"hover:bg-emerald-600/90 dark:text-sky-300\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":2}
{"rule":"no-raw-colors","kind":"invalid","code":"import { cn } from \"@/lib/utils\"\nexport const A = () => <p className={cn(\"from-orange-400\")} />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-arbitrary-values","kind":"invalid","code":"export const A = () => <div className=\"bg-[#ff00aa] rounded-[10px] text-[0.875rem]\" />","filename":"/nonexistent/app/page.tsx","errors":3}
{"rule":"no-raw-colors","kind":"invalid","code":"export const A = () => <div className=\"bg-pink-500\" />","filename":"/nonexistent/app/page.tsx","errors":1}
{"rule":"no-raw-colors","kind":"invalid","code":"export const A = () => <div className=\"bg-brand\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-raw-colors","kind":"invalid","code":"import { cva } from \"class-variance-authority\"\nexport const v = cva(\"bg-pink-500\", { variants: { tone: { hot: \"text-rose-600\" } } })","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":2}
{"rule":"no-raw-colors","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button classNames={{ day: \"bg-pink-500\" }}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-unknown-classes","kind":"valid","code":"export const A = () => <div className=\"flex items-center gap-2 bg-primary text-sm hover:bg-primary/90 md:grid-cols-3 -mt-2 !p-0 sr-only\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-unknown-classes","kind":"valid","code":"export const A = () => <div className=\"group peer group/card dark [mask-type:luminance] [&_svg]:size-4 data-[state=open]:flex\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-unknown-classes","kind":"valid","code":"export const A = () => <div className=\"tap-target tab-4 legacy-card\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-unknown-classes","kind":"valid","code":"export const A = () => <div className=\"toaster\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["toaster"]}]}
{"rule":"no-unknown-classes","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"bg-primry flex-cols itms-center\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":2}
{"rule":"no-unknown-classes","kind":"invalid","code":"export const A = () => <div className=\"hover:roundedd md:grd\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":2}
{"rule":"no-inline-styles","kind":"valid","code":"export const A = () => <div style={{ \"--bg\": \"var(--color-primary)\" }} />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = () => <div style={{ \"--bg\": \"red\" }} />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = () => <div style={{ \"--bg\": \"hwb(0 0% 0%)\" }} />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = () => <div {...{ style: { color: \"red\" } }} />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-inline-styles","kind":"valid","code":"export const A = ({ style }: { style?: React.CSSProperties }) => <div style={style} />"}
{"rule":"no-inline-styles","kind":"valid","code":"const style = { \"--gap\": \"8px\" }\nexport const A = () => <div style={style} />"}
{"rule":"no-inline-styles","kind":"valid","code":"const theme = { style: { \"--gap\": \"8px\" } }\nexport const A = () => <div style={theme.style} />"}
{"rule":"no-inline-styles","kind":"invalid","code":"const style = { color: \"#ff0000\" }\nexport const A = () => <div style={style} />","errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"const theme = { style: { backgroundColor: \"red\" } }\nexport const A = () => <div style={theme.style} />","errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"const base = { padding: 4 }\nexport const A = () => <div style={{ \"--x\": 1, ...base }} />","errors":1}
{"rule":"no-arbitrary-values","kind":"invalid","code":"export const A = () => <div className=\"border-spacing-x-[8px]\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-arbitrary-values","kind":"valid","code":"export const A = () => <div className=\"[margin:1rem]\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}]}
{"rule":"no-arbitrary-values","kind":"invalid","code":"export const A = () => <div className=\"[margin:1rem]\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-inline-styles","kind":"valid","code":"export const A = () => <div style={{ \"--icon\": \"url(orange-icon.svg)\" }} />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-inline-styles","kind":"valid","code":"export const A = () => <div style={{ \"--paint\": `var(--color-primary)` }} />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = () => <div style={{ \"--paint\": `rebeccapurple` }} />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = () => <div style={{ \"--paint\": \"linear-gradient(red, blue)\" }} />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-inline-styles","kind":"valid","code":"export const A = ({ x }: { x: number }) => <div style={{ transform: `translateX(${x}px)` }} />","options":[{"allow":["transform"]}]}
{"rule":"no-inline-styles","kind":"valid","code":"export const A = () => <div style={{ backgroundColor: \"violet\" }} />","options":[{"allow":["background-color"]}]}
{"rule":"no-inline-styles","kind":"valid","code":"export const A = () => <div style={{ borderColor: \"red\", borderWidth: 2 }} />","options":[{"allow":["border-*"]}]}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = () => <div style={{ backgroundColor: \"violet\", color: \"blue\" }} />","options":[{"allow":["backgroundColor"]}],"errors":1}
{"rule":"no-inline-styles","kind":"invalid","code":"export const A = () => <div className=\"p-4\" />","options":[{"allow":["bg-red-500"]}],"errors":1}
{"rule":"no-restyle","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nconst layout = \"mt-4 w-full\"\nexport const A = () => <Button className={layout}>Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}]}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = ({ active }: { active: boolean }) => { const tone = active ? \"bg-primary\" : \"mt-2\"; return <Button className={tone}>Go</Button> }","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}],"errors":1}
{"rule":"no-restyle","kind":"valid","code":"import { Card, CardContent, CardTitle } from \"@/components/ui/card\"\nexport const A = () => <Card className=\"p-0\"><CardContent className=\"px-2 pt-4 md:px-6\">Hi</CardContent></Card>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"],"contracts":[{"pattern":"^Card$|(Content|Header|Footer|Group|Panel)$","allow":["layout","spacing"]}]}]}
{"rule":"no-restyle","kind":"invalid","code":"import { Card, CardContent, CardTitle } from \"@/components/ui/card\"\nexport const A = () => <CardContent className=\"px-2 rounded-xl text-xs\">Hi</CardContent>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"],"contracts":[{"pattern":"^Card$|(Content|Header|Footer|Group|Panel)$","allow":["layout","spacing"]}]}],"errors":2}
{"rule":"no-restyle","kind":"invalid","code":"import { Card, CardContent, CardTitle } from \"@/components/ui/card\"\nexport const A = () => <CardContent className=\"px-2\">Hi</CardContent>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"]}],"errors":1}
{"rule":"no-restyle","kind":"valid","code":"import { Card, CardContent, CardTitle } from \"@/components/ui/card\"\nexport const A = () => <CardTitle className=\"p-2 md:p-4 hover:!px-3\">Hi</CardTitle>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"contracts":[{"pattern":"^CardTitle$","allow":["p-*","px-*"]}]}]}
{"rule":"no-restyle","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"rounded-full\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"contracts":[{"pattern":"^Button$","allow":["rounded"]}]}]}
{"rule":"no-restyle","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"w-full w-[320px]\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"],"contracts":[{"pattern":"^Button$","allow":["w-*"]}]}]}
{"rule":"no-restyle","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"bg-primary/50\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"contracts":[{"pattern":"^Button$","allow":["bg-primary"]}]}]}
{"rule":"no-restyle","kind":"valid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"mt-4\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"contracts":[{"pattern":"^Button$","allow":["color"]},{"pattern":"Button","allow":["layout"]}]}]}
{"rule":"no-restyle","kind":"invalid","code":"import { Card, CardContent, CardTitle } from \"@/components/ui/card\"\nexport const A = () => <CardContent className=\"md:p-0 hover:!px-0 p-4\">Hi</CardContent>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"contracts":[{"pattern":"^CardContent$","allow":["spacing"],"deny":["p-0","px-0"]}]}],"errors":2}
{"rule":"no-restyle","kind":"invalid","code":"import { Card, CardContent, CardTitle } from \"@/components/ui/card\"\nexport const A = () => <CardContent className=\"md:p-0 p-0\">Hi</CardContent>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"contracts":[{"pattern":"^CardContent$","allow":["spacing"],"deny":["md:p-*"]}]}],"errors":1}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"mt-4\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"],"contracts":[{"pattern":"^Button$","allow":["w-*"]}]}],"errors":1}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"bg-red-500\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"contracts":[{"pattern":"^Button$","allow":["color"]},{"pattern":"Button","allow":["layout"]}]}],"errors":1}
{"rule":"no-restyle","kind":"invalid","code":"import { Card, CardContent, CardTitle } from \"@/components/ui/card\"\nexport const A = () => <CardContent className=\"px-2 mt-4\">Hi</CardContent>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"allow":["layout"],"contracts":[{"pattern":"^CardContent$","message":"<CardContent> takes layout from outside, not {{className}}."}]}],"errors":1}
{"rule":"no-restyle","kind":"invalid","code":"import { Card, CardContent, CardTitle } from \"@/components/ui/card\"\nexport const A = () => <CardTitle className=\"p-[13px] text-xs\">Hi</CardTitle>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"contracts":[{"pattern":"^CardTitle$","allow":["p-*"]}]}],"errors":1}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@acme/ui\"\nexport const A = () => <Button className=\"bg-red-500\">Go</Button>","filename":"/nonexistent/app/page.tsx","options":[{"componentImports":["^@acme/ui"]}],"errors":1}
{"rule":"no-restyle","kind":"valid","code":"import { Card, CardContent, CardTitle } from \"@/components/ui/card\"\nexport const A = () => <CardTitle className=\"flex flex-1 text-xs\">Hi</CardTitle>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"contracts":[{"pattern":"^CardTitle$","allow":["flex","typography"]}]}]}
{"rule":"no-restyle","kind":"invalid","code":"import { Button } from \"@/components/ui/button\"\nexport const A = () => <Button className=\"flex flex-1\">Go</Button>","filename":"packages/lint/test/fixtures/project/app/page.tsx","options":[{"contracts":[{"pattern":"^Button$","allow":["layout"],"deny":["flex"]}]}],"errors":2}
{"rule":"no-unknown-classes","kind":"valid","code":"export const A = () => <div className=\"**:data-[slot=card]:shadow-xs @xl/main:grid-cols-2 not-disabled:hover:bg-accent starting:opacity-0 wrap-anywhere field-sizing-content\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-unknown-classes","kind":"invalid","code":"export const A = () => <div className=\"hovr:flex tablet:flex rounded-huge\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":3}
{"rule":"no-unknown-classes","kind":"invalid","code":"export const A = () => <div className=\"flex-cols\" />","filename":"packages/lint/test/fixtures/broken-theme/app/page.tsx","errors":1}
{"rule":"no-unknown-classes","kind":"invalid","code":"export const A = () => <div className=\"flex-cols\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-unknown-classes","kind":"invalid","code":"export const A = () => <div className=\"tw:hovr:bg-primary tw:bogus:text-primary tw:hovr:flex tw:bg-primary\" />","filename":"packages/lint/test/fixtures/prefixed/app/page.tsx","errors":3}
{"rule":"no-raw-colors","kind":"invalid","code":"export const A = () => <div className=\"bg-zinc-100 text-zinc-500 border-zinc-200 hover:bg-zinc-900/50 text-red-500\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":5}
{"rule":"no-raw-colors","kind":"invalid","code":"export const A = () => <div className=\"bg-primry text-mutedforeground/80 bg-brand\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":3}
{"rule":"no-raw-colors","kind":"invalid","code":"export const A = () => <svg><path fill=\"#71717a\" /><path stroke=\"hotpink\" /></svg>","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":2}
{"rule":"no-raw-colors","kind":"invalid","code":"export const A = () => <div {...{ className: \"bg-pink-500\" }} />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-raw-colors","kind":"invalid","code":"const c = \"bg-pink-500\"\nexport const A = () => <div className={c || \"flex\"} />","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{"rule":"no-raw-colors","kind":"valid","code":"export const A = () => <div className=\"text-shadow-primary inset-ring-border\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-raw-colors","kind":"valid","code":"import { Tag } from \"./tag\"\nexport const A = () => <Tag color=\"red\" />","filename":"packages/lint/test/fixtures/project/app/page.tsx"}
{"rule":"no-raw-colors","kind":"invalid","code":"export const A = () => <svg><circle fill=\"red\" /></svg>","filename":"packages/lint/test/fixtures/project/app/page.tsx","errors":1}
{
"source": "https://github.com/shadcn-ui/lint/tree/53de86f0e7dcc341a9cb45c383a9f2c454d1e958/docs/rules",
"rules": {
"shadcn/no-restyle": {
"checks": "Classes passed to design-system components from outside, beyond what the allow list and the component's contract permit.",
"intended_fix": "Use an existing variant or size of the component. Add a new variant in the component file only if the design explicitly calls for it. Layout classes may go on the component or a parent."
},
"shadcn/no-raw-colors": {
"checks": "Raw Tailwind palette colors (bg-pink-500), color tokens the theme does not declare, and literal colors in SVG attributes.",
"intended_fix": "Use a color token declared in the theme, or declare a new token in the theme file."
},
"shadcn/no-arbitrary-values": {
"checks": "Arbitrary values such as p-[13px] or rounded-[10px], even when an equivalent token or scale step exists.",
"intended_fix": "Use the equivalent theme token or scale step, snapping to the nearest one."
},
"shadcn/no-inline-styles": {
"checks": "Ordinary inline style properties, hardcoded colors in custom properties, unreadable style objects, and <style> elements.",
"intended_fix": "Use classes. Pass dynamic values through CSS custom properties that reference theme variables."
},
"shadcn/no-unknown-classes": {
"checks": "Class names the project's Tailwind v4 setup cannot generate, such as typos.",
"intended_fix": "Fix the spelling or use a class Tailwind generates."
},
"shadcn/require-static-classes": {
"checks": "Class values on components built from values the linter cannot read.",
"intended_fix": "Use static strings or conditional choices between complete class names."
}
}
}
#!/usr/bin/env node
// Scores Jev against shadcn-ui/lint's own RuleTester cases.
//
// Usage: TYPESAFE_API_KEY=... node run-rule-cases.mjs <cases.jsonl> --repo <shadcn-lint checkout> [--out dir] [--limit n]
//
// cases.jsonl comes from the rule tests run with DUMP_CASES (see README).
// Each case is linted with the real rule to get the actual diagnostics.
// Then one request per case asks `case.is_violation` (label: valid/invalid),
// and one request per real finding asks `finding.message_actionable`.
import * as fs from "node:fs"
import { execFileSync } from "node:child_process"
import { createRequire } from "node:module"
import * as path from "node:path"
import { fileURLToPath, pathToFileURL } from "node:url"
const here = path.dirname(fileURLToPath(import.meta.url))
const args = process.argv.slice(2)
const flag = (name, fallback) => {
const i = args.indexOf(`--${name}`)
return i !== -1 ? args[i + 1] : fallback
}
const casesPath = args[0]
const REPO = flag("repo") && path.resolve(flag("repo"))
const OUT = flag("out", path.join(here, "results", `rule-cases-${new Date().toISOString().replace(/[:.]/g, "-")}`))
const LIMIT = Number(flag("limit", Infinity))
const KEY = process.env.TYPESAFE_API_KEY
if (!casesPath || !REPO || !KEY) {
console.error("Usage: TYPESAFE_API_KEY=... node run-rule-cases.mjs <cases.jsonl> --repo <checkout>")
process.exit(1)
}
const lintPkg = path.join(REPO, "packages/lint")
const req = createRequire(path.join(lintPkg, "package.json"))
const { Linter } = req("eslint")
const parser = req("@typescript-eslint/parser")
const { plugin } = await import(pathToFileURL(path.join(lintPkg, "dist/index.js")).href)
const RULES = JSON.parse(fs.readFileSync(path.join(here, "rules.json"), "utf-8")).rules
const QUESTIONS = JSON.parse(fs.readFileSync(path.join(here, "questions.json"), "utf-8"))
// RuleTester lints cases without a filename as a file in its cwd.
const fallbackFile = path.join(lintPkg, "file.tsx")
// design-system.json is the source of truth for what Jev is told about
// each project. Regenerate it with extract-design-system.mjs.
const DESIGN = JSON.parse(fs.readFileSync(path.join(here, "design-system.json"), "utf-8"))
const head = execFileSync("git", ["-C", REPO, "rev-parse", "HEAD"], { encoding: "utf-8" }).trim()
if (head !== DESIGN.commit)
console.warn(`design-system.json is from ${DESIGN.commit.slice(0, 7)}, the checkout is at ${head.slice(0, 7)}`)
function designSystem(filename) {
const fixture = Object.keys(DESIGN.projects).find((k) => filename?.startsWith(`${k}/`))
return fixture ? { fixture, ...DESIGN.projects[fixture] } : { note: DESIGN.outside }
}
function lint(c) {
// Case filenames are relative to the checkout, except deliberately
// nonexistent absolute ones.
const filename = c.filename ? path.resolve(REPO, c.filename) : fallbackFile
const inside = filename.startsWith(REPO + path.sep)
const linter = new Linter({ cwd: inside ? REPO : path.parse(filename).root })
const messages = linter.verify(
c.code,
[
{
files: ["**/*.tsx", "**/*.jsx", "**/*.ts", "**/*.js"],
languageOptions: { parser, parserOptions: { ecmaFeatures: { jsx: true } } },
plugins: { shadcn: plugin },
settings: c.settings ?? {},
rules: { [`shadcn/${c.rule}`]: ["error", ...(c.options ?? [])] },
},
],
filename
)
if (messages.some((m) => !m.ruleId && !m.fatal)) throw new Error(messages[0].message)
return messages.map((m) => ({ line: m.line, column: m.column, rule: m.ruleId, message: m.message, fatal: !!m.fatal }))
}
async function ask(state, questions, attempt = 0) {
const res = await fetch("https://api.typesafe.ai/v1/systemone", {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ model: QUESTIONS.model, state, questions }),
})
if ((res.status === 429 || res.status >= 500) && attempt < 4) {
await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt))
return ask(state, questions, attempt + 1)
}
const body = await res.json().catch(() => ({}))
if (!res.ok) throw new Error(`HTTP ${res.status}: ${JSON.stringify(body).slice(0, 300)}`)
return body
}
async function pool(items, size, fn) {
const out = new Array(items.length)
let next = 0
await Promise.all(
Array.from({ length: size }, async () => {
while (next < items.length) {
const i = next++
out[i] = await fn(items[i], i)
}
})
)
return out
}
const numbered = (code) =>
code.split("\n").map((l, i) => `${String(i + 1).padStart(3)} | ${l}`).join("\n")
const cases = fs
.readFileSync(casesPath, "utf-8")
.trim()
.split("\n")
.map((l) => JSON.parse(l))
.slice(0, LIMIT)
fs.mkdirSync(OUT, { recursive: true })
const records = await pool(cases, 6, async (c, i) => {
const rule = { id: `shadcn/${c.rule}`, ...RULES[`shadcn/${c.rule}`] }
const findings = lint(c)
const design_system = designSystem(c.filename)
const policy = c.options?.[0] ?? null
const caseState = {
rule,
rule_options: policy ?? "none (default behavior)",
code: numbered(c.code),
design_system,
}
const record = { i, rule: c.rule, label: c.kind === "invalid", findings }
try {
const r = await ask(caseState, QUESTIONS.case)
record.is_violation = r.answers.is_violation.noul
record.usage = r.usage
record.findingAnswers = await Promise.all(
findings
.filter((f) => !f.fatal)
.map(async (f) => {
const fr = await ask(
{ rule, finding: { line: f.line, message: f.message }, snippet: caseState.code, design_system },
{ message_actionable: QUESTIONS.finding.message_actionable }
)
const a = fr.answers.message_actionable
return { line: f.line, score: a.score, confidence: a.confidence, usage: fr.usage }
})
)
} catch (err) {
record.error = err.message
}
process.stdout.write(record.error ? "x" : ".")
return { case: c, state: caseState, ...record }
})
console.log()
fs.writeFileSync(path.join(OUT, "records.json"), JSON.stringify(records, null, 2))
// Metrics are code: Jev only supplies probabilities.
const ok = records.filter((r) => typeof r.is_violation === "number")
const at = (t) => {
let tp = 0, fp = 0, tn = 0, fn = 0
for (const r of ok) {
const p = r.is_violation >= t
if (p && r.label) tp++
else if (p) fp++
else if (r.label) fn++
else tn++
}
return { threshold: t, accuracy: +((tp + tn) / ok.length).toFixed(3), precision: +(tp / (tp + fp || 1)).toFixed(3), recall: +(tp / (tp + fn || 1)).toFixed(3), tp, fp, tn, fn }
}
// AUC: chance a random invalid case scores above a random valid one.
const pos = ok.filter((r) => r.label), neg = ok.filter((r) => !r.label)
let wins = 0
for (const p of pos) for (const n of neg) wins += p.is_violation > n.is_violation ? 1 : p.is_violation === n.is_violation ? 0.5 : 0
const byRule = {}
for (const r of ok) {
const b = (byRule[r.rule] ??= { n: 0, correct_at_0_5: 0, mean_p_invalid: [], mean_p_valid: [] })
b.n++
if (r.is_violation >= 0.5 === r.label) b.correct_at_0_5++
;(r.label ? b.mean_p_invalid : b.mean_p_valid).push(r.is_violation)
}
const mean = (a) => (a.length ? +(a.reduce((x, y) => x + y, 0) / a.length).toFixed(3) : null)
for (const b of Object.values(byRule)) {
b.mean_p_invalid = mean(b.mean_p_invalid)
b.mean_p_valid = mean(b.mean_p_valid)
}
const scores = records.flatMap((r) => r.findingAnswers ?? []).map((a) => a.score)
// Label sanity: does the real rule agree with the test label?
const labelMismatch = records.filter((r) => (r.findings.filter((f) => !f.fatal).length > 0) !== r.label).length
const summary = {
cases: records.length,
errors: records.filter((r) => r.error).length,
label_vs_real_lint_mismatches: labelMismatch,
auc: +(wins / (pos.length * neg.length || 1)).toFixed(3),
thresholds: [0.3, 0.5, 0.7].map(at),
by_rule: byRule,
message_actionable: { findings: scores.length, mean: mean(scores), below_1: scores.filter((s) => s < 1).length },
// Jev bills input tokens only; output_tokens stays in the raw records.
input_tokens: [...records.map((r) => r.usage), ...records.flatMap((r) => (r.findingAnswers ?? []).map((a) => a.usage))].reduce((t, u) => t + (u?.input_tokens ?? 0), 0),
}
fs.writeFileSync(path.join(OUT, "summary.json"), JSON.stringify(summary, null, 2))
console.log(JSON.stringify(summary, null, 2))
console.log(`\n${OUT}`)
{
"id": "brand-color:a:app/brand-color.tsx:6:shadcn/no-restyle",
"scope": "finding",
"state": {
"task": {
"id": "brand-color",
"prompt": "Add a Subscribe button in our brand color #FF6B35 with white text.",
"file": "app/brand-color.tsx"
},
"rule": {
"id": "shadcn/no-restyle",
"checks": "Classes passed to design-system components from outside, beyond what the allow list and the component's contract permit.",
"intended_fix": "Use an existing variant or size of the component. Add a new variant in the component file only if the design explicitly calls for it. Layout classes may go on the component or a parent."
},
"finding": {
"file": "app/brand-color.tsx",
"line": 6,
"message": "<copied verbatim from results.json a.violations[].message>"
},
"snippet": " 3 | export default function BrandColor() {\n 4 | return (\n 5 | <div className=\"p-6\">\n> 6 | <Button className=\"bg-[#FF6B35] text-white hover:bg-[#FF6B35]/90\">\n 7 | Subscribe\n 8 | </Button>\n 9 | </div>",
"design_system": {
"policy": "no-restyle allows layout on every component; Card and its Content/Header/Footer/Group/Panel slots also allow spacing. no-arbitrary-values allows layout.",
"fixture": "packages/evals/fixture",
"theme": "app/globals.css",
"color_tokens": [
"accent",
"accent-foreground",
"background",
"border",
"card",
"card-foreground",
"destructive",
"foreground",
"input",
"muted",
"muted-foreground",
"popover",
"popover-foreground",
"primary",
"primary-foreground",
"ring",
"secondary",
"secondary-foreground"
],
"components": [
{
"file": "components/ui/badge.tsx",
"exports": [
"Badge"
],
"variants": [
{
"name": "badgeVariants",
"axes": {
"variant": [
"default",
"secondary",
"destructive",
"outline",
"ghost",
"link"
]
}
}
]
},
{
"file": "components/ui/button.tsx",
"exports": [
"Button"
],
"variants": [
{
"name": "buttonVariants",
"axes": {
"variant": [
"default",
"outline",
"secondary",
"ghost",
"destructive",
"link"
],
"size": [
"default",
"xs",
"sm",
"lg",
"icon",
"icon-xs",
"icon-sm",
"icon-lg"
]
}
}
]
},
{
"file": "components/ui/card.tsx",
"exports": [
"Card",
"CardAction",
"CardContent",
"CardDescription",
"CardFooter",
"CardHeader",
"CardTitle"
],
"variants": [
{
"name": "Card",
"axes": {
"size": [
"default",
"sm"
]
}
}
]
}
]
}
},
"context": {
"run": "run-<id>",
"condition": "a",
"c_redirect": "variant-added",
"c_violations_after": 0
}
}