From e775d726bcb1517802f318ba74b6d2e3c68d234b Mon Sep 17 00:00:00 2001 From: sangwook Date: Fri, 17 Apr 2026 17:33:09 +0900 Subject: [PATCH 01/15] feat(cli): enforce vite-plus import lint rule --- packages/cli/package.json | 4 + .../create-missing-typecheck/snap.txt | 12 +- .../snap.txt | 11 +- .../migration-baseurl-tsconfig/snap.txt | 11 +- .../snap.txt | 2 +- .../migration-env-prefix-lint-staged/snap.txt | 2 +- .../migration-eslint-lint-staged/snap.txt | 11 +- .../migration-eslint-lintstagedrc/snap.txt | 11 +- .../snap.txt | 11 +- .../migration-eslint-rerun-mjs/snap.txt | 11 +- .../migration-eslint-rerun/snap.txt | 11 +- .../migration-eslint/snap.txt | 11 +- .../snap.txt | 2 +- .../snap.txt | 2 +- .../snap.txt | 2 +- .../snap.txt | 4 +- .../migration-from-tsdown/snap.txt | 4 +- .../migration-lint-staged-in-scripts/snap.txt | 2 +- .../migration-lintstagedrc-json/snap.txt | 2 +- .../snap.txt | 2 +- .../migration-merge-vite-config-js/snap.txt | 11 +- .../migration-merge-vite-config-ts/snap.txt | 11 +- .../migration-monorepo-bun/snap.txt | 11 +- .../snap.txt | 2 +- .../migration-monorepo-pnpm/snap.txt | 22 ++- .../migration-monorepo-yarn4/snap.txt | 11 +- .../snap.txt | 11 +- .../migration-oxlintrc-jsonc/snap.txt | 11 +- .../migration-prettier-eslint-combo/snap.txt | 11 +- .../migration-prettier-lint-staged/snap.txt | 2 +- .../migration-prettier-pkg-json/snap.txt | 2 +- .../migration-prettier/snap.txt | 2 +- .../migration-subpath/snap.txt | 2 +- .../snap.txt | 2 +- .../new-vite-monorepo/snap.txt | 6 +- .../command-init-inline-config/snap.txt | 6 +- .../lint-vite-plus-imports/package.json | 5 + .../lint-vite-plus-imports/snap.txt | 28 +++ .../lint-vite-plus-imports/src/index.ts | 8 + .../lint-vite-plus-imports/src/types.ts | 9 + .../lint-vite-plus-imports/steps.json | 8 + .../lint-vite-plus-imports/vite.config.ts | 10 ++ .../cli/src/__tests__/init-config.spec.ts | 5 + .../cli/src/__tests__/oxlint-plugin.spec.ts | 128 ++++++++++++++ packages/cli/src/init-config.ts | 7 +- packages/cli/src/migration/migrator.ts | 19 ++- packages/cli/src/oxlint-plugin-config.ts | 61 +++++++ packages/cli/src/oxlint-plugin.ts | 159 ++++++++++++++++++ packages/cli/tsdown.config.ts | 1 + 49 files changed, 635 insertions(+), 64 deletions(-) create mode 100644 packages/cli/snap-tests/lint-vite-plus-imports/package.json create mode 100644 packages/cli/snap-tests/lint-vite-plus-imports/snap.txt create mode 100644 packages/cli/snap-tests/lint-vite-plus-imports/src/index.ts create mode 100644 packages/cli/snap-tests/lint-vite-plus-imports/src/types.ts create mode 100644 packages/cli/snap-tests/lint-vite-plus-imports/steps.json create mode 100644 packages/cli/snap-tests/lint-vite-plus-imports/vite.config.ts create mode 100644 packages/cli/src/__tests__/oxlint-plugin.spec.ts create mode 100644 packages/cli/src/oxlint-plugin-config.ts create mode 100644 packages/cli/src/oxlint-plugin.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 57d3e84be0..646c611d33 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -74,6 +74,10 @@ "types": "./dist/lint.d.ts", "import": "./dist/lint.js" }, + "./oxlint-plugin": { + "types": "./dist/oxlint-plugin.d.ts", + "import": "./dist/oxlint-plugin.js" + }, "./package.json": "./package.json", "./pack": { "types": "./dist/pack.d.ts", diff --git a/packages/cli/snap-tests-global/create-missing-typecheck/snap.txt b/packages/cli/snap-tests-global/create-missing-typecheck/snap.txt index 66db5e4deb..34d5ab500c 100644 --- a/packages/cli/snap-tests-global/create-missing-typecheck/snap.txt +++ b/packages/cli/snap-tests-global/create-missing-typecheck/snap.txt @@ -7,7 +7,11 @@ export default defineConfig({ "*": "vp check --fix", }, fmt: {}, - lint: { options: { typeAware: true, typeCheck: true } }, + lint: { + jsPlugins: [{ name: "vite-plus", specifier: "vite-plus/oxlint-plugin" }], + rules: { "vite-plus/prefer-vite-plus-imports": "error" }, + options: { typeAware: true, typeCheck: true }, + }, }); > vp create vite:monorepo --no-interactive # create monorepo @@ -19,7 +23,11 @@ export default defineConfig({ "*": "vp check --fix", }, fmt: {}, - lint: { options: { typeAware: true, typeCheck: true } }, + lint: { + jsPlugins: [{ name: "vite-plus", specifier: "vite-plus/oxlint-plugin" }], + rules: { "vite-plus/prefer-vite-plus-imports": "error" }, + options: { typeAware: true, typeCheck: true }, + }, run: { cache: true, }, diff --git a/packages/cli/snap-tests-global/migration-auto-create-vite-config/snap.txt b/packages/cli/snap-tests-global/migration-auto-create-vite-config/snap.txt index 1a0cdf5cef..0ebd44c895 100644 --- a/packages/cli/snap-tests-global/migration-auto-create-vite-config/snap.txt +++ b/packages/cli/snap-tests-global/migration-auto-create-vite-config/snap.txt @@ -21,12 +21,19 @@ export default defineConfig({ }, lint: { "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, }); diff --git a/packages/cli/snap-tests-global/migration-baseurl-tsconfig/snap.txt b/packages/cli/snap-tests-global/migration-baseurl-tsconfig/snap.txt index 7e63207146..80c4d0e6c4 100644 --- a/packages/cli/snap-tests-global/migration-baseurl-tsconfig/snap.txt +++ b/packages/cli/snap-tests-global/migration-baseurl-tsconfig/snap.txt @@ -18,9 +18,16 @@ export default defineConfig({ fmt: {}, lint: { "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, - "options": {} + "options": {}, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, }); diff --git a/packages/cli/snap-tests-global/migration-chained-lint-staged-pre-commit/snap.txt b/packages/cli/snap-tests-global/migration-chained-lint-staged-pre-commit/snap.txt index ddb701f8c2..ebb0a07ddc 100644 --- a/packages/cli/snap-tests-global/migration-chained-lint-staged-pre-commit/snap.txt +++ b/packages/cli/snap-tests-global/migration-chained-lint-staged-pre-commit/snap.txt @@ -41,7 +41,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.js": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-env-prefix-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-env-prefix-lint-staged/snap.txt index 82bee592b0..8ca24db5ad 100644 --- a/packages/cli/snap-tests-global/migration-env-prefix-lint-staged/snap.txt +++ b/packages/cli/snap-tests-global/migration-env-prefix-lint-staged/snap.txt @@ -41,7 +41,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.js": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-eslint-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-eslint-lint-staged/snap.txt index 4f1a2a61b7..a01c070072 100644 --- a/packages/cli/snap-tests-global/migration-eslint-lint-staged/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-lint-staged/snap.txt @@ -55,12 +55,19 @@ export default defineConfig({ "builtin": true }, "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, staged: { "*.ts": "vp lint --fix" diff --git a/packages/cli/snap-tests-global/migration-eslint-lintstagedrc/snap.txt b/packages/cli/snap-tests-global/migration-eslint-lintstagedrc/snap.txt index 11030e7aa7..8ada852e45 100644 --- a/packages/cli/snap-tests-global/migration-eslint-lintstagedrc/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-lintstagedrc/snap.txt @@ -56,12 +56,19 @@ export default defineConfig({ "builtin": true }, "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, staged: { "*.ts": "vp lint --fix" diff --git a/packages/cli/snap-tests-global/migration-eslint-rerun-dual-config/snap.txt b/packages/cli/snap-tests-global/migration-eslint-rerun-dual-config/snap.txt index b7dc9a4cd8..bfe2d7898c 100644 --- a/packages/cli/snap-tests-global/migration-eslint-rerun-dual-config/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-rerun-dual-config/snap.txt @@ -50,12 +50,19 @@ export default defineConfig({ "builtin": true }, "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, }); diff --git a/packages/cli/snap-tests-global/migration-eslint-rerun-mjs/snap.txt b/packages/cli/snap-tests-global/migration-eslint-rerun-mjs/snap.txt index 7e5bfbd38d..8c26933011 100644 --- a/packages/cli/snap-tests-global/migration-eslint-rerun-mjs/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-rerun-mjs/snap.txt @@ -47,12 +47,19 @@ export default defineConfig({ "builtin": true }, "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, }); diff --git a/packages/cli/snap-tests-global/migration-eslint-rerun/snap.txt b/packages/cli/snap-tests-global/migration-eslint-rerun/snap.txt index 93b81626b7..e989bd5c19 100644 --- a/packages/cli/snap-tests-global/migration-eslint-rerun/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint-rerun/snap.txt @@ -47,12 +47,19 @@ export default defineConfig({ "builtin": true }, "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, }); diff --git a/packages/cli/snap-tests-global/migration-eslint/snap.txt b/packages/cli/snap-tests-global/migration-eslint/snap.txt index 3996d5fa6b..86cfde5c16 100644 --- a/packages/cli/snap-tests-global/migration-eslint/snap.txt +++ b/packages/cli/snap-tests-global/migration-eslint/snap.txt @@ -62,11 +62,18 @@ export default defineConfig({ "builtin": true }, "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, }); diff --git a/packages/cli/snap-tests-global/migration-existing-husky-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-existing-husky-lint-staged/snap.txt index a981734b59..1312565077 100644 --- a/packages/cli/snap-tests-global/migration-existing-husky-lint-staged/snap.txt +++ b/packages/cli/snap-tests-global/migration-existing-husky-lint-staged/snap.txt @@ -41,7 +41,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.js": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-existing-lint-staged-config/snap.txt b/packages/cli/snap-tests-global/migration-existing-lint-staged-config/snap.txt index 5fe67178f7..3b6824de67 100644 --- a/packages/cli/snap-tests-global/migration-existing-lint-staged-config/snap.txt +++ b/packages/cli/snap-tests-global/migration-existing-lint-staged-config/snap.txt @@ -42,7 +42,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.ts": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-existing-pnpm-exec-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-existing-pnpm-exec-lint-staged/snap.txt index 6ea4119a2a..207d9c2107 100644 --- a/packages/cli/snap-tests-global/migration-existing-pnpm-exec-lint-staged/snap.txt +++ b/packages/cli/snap-tests-global/migration-existing-pnpm-exec-lint-staged/snap.txt @@ -41,7 +41,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.js": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-from-tsdown-json-config/snap.txt b/packages/cli/snap-tests-global/migration-from-tsdown-json-config/snap.txt index 4c42419bb0..9c4c3780ed 100644 --- a/packages/cli/snap-tests-global/migration-from-tsdown-json-config/snap.txt +++ b/packages/cli/snap-tests-global/migration-from-tsdown-json-config/snap.txt @@ -23,7 +23,7 @@ export default defineConfig({ } }, fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, server: { port: 3000, }, @@ -84,7 +84,7 @@ export default defineConfig({ } }, fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, server: { port: 3000, }, diff --git a/packages/cli/snap-tests-global/migration-from-tsdown/snap.txt b/packages/cli/snap-tests-global/migration-from-tsdown/snap.txt index a2d835cefa..66e0feab28 100644 --- a/packages/cli/snap-tests-global/migration-from-tsdown/snap.txt +++ b/packages/cli/snap-tests-global/migration-from-tsdown/snap.txt @@ -28,7 +28,7 @@ export default defineConfig({ }, pack: tsdownConfig, fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, }); > cat package.json # check package.json @@ -90,7 +90,7 @@ export default defineConfig({ }, pack: tsdownConfig, fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, }); > cat package.json # check package.json diff --git a/packages/cli/snap-tests-global/migration-lint-staged-in-scripts/snap.txt b/packages/cli/snap-tests-global/migration-lint-staged-in-scripts/snap.txt index 55dd18e969..e4920df88f 100644 --- a/packages/cli/snap-tests-global/migration-lint-staged-in-scripts/snap.txt +++ b/packages/cli/snap-tests-global/migration-lint-staged-in-scripts/snap.txt @@ -42,7 +42,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.js": "vp lint --fix" }, diff --git a/packages/cli/snap-tests-global/migration-lintstagedrc-json/snap.txt b/packages/cli/snap-tests-global/migration-lintstagedrc-json/snap.txt index 62c8b3c729..2f5f61b302 100644 --- a/packages/cli/snap-tests-global/migration-lintstagedrc-json/snap.txt +++ b/packages/cli/snap-tests-global/migration-lintstagedrc-json/snap.txt @@ -115,7 +115,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.@(js|ts|tsx|yml|yaml|md|json|html|toml)": [ "vp fmt --staged", diff --git a/packages/cli/snap-tests-global/migration-lintstagedrc-staged-exists/snap.txt b/packages/cli/snap-tests-global/migration-lintstagedrc-staged-exists/snap.txt index 07904bd97b..07b7e4dee4 100644 --- a/packages/cli/snap-tests-global/migration-lintstagedrc-staged-exists/snap.txt +++ b/packages/cli/snap-tests-global/migration-lintstagedrc-staged-exists/snap.txt @@ -45,7 +45,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { '*.js': 'vp check --fix', }, diff --git a/packages/cli/snap-tests-global/migration-merge-vite-config-js/snap.txt b/packages/cli/snap-tests-global/migration-merge-vite-config-js/snap.txt index dfa0319374..03eafd3453 100644 --- a/packages/cli/snap-tests-global/migration-merge-vite-config-js/snap.txt +++ b/packages/cli/snap-tests-global/migration-merge-vite-config-js/snap.txt @@ -15,12 +15,19 @@ export default { fmt: {}, lint: { "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, plugins: [react()], } diff --git a/packages/cli/snap-tests-global/migration-merge-vite-config-ts/snap.txt b/packages/cli/snap-tests-global/migration-merge-vite-config-ts/snap.txt index ecabb5b39a..232f34e503 100644 --- a/packages/cli/snap-tests-global/migration-merge-vite-config-ts/snap.txt +++ b/packages/cli/snap-tests-global/migration-merge-vite-config-ts/snap.txt @@ -25,12 +25,19 @@ export default defineConfig({ }, lint: { "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, plugins: [react()], test: { diff --git a/packages/cli/snap-tests-global/migration-monorepo-bun/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-bun/snap.txt index 16ff5605ab..8d1b96dad8 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-bun/snap.txt +++ b/packages/cli/snap-tests-global/migration-monorepo-bun/snap.txt @@ -18,12 +18,19 @@ export default defineConfig({ fmt: {}, lint: { "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, plugins: [react()], }); diff --git a/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt index 0506c38545..3df45e7f12 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt +++ b/packages/cli/snap-tests-global/migration-monorepo-pnpm-overrides-dependency-selector/snap.txt @@ -14,7 +14,7 @@ export default defineConfig({ "*": "vp check --fix" }, fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, plugins: [react()], }); diff --git a/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt index 928f4a9842..0dfbde319b 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt +++ b/packages/cli/snap-tests-global/migration-monorepo-pnpm/snap.txt @@ -26,12 +26,19 @@ export default defineConfig({ }, lint: { "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, plugins: [react()], }); @@ -160,12 +167,19 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ lint: { "rules": { - "no-unused-vars": "warn" + "no-unused-vars": "warn", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, }); diff --git a/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt b/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt index 6c93f55ef4..53de21206c 100644 --- a/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt +++ b/packages/cli/snap-tests-global/migration-monorepo-yarn4/snap.txt @@ -18,12 +18,19 @@ export default defineConfig({ fmt: {}, lint: { "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, plugins: [react()], }); diff --git a/packages/cli/snap-tests-global/migration-oxlintrc-json-with-comments/snap.txt b/packages/cli/snap-tests-global/migration-oxlintrc-json-with-comments/snap.txt index 7fad58c6b1..dd6dc34325 100644 --- a/packages/cli/snap-tests-global/migration-oxlintrc-json-with-comments/snap.txt +++ b/packages/cli/snap-tests-global/migration-oxlintrc-json-with-comments/snap.txt @@ -18,14 +18,21 @@ export default defineConfig({ "correctness": "error" }, "rules": { - "no-console": "error" + "no-console": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "globals": {}, "ignorePatterns": [], "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, }); diff --git a/packages/cli/snap-tests-global/migration-oxlintrc-jsonc/snap.txt b/packages/cli/snap-tests-global/migration-oxlintrc-jsonc/snap.txt index 021146b683..eef2fe4e48 100644 --- a/packages/cli/snap-tests-global/migration-oxlintrc-jsonc/snap.txt +++ b/packages/cli/snap-tests-global/migration-oxlintrc-jsonc/snap.txt @@ -21,12 +21,19 @@ export default defineConfig({ }, lint: { "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, }); diff --git a/packages/cli/snap-tests-global/migration-prettier-eslint-combo/snap.txt b/packages/cli/snap-tests-global/migration-prettier-eslint-combo/snap.txt index 4fa47bf3e2..4322b4b33d 100644 --- a/packages/cli/snap-tests-global/migration-prettier-eslint-combo/snap.txt +++ b/packages/cli/snap-tests-global/migration-prettier-eslint-combo/snap.txt @@ -66,12 +66,19 @@ export default defineConfig({ "builtin": true }, "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "vite-plus/prefer-vite-plus-imports": "error" }, "options": { "typeAware": true, "typeCheck": true - } + }, + "jsPlugins": [ + { + "name": "vite-plus", + "specifier": "vite-plus/oxlint-plugin" + } + ] }, fmt: { semi: true, diff --git a/packages/cli/snap-tests-global/migration-prettier-lint-staged/snap.txt b/packages/cli/snap-tests-global/migration-prettier-lint-staged/snap.txt index 2623fee781..bd5f56f984 100644 --- a/packages/cli/snap-tests-global/migration-prettier-lint-staged/snap.txt +++ b/packages/cli/snap-tests-global/migration-prettier-lint-staged/snap.txt @@ -42,7 +42,7 @@ peerDependencyRules: import { defineConfig } from "vite-plus"; export default defineConfig({ - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, staged: { "*.ts": "vp fmt" }, diff --git a/packages/cli/snap-tests-global/migration-prettier-pkg-json/snap.txt b/packages/cli/snap-tests-global/migration-prettier-pkg-json/snap.txt index 2051464170..82a6ffad0e 100644 --- a/packages/cli/snap-tests-global/migration-prettier-pkg-json/snap.txt +++ b/packages/cli/snap-tests-global/migration-prettier-pkg-json/snap.txt @@ -46,7 +46,7 @@ export default defineConfig({ staged: { "*": "vp check --fix" }, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, fmt: { semi: true, singleQuote: true, diff --git a/packages/cli/snap-tests-global/migration-prettier/snap.txt b/packages/cli/snap-tests-global/migration-prettier/snap.txt index 1c8d721995..f76b2039cb 100644 --- a/packages/cli/snap-tests-global/migration-prettier/snap.txt +++ b/packages/cli/snap-tests-global/migration-prettier/snap.txt @@ -49,7 +49,7 @@ export default defineConfig({ staged: { "*": "vp check --fix" }, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, fmt: { semi: true, singleQuote: true, diff --git a/packages/cli/snap-tests-global/migration-subpath/snap.txt b/packages/cli/snap-tests-global/migration-subpath/snap.txt index 9a681faae8..03a682d1c8 100644 --- a/packages/cli/snap-tests-global/migration-subpath/snap.txt +++ b/packages/cli/snap-tests-global/migration-subpath/snap.txt @@ -31,7 +31,7 @@ import { defineConfig } from 'vite-plus'; export default defineConfig({ fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, }); > git config --local core.hooksPath || echo 'core.hooksPath is not set' # should NOT be set diff --git a/packages/cli/snap-tests-global/migration-tsconfig-esmoduleinterop/snap.txt b/packages/cli/snap-tests-global/migration-tsconfig-esmoduleinterop/snap.txt index 3e7d9a8025..0522c85f36 100644 --- a/packages/cli/snap-tests-global/migration-tsconfig-esmoduleinterop/snap.txt +++ b/packages/cli/snap-tests-global/migration-tsconfig-esmoduleinterop/snap.txt @@ -25,7 +25,7 @@ export default defineConfig({ "*": "vp check --fix" }, fmt: {}, - lint: {"options":{"typeAware":true,"typeCheck":true}}, + lint: {"jsPlugins":[{"name":"vite-plus","specifier":"vite-plus/oxlint-plugin"}],"rules":{"vite-plus/prefer-vite-plus-imports":"error"},"options":{"typeAware":true,"typeCheck":true}}, }); > cat package.json # check package.json diff --git a/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt b/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt index 26df0d278b..271e3e0733 100644 --- a/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt +++ b/packages/cli/snap-tests-global/new-vite-monorepo/snap.txt @@ -37,7 +37,11 @@ export default defineConfig({ "*": "vp check --fix", }, fmt: {}, - lint: { options: { typeAware: true, typeCheck: true } }, + lint: { + jsPlugins: [{ name: "vite-plus", specifier: "vite-plus/oxlint-plugin" }], + rules: { "vite-plus/prefer-vite-plus-imports": "error" }, + options: { typeAware: true, typeCheck: true }, + }, run: { cache: true, }, diff --git a/packages/cli/snap-tests/command-init-inline-config/snap.txt b/packages/cli/snap-tests/command-init-inline-config/snap.txt index 0f0dd21907..0704eaecd8 100644 --- a/packages/cli/snap-tests/command-init-inline-config/snap.txt +++ b/packages/cli/snap-tests/command-init-inline-config/snap.txt @@ -5,7 +5,11 @@ Added 'lint' to 'vite.config.ts'. import { defineConfig } from "vite-plus"; export default defineConfig({ - lint: { options: { typeAware: true, typeCheck: true } }, + lint: { + jsPlugins: [{ name: "vite-plus", specifier: "vite-plus/oxlint-plugin" }], + rules: { "vite-plus/prefer-vite-plus-imports": "error" }, + options: { typeAware: true, typeCheck: true }, + }, }); > test ! -f .oxlintrc.json # check .oxlintrc.json is removed diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/package.json b/packages/cli/snap-tests/lint-vite-plus-imports/package.json new file mode 100644 index 0000000000..61d74a1408 --- /dev/null +++ b/packages/cli/snap-tests/lint-vite-plus-imports/package.json @@ -0,0 +1,5 @@ +{ + "name": "lint-vite-plus-imports", + "version": "0.0.0", + "private": true +} diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt new file mode 100644 index 0000000000..867a0f61a7 --- /dev/null +++ b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt @@ -0,0 +1,28 @@ +> vp lint --fix src/index.ts src/types.ts # rewrite vite and vitest imports via the vite-plus oxlint plugin +Found 0 warnings and 0 errors. +Finished in ms on 2 files with rules using threads. + +> cat src/index.ts +import { defineConfig } from 'vite-plus'; + +const configPromise = import('vite-plus'); + +export { expect } from 'vite-plus/test'; + +void defineConfig; +void configPromise; + +> cat src/types.ts +type TestFn = typeof import('vite-plus/test')['test']; + +declare module 'vite-plus/test/browser-playwright' {} + +import client = require('vite-plus/client'); + +export type { TestFn }; + +void client; + +> vp lint src/index.ts src/types.ts # confirm the rewritten files are clean +Found 0 warnings and 0 errors. +Finished in ms on 2 files with rules using threads. diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/src/index.ts b/packages/cli/snap-tests/lint-vite-plus-imports/src/index.ts new file mode 100644 index 0000000000..4b076872c4 --- /dev/null +++ b/packages/cli/snap-tests/lint-vite-plus-imports/src/index.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; + +const configPromise = import('vitest/config'); + +export { expect } from 'vitest'; + +void defineConfig; +void configPromise; diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/src/types.ts b/packages/cli/snap-tests/lint-vite-plus-imports/src/types.ts new file mode 100644 index 0000000000..2f59ef7e1e --- /dev/null +++ b/packages/cli/snap-tests/lint-vite-plus-imports/src/types.ts @@ -0,0 +1,9 @@ +type TestFn = (typeof import('vitest'))['test']; + +declare module '@vitest/browser-playwright' {} + +import client = require('vite/client'); + +export type { TestFn }; + +void client; diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/steps.json b/packages/cli/snap-tests/lint-vite-plus-imports/steps.json new file mode 100644 index 0000000000..3ec5635e82 --- /dev/null +++ b/packages/cli/snap-tests/lint-vite-plus-imports/steps.json @@ -0,0 +1,8 @@ +{ + "commands": [ + "vp lint --fix src/index.ts src/types.ts # rewrite vite and vitest imports via the vite-plus oxlint plugin", + "cat src/index.ts", + "cat src/types.ts", + "vp lint src/index.ts src/types.ts # confirm the rewritten files are clean" + ] +} diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/vite.config.ts b/packages/cli/snap-tests/lint-vite-plus-imports/vite.config.ts new file mode 100644 index 0000000000..ccf62c766b --- /dev/null +++ b/packages/cli/snap-tests/lint-vite-plus-imports/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + lint: { + jsPlugins: [{ name: 'vite-plus', specifier: 'vite-plus/oxlint-plugin' }], + rules: { + 'vite-plus/prefer-vite-plus-imports': 'error', + }, + }, +}); diff --git a/packages/cli/src/__tests__/init-config.spec.ts b/packages/cli/src/__tests__/init-config.spec.ts index e51a2c3031..515c3f20cb 100644 --- a/packages/cli/src/__tests__/init-config.spec.ts +++ b/packages/cli/src/__tests__/init-config.spec.ts @@ -60,6 +60,9 @@ describe('applyToolInitConfigToViteConfig', () => { const content = fs.readFileSync(viteConfigPath, 'utf8'); expect(content).toContain('import { defineConfig } from'); expect(content).toContain('vite-plus'); + expect(content).toContain('jsPlugins'); + expect(content).toContain('vite-plus/oxlint-plugin'); + expect(content).toContain('prefer-vite-plus-imports'); expect(content).toContain('typeAware'); expect(content).toContain('typeCheck'); expect(fs.existsSync(path.join(projectPath, '.oxlintrc.json'))).toBe(false); @@ -119,6 +122,8 @@ describe('applyToolInitConfigToViteConfig', () => { expect(result.action).toBe('added'); const content = fs.readFileSync(path.join(projectPath, 'vite.config.ts'), 'utf8'); + expect(content).toContain('vite-plus/oxlint-plugin'); + expect(content).toContain('prefer-vite-plus-imports'); expect(content).toContain('typeAware'); expect(content).toContain('typeCheck'); expect(content).not.toContain('jsx-a11y'); diff --git a/packages/cli/src/__tests__/oxlint-plugin.spec.ts b/packages/cli/src/__tests__/oxlint-plugin.spec.ts new file mode 100644 index 0000000000..57b1080634 --- /dev/null +++ b/packages/cli/src/__tests__/oxlint-plugin.spec.ts @@ -0,0 +1,128 @@ +import { RuleTester } from 'oxlint/plugins-dev'; +import { describe, expect, it } from 'vitest'; + +import { + createDefaultVitePlusLintConfig, + ensureVitePlusImportRuleDefaults, + PREFER_VITE_PLUS_IMPORTS_RULE, + PREFER_VITE_PLUS_IMPORTS_RULE_NAME, + VITE_PLUS_OXLINT_PLUGIN_SPECIFIER, +} from '../oxlint-plugin-config.js'; +import { preferVitePlusImportsRule, rewriteVitePlusImportSpecifier } from '../oxlint-plugin.js'; + +type TestedRule = Parameters[1]; + +describe('oxlint plugin config defaults', () => { + it('adds vite-plus js plugin and lint rule defaults', () => { + expect( + createDefaultVitePlusLintConfig({ + includeTypeAwareDefaults: true, + }), + ).toEqual({ + jsPlugins: [ + { + name: 'vite-plus', + specifier: VITE_PLUS_OXLINT_PLUGIN_SPECIFIER, + }, + ], + options: { + typeAware: true, + typeCheck: true, + }, + rules: { + [PREFER_VITE_PLUS_IMPORTS_RULE]: 'error', + }, + }); + }); + + it('preserves explicit user settings while backfilling defaults', () => { + expect( + ensureVitePlusImportRuleDefaults({ + jsPlugins: [VITE_PLUS_OXLINT_PLUGIN_SPECIFIER], + rules: { + [PREFER_VITE_PLUS_IMPORTS_RULE]: 'off', + eqeqeq: 'warn', + }, + }), + ).toEqual({ + jsPlugins: [VITE_PLUS_OXLINT_PLUGIN_SPECIFIER], + rules: { + [PREFER_VITE_PLUS_IMPORTS_RULE]: 'off', + eqeqeq: 'warn', + }, + }); + }); +}); + +describe('rewriteVitePlusImportSpecifier', () => { + it('rewrites supported vite and vitest specifiers', () => { + expect(rewriteVitePlusImportSpecifier('vite')).toBe('vite-plus'); + expect(rewriteVitePlusImportSpecifier('vite/client')).toBe('vite-plus/client'); + expect(rewriteVitePlusImportSpecifier('vitest')).toBe('vite-plus/test'); + expect(rewriteVitePlusImportSpecifier('vitest/config')).toBe('vite-plus'); + expect(rewriteVitePlusImportSpecifier('@vitest/browser')).toBe('vite-plus/test/browser'); + expect(rewriteVitePlusImportSpecifier('@vitest/browser-playwright/provider')).toBe( + 'vite-plus/test/browser-playwright/provider', + ); + expect(rewriteVitePlusImportSpecifier('tsx')).toBeNull(); + }); +}); + +new RuleTester({ + languageOptions: { + sourceType: 'module', + }, +}).run(PREFER_VITE_PLUS_IMPORTS_RULE_NAME, preferVitePlusImportsRule as TestedRule, { + valid: [ + `import { defineConfig } from 'vite-plus'`, + `export { expect } from 'vite-plus/test'`, + { + code: `declare module 'vite-plus/test/browser' {}`, + filename: 'types.ts', + }, + { + code: `type TestFn = typeof import('vite-plus/test')['test']`, + filename: 'types.ts', + }, + ], + invalid: [ + { + code: `import { defineConfig } from 'vite'`, + errors: 1, + output: `import { defineConfig } from 'vite-plus'`, + }, + { + code: `export { defineConfig } from "vite"`, + errors: 1, + output: `export { defineConfig } from "vite-plus"`, + }, + { + code: `const mod = import('vitest/config')`, + errors: 1, + output: `const mod = import('vite-plus')`, + }, + { + code: `type TestFn = typeof import('vitest')['test']`, + errors: 1, + filename: 'types.ts', + output: `type TestFn = typeof import('vite-plus/test')['test']`, + }, + { + code: `declare module '@vitest/browser-playwright' {}`, + errors: 1, + filename: 'types.ts', + output: `declare module 'vite-plus/test/browser-playwright' {}`, + }, + { + code: `import foo = require('vite/client')`, + errors: 1, + filename: 'types.ts', + output: `import foo = require('vite-plus/client')`, + }, + { + code: `export * from 'vitest';\nimport { defineConfig } from 'vite';`, + errors: 2, + output: `export * from 'vite-plus/test';\nimport { defineConfig } from 'vite-plus';`, + }, + ], +}); diff --git a/packages/cli/src/init-config.ts b/packages/cli/src/init-config.ts index 7439c69630..15c4ad7e87 100644 --- a/packages/cli/src/init-config.ts +++ b/packages/cli/src/init-config.ts @@ -2,6 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { mergeJsonConfig } from '../binding/index.js'; +import { createDefaultVitePlusLintConfig } from './oxlint-plugin-config.ts'; import { fmt as resolveFmt } from './resolve-fmt.ts'; import { runCommandSilently } from './utils/command.ts'; import { BASEURL_TSCONFIG_WARNING, VITE_PLUS_NAME } from './utils/constants.ts'; @@ -233,11 +234,13 @@ export async function applyToolInitConfigToViteConfig( const lintInitConfigPath = path.join(projectPath, '.vite-plus-lint-init.oxlintrc.json'); // Skip typeAware/typeCheck when tsconfig.json has baseUrl (unsupported by tsgolint) const hasBaseUrl = hasBaseUrlInTsconfig(projectPath); - const initOptions = hasBaseUrl ? {} : { typeAware: true, typeCheck: true }; + const initConfig = createDefaultVitePlusLintConfig({ + includeTypeAwareDefaults: !hasBaseUrl, + }); if (hasBaseUrl) { warnMsg(BASEURL_TSCONFIG_WARNING); } - fs.writeFileSync(lintInitConfigPath, JSON.stringify({ options: initOptions })); + fs.writeFileSync(lintInitConfigPath, JSON.stringify(initConfig)); const mergeResult = mergeJsonConfig(viteConfigPath, lintInitConfigPath, spec.configKey); if (!mergeResult.updated) { diff --git a/packages/cli/src/migration/migrator.ts b/packages/cli/src/migration/migrator.ts index 722e7b231e..0d7a978708 100644 --- a/packages/cli/src/migration/migrator.ts +++ b/packages/cli/src/migration/migrator.ts @@ -3,6 +3,7 @@ import path from 'node:path'; import * as prompts from '@voidzero-dev/vite-plus-prompts'; import spawn from 'cross-spawn'; +import type { OxlintConfig } from 'oxlint'; import semver from 'semver'; import { Scalar, YAMLMap, YAMLSeq } from 'yaml'; @@ -15,6 +16,10 @@ import { rewriteImportsInDirectory, type DownloadPackageManagerResult, } from '../../binding/index.js'; +import { + createDefaultVitePlusLintConfig, + ensureVitePlusImportRuleDefaults, +} from '../oxlint-plugin-config.ts'; import { PackageManager, type WorkspaceInfo, type WorkspacePackage } from '../types/index.ts'; import { runCommandSilently } from '../utils/command.ts'; import { @@ -1678,7 +1683,7 @@ export function mergeViteConfigFiles( if (configs.oxlintConfig) { // Inject options.typeAware and options.typeCheck defaults before merging const fullOxlintPath = path.join(projectPath, configs.oxlintConfig); - const oxlintJson = readJsonFile(fullOxlintPath, true) as { options?: Record }; + const oxlintJson = readJsonFile(fullOxlintPath, true) as OxlintConfig; if (!oxlintJson.options) { oxlintJson.options = {}; } @@ -1693,7 +1698,8 @@ export function mergeViteConfigFiles( } else { warnMigration(BASEURL_TSCONFIG_WARNING, report); } - fs.writeFileSync(fullOxlintPath, JSON.stringify(oxlintJson, null, 2)); + const normalizedOxlintConfig = ensureVitePlusImportRuleDefaults(oxlintJson); + fs.writeFileSync(fullOxlintPath, JSON.stringify(normalizedOxlintConfig, null, 2)); // merge oxlint config into vite.config.ts mergeAndRemoveJsonConfig(projectPath, viteConfig, configs.oxlintConfig, 'lint', silent, report); } @@ -1713,14 +1719,15 @@ export function injectLintTypeCheckDefaults( silent = false, report?: MigrationReport, ): void { - if (hasBaseUrlInTsconfig(projectPath)) { - return; - } injectConfigDefaults( projectPath, 'lint', '.vite-plus-lint-init.oxlintrc.json', - JSON.stringify({ options: { typeAware: true, typeCheck: true } }), + JSON.stringify( + createDefaultVitePlusLintConfig({ + includeTypeAwareDefaults: !hasBaseUrlInTsconfig(projectPath), + }), + ), silent, report, ); diff --git a/packages/cli/src/oxlint-plugin-config.ts b/packages/cli/src/oxlint-plugin-config.ts new file mode 100644 index 0000000000..5d9c21fffa --- /dev/null +++ b/packages/cli/src/oxlint-plugin-config.ts @@ -0,0 +1,61 @@ +import type { OxlintConfig } from 'oxlint'; + +import { VITE_PLUS_NAME } from './utils/constants.ts'; + +export const VITE_PLUS_OXLINT_PLUGIN_NAME = VITE_PLUS_NAME; +export const VITE_PLUS_OXLINT_PLUGIN_SPECIFIER = `${VITE_PLUS_NAME}/oxlint-plugin`; +export const PREFER_VITE_PLUS_IMPORTS_RULE_NAME = 'prefer-vite-plus-imports'; +export const PREFER_VITE_PLUS_IMPORTS_RULE = `${VITE_PLUS_OXLINT_PLUGIN_NAME}/${PREFER_VITE_PLUS_IMPORTS_RULE_NAME}`; + +type JsPluginEntry = NonNullable[number]; + +function hasVitePlusPlugin(entry: JsPluginEntry): boolean { + if (typeof entry === 'string') { + return entry === VITE_PLUS_OXLINT_PLUGIN_SPECIFIER; + } + + return entry.specifier === VITE_PLUS_OXLINT_PLUGIN_SPECIFIER; +} + +function isRuleRecord( + value: OxlintConfig['rules'] | undefined, +): value is NonNullable { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +export function ensureVitePlusImportRuleDefaults< + T extends Pick, +>(config: T): T { + const jsPlugins = Array.isArray(config.jsPlugins) ? [...config.jsPlugins] : []; + if (!jsPlugins.some(hasVitePlusPlugin)) { + jsPlugins.push({ + name: VITE_PLUS_OXLINT_PLUGIN_NAME, + specifier: VITE_PLUS_OXLINT_PLUGIN_SPECIFIER, + }); + } + + const rules = isRuleRecord(config.rules) ? { ...config.rules } : {}; + if (!(PREFER_VITE_PLUS_IMPORTS_RULE in rules)) { + rules[PREFER_VITE_PLUS_IMPORTS_RULE] = 'error'; + } + + return { + ...config, + jsPlugins, + rules, + }; +} + +export function createDefaultVitePlusLintConfig(options?: { + includeTypeAwareDefaults?: boolean; +}): Pick { + const config: Pick = + ensureVitePlusImportRuleDefaults({}); + if (options?.includeTypeAwareDefaults) { + config.options = { + typeAware: true, + typeCheck: true, + }; + } + return config; +} diff --git a/packages/cli/src/oxlint-plugin.ts b/packages/cli/src/oxlint-plugin.ts new file mode 100644 index 0000000000..3e90f3557b --- /dev/null +++ b/packages/cli/src/oxlint-plugin.ts @@ -0,0 +1,159 @@ +import { + PREFER_VITE_PLUS_IMPORTS_RULE_NAME, + VITE_PLUS_OXLINT_PLUGIN_NAME, +} from './oxlint-plugin-config.ts'; + +interface StringLiteralLike { + raw: string | null; + type: 'Literal'; + value: string; +} + +interface ReportContext { + report(diagnostic: { + data: Record; + fix: (fixer: Fixer) => unknown; + messageId: 'preferVitePlusImports'; + node: StringLiteralLike; + }): void; +} + +interface Fixer { + replaceText(node: unknown, text: string): unknown; +} + +function isStringLiteralLike(value: unknown): value is StringLiteralLike { + return ( + typeof value === 'object' && + value !== null && + 'type' in value && + value.type === 'Literal' && + 'value' in value && + typeof value.value === 'string' + ); +} + +function rewriteVitePlusImportSpecifier(specifier: string): string | null { + if (specifier === 'vite') { + return 'vite-plus'; + } + + if (specifier.startsWith('vite/')) { + return `vite-plus/${specifier.slice('vite/'.length)}`; + } + + if (specifier === 'vitest/config') { + return 'vite-plus'; + } + + if (specifier === 'vitest') { + return 'vite-plus/test'; + } + + if (specifier.startsWith('vitest/')) { + return `vite-plus/test/${specifier.slice('vitest/'.length)}`; + } + + for (const prefix of [ + '@vitest/browser-playwright', + '@vitest/browser-preview', + '@vitest/browser-webdriverio', + '@vitest/browser', + ]) { + if (specifier === prefix) { + return `vite-plus/test/${prefix.slice('@vitest/'.length)}`; + } + + if (specifier.startsWith(`${prefix}/`)) { + return `vite-plus/test/${specifier.slice('@vitest/'.length)}`; + } + } + + return null; +} + +function quoteSpecifier(literal: StringLiteralLike, replacement: string): string { + const quote = literal.raw?.startsWith("'") ? "'" : '"'; + return `${quote}${replacement}${quote}`; +} + +function maybeReportLiteral(context: ReportContext, literal: StringLiteralLike | null | undefined) { + if (!literal || typeof literal.value !== 'string') { + return; + } + + const replacement = rewriteVitePlusImportSpecifier(literal.value); + if (!replacement) { + return; + } + + context.report({ + node: literal, + messageId: 'preferVitePlusImports', + data: { + from: literal.value, + to: replacement, + }, + fix(fixer) { + return fixer.replaceText(literal, quoteSpecifier(literal, replacement)); + }, + }); +} + +export const preferVitePlusImportsRule = { + meta: { + type: 'problem', + docs: { + description: 'Prefer vite-plus module specifiers over vite and vitest packages.', + recommended: true, + url: 'https://github.com/voidzero-dev/vite-plus/issues/1301', + }, + fixable: 'code', + messages: { + preferVitePlusImports: "Use '{{to}}' instead of '{{from}}' in Vite+ projects.", + }, + }, + create(context: any) { + return { + ImportDeclaration(node: { source: StringLiteralLike }) { + maybeReportLiteral(context, node.source); + }, + ExportAllDeclaration(node: { source: StringLiteralLike }) { + maybeReportLiteral(context, node.source); + }, + ExportNamedDeclaration(node: { source: StringLiteralLike | null }) { + maybeReportLiteral(context, node.source); + }, + ImportExpression(node: { source: unknown }) { + if (!isStringLiteralLike(node.source)) { + return; + } + maybeReportLiteral(context, node.source); + }, + TSImportType(node: { source: StringLiteralLike }) { + maybeReportLiteral(context, node.source); + }, + TSExternalModuleReference(node: { expression: StringLiteralLike }) { + maybeReportLiteral(context, node.expression); + }, + TSModuleDeclaration(node: { global?: boolean; id: StringLiteralLike | { type: string } }) { + if (node.global || !isStringLiteralLike(node.id)) { + return; + } + maybeReportLiteral(context, node.id); + }, + }; + }, +}; + +const plugin = { + meta: { + name: VITE_PLUS_OXLINT_PLUGIN_NAME, + }, + rules: { + [PREFER_VITE_PLUS_IMPORTS_RULE_NAME]: preferVitePlusImportsRule, + }, +}; + +export default plugin; +export { rewriteVitePlusImportSpecifier }; diff --git a/packages/cli/tsdown.config.ts b/packages/cli/tsdown.config.ts index 1e619c6d2b..4cde870f15 100644 --- a/packages/cli/tsdown.config.ts +++ b/packages/cli/tsdown.config.ts @@ -28,6 +28,7 @@ export default defineConfig([ 'define-config': './src/define-config.ts', fmt: './src/fmt.ts', lint: './src/lint.ts', + 'oxlint-plugin': './src/oxlint-plugin.ts', pack: './src/pack.ts', 'pack-bin': './src/pack-bin.ts', // Global commands — explicit entries ensure lazy loading via dynamic import in bin.ts. From 818f7901faf5c32c6f49fe7cc4f2c2dc56f4ed81 Mon Sep 17 00:00:00 2001 From: sangwook Date: Fri, 17 Apr 2026 18:35:14 +0900 Subject: [PATCH 02/15] test: update lint-vite-plus-imports snap --- packages/cli/snap-tests/lint-vite-plus-imports/snap.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt index 867a0f61a7..1c6f0a26c2 100644 --- a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt +++ b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt @@ -13,7 +13,7 @@ void defineConfig; void configPromise; > cat src/types.ts -type TestFn = typeof import('vite-plus/test')['test']; +type TestFn = (typeof import('vite-plus/test'))['test']; declare module 'vite-plus/test/browser-playwright' {} From 1bb4b1da662721c956d1ca3a7f90fa193e6d6c85 Mon Sep 17 00:00:00 2001 From: sangwook Date: Fri, 17 Apr 2026 22:08:18 +0900 Subject: [PATCH 03/15] test: assert lint-vite-plus-imports fails before fix --- .../lint-vite-plus-imports/snap.txt | 51 +++++++++++++++++++ .../lint-vite-plus-imports/steps.json | 1 + 2 files changed, 52 insertions(+) diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt index 1c6f0a26c2..96d2af014b 100644 --- a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt +++ b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt @@ -1,3 +1,54 @@ +[1]> vp lint src/index.ts src/types.ts # should fail before fix + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vite' in Vite+ projects. + ╭─[src/index.ts:1:30] + 1 │ import { defineConfig } from 'vite'; + · ────── + 2 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vitest/config' in Vite+ projects. + ╭─[src/index.ts:3:30] + 2 │ + 3 │ const configPromise = import('vitest/config'); + · ─────────────── + 4 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. + ╭─[src/index.ts:5:24] + 4 │ + 5 │ export { expect } from 'vitest'; + · ──────── + 6 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. + ╭─[src/types.ts:1:30] + 1 │ type TestFn = (typeof import('vitest'))['test']; + · ──────── + 2 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test/browser-playwright' instead of '@vitest/browser-playwright' in Vite+ projects. + ╭─[src/types.ts:3:16] + 2 │ + 3 │ declare module '@vitest/browser-playwright' {} + · ──────────────────────────── + 4 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/client' instead of 'vite/client' in Vite+ projects. + ╭─[src/types.ts:5:25] + 4 │ + 5 │ import client = require('vite/client'); + · ───────────── + 6 │ + ╰──── + +Found 0 warnings and 6 errors. +Finished in ms on 2 files with rules using threads. + > vp lint --fix src/index.ts src/types.ts # rewrite vite and vitest imports via the vite-plus oxlint plugin Found 0 warnings and 0 errors. Finished in ms on 2 files with rules using threads. diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/steps.json b/packages/cli/snap-tests/lint-vite-plus-imports/steps.json index 3ec5635e82..71678b890d 100644 --- a/packages/cli/snap-tests/lint-vite-plus-imports/steps.json +++ b/packages/cli/snap-tests/lint-vite-plus-imports/steps.json @@ -1,5 +1,6 @@ { "commands": [ + "vp lint src/index.ts src/types.ts # should fail before fix", "vp lint --fix src/index.ts src/types.ts # rewrite vite and vitest imports via the vite-plus oxlint plugin", "cat src/index.ts", "cat src/types.ts", From 668fb550bba29f673922e41e93292569bc47876f Mon Sep 17 00:00:00 2001 From: sangwook Date: Fri, 17 Apr 2026 22:22:32 +0900 Subject: [PATCH 04/15] fix: rewrite vitest browser imports to exported paths --- .../lint-vite-plus-imports/snap.txt | 86 +++++++++++++------ .../lint-vite-plus-imports/src/types.ts | 6 +- .../cli/src/__tests__/oxlint-plugin.spec.ts | 45 +++++++++- packages/cli/src/oxlint-plugin.ts | 32 +++++-- 4 files changed, 134 insertions(+), 35 deletions(-) diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt index 96d2af014b..fb5c05641a 100644 --- a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt +++ b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt @@ -1,5 +1,60 @@ [1]> vp lint src/index.ts src/types.ts # should fail before fix + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. + ╭─[src/types.ts:1:30] + 1 │ type TestFn = (typeof import('vitest'))['test']; + · ──────── + 2 │ type BrowserContext = typeof import('@vitest/browser/context'); + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test/browser/context' instead of '@vitest/browser/context' in Vite+ projects. + ╭─[src/types.ts:2:37] + 1 │ type TestFn = (typeof import('vitest'))['test']; + 2 │ type BrowserContext = typeof import('@vitest/browser/context'); + · ───────────────────────── + 3 │ type BrowserClient = typeof import('@vitest/browser/client'); + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test/client' instead of '@vitest/browser/client' in Vite+ projects. + ╭─[src/types.ts:3:36] + 2 │ type BrowserContext = typeof import('@vitest/browser/context'); + 3 │ type BrowserClient = typeof import('@vitest/browser/client'); + · ──────────────────────── + 4 │ type PlaywrightProvider = typeof import('@vitest/browser-playwright/provider'); + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test/browser/providers/playwright' instead of '@vitest/browser-playwright/provider' in Vite+ projects. + ╭─[src/types.ts:4:41] + 3 │ type BrowserClient = typeof import('@vitest/browser/client'); + 4 │ type PlaywrightProvider = typeof import('@vitest/browser-playwright/provider'); + · ───────────────────────────────────── + 5 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test/browser-playwright' instead of '@vitest/browser-playwright' in Vite+ projects. + ╭─[src/types.ts:6:16] + 5 │ + 6 │ declare module '@vitest/browser-playwright' {} + · ──────────────────────────── + 7 │ declare module '@vitest/browser-playwright/context' {} + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test/browser/context' instead of '@vitest/browser-playwright/context' in Vite+ projects. + ╭─[src/types.ts:7:16] + 6 │ declare module '@vitest/browser-playwright' {} + 7 │ declare module '@vitest/browser-playwright/context' {} + · ──────────────────────────────────── + 8 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/client' instead of 'vite/client' in Vite+ projects. + ╭─[src/types.ts:9:25] + 8 │ + 9 │ import client = require('vite/client'); + · ───────────── + 10 │ + ╰──── + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vite' in Vite+ projects. ╭─[src/index.ts:1:30] 1 │ import { defineConfig } from 'vite'; @@ -23,30 +78,7 @@ 6 │ ╰──── - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. - ╭─[src/types.ts:1:30] - 1 │ type TestFn = (typeof import('vitest'))['test']; - · ──────── - 2 │ - ╰──── - - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test/browser-playwright' instead of '@vitest/browser-playwright' in Vite+ projects. - ╭─[src/types.ts:3:16] - 2 │ - 3 │ declare module '@vitest/browser-playwright' {} - · ──────────────────────────── - 4 │ - ╰──── - - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/client' instead of 'vite/client' in Vite+ projects. - ╭─[src/types.ts:5:25] - 4 │ - 5 │ import client = require('vite/client'); - · ───────────── - 6 │ - ╰──── - -Found 0 warnings and 6 errors. +Found 0 warnings and 10 errors. Finished in ms on 2 files with rules using threads. > vp lint --fix src/index.ts src/types.ts # rewrite vite and vitest imports via the vite-plus oxlint plugin @@ -65,12 +97,16 @@ void configPromise; > cat src/types.ts type TestFn = (typeof import('vite-plus/test'))['test']; +type BrowserContext = typeof import('vite-plus/test/browser/context'); +type BrowserClient = typeof import('vite-plus/test/client'); +type PlaywrightProvider = typeof import('vite-plus/test/browser/providers/playwright'); declare module 'vite-plus/test/browser-playwright' {} +declare module 'vite-plus/test/browser/context' {} import client = require('vite-plus/client'); -export type { TestFn }; +export type { BrowserClient, BrowserContext, PlaywrightProvider, TestFn }; void client; diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/src/types.ts b/packages/cli/snap-tests/lint-vite-plus-imports/src/types.ts index 2f59ef7e1e..5a7c44a684 100644 --- a/packages/cli/snap-tests/lint-vite-plus-imports/src/types.ts +++ b/packages/cli/snap-tests/lint-vite-plus-imports/src/types.ts @@ -1,9 +1,13 @@ type TestFn = (typeof import('vitest'))['test']; +type BrowserContext = typeof import('@vitest/browser/context'); +type BrowserClient = typeof import('@vitest/browser/client'); +type PlaywrightProvider = typeof import('@vitest/browser-playwright/provider'); declare module '@vitest/browser-playwright' {} +declare module '@vitest/browser-playwright/context' {} import client = require('vite/client'); -export type { TestFn }; +export type { BrowserClient, BrowserContext, PlaywrightProvider, TestFn }; void client; diff --git a/packages/cli/src/__tests__/oxlint-plugin.spec.ts b/packages/cli/src/__tests__/oxlint-plugin.spec.ts index 57b1080634..8d91b0b32f 100644 --- a/packages/cli/src/__tests__/oxlint-plugin.spec.ts +++ b/packages/cli/src/__tests__/oxlint-plugin.spec.ts @@ -61,9 +61,26 @@ describe('rewriteVitePlusImportSpecifier', () => { expect(rewriteVitePlusImportSpecifier('vitest')).toBe('vite-plus/test'); expect(rewriteVitePlusImportSpecifier('vitest/config')).toBe('vite-plus'); expect(rewriteVitePlusImportSpecifier('@vitest/browser')).toBe('vite-plus/test/browser'); + expect(rewriteVitePlusImportSpecifier('@vitest/browser/context')).toBe( + 'vite-plus/test/browser/context', + ); + expect(rewriteVitePlusImportSpecifier('@vitest/browser/client')).toBe('vite-plus/test/client'); + expect(rewriteVitePlusImportSpecifier('@vitest/browser/locators')).toBe( + 'vite-plus/test/locators', + ); + expect(rewriteVitePlusImportSpecifier('@vitest/browser-playwright/context')).toBe( + 'vite-plus/test/browser/context', + ); expect(rewriteVitePlusImportSpecifier('@vitest/browser-playwright/provider')).toBe( - 'vite-plus/test/browser-playwright/provider', + 'vite-plus/test/browser/providers/playwright', ); + expect(rewriteVitePlusImportSpecifier('@vitest/browser-preview/provider')).toBe( + 'vite-plus/test/browser/providers/preview', + ); + expect(rewriteVitePlusImportSpecifier('@vitest/browser-webdriverio/provider')).toBe( + 'vite-plus/test/browser/providers/webdriverio', + ); + expect(rewriteVitePlusImportSpecifier('@vitest/browser-playwright/locators')).toBeNull(); expect(rewriteVitePlusImportSpecifier('tsx')).toBeNull(); }); }); @@ -80,6 +97,14 @@ new RuleTester({ code: `declare module 'vite-plus/test/browser' {}`, filename: 'types.ts', }, + { + code: `type BrowserClient = typeof import('vite-plus/test/client')`, + filename: 'types.ts', + }, + { + code: `type PlaywrightProvider = typeof import('vite-plus/test/browser/providers/playwright')`, + filename: 'types.ts', + }, { code: `type TestFn = typeof import('vite-plus/test')['test']`, filename: 'types.ts', @@ -113,6 +138,24 @@ new RuleTester({ filename: 'types.ts', output: `declare module 'vite-plus/test/browser-playwright' {}`, }, + { + code: `declare module '@vitest/browser-playwright/context' {}`, + errors: 1, + filename: 'types.ts', + output: `declare module 'vite-plus/test/browser/context' {}`, + }, + { + code: `type BrowserClient = typeof import('@vitest/browser/client')`, + errors: 1, + filename: 'types.ts', + output: `type BrowserClient = typeof import('vite-plus/test/client')`, + }, + { + code: `type PlaywrightProvider = typeof import('@vitest/browser-playwright/provider')`, + errors: 1, + filename: 'types.ts', + output: `type PlaywrightProvider = typeof import('vite-plus/test/browser/providers/playwright')`, + }, { code: `import foo = require('vite/client')`, errors: 1, diff --git a/packages/cli/src/oxlint-plugin.ts b/packages/cli/src/oxlint-plugin.ts index 3e90f3557b..b0cd4a66ba 100644 --- a/packages/cli/src/oxlint-plugin.ts +++ b/packages/cli/src/oxlint-plugin.ts @@ -54,18 +54,34 @@ function rewriteVitePlusImportSpecifier(specifier: string): string | null { return `vite-plus/test/${specifier.slice('vitest/'.length)}`; } - for (const prefix of [ - '@vitest/browser-playwright', - '@vitest/browser-preview', - '@vitest/browser-webdriverio', - '@vitest/browser', - ]) { + if (specifier === '@vitest/browser') { + return 'vite-plus/test/browser'; + } + + const browserSubpathRewrites: Record = { + '@vitest/browser/context': 'vite-plus/test/browser/context', + '@vitest/browser/client': 'vite-plus/test/client', + '@vitest/browser/locators': 'vite-plus/test/locators', + }; + if (specifier in browserSubpathRewrites) { + return browserSubpathRewrites[specifier]; + } + + for (const [prefix, provider] of [ + ['@vitest/browser-playwright', 'playwright'], + ['@vitest/browser-preview', 'preview'], + ['@vitest/browser-webdriverio', 'webdriverio'], + ] as const) { if (specifier === prefix) { return `vite-plus/test/${prefix.slice('@vitest/'.length)}`; } - if (specifier.startsWith(`${prefix}/`)) { - return `vite-plus/test/${specifier.slice('@vitest/'.length)}`; + if (specifier === `${prefix}/context`) { + return 'vite-plus/test/browser/context'; + } + + if (specifier === `${prefix}/provider`) { + return `vite-plus/test/browser/providers/${provider}`; } } From 1a5d13dba74919379660caba3c463a3cce915769 Mon Sep 17 00:00:00 2001 From: sangwook Date: Fri, 17 Apr 2026 22:32:52 +0900 Subject: [PATCH 05/15] test: update lint-vite-plus-imports snap --- .../lint-vite-plus-imports/snap.txt | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt index fb5c05641a..7c3c1befcc 100644 --- a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt +++ b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt @@ -1,5 +1,28 @@ [1]> vp lint src/index.ts src/types.ts # should fail before fix + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vite' in Vite+ projects. + ╭─[src/index.ts:1:30] + 1 │ import { defineConfig } from 'vite'; + · ────── + 2 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vitest/config' in Vite+ projects. + ╭─[src/index.ts:3:30] + 2 │ + 3 │ const configPromise = import('vitest/config'); + · ─────────────── + 4 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. + ╭─[src/index.ts:5:24] + 4 │ + 5 │ export { expect } from 'vitest'; + · ──────── + 6 │ + ╰──── + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. ╭─[src/types.ts:1:30] 1 │ type TestFn = (typeof import('vitest'))['test']; @@ -55,29 +78,6 @@ 10 │ ╰──── - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vite' in Vite+ projects. - ╭─[src/index.ts:1:30] - 1 │ import { defineConfig } from 'vite'; - · ────── - 2 │ - ╰──── - - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vitest/config' in Vite+ projects. - ╭─[src/index.ts:3:30] - 2 │ - 3 │ const configPromise = import('vitest/config'); - · ─────────────── - 4 │ - ╰──── - - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. - ╭─[src/index.ts:5:24] - 4 │ - 5 │ export { expect } from 'vitest'; - · ──────── - 6 │ - ╰──── - Found 0 warnings and 10 errors. Finished in ms on 2 files with rules using threads. From 0708f9bf18557fb97182eef74b1195d6c9d74185 Mon Sep 17 00:00:00 2001 From: sangwook Date: Fri, 17 Apr 2026 22:51:28 +0900 Subject: [PATCH 06/15] refactor oxlint plugin typing --- packages/cli/src/oxlint-plugin.ts | 49 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/packages/cli/src/oxlint-plugin.ts b/packages/cli/src/oxlint-plugin.ts index b0cd4a66ba..4fd44b9065 100644 --- a/packages/cli/src/oxlint-plugin.ts +++ b/packages/cli/src/oxlint-plugin.ts @@ -1,25 +1,22 @@ +import type { RuleTester } from 'oxlint/plugins-dev'; + import { PREFER_VITE_PLUS_IMPORTS_RULE_NAME, VITE_PLUS_OXLINT_PLUGIN_NAME, } from './oxlint-plugin-config.ts'; -interface StringLiteralLike { - raw: string | null; - type: 'Literal'; - value: string; -} - -interface ReportContext { - report(diagnostic: { - data: Record; - fix: (fixer: Fixer) => unknown; - messageId: 'preferVitePlusImports'; - node: StringLiteralLike; - }): void; -} +type OxlintRule = Parameters[1]; +type CreateFn = Exclude; +type OxlintVisitor = ReturnType; +type ReportContext = Parameters[0]; +type VisitorNode = Parameters>[0]; -interface Fixer { - replaceText(node: unknown, text: string): unknown; +type StringLiteralLike = VisitorNode<'ImportDeclaration'>['source']; +interface OxlintPlugin { + meta: { + name: string; + }; + rules: Record; } function isStringLiteralLike(value: unknown): value is StringLiteralLike { @@ -116,7 +113,7 @@ function maybeReportLiteral(context: ReportContext, literal: StringLiteralLike | }); } -export const preferVitePlusImportsRule = { +export const preferVitePlusImportsRule: OxlintRule = { meta: { type: 'problem', docs: { @@ -129,30 +126,30 @@ export const preferVitePlusImportsRule = { preferVitePlusImports: "Use '{{to}}' instead of '{{from}}' in Vite+ projects.", }, }, - create(context: any) { + create(context: ReportContext) { return { - ImportDeclaration(node: { source: StringLiteralLike }) { + ImportDeclaration(node: VisitorNode<'ImportDeclaration'>) { maybeReportLiteral(context, node.source); }, - ExportAllDeclaration(node: { source: StringLiteralLike }) { + ExportAllDeclaration(node: VisitorNode<'ExportAllDeclaration'>) { maybeReportLiteral(context, node.source); }, - ExportNamedDeclaration(node: { source: StringLiteralLike | null }) { + ExportNamedDeclaration(node: VisitorNode<'ExportNamedDeclaration'>) { maybeReportLiteral(context, node.source); }, - ImportExpression(node: { source: unknown }) { + ImportExpression(node: VisitorNode<'ImportExpression'>) { if (!isStringLiteralLike(node.source)) { return; } maybeReportLiteral(context, node.source); }, - TSImportType(node: { source: StringLiteralLike }) { + TSImportType(node: VisitorNode<'TSImportType'>) { maybeReportLiteral(context, node.source); }, - TSExternalModuleReference(node: { expression: StringLiteralLike }) { + TSExternalModuleReference(node: VisitorNode<'TSExternalModuleReference'>) { maybeReportLiteral(context, node.expression); }, - TSModuleDeclaration(node: { global?: boolean; id: StringLiteralLike | { type: string } }) { + TSModuleDeclaration(node: VisitorNode<'TSModuleDeclaration'>) { if (node.global || !isStringLiteralLike(node.id)) { return; } @@ -162,7 +159,7 @@ export const preferVitePlusImportsRule = { }, }; -const plugin = { +const plugin: OxlintPlugin = { meta: { name: VITE_PLUS_OXLINT_PLUGIN_NAME, }, From e0d60bce27d3bb210d7438af0756141527305b22 Mon Sep 17 00:00:00 2001 From: sangwook Date: Fri, 17 Apr 2026 22:58:39 +0900 Subject: [PATCH 07/15] use createOnce for oxlint rule --- packages/cli/src/oxlint-plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/oxlint-plugin.ts b/packages/cli/src/oxlint-plugin.ts index 4fd44b9065..454c2862b5 100644 --- a/packages/cli/src/oxlint-plugin.ts +++ b/packages/cli/src/oxlint-plugin.ts @@ -126,7 +126,7 @@ export const preferVitePlusImportsRule: OxlintRule = { preferVitePlusImports: "Use '{{to}}' instead of '{{from}}' in Vite+ projects.", }, }, - create(context: ReportContext) { + createOnce(context: ReportContext) { return { ImportDeclaration(node: VisitorNode<'ImportDeclaration'>) { maybeReportLiteral(context, node.source); From b331f463eed5214d05edb21ef2c12901560b7d48 Mon Sep 17 00:00:00 2001 From: sangwook Date: Fri, 17 Apr 2026 23:01:22 +0900 Subject: [PATCH 08/15] test: update lint-vite-plus-imports snap --- .../lint-vite-plus-imports/snap.txt | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt index 7c3c1befcc..fb5c05641a 100644 --- a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt +++ b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt @@ -1,28 +1,5 @@ [1]> vp lint src/index.ts src/types.ts # should fail before fix - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vite' in Vite+ projects. - ╭─[src/index.ts:1:30] - 1 │ import { defineConfig } from 'vite'; - · ────── - 2 │ - ╰──── - - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vitest/config' in Vite+ projects. - ╭─[src/index.ts:3:30] - 2 │ - 3 │ const configPromise = import('vitest/config'); - · ─────────────── - 4 │ - ╰──── - - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. - ╭─[src/index.ts:5:24] - 4 │ - 5 │ export { expect } from 'vitest'; - · ──────── - 6 │ - ╰──── - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. ╭─[src/types.ts:1:30] 1 │ type TestFn = (typeof import('vitest'))['test']; @@ -78,6 +55,29 @@ 10 │ ╰──── + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vite' in Vite+ projects. + ╭─[src/index.ts:1:30] + 1 │ import { defineConfig } from 'vite'; + · ────── + 2 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vitest/config' in Vite+ projects. + ╭─[src/index.ts:3:30] + 2 │ + 3 │ const configPromise = import('vitest/config'); + · ─────────────── + 4 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. + ╭─[src/index.ts:5:24] + 4 │ + 5 │ export { expect } from 'vitest'; + · ──────── + 6 │ + ╰──── + Found 0 warnings and 10 errors. Finished in ms on 2 files with rules using threads. From fdcbfc1ca1c2cdfebbe471f9b43c374e875b873b Mon Sep 17 00:00:00 2001 From: sangwook Date: Fri, 17 Apr 2026 23:11:04 +0900 Subject: [PATCH 09/15] test: remove unnecessary oxlint rule cast --- packages/cli/src/__tests__/oxlint-plugin.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/cli/src/__tests__/oxlint-plugin.spec.ts b/packages/cli/src/__tests__/oxlint-plugin.spec.ts index 8d91b0b32f..45a5d1bb44 100644 --- a/packages/cli/src/__tests__/oxlint-plugin.spec.ts +++ b/packages/cli/src/__tests__/oxlint-plugin.spec.ts @@ -10,8 +10,6 @@ import { } from '../oxlint-plugin-config.js'; import { preferVitePlusImportsRule, rewriteVitePlusImportSpecifier } from '../oxlint-plugin.js'; -type TestedRule = Parameters[1]; - describe('oxlint plugin config defaults', () => { it('adds vite-plus js plugin and lint rule defaults', () => { expect( @@ -89,7 +87,7 @@ new RuleTester({ languageOptions: { sourceType: 'module', }, -}).run(PREFER_VITE_PLUS_IMPORTS_RULE_NAME, preferVitePlusImportsRule as TestedRule, { +}).run(PREFER_VITE_PLUS_IMPORTS_RULE_NAME, preferVitePlusImportsRule, { valid: [ `import { defineConfig } from 'vite-plus'`, `export { expect } from 'vite-plus/test'`, From a0e94c984a2d5497344a3aaadf2b3e1e89b1e637 Mon Sep 17 00:00:00 2001 From: sangwook Date: Fri, 17 Apr 2026 23:28:28 +0900 Subject: [PATCH 10/15] test: update lint-vite-plus-imports snap --- .../lint-vite-plus-imports/snap.txt | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt index fb5c05641a..7c3c1befcc 100644 --- a/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt +++ b/packages/cli/snap-tests/lint-vite-plus-imports/snap.txt @@ -1,5 +1,28 @@ [1]> vp lint src/index.ts src/types.ts # should fail before fix + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vite' in Vite+ projects. + ╭─[src/index.ts:1:30] + 1 │ import { defineConfig } from 'vite'; + · ────── + 2 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vitest/config' in Vite+ projects. + ╭─[src/index.ts:3:30] + 2 │ + 3 │ const configPromise = import('vitest/config'); + · ─────────────── + 4 │ + ╰──── + + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. + ╭─[src/index.ts:5:24] + 4 │ + 5 │ export { expect } from 'vitest'; + · ──────── + 6 │ + ╰──── + × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. ╭─[src/types.ts:1:30] 1 │ type TestFn = (typeof import('vitest'))['test']; @@ -55,29 +78,6 @@ 10 │ ╰──── - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vite' in Vite+ projects. - ╭─[src/index.ts:1:30] - 1 │ import { defineConfig } from 'vite'; - · ────── - 2 │ - ╰──── - - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus' instead of 'vitest/config' in Vite+ projects. - ╭─[src/index.ts:3:30] - 2 │ - 3 │ const configPromise = import('vitest/config'); - · ─────────────── - 4 │ - ╰──── - - × vite-plus(prefer-vite-plus-imports): Use 'vite-plus/test' instead of 'vitest' in Vite+ projects. - ╭─[src/index.ts:5:24] - 4 │ - 5 │ export { expect } from 'vitest'; - · ──────── - 6 │ - ╰──── - Found 0 warnings and 10 errors. Finished in ms on 2 files with rules using threads. From 57897c745140d43ed4c3268f5e808e559e71f74d Mon Sep 17 00:00:00 2001 From: sangwook Date: Tue, 21 Apr 2026 20:12:13 +0900 Subject: [PATCH 11/15] fix: use oxlint plugin helpers --- packages/cli/package.json | 1 + packages/cli/src/oxlint-plugin.ts | 47 +++++++++++-------------------- pnpm-lock.yaml | 12 ++++++++ pnpm-workspace.yaml | 1 + 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 646c611d33..8d0c4c1a46 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -333,6 +333,7 @@ }, "dependencies": { "@oxc-project/types": "catalog:", + "@oxlint/plugins": "catalog:", "@voidzero-dev/vite-plus-core": "workspace:*", "@voidzero-dev/vite-plus-test": "workspace:*", "oxfmt": "catalog:", diff --git a/packages/cli/src/oxlint-plugin.ts b/packages/cli/src/oxlint-plugin.ts index 454c2862b5..2fb0dc7001 100644 --- a/packages/cli/src/oxlint-plugin.ts +++ b/packages/cli/src/oxlint-plugin.ts @@ -1,25 +1,12 @@ -import type { RuleTester } from 'oxlint/plugins-dev'; +import { definePlugin, defineRule } from '@oxlint/plugins'; +import type { Context, ESTree } from '@oxlint/plugins'; import { PREFER_VITE_PLUS_IMPORTS_RULE_NAME, VITE_PLUS_OXLINT_PLUGIN_NAME, } from './oxlint-plugin-config.ts'; -type OxlintRule = Parameters[1]; -type CreateFn = Exclude; -type OxlintVisitor = ReturnType; -type ReportContext = Parameters[0]; -type VisitorNode = Parameters>[0]; - -type StringLiteralLike = VisitorNode<'ImportDeclaration'>['source']; -interface OxlintPlugin { - meta: { - name: string; - }; - rules: Record; -} - -function isStringLiteralLike(value: unknown): value is StringLiteralLike { +function isStringLiteralLike(value: unknown): value is ESTree.StringLiteral { return ( typeof value === 'object' && value !== null && @@ -85,12 +72,12 @@ function rewriteVitePlusImportSpecifier(specifier: string): string | null { return null; } -function quoteSpecifier(literal: StringLiteralLike, replacement: string): string { +function quoteSpecifier(literal: ESTree.StringLiteral, replacement: string): string { const quote = literal.raw?.startsWith("'") ? "'" : '"'; return `${quote}${replacement}${quote}`; } -function maybeReportLiteral(context: ReportContext, literal: StringLiteralLike | null | undefined) { +function maybeReportLiteral(context: Context, literal: ESTree.StringLiteral | null | undefined) { if (!literal || typeof literal.value !== 'string') { return; } @@ -113,7 +100,7 @@ function maybeReportLiteral(context: ReportContext, literal: StringLiteralLike | }); } -export const preferVitePlusImportsRule: OxlintRule = { +export const preferVitePlusImportsRule = defineRule({ meta: { type: 'problem', docs: { @@ -126,30 +113,30 @@ export const preferVitePlusImportsRule: OxlintRule = { preferVitePlusImports: "Use '{{to}}' instead of '{{from}}' in Vite+ projects.", }, }, - createOnce(context: ReportContext) { + createOnce(context: Context) { return { - ImportDeclaration(node: VisitorNode<'ImportDeclaration'>) { + ImportDeclaration(node) { maybeReportLiteral(context, node.source); }, - ExportAllDeclaration(node: VisitorNode<'ExportAllDeclaration'>) { + ExportAllDeclaration(node) { maybeReportLiteral(context, node.source); }, - ExportNamedDeclaration(node: VisitorNode<'ExportNamedDeclaration'>) { + ExportNamedDeclaration(node) { maybeReportLiteral(context, node.source); }, - ImportExpression(node: VisitorNode<'ImportExpression'>) { + ImportExpression(node) { if (!isStringLiteralLike(node.source)) { return; } maybeReportLiteral(context, node.source); }, - TSImportType(node: VisitorNode<'TSImportType'>) { + TSImportType(node) { maybeReportLiteral(context, node.source); }, - TSExternalModuleReference(node: VisitorNode<'TSExternalModuleReference'>) { + TSExternalModuleReference(node) { maybeReportLiteral(context, node.expression); }, - TSModuleDeclaration(node: VisitorNode<'TSModuleDeclaration'>) { + TSModuleDeclaration(node) { if (node.global || !isStringLiteralLike(node.id)) { return; } @@ -157,16 +144,16 @@ export const preferVitePlusImportsRule: OxlintRule = { }, }; }, -}; +}); -const plugin: OxlintPlugin = { +const plugin = definePlugin({ meta: { name: VITE_PLUS_OXLINT_PLUGIN_NAME, }, rules: { [PREFER_VITE_PLUS_IMPORTS_RULE_NAME]: preferVitePlusImportsRule, }, -}; +}); export default plugin; export { rewriteVitePlusImportSpecifier }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c761def285..13b4cca173 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,9 @@ catalogs: '@oxc-project/types': specifier: '=0.126.0' version: 0.126.0 + '@oxlint/plugins': + specifier: '=1.60.0' + version: 1.60.0 '@rollup/plugin-commonjs': specifier: ^29.0.0 version: 29.0.0 @@ -337,6 +340,9 @@ importers: '@oxc-project/types': specifier: 'catalog:' version: 0.126.0 + '@oxlint/plugins': + specifier: 'catalog:' + version: 1.60.0 '@voidzero-dev/vite-plus-core': specifier: workspace:* version: link:../core @@ -4472,6 +4478,10 @@ packages: cpu: [x64] os: [win32] + '@oxlint/plugins@1.60.0': + resolution: {integrity: sha512-wxEoVVAS5FQAXiA8jPY+NWTOTKTzbSViMfW7yU5OQ7+f34W0KYfy2iuSLC9o1fiYn5xt3LQLxZBREFJtDlQLRw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@package-json/types@0.0.12': resolution: {integrity: sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==} @@ -11933,6 +11943,8 @@ snapshots: '@oxlint/binding-win32-x64-msvc@1.60.0': optional: true + '@oxlint/plugins@1.60.0': {} + '@package-json/types@0.0.12': {} '@parcel/watcher-android-arm64@2.5.1': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 816254baad..de653f5a03 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -19,6 +19,7 @@ catalog: '@oxc-node/core': ^0.1.0 '@oxc-project/runtime': =0.126.0 '@oxc-project/types': =0.126.0 + '@oxlint/plugins': =1.60.0 '@pnpm/find-workspace-packages': ^6.0.9 '@rollup/plugin-commonjs': ^29.0.0 '@rollup/plugin-json': ^6.1.0 From cb946a880259058ee04d6b475ea5871241007518 Mon Sep 17 00:00:00 2001 From: sangwook Date: Tue, 21 Apr 2026 20:34:08 +0900 Subject: [PATCH 12/15] fix: expose oxlint plugin for sync resolution --- packages/cli/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 8d0c4c1a46..e8da79e330 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -76,7 +76,9 @@ }, "./oxlint-plugin": { "types": "./dist/oxlint-plugin.d.ts", - "import": "./dist/oxlint-plugin.js" + "module-sync": "./dist/oxlint-plugin.js", + "import": "./dist/oxlint-plugin.js", + "default": "./dist/oxlint-plugin.js" }, "./package.json": "./package.json", "./pack": { From 36200f0c5b802e11a25b3e09a0c0fb1ac33dd7c5 Mon Sep 17 00:00:00 2001 From: sangwook Date: Tue, 21 Apr 2026 20:47:51 +0900 Subject: [PATCH 13/15] fix: prioritize oxlint plugin runtime export --- packages/cli/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index e8da79e330..bf63bf73d8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -75,10 +75,10 @@ "import": "./dist/lint.js" }, "./oxlint-plugin": { - "types": "./dist/oxlint-plugin.d.ts", "module-sync": "./dist/oxlint-plugin.js", "import": "./dist/oxlint-plugin.js", - "default": "./dist/oxlint-plugin.js" + "default": "./dist/oxlint-plugin.js", + "types": "./dist/oxlint-plugin.d.ts" }, "./package.json": "./package.json", "./pack": { From 2edb9834fca9aac31d8a9d6736f3bb5a6b9d55b1 Mon Sep 17 00:00:00 2001 From: sangwook Date: Tue, 21 Apr 2026 22:03:33 +0900 Subject: [PATCH 14/15] fix: simplify oxlint literal guard --- packages/cli/src/oxlint-plugin.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/oxlint-plugin.ts b/packages/cli/src/oxlint-plugin.ts index 2fb0dc7001..580fca7e50 100644 --- a/packages/cli/src/oxlint-plugin.ts +++ b/packages/cli/src/oxlint-plugin.ts @@ -6,15 +6,10 @@ import { VITE_PLUS_OXLINT_PLUGIN_NAME, } from './oxlint-plugin-config.ts'; -function isStringLiteralLike(value: unknown): value is ESTree.StringLiteral { - return ( - typeof value === 'object' && - value !== null && - 'type' in value && - value.type === 'Literal' && - 'value' in value && - typeof value.value === 'string' - ); +function isStringLiteralLike( + value: ESTree.Expression | ESTree.TSModuleDeclaration['id'], +): value is ESTree.StringLiteral { + return value.type === 'Literal'; } function rewriteVitePlusImportSpecifier(specifier: string): string | null { From 7930f65f64ab054258c48b6c87945c5f34215b4b Mon Sep 17 00:00:00 2001 From: sangwook Date: Tue, 21 Apr 2026 22:45:04 +0900 Subject: [PATCH 15/15] fix: add node condition to oxlint-plugin exports --- packages/cli/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/package.json b/packages/cli/package.json index bf63bf73d8..ca3f5e3cbc 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -76,6 +76,7 @@ }, "./oxlint-plugin": { "module-sync": "./dist/oxlint-plugin.js", + "node": "./dist/oxlint-plugin.js", "import": "./dist/oxlint-plugin.js", "default": "./dist/oxlint-plugin.js", "types": "./dist/oxlint-plugin.d.ts"