diff --git a/.github/workflows/interop.yml b/.github/workflows/interop.yml index 63087d0..d1c0c90 100644 --- a/.github/workflows/interop.yml +++ b/.github/workflows/interop.yml @@ -3,8 +3,9 @@ name: Interop # Early warning, not a gate: checks the pages that name other plugins against # them (bench/interop.mjs) — /shadcn's table and combined config against # @shadcn/lint, /migration/from-better-tailwindcss's rule map against -# eslint-plugin-better-tailwindcss — with the versions the pages name (pinned in -# bench/package-lock.json) and with their latest releases. A red run means a +# eslint-plugin-better-tailwindcss, /migration/from-eslint-plugin-tailwindcss's +# against eslint-plugin-tailwindcss — with the versions the pages name (pinned +# in bench/package-lock.json) and with their latest releases. A red run means a # page needs updating, or that one of them changed what it reports. on: schedule: @@ -46,7 +47,7 @@ jobs: working-directory: bench run: npm install --no-save @shadcn/lint@latest eslint-plugin-better-tailwindcss@latest - vite-plus@latest + eslint-plugin-tailwindcss@latest vite-plus@latest - name: Check the pages against them working-directory: bench diff --git a/CLAUDE.md b/CLAUDE.md index 0a920bf..795a465 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -904,9 +904,13 @@ breaks one fails `pnpm test` (or CI) instead of shipping: - **What the examples do** — `doc-examples.test.ts` runs every ✗ / ✓ example of every rule page (EN/ES) through the real oxlint; `doc-configs.test.ts` runs every `.oxlintrc.json` snippet; `readme-hero.test.ts` the root README's hero; `skill.test.ts` the agent skill. -- **What other tools do** — `shadcn-interop.test.ts` and `better-tailwindcss-map.test.ts` here, and - `bench/interop.mjs` weekly (`interop.yml`) against @shadcn/lint and better-tailwindcss, pinned and - latest; `bench/vite-plus.mjs` in the same job holds `/monorepo`'s Vite+ warning against `vp lint`. +- **What other tools do** — `shadcn-interop.test.ts`, `better-tailwindcss-map.test.ts` and + `eslint-plugin-tailwindcss-map.test.ts` here, and `bench/interop.mjs` weekly (`interop.yml`) + against @shadcn/lint, better-tailwindcss and eslint-plugin-tailwindcss, pinned and latest; + `bench/vite-plus.mjs` in the same job holds `/monorepo`'s Vite+ warning against `vp lint`. + eslint-plugin-tailwindcss registers as `tailwindcss` too (same rule prefix, same + `settings.tailwindcss` key, other keys), so oxlint refuses to load it beside this plugin: + `interop.mjs` lints it alone, with an absolute `cssConfigPath`. - **What the benchmark measured** — `/benchmark` and `/comparison` are generated from `bench/results/latest.json`; `bench.yml` checks weekly that a fresh `score.mjs` run agrees. - **What the site serves** — `packages/docs/scripts/check-dist.ts` (in the docs build: titles, diff --git a/bench/README.md b/bench/README.md index bbcf18f..5b8f8ed 100644 --- a/bench/README.md +++ b/bench/README.md @@ -113,7 +113,10 @@ main test suite (`tests/docs/shadcn-interop.test.ts`). For `/migration/from-better-tailwindcss`: every row of `packages/docs/data/better-tailwindcss.json` — its example must be reported by their rule and by ours, each with the row's options — and every -rule of eslint-plugin-better-tailwindcss must have a row. +rule of eslint-plugin-better-tailwindcss must have a row. The same for +`/migration/from-eslint-plugin-tailwindcss` and `packages/docs/data/eslint-plugin-tailwindcss.json`, +with eslint-plugin-tailwindcss run as an oxlint JS plugin on its own: it registers as `tailwindcss` +too, so oxlint won't load it beside this plugin. Exits 1 on any disagreement. `.github/workflows/interop.yml` runs it weekly with the pinned versions and with the latest releases. diff --git a/bench/interop.mjs b/bench/interop.mjs index 4a44d3a..0d6ecf9 100644 --- a/bench/interop.mjs +++ b/bench/interop.mjs @@ -9,6 +9,10 @@ // packages/docs/data/better-tailwindcss.json — its example must be reported // by their rule and by ours, each with the row's options — and every rule of // eslint-plugin-better-tailwindcss must have a row. +// - /migration/from-eslint-plugin-tailwindcss: the same for +// packages/docs/data/eslint-plugin-tailwindcss.json, against +// eslint-plugin-tailwindcss run as an oxlint JS plugin. It registers as +// `tailwindcss` too, so it is linted on its own, never beside this plugin. // // Exits 1 on any disagreement. // @@ -51,6 +55,9 @@ const data = JSON.parse(readFileSync(join(REPO, 'packages/docs/data/shadcn-lint. const btw = JSON.parse( readFileSync(join(REPO, 'packages/docs/data/better-tailwindcss.json'), 'utf8'), ) +const eptw = JSON.parse( + readFileSync(join(REPO, 'packages/docs/data/eslint-plugin-tailwindcss.json'), 'utf8'), +) const page = readFileSync(join(REPO, 'packages/docs/shadcn.md'), 'utf8') const version = (pkg) => JSON.parse(readFileSync(join(BENCH, 'node_modules', pkg, 'package.json'), 'utf8')).version @@ -74,6 +81,9 @@ for (const { file, example } of exampleFiles(data.concerns)) { btw.rules.forEach((row, i) => writeFileSync(join(project, 'src', `btw-${i}.tsx`), exampleModule(row.example)), ) +eptw.rules.forEach((row, i) => + writeFileSync(join(project, 'src', `eptw-${i}.tsx`), exampleModule(row.example)), +) function lint(config) { writeFileSync(join(project, '.oxlintrc.json'), JSON.stringify(config)) @@ -140,14 +150,41 @@ const mapping = mappingGaps( ours, ) +// 4. eslint-plugin-tailwindcss: each row's two rules, with default options. A +// relative cssConfigPath resolves against the nearest eslint.config.* or +// package.json above the linted file — bench/'s here — so it gets an absolute one. +const eptwTheirs = lint({ + categories: { correctness: 'off' }, + jsPlugins: ['eslint-plugin-tailwindcss'], + settings: { tailwindcss: { cssConfigPath: join(project, 'app/globals.css') } }, + rules: Object.fromEntries(eptw.rules.map((r) => [`tailwindcss/${r.theirs}`, 'warn'])), +}) +const eptwOurs = lint({ + categories: { correctness: 'off' }, + jsPlugins: [values.plugin === 'local' ? LOCAL_DIST.replaceAll('\\', '/') : 'oxlint-tailwindcss'], + settings: { tailwindcss: { entryPoint: 'app/globals.css' } }, + rules: Object.fromEntries(eptw.rules.map((r) => [`tailwindcss/${r.ours}`, 'warn'])), +}) +const eptwPlugin = await import('eslint-plugin-tailwindcss') +const eptwMapping = mappingGaps( + eptw.rules, + Object.keys((eptwPlugin.default ?? eptwPlugin).rules), + eptwTheirs, + eptwOurs, + { file: 'eptw', plugin: 'tailwindcss' }, +) + console.log( `@shadcn/lint ${version('@shadcn/lint')} (the page says ${data.shadcnLint}), ` + `eslint-plugin-better-tailwindcss ${version('eslint-plugin-better-tailwindcss')} ` + - `(the page says ${btw.betterTailwindcss}), oxlint ${version('oxlint')}, ` + + `(the page says ${btw.betterTailwindcss}), ` + + `eslint-plugin-tailwindcss ${version('eslint-plugin-tailwindcss')} ` + + `(the page says ${eptw.eslintPluginTailwindcss}), oxlint ${version('oxlint')}, ` + `oxlint-tailwindcss ${values.plugin}`, ) -const problems = [...column, ...gaps, ...mapping] +const problems = [...column, ...gaps, ...mapping, ...eptwMapping] for (const line of problems) console.log(` ✗ ${line}`) if (problems.length > 0) process.exit(1) console.log(` ✓ /shadcn: ${data.concerns.length} concerns, the table and the combined config hold`) console.log(` ✓ /migration/from-better-tailwindcss: ${btw.rules.length} rules map`) +console.log(` ✓ /migration/from-eslint-plugin-tailwindcss: ${eptw.rules.length} rules map`) diff --git a/bench/lib/interop.mjs b/bench/lib/interop.mjs index 297b25c..fe91cdb 100644 --- a/bench/lib/interop.mjs +++ b/bench/lib/interop.mjs @@ -97,14 +97,22 @@ export function generatedFence(markdown, id) { } /** - * /migration/from-better-tailwindcss: a row whose example one of its two - * rules doesn't report, and a rule of theirs with no row. + * A migration guide (/migration/from-better-tailwindcss, + * /migration/from-eslint-plugin-tailwindcss): a row whose example one of its + * two rules doesn't report, and a rule of theirs with no row. Row `i`'s example + * is linted in `-.tsx`; their diagnostics carry `()`. */ -export function mappingGaps(rows, theirRules, firedTheirs, firedOurs) { +export function mappingGaps( + rows, + theirRules, + firedTheirs, + firedOurs, + { file: prefix = 'btw', plugin = 'better-tailwindcss' } = {}, +) { const out = [] rows.forEach((row, i) => { - const file = `btw-${i}.tsx` - if (!firedTheirs.get(file)?.has(`better-tailwindcss/${row.theirs}`)) { + const file = `${prefix}-${i}.tsx` + if (!firedTheirs.get(file)?.has(`${plugin}/${row.theirs}`)) { out.push(`${row.theirs}: doesn't report ${row.example}`) } if (!firedOurs.get(file)?.has(`tailwindcss/${row.ours}`)) { diff --git a/bench/package-lock.json b/bench/package-lock.json index 7da579b..d393c60 100644 --- a/bench/package-lock.json +++ b/bench/package-lock.json @@ -11,6 +11,7 @@ "@shadcn/lint": "0.2.0", "@tailwindcss/node": "4.3.3", "eslint-plugin-better-tailwindcss": "4.9.0", + "eslint-plugin-tailwindcss": "4.4.0", "oxlint": "1.87.0", "oxlint-tailwindcss": "1.15.1", "shadcn": "4.21.4", @@ -469,6 +470,32 @@ "dev": true, "license": "MIT" }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, "node_modules/@dotenvx/dotenvx": { "version": "1.75.1", "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.75.1.tgz", @@ -664,6 +691,93 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers/node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, "node_modules/@eslint/core": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", @@ -691,6 +805,46 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, "node_modules/@hono/node-server": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", @@ -704,6 +858,77 @@ "hono": "^4" } }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -754,6 +979,32 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.1.tgz", @@ -2492,6 +2743,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -2513,6 +2772,160 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.1.tgz", + "integrity": "sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.71.1", + "@typescript-eslint/types": "^8.71.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.1.tgz", + "integrity": "sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.1.tgz", + "integrity": "sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.1.tgz", + "integrity": "sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.1.tgz", + "integrity": "sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.71.1", + "@typescript-eslint/tsconfig-utils": "8.71.1", + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/visitor-keys": "8.71.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.1.tgz", + "integrity": "sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.71.1", + "@typescript-eslint/types": "8.71.1", + "@typescript-eslint/typescript-estree": "8.71.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.71.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.1.tgz", + "integrity": "sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.71.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@valibot/to-json-schema": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@valibot/to-json-schema/-/to-json-schema-1.8.0.tgz", @@ -3263,6 +3676,30 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", @@ -3525,6 +3962,21 @@ "node": ">= 0.8" } }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -3742,6 +4194,13 @@ "node": ">=10" } }, + "node_modules/confbox": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", @@ -3911,6 +4370,14 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -4181,6 +4648,80 @@ "dev": true, "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.12.0.tgz", + "integrity": "sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==", + "dev": true, + "license": "MIT", + "peer": true, + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "11.1.5 || >11.1.6 <12", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, "node_modules/eslint-plugin-better-tailwindcss": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/eslint-plugin-better-tailwindcss/-/eslint-plugin-better-tailwindcss-4.9.0.tgz", @@ -4214,18 +4755,269 @@ } } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/eslint-plugin-tailwindcss": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-4.4.0.tgz", + "integrity": "sha512-cPyEIeBTVbPU30oQTm7pOqOYurb5vtGaGhRCajYsSUwpUS9fvswB13dOLDUjGJJ+Rt8ylXf4L0hrhLA7Hf7WTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.37.0", + "postcss": "^8.4.4", + "postcss-nested": "^7.0.2", + "synckit": "^0.11.11", + "tailwind-api-utils": "^1.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "eslint": "^9.0.0 || ^10.0.0", + "tailwindcss": "^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" }, "engines": { - "node": ">=4" + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" } }, "node_modules/estree-walker": { @@ -4238,6 +5030,17 @@ "@types/estree": "^1.0.0" } }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -4372,6 +5175,13 @@ "express": ">= 4.11" } }, + "node_modules/exsolve": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.3.tgz", + "integrity": "sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4396,6 +5206,22 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/fast-uri": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", @@ -4464,6 +5290,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^6.1.23" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4512,6 +5349,19 @@ "node": ">=6" } }, + "node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" + } + }, "node_modules/flatted": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", @@ -4734,6 +5584,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -4757,6 +5621,14 @@ "node": ">=16.9.0" } }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -4815,6 +5687,17 @@ "node": ">= 4" } }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -5085,6 +5968,14 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -5111,6 +6002,17 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -5121,6 +6023,21 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -5394,6 +6311,24 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -5632,6 +6567,38 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -5668,6 +6635,14 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/negotiator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", @@ -5856,6 +6831,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", @@ -6217,6 +7211,18 @@ "node": ">=16.20.0" } }, + "node_modules/pkg-types": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.3.tgz", + "integrity": "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.3.1", + "exsolve": "^1.1.1", + "pathe": "^2.0.3" + } + }, "node_modules/pkg-up": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", @@ -6269,6 +7275,32 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-nested": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-7.0.2.tgz", + "integrity": "sha512-5osppouFc0VR9/VYzYxO03VaDa3e8F23Kfd6/9qcZTUI8P58GIYlArOET2Wq0ywSl2o2PjELhYOFI4W7l5QHKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, "node_modules/postcss-selector-parser": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", @@ -6296,6 +7328,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -6382,6 +7425,39 @@ "url": "https://opencollective.com/express" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/qs": { "version": "6.16.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", @@ -6399,6 +7475,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7140,6 +8233,24 @@ "url": "https://www.buymeacoffee.com/systeminfo" } }, + "node_modules/tailwind-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tailwind-api-utils/-/tailwind-api-utils-1.0.3.tgz", + "integrity": "sha512-KpzUHkH1ug1sq4394SLJX38ZtpeTiqQ1RVyFTTSY2XuHsNSTWUkRo108KmyyrMWdDbQrLYkSHaNKj/a3bmA4sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "local-pkg": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/hyoban" + }, + "peerDependencies": { + "tailwindcss": "^3.3.0 || ^4.0.0 || ^4.0.0-beta" + } + }, "node_modules/tailwind-csstree": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/tailwind-csstree/-/tailwind-csstree-0.3.3.tgz", @@ -7280,6 +8391,19 @@ "node": ">=6" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-morph": { "version": "26.0.0", "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", @@ -7356,6 +8480,20 @@ "url": "https://github.com/sponsors/Wombosvideo" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", @@ -7402,6 +8540,28 @@ "url": "https://opencollective.com/express" } }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici": { "version": "7.30.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.30.0.tgz", @@ -7476,6 +8636,17 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -8461,6 +9632,17 @@ "node": ">=20.11" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -8527,6 +9709,20 @@ "dev": true, "license": "ISC" }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yocto-spinner": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.2.2.tgz", diff --git a/bench/package.json b/bench/package.json index 61c4df1..3bf1a96 100644 --- a/bench/package.json +++ b/bench/package.json @@ -12,6 +12,7 @@ "@shadcn/lint": "0.2.0", "@tailwindcss/node": "4.3.3", "eslint-plugin-better-tailwindcss": "4.9.0", + "eslint-plugin-tailwindcss": "4.4.0", "oxlint": "1.87.0", "oxlint-tailwindcss": "1.15.1", "shadcn": "4.21.4", diff --git a/bench/test/interop.test.mjs b/bench/test/interop.test.mjs index b07fbf7..677cc9a 100644 --- a/bench/test/interop.test.mjs +++ b/bench/test/interop.test.mjs @@ -92,4 +92,16 @@ describe('mappingGaps', () => { 'no-z: a rule of theirs with no row', ]) }) + + it("takes another guide's file prefix and plugin name", () => { + // eslint-plugin-tailwindcss registers as `tailwindcss`, like this plugin. + const at = { file: 'eptw', plugin: 'tailwindcss' } + const theirs = firedRules({ diagnostics: [diag('eptw-0.tsx', 'tailwindcss(no-x)')] }) + const ours = firedRules({ diagnostics: [diag('eptw-0.tsx', 'tailwindcss(no-y)')] }) + assert.deepEqual(mappingGaps(rows, ['no-x'], theirs, ours, at), []) + assert.deepEqual(mappingGaps(rows, ['no-x'], ours, theirs, at), [ + "no-x: doesn't report ", + "no-y (for no-x): doesn't report ", + ]) + }) }) diff --git a/packages/docs/.vitepress/config.ts b/packages/docs/.vitepress/config.ts index 165519f..45ba504 100644 --- a/packages/docs/.vitepress/config.ts +++ b/packages/docs/.vitepress/config.ts @@ -139,6 +139,10 @@ export default defineConfig({ items: [ { text: 'v0 → v1', link: '/migration/v0-to-v1' }, { text: 'From better-tailwindcss', link: '/migration/from-better-tailwindcss' }, + { + text: 'From eslint-plugin-tailwindcss', + link: '/migration/from-eslint-plugin-tailwindcss', + }, ], }, ], @@ -228,6 +232,10 @@ export default defineConfig({ items: [ { text: 'v0 → v1', link: '/es/migration/v0-to-v1' }, { text: 'Desde better-tailwindcss', link: '/es/migration/from-better-tailwindcss' }, + { + text: 'Desde eslint-plugin-tailwindcss', + link: '/es/migration/from-eslint-plugin-tailwindcss', + }, ], }, ], diff --git a/packages/docs/CLAUDE.md b/packages/docs/CLAUDE.md index 0e74fbe..a69b1b6 100644 --- a/packages/docs/CLAUDE.md +++ b/packages/docs/CLAUDE.md @@ -106,7 +106,11 @@ rule on); its @shadcn/lint column and the combined config by `bench/interop.mjs` `interop.yml`. When @shadcn/lint changes what it reports, update the data, not the page. The same goes for `/migration/from-better-tailwindcss` and `data/better-tailwindcss.json`: every row's example is reported by both of its rules (`bench/interop.mjs`), and the rules and settings it names -exist here (`tests/docs/better-tailwindcss-map.test.ts`). +exist here (`tests/docs/better-tailwindcss-map.test.ts`). And for +`/migration/from-eslint-plugin-tailwindcss` and `data/eslint-plugin-tailwindcss.json`, checked the +same way (`tests/docs/eslint-plugin-tailwindcss-map.test.ts` also holds the version and rule count +the page's prose names). Both guides render through the same `migration*` functions in +`scripts/blocks.ts`. `/benchmark` and `/comparison` hold no hand-typed number: their tables come from `bench/results/latest.json` (`scripts/bench-blocks.ts`), which `bench/score.mjs` writes and diff --git a/packages/docs/data/eslint-plugin-tailwindcss.json b/packages/docs/data/eslint-plugin-tailwindcss.json new file mode 100644 index 0000000..1f8049d --- /dev/null +++ b/packages/docs/data/eslint-plugin-tailwindcss.json @@ -0,0 +1,141 @@ +{ + "$comment": "eslint-plugin-tailwindcss (francoismassart) → oxlint-tailwindcss. Generates the tables on /migration/from-eslint-plugin-tailwindcss (scripts/blocks.ts). Every example is reported by both rules of its row, with default options: checked by bench/interop.mjs (weekly, with the latest release, in .github/workflows/interop.yml), which also checks that every rule of the pinned version has a row; that every rule and setting named here exists, by tests/docs/eslint-plugin-tailwindcss-map.test.ts.", + "eslintPluginTailwindcss": "4.4.0", + "rules": [ + { + "theirs": "classnames-order", + "ours": "enforce-sort-order", + "example": "
x
", + "notes": { + "en": "Both sort by Tailwind's own class order, the one prettier-plugin-tailwindcss uses. `mode: \"strict\"` also groups classes by variant chain.", + "es": "Las dos ordenan con el orden de clases de Tailwind, el que usa prettier-plugin-tailwindcss. `mode: \"strict\"` además agrupa las clases por cadena de variants." + } + }, + { + "theirs": "enforces-canonical-classname", + "ours": "enforce-canonical", + "example": "
x
", + "notes": { + "en": "Both ask Tailwind for the canonical form, but this rule rewrites a class only when the two print the same CSS: `[&>*]:flex-1` → `*:flex-1` (another selector) and `aspect-[16/9]` → `aspect-video` (a theme variable) stay as written. The rest of what theirs folds in has its own rule here: v3 renames (`bg-gradient-to-r`) are `no-deprecated-classes`, `bg-[var(--x)]` → `bg-(--x)` is `enforce-consistent-variable-syntax`, `!p-4` → `p-4!` is `enforce-consistent-important-position`, and `-m-[5px]` → `m-[-5px]` is `enforce-negative-arbitrary-values`.", + "es": "Las dos le piden a Tailwind la forma canónica, pero esta regla solo reescribe una clase cuando las dos imprimen el mismo CSS: `[&>*]:flex-1` → `*:flex-1` (otro selector) y `aspect-[16/9]` → `aspect-video` (una variable del tema) se quedan como están. Lo demás que la suya incluye tiene aquí su propia regla: los renombres de v3 (`bg-gradient-to-r`) son de `no-deprecated-classes`, `bg-[var(--x)]` → `bg-(--x)` de `enforce-consistent-variable-syntax`, `!p-4` → `p-4!` de `enforce-consistent-important-position` y `-m-[5px]` → `m-[-5px]` de `enforce-negative-arbitrary-values`." + } + }, + { + "theirs": "enforces-negative-arbitrary-values", + "ours": "enforce-negative-arbitrary-values", + "example": "
x
" + }, + { + "theirs": "enforces-shorthand", + "ours": "enforce-shorthand", + "example": "
x
", + "notes": { + "en": "Their `overflow-hidden text-ellipsis whitespace-nowrap` → `truncate` has no equivalent here. Four corners or sides become one class at once (`rounded-tl-md rounded-tr-md rounded-br-md rounded-bl-md` → `rounded-md`), where theirs merges them two by two.", + "es": "Su `overflow-hidden text-ellipsis whitespace-nowrap` → `truncate` no tiene equivalente aquí. Cuatro esquinas o lados se vuelven una sola clase de una vez (`rounded-tl-md rounded-tr-md rounded-br-md rounded-bl-md` → `rounded-md`), donde la suya los une de dos en dos." + } + }, + { + "theirs": "important-modifier-suffix", + "ours": "enforce-consistent-important-position", + "example": "
x
", + "notes": { + "en": "The suffix (`p-4!`) is the default here too; `position: \"prefix\"` asks for `!p-4` instead.", + "es": "Aquí el sufijo (`p-4!`) también es el default; `position: \"prefix\"` pide `!p-4`." + } + }, + { + "theirs": "no-arbitrary-value", + "ours": "no-arbitrary-value", + "example": "
x
", + "notes": { + "en": "Off in both recommended configs. Here `allow` tolerates arbitrary values under the prefixes you list (`grid-cols-`), `allowVariables` a value that is only a CSS variable, and the message names the closest values in your theme.", + "es": "Apagada en los dos configs recomendados. Aquí `allow` tolera valores arbitrarios bajo los prefijos que listes (`grid-cols-`), `allowVariables` un valor que es solo una variable CSS, y el mensaje nombra los valores más cercanos de tu tema." + } + }, + { + "theirs": "no-contradicting-classname", + "ours": "no-conflicting-classes", + "example": "
x
", + "notes": { + "en": "Decided from the CSS each class prints, and the message names the class that wins. It also reports a class another one makes redundant (`reportRedundant`), and `allow` exempts pairs. A variant that repeats its base class (`flex hover:flex`) is `no-contradicting-variants`.", + "es": "Se decide con el CSS que imprime cada clase, y el mensaje nombra la que gana. También reporta una clase que otra vuelve redundante (`reportRedundant`), y `allow` exime pares. Una variant que repite su clase base (`flex hover:flex`) es cosa de `no-contradicting-variants`." + } + }, + { + "theirs": "no-custom-classname", + "ours": "no-unknown-classes", + "example": "
x
", + "notes": { + "en": "`whitelist` → `allowlist` for exact names and `ignorePrefixes` for families (`custom-`); a regular expression that is neither has no counterpart. The classes your CSS defines (`@layer components`, `@utility`) are always known, and a typo gets the closest class as a suggestion.", + "es": "`whitelist` → `allowlist` para nombres exactos e `ignorePrefixes` para familias (`custom-`); una expresión regular que no sea ninguna de las dos no tiene equivalente. Las clases que define tu CSS (`@layer components`, `@utility`) siempre se conocen, y un typo recibe como sugerencia la clase más cercana." + } + }, + { + "theirs": "no-unnecessary-arbitrary-value", + "ours": "no-unnecessary-arbitrary-value", + "example": "
x
", + "notes": { + "en": "This rule rewrites only when the named class prints the same CSS. Theirs also turns a value equal to a scale step or a theme value into its token (`m-[8px]` → `m-2`, `w-[20rem]` → `w-xs`): here that is `prefer-scale-token`, as a suggestion, since the token reads a variable your theme can change. `z-[123]` → `z-123` is `enforce-canonical`.", + "es": "Esta regla solo reescribe cuando la clase con nombre imprime el mismo CSS. La suya además convierte en su token un valor igual a un paso de la escala o a un valor del tema (`m-[8px]` → `m-2`, `w-[20rem]` → `w-xs`): aquí eso es `prefer-scale-token`, como sugerencia, porque el token lee una variable que tu tema puede cambiar. `z-[123]` → `z-123` es de `enforce-canonical`." + } + } + ], + "settings": [ + { + "theirs": "cssConfigPath", + "ours": "entryPoint", + "notes": { + "en": "Required in both. A relative path resolves against the nearest oxlint config here, and against the nearest `eslint.config.*` or `package.json` in theirs. A glob → CSS mapping covers a monorepo.", + "es": "Obligatorio en los dos. Una ruta relativa se resuelve aquí contra el config de oxlint más cercano, y en el suyo contra el `eslint.config.*` o `package.json` más cercano. Un mapeo glob → CSS cubre un monorepo." + } + }, + { + "theirs": "attributes", + "ours": "attributes, attributePatterns", + "notes": { + "en": "Setting theirs replaces its defaults (`class`, `className`, `ngClass`, `@apply`); here `attributes` adds to `class` and `className`, and `attributePatterns` matches names with regular expressions. `ngClass` and `@apply` have nothing to match: oxlint hands JS plugins neither Angular templates nor CSS.", + "es": "Fijar el suyo reemplaza sus defaults (`class`, `className`, `ngClass`, `@apply`); aquí `attributes` se suma a `class` y `className`, y `attributePatterns` encuentra nombres con expresiones regulares. `ngClass` y `@apply` no tienen nada que encontrar: oxlint no les pasa a los JS plugins ni templates de Angular ni CSS." + } + }, + { + "theirs": "functions", + "ours": "callees, tags", + "notes": { + "en": "Theirs is one list for calls and tagged templates, and setting it replaces the defaults; `callees` and `tags` add to this plugin's. Of their defaults, `classNames(…)` and `tw(…)` as a call aren't read here: add them to `callees` if you use them.", + "es": "El suyo es una sola lista para llamadas y tagged templates, y fijarla reemplaza los defaults; `callees` y `tags` se suman a los de este plugin. De sus defaults, `classNames(…)` y `tw(…)` como llamada no se leen aquí: agrégalos a `callees` si los usas." + } + }, + { + "theirs": "parseKeyFunctions", + "ours": null, + "notes": { + "en": "The keys of an object passed to any callee are read as classes (`cn({ \"p-4\": isOpen })`), and its values aren't. Theirs reads the keys only in these functions (by default `classnames`, `classNames`, `clsx`) and the values elsewhere.", + "es": "Las claves de un objeto que recibe cualquier callee se leen como clases (`cn({ \"p-4\": isOpen })`), y sus valores no. El suyo lee las claves solo en estas funciones (por defecto `classnames`, `classNames`, `clsx`) y los valores en las demás." + } + }, + { + "theirs": "ignoredKeys", + "ours": null, + "notes": { + "en": "`cva` and `tv` configs are read by their structure: `defaultVariants` is skipped, `compoundVariants` and `compoundSlots` are read.", + "es": "Los configs de `cva` y `tv` se leen por su estructura: `defaultVariants` se salta, `compoundVariants` y `compoundSlots` se leen." + } + }, + { + "theirs": "cacheMaxSize", + "ours": null, + "notes": { + "en": "The design system is cached on disk, keyed by the content of your CSS, and rebuilt when it changes: there is nothing to tune.", + "es": "El design system se guarda en caché en disco, indexado por el contenido de tu CSS, y se reconstruye cuando cambia: no hay nada que ajustar." + } + }, + { + "theirs": "cacheMaxAge", + "ours": null, + "notes": { + "en": "Same as `cacheMaxSize`.", + "es": "Igual que `cacheMaxSize`." + } + } + ] +} diff --git a/packages/docs/es/faq.md b/packages/docs/es/faq.md index 5a90efe..9f22f11 100644 --- a/packages/docs/es/faq.md +++ b/packages/docs/es/faq.md @@ -91,6 +91,12 @@ Cada una de sus reglas tiene su equivalente aquí; la [guía de migración](/es/migration/from-better-tailwindcss) mapea las reglas y sus settings, y recorre el cambio. +## Uso eslint-plugin-tailwindcss. ¿Cómo me cambio? + +Cada una de sus reglas también tiene su equivalente aquí. La +[guía de migración](/es/migration/from-eslint-plugin-tailwindcss) mapea las reglas y sus settings, y +te lleva de un config de ESLint a un `.oxlintrc.json`. + ## ¿Cómo lo hago rápido en CI? El design system se precalcula una vez por entry point y se guarda en caché en disco. Conserva esa diff --git a/packages/docs/es/migration/from-eslint-plugin-tailwindcss.md b/packages/docs/es/migration/from-eslint-plugin-tailwindcss.md new file mode 100644 index 0000000..273112b --- /dev/null +++ b/packages/docs/es/migration/from-eslint-plugin-tailwindcss.md @@ -0,0 +1,147 @@ +--- +title: "Migrar desde eslint-plugin-tailwindcss" +description: "Pasa de eslint-plugin-tailwindcss a oxlint-tailwindcss: cada una de sus reglas y settings, qué cambia y el paso de ESLint a oxlint, paso a paso." +--- + +# Migrar desde eslint-plugin-tailwindcss + +[eslint-plugin-tailwindcss](https://github.com/francoismassart/eslint-plugin-tailwindcss) es el +plugin de ESLint para Tailwind CSS; sus versiones 4.x leen Tailwind v4. oxlint-tailwindcss está +hecho para oxlint y Tailwind v4. Cada una de las 9 reglas de eslint-plugin-tailwindcss 4.4.0 tiene +su equivalente aquí, y cada par reporta el mismo ejemplo — las tablas de abajo se verifican contra +él cada semana, con su última versión. + +## Reglas + + + +| eslint-plugin-tailwindcss | oxlint-tailwindcss | Notas | +| ------------------------------------ | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `classnames-order` | [`enforce-sort-order`](/es/rules/enforce-sort-order) | Las dos ordenan con el orden de clases de Tailwind, el que usa prettier-plugin-tailwindcss. `mode: "strict"` además agrupa las clases por cadena de variants. | +| `enforces-canonical-classname` | [`enforce-canonical`](/es/rules/enforce-canonical) | Las dos le piden a Tailwind la forma canónica, pero esta regla solo reescribe una clase cuando las dos imprimen el mismo CSS: `[&>*]:flex-1` → `*:flex-1` (otro selector) y `aspect-[16/9]` → `aspect-video` (una variable del tema) se quedan como están. Lo demás que la suya incluye tiene aquí su propia regla: los renombres de v3 (`bg-gradient-to-r`) son de `no-deprecated-classes`, `bg-[var(--x)]` → `bg-(--x)` de `enforce-consistent-variable-syntax`, `!p-4` → `p-4!` de `enforce-consistent-important-position` y `-m-[5px]` → `m-[-5px]` de `enforce-negative-arbitrary-values`. | +| `enforces-negative-arbitrary-values` | [`enforce-negative-arbitrary-values`](/es/rules/enforce-negative-arbitrary-values) | | +| `enforces-shorthand` | [`enforce-shorthand`](/es/rules/enforce-shorthand) | Su `overflow-hidden text-ellipsis whitespace-nowrap` → `truncate` no tiene equivalente aquí. Cuatro esquinas o lados se vuelven una sola clase de una vez (`rounded-tl-md rounded-tr-md rounded-br-md rounded-bl-md` → `rounded-md`), donde la suya los une de dos en dos. | +| `important-modifier-suffix` | [`enforce-consistent-important-position`](/es/rules/enforce-consistent-important-position) | Aquí el sufijo (`p-4!`) también es el default; `position: "prefix"` pide `!p-4`. | +| `no-arbitrary-value` | [`no-arbitrary-value`](/es/rules/no-arbitrary-value) | Apagada en los dos configs recomendados. Aquí `allow` tolera valores arbitrarios bajo los prefijos que listes (`grid-cols-`), `allowVariables` un valor que es solo una variable CSS, y el mensaje nombra los valores más cercanos de tu tema. | +| `no-contradicting-classname` | [`no-conflicting-classes`](/es/rules/no-conflicting-classes) | Se decide con el CSS que imprime cada clase, y el mensaje nombra la que gana. También reporta una clase que otra vuelve redundante (`reportRedundant`), y `allow` exime pares. Una variant que repite su clase base (`flex hover:flex`) es cosa de `no-contradicting-variants`. | +| `no-custom-classname` | [`no-unknown-classes`](/es/rules/no-unknown-classes) | `whitelist` → `allowlist` para nombres exactos e `ignorePrefixes` para familias (`custom-`); una expresión regular que no sea ninguna de las dos no tiene equivalente. Las clases que define tu CSS (`@layer components`, `@utility`) siempre se conocen, y un typo recibe como sugerencia la clase más cercana. | +| `no-unnecessary-arbitrary-value` | [`no-unnecessary-arbitrary-value`](/es/rules/no-unnecessary-arbitrary-value) | Esta regla solo reescribe cuando la clase con nombre imprime el mismo CSS. La suya además convierte en su token un valor igual a un paso de la escala o a un valor del tema (`m-[8px]` → `m-2`, `w-[20rem]` → `w-xs`): aquí eso es `prefer-scale-token`, como sugerencia, porque el token lee una variable que tu tema puede cambiar. `z-[123]` → `z-123` es de `enforce-canonical`. | + + + +## Settings + +Los dos plugins leen `settings.tailwindcss`, con claves distintas: + + + +| eslint-plugin-tailwindcss | oxlint-tailwindcss | Notas | +| ------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `cssConfigPath` | `entryPoint` | Obligatorio en los dos. Una ruta relativa se resuelve aquí contra el config de oxlint más cercano, y en el suyo contra el `eslint.config.*` o `package.json` más cercano. Un mapeo glob → CSS cubre un monorepo. | +| `attributes` | `attributes`, `attributePatterns` | Fijar el suyo reemplaza sus defaults (`class`, `className`, `ngClass`, `@apply`); aquí `attributes` se suma a `class` y `className`, y `attributePatterns` encuentra nombres con expresiones regulares. `ngClass` y `@apply` no tienen nada que encontrar: oxlint no les pasa a los JS plugins ni templates de Angular ni CSS. | +| `functions` | `callees`, `tags` | El suyo es una sola lista para llamadas y tagged templates, y fijarla reemplaza los defaults; `callees` y `tags` se suman a los de este plugin. De sus defaults, `classNames(…)` y `tw(…)` como llamada no se leen aquí: agrégalos a `callees` si los usas. | +| `parseKeyFunctions` | — | Las claves de un objeto que recibe cualquier callee se leen como clases (`cn({ "p-4": isOpen })`), y sus valores no. El suyo lee las claves solo en estas funciones (por defecto `classnames`, `classNames`, `clsx`) y los valores en las demás. | +| `ignoredKeys` | — | Los configs de `cva` y `tv` se leen por su estructura: `defaultVariants` se salta, `compoundVariants` y `compoundSlots` se leen. | +| `cacheMaxSize` | — | El design system se guarda en caché en disco, indexado por el contenido de tu CSS, y se reconstruye cuando cambia: no hay nada que ajustar. | +| `cacheMaxAge` | — | Igual que `cacheMaxSize`. | + + + +## El cambio, paso a paso + +1. Instala oxlint y el plugin: `pnpm add -D oxlint oxlint-tailwindcss`. +2. Crea `.oxlintrc.json` con `"jsPlugins": ["oxlint-tailwindcss"]` y pasa `settings.tailwindcss` a + las claves de este plugin con la tabla de settings: `cssConfigPath` pasa a ser `entryPoint`, que + es obligatorio. +3. Renombra cada `tailwindcss/` al `tailwindcss/` de la tabla de reglas — los dos + plugins usan el prefijo `tailwindcss`. O parte del [config recomendado](/es/setup) y agrega lo + que tenías encima. +4. Quita eslint-plugin-tailwindcss de tu config de ESLint y desinstálalo. Los dos plugins se llaman + `tailwindcss`, así que oxlint no carga los dos a la vez: se detiene con + `Plugin name 'tailwindcss' is already registered`. +5. Corre `oxlint`, y después `oxlint --fix` para los autofixes. + +Un config típico, antes: + +```js +// eslint.config.mjs +import tailwindcss from "eslint-plugin-tailwindcss"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + tailwindcss.configs.recommended, + { + settings: { + tailwindcss: { + cssConfigPath: "./src/styles.css", + functions: ["cn", "cva", "classNames"], + }, + }, + rules: { + "tailwindcss/no-custom-classname": ["warn", { whitelist: ["swiper-.*", "legacy-card"] }], + "tailwindcss/no-arbitrary-value": "warn", + }, + }, +]); +``` + +Y después, con las reglas que encendía el config recomendado: + +```jsonc +// .oxlintrc.json +{ + "jsPlugins": ["oxlint-tailwindcss"], + "settings": { + "tailwindcss": { "entryPoint": "src/styles.css", "callees": ["classNames"] } + }, + "rules": { + "tailwindcss/enforce-sort-order": "warn", + "tailwindcss/enforce-canonical": "warn", + "tailwindcss/enforce-negative-arbitrary-values": "warn", + "tailwindcss/enforce-shorthand": "warn", + "tailwindcss/enforce-consistent-important-position": "warn", + "tailwindcss/no-unknown-classes": [ + "warn", + { "ignorePrefixes": ["swiper-"], "allowlist": ["legacy-card"] } + ], + "tailwindcss/no-conflicting-classes": "error", + "tailwindcss/no-unnecessary-arbitrary-value": "warn", + "tailwindcss/no-arbitrary-value": "warn" + } +} +``` + +## Qué más cambia + +- **Templates de Vue y Svelte.** Su config recomendado lintea los archivos `.vue` y `.svelte`, + templates incluidos, con los parsers de ESLint. En oxlint, los JS plugins ven solo los bloques + `