Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Authored against the API vite-plus re-exports, with no `@oxlint/plugins`
// dependency of its own: the point of the test is that this resolves and loads.
import { definePlugin, defineRule } from 'vite-plus/lint/plugins';

const noFoo = defineRule({
meta: { messages: { noFoo: 'Do not name things "foo".' } },
create(context) {
return {
Identifier(node) {
if (node.name === 'foo') {
context.report({ node, messageId: 'noFoo' });
}
},
};
},
});

export default definePlugin({
meta: { name: 'local' },
rules: { 'no-foo': noFoo },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "lint-oxlint-plugin-api",
"version": "0.0.0",
"private": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[[case]]
name = "lint_oxlint_plugin_api"
vp = "local"
skip-platforms = [{ os = "linux", libc = "musl" }]
steps = [
{ argv = [
"vp",
"lint",
"src/uses-foo.ts",
], comment = "the local JS plugin imports its API from vite-plus/lint/plugins. It declares no @oxlint/plugins dependency. A reported diagnostic therefore proves the export resolved and loaded", continue-on-failure = true },
{ argv = [
"vp",
"lint",
"src/legacy-imports.ts",
], comment = "prefer-vite-plus-imports reports the three legacy authoring specifiers", continue-on-failure = true },
{ argv = [
"vp",
"lint",
"src/config-surface.ts",
], comment = "oxlint still owns defineConfig and OxlintOverride, so these are clean", continue-on-failure = true },
{ argv = [
"vp",
"lint",
"--fix",
"src/legacy-imports.ts",
], comment = "the autofix matches what vp migrate rewrites", continue-on-failure = true },
{ argv = [
"vpt",
"print-file",
"src/legacy-imports.ts",
], continue-on-failure = true },
{ argv = [
"vp",
"lint",
"src/legacy-imports.ts",
], comment = "confirm the rewritten file is clean", continue-on-failure = true },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# lint_oxlint_plugin_api

## `vp lint src/uses-foo.ts`

the local JS plugin imports its API from vite-plus/lint/plugins. It declares no @oxlint/plugins dependency. A reported diagnostic therefore proves the export resolved and loaded

**Exit code:** 1

```

× local(no-foo): Do not name things "foo".
╭─[src/uses-foo.ts:1:14]
1 │ export const foo = 1;
· ───
2 │ export const bar = 2;
╰────

Found 0 warnings and 1 error.
Finished in <duration> on 1 file with <n> rules using <n> threads.
```

## `vp lint src/legacy-imports.ts`

prefer-vite-plus-imports reports the three legacy authoring specifiers

**Exit code:** 1

```

× vite-plus(prefer-vite-plus-imports): Use 'vite-plus/lint/plugins' instead of 'oxlint' in Vite+ projects.
╭─[src/legacy-imports.ts:1:28]
1 │ import { defineRule } from 'oxlint';
· ────────
2 │ import { definePlugin } from '@oxlint/plugins';
╰────

× vite-plus(prefer-vite-plus-imports): Use 'vite-plus/lint/plugins' instead of '@oxlint/plugins' in Vite+ projects.
╭─[src/legacy-imports.ts:2:30]
1 │ import { defineRule } from 'oxlint';
2 │ import { definePlugin } from '@oxlint/plugins';
· ─────────────────
3 │ import { RuleTester } from 'oxlint/plugins-dev';
╰────

× vite-plus(prefer-vite-plus-imports): Use 'vite-plus/lint/plugins-dev' instead of 'oxlint/plugins-dev' in Vite+ projects.
╭─[src/legacy-imports.ts:3:28]
2 │ import { definePlugin } from '@oxlint/plugins';
3 │ import { RuleTester } from 'oxlint/plugins-dev';
· ────────────────────
4 │
╰────

Found 0 warnings and 3 errors.
Finished in <duration> on 1 file with <n> rules using <n> threads.
```

## `vp lint src/config-surface.ts`

oxlint still owns defineConfig and OxlintOverride, so these are clean

```
Found 0 warnings and 0 errors.
Finished in <duration> on 1 file with <n> rules using <n> threads.
```

## `vp lint --fix src/legacy-imports.ts`

the autofix matches what vp migrate rewrites

```
Found 0 warnings and 0 errors.
Finished in <duration> on 1 file with <n> rules using <n> threads.
```

## `vpt print-file src/legacy-imports.ts`

```
import { defineRule } from 'vite-plus/lint/plugins';
import { definePlugin } from 'vite-plus/lint/plugins';
import { RuleTester } from 'vite-plus/lint/plugins-dev';

export { defineRule, definePlugin, RuleTester };
```

## `vp lint src/legacy-imports.ts`

confirm the rewritten file is clean

```
Found 0 warnings and 0 errors.
Finished in <duration> on 1 file with <n> rules using <n> threads.
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineConfig } from 'oxlint';
import type { OxlintOverride } from 'oxlint';

export const override: OxlintOverride = { files: ['**/*.ts'] };

export default defineConfig({ overrides: [override] });
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineRule } from 'oxlint';
import { definePlugin } from '@oxlint/plugins';
import { RuleTester } from 'oxlint/plugins-dev';

export { defineRule, definePlugin, RuleTester };
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const foo = 1;
export const bar = 2;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from 'vite-plus';

export default defineConfig({
lint: {
jsPlugins: [
'./lint/plugin.js',
{ name: 'vite-plus', specifier: 'vite-plus/oxlint-plugin' },
],
rules: {
'local/no-foo': 'error',
'vite-plus/prefer-vite-plus-imports': 'error',
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"jsPlugins": ["./lint/plugin.js"],
"rules": {
"local/no-foo": "error"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineRule } from 'oxlint';

export const noFoo = defineRule({
meta: { messages: { noFoo: 'Do not name things "foo".' } },
create(context) {
return {
Identifier(node) {
if (node.name === 'foo') {
context.report({ node, messageId: 'noFoo' });
}
},
};
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Context } from 'oxlint';
import { RuleTester } from 'oxlint/plugins-dev';

import { noFoo } from './no-foo.js';

export type RuleContext = Context;

new RuleTester().run('no-foo', noFoo, {
valid: ['const bar = 1;'],
invalid: [{ code: 'const foo = 1;', errors: 1 }],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { definePlugin } from '@oxlint/plugins';

import { noFoo } from './no-foo.js';

export default definePlugin({
meta: { name: 'local' },
rules: { 'no-foo': noFoo },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from 'oxlint';
import type { OxlintOverride } from 'oxlint';

export const testOverride: OxlintOverride = {
files: ['**/*.test.ts'],
rules: { 'local/no-foo': 'off' },
};

export default defineConfig({ overrides: [testOverride] });
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "migration-oxlint-js-plugin-imports",
"scripts": {
"lint": "oxlint ."
},
"devDependencies": {
"@oxlint/plugins": "^1.0.0",
"oxlint": "^1.0.0",
"vite": "^7.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[[case]]
name = "migration_oxlint_js_plugin_imports"
vp = "global"
steps = [
{ argv = [
"vp",
"migrate",
"--no-interactive",
], comment = "the standalone oxlint dependency goes away, so the JS plugin's authoring imports must move to vite-plus", continue-on-failure = true },
{ argv = [
"vpt",
"print-file",
"package.json",
], comment = "oxlint and @oxlint/plugins are both gone from devDependencies, and nothing replaces them. The API now comes from vite-plus", continue-on-failure = true },
{ argv = [
"vpt",
"print-file",
"lint/no-foo.js",
], comment = "legacy `defineRule` from 'oxlint' -> 'vite-plus/lint/plugins'", continue-on-failure = true },
{ argv = [
"vpt",
"print-file",
"lint/plugin.js",
], comment = "'@oxlint/plugins' -> 'vite-plus/lint/plugins'", continue-on-failure = true },
{ argv = [
"vpt",
"print-file",
"lint/no-foo.test.ts",
], comment = "RuleTester lives in 'oxlint/plugins-dev' upstream and breaks the same way, so it maps to 'vite-plus/lint/plugins-dev'. The plugin type import follows the runtime API", continue-on-failure = true },
{ argv = [
"vpt",
"print-file",
"lint/shared-config.ts",
], comment = "the config surface is NOT redirected. vite-plus/lint/plugins has no defineConfig or OxlintOverride. KNOWN PRE-EXISTING GAP, wider than this PR: `oxlint` is in REMOVE_PACKAGES, so the migration deletes the dependency while this import survives. Under pnpm strict layout the import then fails to resolve. That predates the plugin-API rewrite, since config-surface imports were never rewritten and `oxlint` was always removed. Recorded so a fix shows up as a snapshot diff", continue-on-failure = true },
{ argv = [
"vpt",
"print-file",
"vite.config.ts",
], comment = "the jsPlugins entry survives the .oxlintrc.json merge. It still points at the plugin file, which is now rewritten. KNOWN PRE-EXISTING GAP, unrelated to the import rewrite: the merge drops `local/no-foo`. sanitizeMigratedOxlintConfig derives a plugin's rule namespace from its package name, and a relative-path plugin has no package name. Its namespace comes from `meta.name` at load time instead. Recorded here so a fix shows up as a snapshot diff", continue-on-failure = true },
]
Loading
Loading