diff --git a/.hongdown.toml b/.hongdown.toml index 399e1c73c..e5b6adc8b 100644 --- a/.hongdown.toml +++ b/.hongdown.toml @@ -90,6 +90,7 @@ proper_nouns = [ "Object Integrity Proofs", "OpenTelemetry", "OpenTelemetry Collector", + "Oxlint", "Piefed", "Pixelfed", "Pleroma", diff --git a/cspell.json b/cspell.json index b6a11b6bd..27ecb7644 100644 --- a/cspell.json +++ b/cspell.json @@ -83,6 +83,8 @@ "nuxt", "operatables", "optique", + "oxlint", + "oxlintrc", "phensley", "Pico", "Pixelfed", diff --git a/deno.json b/deno.json index 396f2554a..c7412369b 100644 --- a/deno.json +++ b/deno.json @@ -33,6 +33,7 @@ "./examples/astro", "./examples/fresh", "./examples/hono-sample", + "./examples/lint/oxlint", "./examples/monitoring", "./examples/rfc-9421-test", "./test/smoke/harness" diff --git a/deno.lock b/deno.lock index c7c9ff265..601e31b62 100644 --- a/deno.lock +++ b/deno.lock @@ -150,6 +150,8 @@ "npm:miniflare@^4.20250523.0": "4.20250906.0", "npm:mysql2@^3.22.3": "3.22.3_@types+node@24.12.2", "npm:ora@^8.2.0": "8.2.0", + "npm:oxlint@*": "1.66.0", + "npm:oxlint@^1.66.0": "1.66.0", "npm:pkijs@^3.2.5": "3.4.0", "npm:pkijs@^3.3.3": "3.4.0", "npm:pngjs@7": "7.0.0", @@ -2572,6 +2574,101 @@ "@oxc-project/types@0.129.0": { "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==" }, + "@oxlint/binding-android-arm-eabi@1.66.0": { + "integrity": "sha512-f7kq8N51T4phpzqfBpA2qaVTI/KrkCmNwaj3t/97I/WLTDI+UhlP5GL9eER+zVxBhtlx5rKXWByJU1/zDAvyaw==", + "os": ["android"], + "cpu": ["arm"] + }, + "@oxlint/binding-android-arm64@1.66.0": { + "integrity": "sha512-xu6QO71tdDS9mjmLZ3AqhtaVHBvdmsOKkYnReNNDgh+XiwnsipeQOIxbiYOOO0iAXycJ+GK0wdMSZP/2j/AmSg==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@oxlint/binding-darwin-arm64@1.66.0": { + "integrity": "sha512-HZ24VimSOC7mxuEA99e0H2FS0C1yO3+iW13jPRAk+e2njsUs3QeAXsafCDyaIrV/MirdOVez+etQNQsJE43zNQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@oxlint/binding-darwin-x64@1.66.0": { + "integrity": "sha512-awhj8ZvJrrRSnXj7V++rpZvTmnl99L6mi0B7gg7Cp7BN6cKpzuI481bHNLvXGA9GB1/oEgA3ponuyoAc6Md12A==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@oxlint/binding-freebsd-x64@1.66.0": { + "integrity": "sha512-KQF0oVV21/FjIqkRuL8Q1vh8ECsE5+ocdH5tcqTQ4ZnYuDVoYibQUNfqBjQaUsP6UIIda5Y75Wpm5p4RgQWiWw==", + "os": ["freebsd"], + "cpu": ["x64"] + }, + "@oxlint/binding-linux-arm-gnueabihf@1.66.0": { + "integrity": "sha512-9u1rgwZSEXWb30vbFZzQ78HVXBo0WCKNwJ3a2InRUTNMRng+PUDIoSFmA+m4HdUfBaIqftShq8J8qHc+eE/Vig==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@oxlint/binding-linux-arm-musleabihf@1.66.0": { + "integrity": "sha512-Ynot2HR1bHxUaNWoC280MVTDfZuaWuP3XfSMRDhyuZrVjhzoaBCVFlw8h8qeZjWKVUBhPWFIxB7AQTlK8Z2WWg==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@oxlint/binding-linux-arm64-gnu@1.66.0": { + "integrity": "sha512-xCbgzciGgo+A4aQZEknsNrNiIwY7sU5SfRuMmRjPIvZAgdF34cIHiKvwOsS5XRLjlTVSFwitmq6YclTtHTfU+g==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@oxlint/binding-linux-arm64-musl@1.66.0": { + "integrity": "sha512-hmo+ZB/lHkR1HdDmnziNpzSLmulnUSu10VEqX2Yex7OwvoBAbjJQLvy4gIBRV3AAwWnCvAxKp5Nv1GE6LU1QMg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@oxlint/binding-linux-ppc64-gnu@1.66.0": { + "integrity": "sha512-2Invd4Uyy81mVooQC5FBtfxSNrvcX1OxbMlVQ6M2erRrNI2awFYF26YNW2yFxdVFZ4ffNOWKghtMjhnUPsXsVA==", + "os": ["linux"], + "cpu": ["ppc64"] + }, + "@oxlint/binding-linux-riscv64-gnu@1.66.0": { + "integrity": "sha512-s0iXPDQVdgayE3RGa/N2DZF7tjgg0TwEtD1sGoDxqPDGrIXgo45H0yHknT0f9A0yteASsweYZtDyTuVlM4aSag==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@oxlint/binding-linux-riscv64-musl@1.66.0": { + "integrity": "sha512-OekL4XFiu7RPK0JIZi8VeHgtIXPREf42t8Cy/rKEsC+P3gcqDgNAAGiyuUOpdbG4wwbfue1q4CHcCO7spSve6w==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@oxlint/binding-linux-s390x-gnu@1.66.0": { + "integrity": "sha512-Ga1D0kj1SFslm34ThA/BdkUlyAYEnTsXyRC4pF0C5agZSwtGdHYWMTQWemUfBGp4RCG4QWXgdO+HmmmKqOtlBg==", + "os": ["linux"], + "cpu": ["s390x"] + }, + "@oxlint/binding-linux-x64-gnu@1.66.0": { + "integrity": "sha512-p5jfP1wUZe/IC3qpQO84n9DRnf9g3lKRtLBlQq23ykyrDglHcVx7sWmVTlPuU6SBw8mNnPzyOn022G3XZHnlww==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@oxlint/binding-linux-x64-musl@1.66.0": { + "integrity": "sha512-vUB/sYlYZorDL1ZD+o9mRv7zbsykrrFRtmgS6R8musZqLtrPRQn1gc1eGpuX+sfdccz42STl/AqldY6XRb2upQ==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@oxlint/binding-openharmony-arm64@1.66.0": { + "integrity": "sha512-yde+6p/F59xRkGR9H1HfngWRif1QRJjynZK349l+UI0H6w9hL3G8/AVaTHFyTtLVQ56qtNbX2/5Dc77n1ovnOg==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, + "@oxlint/binding-win32-arm64-msvc@1.66.0": { + "integrity": "sha512-O9GLucgoTdmOrbBX+EjzNe7o/Ze5TFOvXcib6bzUOtBOmj6cV+zw18NgB+cGKAkDw1Pdqs8vGkfHbbsLuDtXWg==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@oxlint/binding-win32-ia32-msvc@1.66.0": { + "integrity": "sha512-m3Pjwc2MfTcom4E4gOv7DyuGyt7OfGNCbmqDHd+N7EzXmP+ppHuudm2NjcA3AjV5TSeGxaguVF4SbTKHe1USYA==", + "os": ["win32"], + "cpu": ["ia32"] + }, + "@oxlint/binding-win32-x64-msvc@1.66.0": { + "integrity": "sha512-/DbBvw8UFBhja6PqudUjV4UtfsJr0Oa7jUjWVKB0g86lj/VwnPrkngn0sFql3c9RDA0O16dh7ozsXb6GjNAzBQ==", + "os": ["win32"], + "cpu": ["x64"] + }, "@parcel/watcher-android-arm64@2.5.6": { "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", "os": ["android"], @@ -7071,6 +7168,31 @@ "strip-ansi@7.2.0" ] }, + "oxlint@1.66.0": { + "integrity": "sha512-N4LLxYLd94KEBqXDMDM5f+2PUpItTjDLreXe2Gn5KhjhCK4Qp2YUXaBi8Yu325ryOgKwt22m45fpD7nPOn69Yw==", + "optionalDependencies": [ + "@oxlint/binding-android-arm-eabi", + "@oxlint/binding-android-arm64", + "@oxlint/binding-darwin-arm64", + "@oxlint/binding-darwin-x64", + "@oxlint/binding-freebsd-x64", + "@oxlint/binding-linux-arm-gnueabihf", + "@oxlint/binding-linux-arm-musleabihf", + "@oxlint/binding-linux-arm64-gnu", + "@oxlint/binding-linux-arm64-musl", + "@oxlint/binding-linux-ppc64-gnu", + "@oxlint/binding-linux-riscv64-gnu", + "@oxlint/binding-linux-riscv64-musl", + "@oxlint/binding-linux-s390x-gnu", + "@oxlint/binding-linux-x64-gnu", + "@oxlint/binding-linux-x64-musl", + "@oxlint/binding-openharmony-arm64", + "@oxlint/binding-win32-arm64-msvc", + "@oxlint/binding-win32-ia32-msvc", + "@oxlint/binding-win32-x64-msvc" + ], + "bin": true + }, "p-limit@3.1.0": { "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dependencies": [ @@ -9554,7 +9676,8 @@ "packages/lint": { "dependencies": [ "npm:@types/estree@^1.0.8", - "npm:eslint@9" + "npm:eslint@9", + "npm:oxlint@^1.66.0" ], "packageJson": { "dependencies": [ diff --git a/docs/manual/lint.md b/docs/manual/lint.md index 2772ebb98..e1a9695ef 100644 --- a/docs/manual/lint.md +++ b/docs/manual/lint.md @@ -1,8 +1,8 @@ --- description: >- - Fedify provides linting plugins for Deno Lint and ESLint to help you catch - common mistakes and enforce best practices when building federated server - apps. + Fedify provides linting plugins for Deno Lint, ESLint, and Oxlint to help you + catch common mistakes and enforce best practices when building federated + server apps. --- Linting @@ -15,8 +15,9 @@ _This package is available since Fedify 2.0.0._ > app to catch common mistakes early and enforce best practices. Fedify provides the [`@fedify/lint`] package, which includes lint rules -specifically designed for Fedify applications. It supports both [Deno Lint] and -[ESLint], so you can use it regardless of your JavaScript/TypeScript runtime. +specifically designed for Fedify applications. It supports [Deno Lint], +[ESLint], and [Oxlint], so you can use it regardless of your +JavaScript/TypeScript runtime. The plugin includes rules that check for: @@ -29,6 +30,7 @@ The plugin includes rules that check for: [`@fedify/lint`]: https://jsr.io/@fedify/lint [Deno Lint]: https://docs.deno.com/runtime/reference/lint_plugins/ [ESLint]: https://eslint.org/ +[Oxlint]: https://oxc.rs/docs/guide/usage/linter/ Installation @@ -262,6 +264,114 @@ bunx eslint . ::: +Oxlint +------ + +[Oxlint] is a fast Rust-based linter that supports ESLint-compatible JS +plugins. `@fedify/lint` exposes its rules through Oxlint's [JS plugin API] +via the `@fedify/lint/oxlint` subpath export. + +> [!NOTE] +> Oxlint's JS plugin API is currently in alpha and not subject to semver. + +[JS plugin API]: https://oxc.rs/docs/guide/usage/linter/writing-js-plugins.html + +### Basic setup + +Add the plugin to your _.oxlintrc.json_ via the `jsPlugins` field, then enable +the rules you want: + +~~~~ json +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json", + "jsPlugins": ["@fedify/lint/oxlint"], + "rules": { + "@fedify/lint/actor-id-required": "error", + "@fedify/lint/actor-id-mismatch": "error" + } +} +~~~~ + +Rule IDs are namespaced under `@fedify/lint/`, matching the ESLint preset. + +### Custom configuration + +Each rule accepts `"error"`, `"warn"`, or `"off"`. Enable any subset listed in +the [*Rules* section](#rules) below: + +~~~~ json +{ + "jsPlugins": ["@fedify/lint/oxlint"], + "rules": { + "@fedify/lint/actor-id-required": "error", + "@fedify/lint/actor-id-mismatch": "error", + "@fedify/lint/actor-inbox-property-required": "warn", + "@fedify/lint/actor-outbox-property-required": "warn", + "@fedify/lint/actor-followers-property-required": "warn", + "@fedify/lint/actor-public-key-required": "warn", + "@fedify/lint/actor-assertion-method-required": "warn", + "@fedify/lint/collection-filtering-not-implemented": "warn" + } +} +~~~~ + +### Running Oxlint + +Add a script to _package.json_: + +~~~~ jsonc +{ + "scripts": { + "lint": "oxlint ." + } +} +~~~~ + +Then run the linter: + +::: code-group + +~~~~ sh [npm] +npm run lint +~~~~ + +~~~~ sh [pnpm] +pnpm lint +~~~~ + +~~~~ sh [Yarn] +yarn lint +~~~~ + +~~~~ sh [Bun] +bun lint +~~~~ + +::: + +Or invoke Oxlint directly: + +::: code-group + +~~~~ sh [npm] +npx oxlint . +~~~~ + +~~~~ sh [pnpm] +pnpx oxlint . +~~~~ + +~~~~ sh [Yarn] +yarn oxlint . +~~~~ + +~~~~ sh [Bun] +bunx oxlint . +~~~~ + +::: + + Rules ----- @@ -1217,10 +1327,12 @@ See also - [`@fedify/lint` on npm] - [Deno Lint plugins documentation] - [ESLint documentation] + - [Oxlint documentation] - [Example project] [`@fedify/lint` on JSR]: https://jsr.io/@fedify/lint [`@fedify/lint` on npm]: https://www.npmjs.com/package/@fedify/lint [Deno Lint plugins documentation]: https://docs.deno.com/runtime/reference/lint_plugins/ [ESLint documentation]: https://eslint.org/ +[Oxlint documentation]: https://oxc.rs/docs/guide/usage/linter/ [Example project]: https://github.com/fedify-dev/fedify/tree/main/examples/lint diff --git a/examples/lint/oxlint/.oxlintrc.json b/examples/lint/oxlint/.oxlintrc.json new file mode 100644 index 000000000..fad2f581b --- /dev/null +++ b/examples/lint/oxlint/.oxlintrc.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json", + "jsPlugins": ["@fedify/lint/oxlint"], + "rules": { + "@fedify/lint/actor-id-required": "error", + "@fedify/lint/actor-id-mismatch": "error", + "@fedify/lint/actor-inbox-property-required": "warn", + "@fedify/lint/actor-outbox-property-required": "warn", + "@fedify/lint/actor-followers-property-required": "warn" + } +} diff --git a/examples/lint/oxlint/README.md b/examples/lint/oxlint/README.md new file mode 100644 index 000000000..e6e1eddcf --- /dev/null +++ b/examples/lint/oxlint/README.md @@ -0,0 +1,72 @@ + + +@fedify/lint with Oxlint +======================== + +This example demonstrates how to use [`@fedify/lint`] together with [Oxlint] +to catch common Fedify federation mistakes. Note that Oxlint's JS plugin +support is upstream alpha and may be unstable. + +[`@fedify/lint`]: https://www.npmjs.com/package/@fedify/lint +[Oxlint]: https://oxc.rs/docs/guide/usage/linter/ + + +Layout +------ + + - *.oxlintrc.json* — Oxlint configuration that enables `@fedify/lint` + via the JS plugin API. + - *federation.ts* — code that intentionally violates several rules + (missing `id`, `inbox`, `outbox`, `followers`). + - *federation.fixed.ts* — corrected version that passes all rules. + + +Usage +----- + +Install dependencies and run the linter: + +~~~~ sh +pnpm install +pnpm lint +~~~~ + +You should see at least one `@fedify/lint(actor-id-required)` error on +*federation.ts*. Running against *federation.fixed.ts* alone produces no +diagnostics: + +~~~~ sh +pnpm lint:fixed +~~~~ + +The same tasks are also wired into *deno.json*, so you can invoke Oxlint +through Deno (the plugin still resolves out of *node\_modules*, so +`pnpm install` is required first): + +~~~~ sh +deno task lint +deno task lint:fixed +~~~~ + + +How it works +------------ + +The plugin is loaded via the `jsPlugins` field in *.oxlintrc.json*: + +~~~~ json +{ + "jsPlugins": ["@fedify/lint/oxlint"], + "rules": { + "@fedify/lint/actor-id-required": "error" + } +} +~~~~ + +`@fedify/lint/oxlint` is a subpath export that exposes the same rules as the +ESLint plugin in Oxlint's plugin shape. Rule IDs are namespaced under +`@fedify/lint/`. + +See the [Linting] manual for the full rule reference. + +[Linting]: https://fedify.dev/manual/lint diff --git a/examples/lint/oxlint/deno.json b/examples/lint/oxlint/deno.json new file mode 100644 index 000000000..7e1b4d1d0 --- /dev/null +++ b/examples/lint/oxlint/deno.json @@ -0,0 +1,6 @@ +{ + "tasks": { + "lint": "deno run -A npm:oxlint .", + "lint:fixed": "deno run -A npm:oxlint federation.fixed.ts" + } +} diff --git a/examples/lint/oxlint/federation.fixed.ts b/examples/lint/oxlint/federation.fixed.ts new file mode 100644 index 000000000..5b4a22f56 --- /dev/null +++ b/examples/lint/oxlint/federation.fixed.ts @@ -0,0 +1,21 @@ +import { + createFederation, + InProcessMessageQueue, + MemoryKvStore, +} from "@fedify/fedify"; +import { Person } from "@fedify/vocab"; + +const federation = createFederation({ + kv: new MemoryKvStore(), + queue: new InProcessMessageQueue(), +}); + +federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => { + return new Person({ + id: ctx.getActorUri(identifier), + name: "Example User", + inbox: ctx.getInboxUri(identifier), + outbox: ctx.getOutboxUri(identifier), + followers: ctx.getFollowersUri(identifier), + }); +}); diff --git a/examples/lint/oxlint/federation.ts b/examples/lint/oxlint/federation.ts new file mode 100644 index 000000000..7704040a5 --- /dev/null +++ b/examples/lint/oxlint/federation.ts @@ -0,0 +1,17 @@ +import { + createFederation, + InProcessMessageQueue, + MemoryKvStore, +} from "@fedify/fedify"; +import { Person } from "@fedify/vocab"; + +const federation = createFederation({ + kv: new MemoryKvStore(), + queue: new InProcessMessageQueue(), +}); + +federation.setActorDispatcher("/users/{identifier}", (_ctx, _identifier) => { + return new Person({ + name: "Example User", + }); +}); diff --git a/examples/lint/oxlint/package.json b/examples/lint/oxlint/package.json new file mode 100644 index 000000000..5dbef1cd8 --- /dev/null +++ b/examples/lint/oxlint/package.json @@ -0,0 +1,19 @@ +{ + "name": "@fedify/example-lint-oxlint", + "version": "0.0.0", + "private": true, + "description": "Example project demonstrating @fedify/lint with oxlint", + "type": "module", + "scripts": { + "lint": "oxlint .", + "lint:fixed": "oxlint federation.fixed.ts" + }, + "dependencies": { + "@fedify/fedify": "workspace:^", + "@fedify/vocab": "workspace:^" + }, + "devDependencies": { + "@fedify/lint": "workspace:^", + "oxlint": "catalog:" + } +} diff --git a/examples/test-examples/mod.ts b/examples/test-examples/mod.ts index 5c5318a8a..c15abd073 100644 --- a/examples/test-examples/mod.ts +++ b/examples/test-examples/mod.ts @@ -293,6 +293,16 @@ const SCRIPT_EXAMPLES: ScriptExample[] = [ cmd: ["deno", "run", "--allow-all", "main.ts"], description: "Custom collection demonstration (in-process federation)", }, + { + // Oxlint example for @fedify/lint. The corrected fixture must lint + // clean, and the intentionally violating fixture must produce + // diagnostics (non-zero exit). Requires the @fedify/lint package to + // be built first (`mise run prepare-each lint`). + name: "lint", + dir: "lint/oxlint", + cmd: ["sh", "-c", "pnpm lint:fixed && ! pnpm lint"], + description: "@fedify/lint with Oxlint (fixed passes, violating fails)", + }, ]; const MULTI_HANDLE_EXAMPLES: MultiHandleExample[] = [ diff --git a/mise.toml b/mise.toml index 4231499bb..d82465775 100644 --- a/mise.toml +++ b/mise.toml @@ -181,6 +181,10 @@ done [tasks."test:examples"] description = "Run tests for all example projects" +# `prepare` builds @fedify/lint's dist/oxlint.js loader (via build:self) and, +# through its `install` dependency, provisions the prebuilt oxlint native +# binary into node_modules — both are needed by the lint/oxlint example. +depends = ["prepare"] run = "deno run -A examples/test-examples/mod.ts" [tasks."test:monitoring"] diff --git a/packages/lint/README.md b/packages/lint/README.md index 877f49272..89c678a9d 100644 --- a/packages/lint/README.md +++ b/packages/lint/README.md @@ -1,7 +1,7 @@ -@fedify/lint: ESLint plugin for Fedify -====================================== +@fedify/lint: Lint plugins for Fedify +===================================== [![JSR][JSR badge]][JSR] [![npm][npm badge]][npm] @@ -9,10 +9,10 @@ *This package is available since Fedify 2.0.0.* -This package provides [Deno Lint] and [ESLint] plugin with lint rules -specifically designed for [Fedify] applications. It helps you catch common -mistakes and enforce best practices when building federated server apps with -Fedify. +This package provides [Deno Lint], [ESLint], and [Oxlint] plugins with lint +rules specifically designed for [Fedify] applications. It helps you catch +common mistakes and enforce best practices when building federated server apps +with Fedify. The plugin includes rules that check for: @@ -30,6 +30,7 @@ The plugin includes rules that check for: [@fedify@hollo.social]: https://hollo.social/@fedify [Deno Lint]: https://docs.deno.com/runtime/reference/lint_plugins/ [ESLint]: https://eslint.org/ +[Oxlint]: https://oxc.rs/docs/guide/usage/linter/ [Fedify]: https://fedify.dev/ ### Deno Lint configuration example @@ -62,6 +63,21 @@ import fedifyLint from "@fedify/lint"; export default fedifyLint; ~~~~ +### Oxlint configuration example + +~~~~ jsonc +// .oxlintrc.json + +{ + "jsPlugins": ["@fedify/lint/oxlint"], + "rules": { + "@fedify/lint/actor-id-required": "error", + "@fedify/lint/actor-id-mismatch": "error", + "@fedify/lint/actor-inbox-property-required": "warn" + } +} +~~~~ + Features -------- @@ -403,6 +419,114 @@ bunx eslint . ::: +Usage (Oxlint) +-------------- + +[Oxlint] is a fast Rust-based linter that supports ESLint-compatible JS plugins. +The `@fedify/lint/oxlint` subpath export plugs the Fedify rules into Oxlint's +[JS plugin API]. + +> [!NOTE] +> Oxlint's JS plugin API is currently in alpha and not subject to semver. + +[JS plugin API]: https://oxc.rs/docs/guide/usage/linter/writing-js-plugins.html + +### Basic setup + +Add the plugin and the rules you want to enable to your *.oxlintrc.json*: + +~~~~ json +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json", + "jsPlugins": ["@fedify/lint/oxlint"], + "rules": { + "@fedify/lint/actor-id-required": "error", + "@fedify/lint/actor-id-mismatch": "error" + } +} +~~~~ + +Rule IDs are namespaced under `@fedify/lint/`, matching the ESLint +configuration above. + +### Custom configuration + +Enable any subset of the rules listed in the *Features* section above. Each +rule can be set to `"error"`, `"warn"`, or `"off"`: + +~~~~ json +{ + "jsPlugins": ["@fedify/lint/oxlint"], + "rules": { + "@fedify/lint/actor-id-required": "error", + "@fedify/lint/actor-id-mismatch": "error", + "@fedify/lint/actor-inbox-property-required": "warn", + "@fedify/lint/actor-outbox-property-required": "warn", + "@fedify/lint/actor-followers-property-required": "warn", + "@fedify/lint/actor-public-key-required": "warn", + "@fedify/lint/actor-assertion-method-required": "warn", + "@fedify/lint/collection-filtering-not-implemented": "warn" + } +} +~~~~ + +### Running Oxlint + +Add a script to *package.json*: + +~~~~ jsonc +{ + "scripts": { + "lint": "oxlint ." + } +} +~~~~ + +Then run: + +::: code-group + +~~~~ sh [npm] +npm run lint +~~~~ + +~~~~ sh [pnpm] +pnpm lint +~~~~ + +~~~~ sh [Yarn] +yarn lint +~~~~ + +~~~~ sh [Bun] +bun lint +~~~~ + +::: + +Or invoke Oxlint directly: + +::: code-group + +~~~~ sh [npm] +npx oxlint . +~~~~ + +~~~~ sh [pnpm] +pnpx oxlint . +~~~~ + +~~~~ sh [Yarn] +yarn oxlint . +~~~~ + +~~~~ sh [Bun] +bunx oxlint . +~~~~ + +::: + + See also -------- diff --git a/packages/lint/deno.json b/packages/lint/deno.json index cf6a0fa27..a785cfda0 100644 --- a/packages/lint/deno.json +++ b/packages/lint/deno.json @@ -3,11 +3,13 @@ "version": "2.3.0", "license": "MIT", "exports": { - ".": "./src/mod.ts" + ".": "./src/mod.ts", + "./oxlint": "./src/oxlint.ts" }, "imports": { "eslint": "npm:eslint@^9.0.0", - "estree": "npm:@types/estree@^1.0.8" + "estree": "npm:@types/estree@^1.0.8", + "oxlint": "npm:oxlint@^1.66.0" }, "publish": { "exclude": [ @@ -16,6 +18,6 @@ ] }, "tasks": { - "test": "deno test --allow-env" + "test": "deno test --allow-env --allow-read --allow-run --allow-sys --allow-write" } } diff --git a/packages/lint/package.json b/packages/lint/package.json index 6bbca994f..da1449c12 100644 --- a/packages/lint/package.json +++ b/packages/lint/package.json @@ -8,7 +8,9 @@ "Fediverse", "Lint", "ESLint", - "ESLint Plugin" + "ESLint Plugin", + "oxlint", + "oxc" ], "author": { "name": "Chanhaeng Lee", @@ -38,6 +40,11 @@ "import": "./dist/index.js", "require": "./dist/index.cjs" }, + "./oxlint": { + "types": "./dist/oxlint.d.ts", + "import": "./dist/oxlint.js", + "require": "./dist/oxlint.cjs" + }, "./package.json": "./package.json" }, "files": [ @@ -60,9 +67,11 @@ "@typescript-eslint/utils": "^8.0.0" }, "devDependencies": { + "@fedify/fixture": "workspace:*", "@types/eslint": "^9.0.0", "@types/estree": "^1.0.9", "eslint": "^9.0.0", + "oxlint": "catalog:", "tsdown": "catalog:", "typescript": "catalog:" }, diff --git a/packages/lint/src/lib/oxlint.ts b/packages/lint/src/lib/oxlint.ts new file mode 100644 index 000000000..b0a735fa6 --- /dev/null +++ b/packages/lint/src/lib/oxlint.ts @@ -0,0 +1,145 @@ +/** + * Shared helpers for the oxlint-based tests. + * + * oxlint is a Rust binary, so the only way to exercise the JS plugin loader + * end-to-end is to spawn it as a subprocess against a real config file. Both + * `tests/oxlint.test.ts` and the oxlint lane of `tests/integration.test.ts` + * need the same setup — locating the built loader and the binary, writing a + * tmpdir fixture, running oxlint, and parsing its JSON diagnostics — so that + * logic lives here once. + */ +import { spawnSync } from "node:child_process"; +import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import process from "node:process"; +import { fileURLToPath } from "node:url"; + +const here = dirname(fileURLToPath(import.meta.url)); + +/** Absolute path to the built oxlint plugin loader (`dist/oxlint.js`). */ +export const oxlintPluginPath: string = resolve(here, "../../dist/oxlint.js"); + +/** Whether the built loader exists. */ +export const pluginBuilt: boolean = existsSync(oxlintPluginPath); + +/** + * Locate the oxlint binary: prefer the package- and workspace-local + * `node_modules/.bin`, then fall back to whatever is on `PATH`. Returns + * `null` when it cannot be found. + */ +export function findOxlint(): string | null { + const candidates = [ + resolve(here, "../../node_modules/.bin/oxlint"), + resolve(here, "../../../../node_modules/.bin/oxlint"), + ]; + for (const candidate of candidates) { + if (existsSync(candidate)) return candidate; + } + const where = spawnSync( + process.platform === "win32" ? "where" : "which", + ["oxlint"], + { encoding: "utf8" }, + ); + if (where.status === 0 && where.stdout) { + return where.stdout.trim().split(/\r?\n/)[0]; + } + return null; +} + +/** The resolved oxlint binary path, or `null` when it is unavailable. */ +export const oxlintBin: string | null = findOxlint(); + +/** + * `true` when either the built loader or the oxlint binary is missing, in + * which case the oxlint-based tests should be skipped via `{ ignore }`. + */ +export const oxlintUnavailable: boolean = !pluginBuilt || oxlintBin == null; + +/** + * Print a single warning explaining why the oxlint tests are being skipped. + * Call this only when {@link oxlintUnavailable} is `true`. + */ +export function warnOxlintSkipped(): void { + const missing: string[] = []; + if (!pluginBuilt) missing.push(`built loader at ${oxlintPluginPath}`); + if (oxlintBin == null) { + missing.push("oxlint binary on PATH or in node_modules"); + } + console.warn( + `Skipping oxlint tests — missing: ${missing.join(", ")}.\n` + + "To enable them, run `mise run install && mise run prepare-each lint` " + + "from the repository root so both the loader and the oxlint binary " + + "are available. Prefer `mise run test` (full suite) or " + + "`mise run test-each lint` (this package) to run tests — those tasks " + + "build the prerequisites for you.", + ); +} + +/** A single diagnostic as reported by oxlint's `--format=json` output. */ +export interface OxlintDiagnostic { + code?: string; + message?: string; + severity?: string; +} + +/** The result of running oxlint over a fixture. */ +export interface OxlintRunResult { + /** Process exit status (`null` if terminated by a signal). */ + status: number | null; + /** Parsed diagnostics. */ + diagnostics: OxlintDiagnostic[]; + /** Raw stdout, kept for assertion messages. */ + stdout: string; + /** Raw stderr, kept for assertion messages. */ + stderr: string; +} + +/** + * Run oxlint over a single source file inside a throwaway tmpdir. + * + * Writes `.oxlintrc.json` (always wiring up the built plugin loader and + * merging in `config`) plus a source file named `fileName` containing `code`, + * spawns oxlint with `--format=json`, parses the JSON report, and removes the + * tmpdir before returning. + * + * Callers must ensure {@link oxlintBin} is non-null first. + */ +export function runOxlint( + code: string, + config: Record, + fileName = "code.ts", +): OxlintRunResult { + const dir = mkdtempSync(join(tmpdir(), "fedify-lint-oxlint-")); + try { + writeFileSync( + join(dir, ".oxlintrc.json"), + JSON.stringify({ jsPlugins: [oxlintPluginPath], ...config }), + ); + writeFileSync(join(dir, fileName), code); + + const result = spawnSync(oxlintBin!, ["--format=json", fileName], { + cwd: dir, + encoding: "utf8", + }); + + let payload: { diagnostics?: OxlintDiagnostic[] }; + try { + payload = JSON.parse(result.stdout); + } catch (err) { + throw new Error( + `Failed to parse oxlint JSON output: ${(err as Error).message}\n` + + `stdout: ${result.stdout}\nstderr: ${result.stderr}`, + ); + } + + return { + status: result.status, + diagnostics: payload.diagnostics ?? [], + stdout: result.stdout, + stderr: result.stderr, + }; + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} diff --git a/packages/lint/src/oxlint.ts b/packages/lint/src/oxlint.ts new file mode 100644 index 000000000..f9a0f0461 --- /dev/null +++ b/packages/lint/src/oxlint.ts @@ -0,0 +1,135 @@ +/** + * oxlint plugin for Fedify. + * + * Exposes the same rule set as the ESLint plugin in a shape suitable for + * oxlint's JS plugin API (https://oxc.rs/docs/guide/usage/linter/writing-js-plugins.html). + * + * Configure via `.oxlintrc.json`: + * + * ```json + * { + * "jsPlugins": ["@fedify/lint/oxlint"], + * "rules": { + * "@fedify/lint/actor-id-required": "error" + * } + * } + * ``` + */ +import type { Rule } from "eslint"; +import metadataRaw from "../deno.json" with { type: "json" }; +import { RULE_IDS } from "./lib/const.ts"; +import { + eslint as actorAssertionMethodRequired, +} from "./rules/actor-assertion-method-required.ts"; +import { + eslint as actorFeaturedPropertyMismatch, +} from "./rules/actor-featured-property-mismatch.ts"; +import { + eslint as actorFeaturedPropertyRequired, +} from "./rules/actor-featured-property-required.ts"; +import { + eslint as actorFeaturedTagsPropertyMismatch, +} from "./rules/actor-featured-tags-property-mismatch.ts"; +import { + eslint as actorFeaturedTagsPropertyRequired, +} from "./rules/actor-featured-tags-property-required.ts"; +import { + eslint as actorFollowersPropertyMismatch, +} from "./rules/actor-followers-property-mismatch.ts"; +import { + eslint as actorFollowersPropertyRequired, +} from "./rules/actor-followers-property-required.ts"; +import { + eslint as actorFollowingPropertyMismatch, +} from "./rules/actor-following-property-mismatch.ts"; +import { + eslint as actorFollowingPropertyRequired, +} from "./rules/actor-following-property-required.ts"; +import { eslint as actorIdMismatch } from "./rules/actor-id-mismatch.ts"; +import { eslint as actorIdRequired } from "./rules/actor-id-required.ts"; +import { + eslint as actorInboxPropertyMismatch, +} from "./rules/actor-inbox-property-mismatch.ts"; +import { + eslint as actorInboxPropertyRequired, +} from "./rules/actor-inbox-property-required.ts"; +import { + eslint as actorLikedPropertyMismatch, +} from "./rules/actor-liked-property-mismatch.ts"; +import { + eslint as actorLikedPropertyRequired, +} from "./rules/actor-liked-property-required.ts"; +import { + eslint as actorOutboxPropertyMismatch, +} from "./rules/actor-outbox-property-mismatch.ts"; +import { + eslint as actorOutboxPropertyRequired, +} from "./rules/actor-outbox-property-required.ts"; +import { + eslint as actorPublicKeyRequired, +} from "./rules/actor-public-key-required.ts"; +import { + eslint as actorSharedInboxPropertyMismatch, +} from "./rules/actor-shared-inbox-property-mismatch.ts"; +import { + eslint as actorSharedInboxPropertyRequired, +} from "./rules/actor-shared-inbox-property-required.ts"; +import { + eslint as collectionFiltering, +} from "./rules/collection-filtering-not-implemented.ts"; +import { + eslint as outboxListenerDeliveryRequired, +} from "./rules/outbox-listener-delivery-required.ts"; + +const rules: Record< + typeof RULE_IDS[keyof typeof RULE_IDS], + Rule.RuleModule +> = { + [RULE_IDS.actorIdMismatch]: actorIdMismatch, + [RULE_IDS.actorIdRequired]: actorIdRequired, + [RULE_IDS.actorFollowingPropertyRequired]: actorFollowingPropertyRequired, + [RULE_IDS.actorFollowingPropertyMismatch]: actorFollowingPropertyMismatch, + [RULE_IDS.actorFollowersPropertyRequired]: actorFollowersPropertyRequired, + [RULE_IDS.actorFollowersPropertyMismatch]: actorFollowersPropertyMismatch, + [RULE_IDS.actorOutboxPropertyRequired]: actorOutboxPropertyRequired, + [RULE_IDS.actorOutboxPropertyMismatch]: actorOutboxPropertyMismatch, + [RULE_IDS.actorLikedPropertyRequired]: actorLikedPropertyRequired, + [RULE_IDS.actorLikedPropertyMismatch]: actorLikedPropertyMismatch, + [RULE_IDS.actorFeaturedPropertyRequired]: actorFeaturedPropertyRequired, + [RULE_IDS.actorFeaturedPropertyMismatch]: actorFeaturedPropertyMismatch, + [RULE_IDS.actorFeaturedTagsPropertyRequired]: + actorFeaturedTagsPropertyRequired, + [RULE_IDS.actorFeaturedTagsPropertyMismatch]: + actorFeaturedTagsPropertyMismatch, + [RULE_IDS.actorInboxPropertyRequired]: actorInboxPropertyRequired, + [RULE_IDS.actorInboxPropertyMismatch]: actorInboxPropertyMismatch, + [RULE_IDS.actorSharedInboxPropertyRequired]: actorSharedInboxPropertyRequired, + [RULE_IDS.actorSharedInboxPropertyMismatch]: actorSharedInboxPropertyMismatch, + [RULE_IDS.actorPublicKeyRequired]: actorPublicKeyRequired, + [RULE_IDS.actorAssertionMethodRequired]: actorAssertionMethodRequired, + [RULE_IDS.collectionFilteringNotImplemented]: collectionFiltering, + [RULE_IDS.outboxListenerDeliveryRequired]: outboxListenerDeliveryRequired, +}; + +/** + * Plugin object compatible with Oxlint's JS plugin API. + * + * Kept module-private on purpose: consumers should rely on the default + * export and the Oxlint runtime to resolve the shape, rather than + * importing this type and risking drift from upstream's `Plugin` + * definition in `@oxlint/plugins`. + */ +interface OxlintPlugin { + meta: { name: string; version: string }; + rules: Record; +} + +const plugin: OxlintPlugin = { + meta: { + name: metadataRaw.name, + version: metadataRaw.version, + }, + rules, +}; + +export default plugin; diff --git a/packages/lint/src/tests/integration.test.ts b/packages/lint/src/tests/integration.test.ts index 6136eca56..778d37d1b 100644 --- a/packages/lint/src/tests/integration.test.ts +++ b/packages/lint/src/tests/integration.test.ts @@ -12,6 +12,11 @@ import { Linter } from "eslint"; import { equal, ok } from "node:assert/strict"; import { test } from "node:test"; import { plugin as eslintPlugin } from "../index.ts"; +import { + oxlintUnavailable, + runOxlint, + warnOxlintSkipped, +} from "../lib/oxlint.ts"; import { replace } from "../lib/utils.ts"; import denoPlugin from "../mod.ts"; @@ -24,9 +29,16 @@ type Diagnostic = { /** * Run all lint rules on the given code and return diagnostics. + * + * The in-process linter for the current runtime (Deno Lint or ESLint) always + * runs. When the built oxlint loader and the oxlint binary are available, + * the same code additionally goes through oxlint, so every rule is exercised + * end-to-end through the oxlint JS plugin adapter as well. */ -const lintTest = (code: string): Diagnostic[] => - "Deno" in globalThis ? testDenoLint(code) : testEslint(code); +const lintTest = (code: string): Diagnostic[] => [ + ...("Deno" in globalThis ? testDenoLint(code) : testEslint(code)), + ...(oxlintUnavailable ? [] : testOxlint(code)), +]; const testDenoLint = (code: string) => Deno.lint.runPlugin( @@ -58,6 +70,27 @@ function testEslint(code: string) { })); } +// oxlint is a Rust binary, so it is exercised as a subprocess against a real +// config file — the setup lives in ../lib/oxlint.ts and is shared with +// oxlint.test.ts. When the built loader or the binary is missing, the oxlint +// lane is skipped with a warning. +if (oxlintUnavailable) warnOxlintSkipped(); + +function testOxlint(code: string): Diagnostic[] { + const { diagnostics } = runOxlint(code, { + // Turn off oxlint's built-in rules so that only the diagnostics of this + // plugin surface. + categories: { correctness: "off" }, + rules: eslintPlugin.configs.recommended.rules, + }); + return diagnostics.map((d) => ({ + // Normalize oxlint's "@fedify/lint(rule-name)" code into the same + // "/rule-name" shape the other lanes report. + id: (d.code ?? "").replace(/^@fedify\/lint\((.+)\)$/, `${PLUGIN_NAME}/$1`), + message: d.message ?? "", + })); +} + /** * Assert that the code passes all lint rules (no diagnostics). */ diff --git a/packages/lint/src/tests/oxlint.test.ts b/packages/lint/src/tests/oxlint.test.ts new file mode 100644 index 000000000..f9f3bc51b --- /dev/null +++ b/packages/lint/src/tests/oxlint.test.ts @@ -0,0 +1,81 @@ +/** + * Integration test for the oxlint plugin entry. + * + * Spawns the oxlint binary against a tmpdir fixture that violates + * `actor-id-required`, parses the JSON diagnostics, and asserts the + * `@fedify/lint/actor-id-required` rule fires. + * + * Runtime notes: + * + * - The shared helpers in `../lib/oxlint.ts` use `node:child_process`, + * `node:fs`, `node:os`, `node:path`, and `node:process`. Under Deno + * these resolve via the Node compatibility layer, so the same source + * runs under both `pnpm test` (via `node:test`) and `deno task test` + * (via `Deno.test`) — `@fedify/fixture` dispatches the test definition + * to the appropriate runtime. + * - Other rule tests in this package use the in-process linter APIs + * (`Deno.lint.runPlugin` / ESLint's `Linter`). This one is + * different on purpose: oxlint is a Rust binary, so we spawn it as + * a subprocess against a real config file. That's the only way to + * exercise the JS plugin loader end-to-end. + * - Two preconditions are checked at module load (see {@link + * oxlintUnavailable}). If either is missing, the test is skipped via + * `{ ignore }`: + * * the built loader at `/dist/oxlint.js` + * * the oxlint binary, located under `/node_modules/.bin`, + * the workspace root, or anywhere on `PATH` + */ +import { test } from "@fedify/fixture"; +import { ok } from "node:assert/strict"; +import { + oxlintUnavailable, + runOxlint, + warnOxlintSkipped, +} from "../lib/oxlint.ts"; + +if (oxlintUnavailable) warnOxlintSkipped(); + +const BAD_CODE = + `import { createFederation, InProcessMessageQueue, MemoryKvStore } from "@fedify/fedify"; +import { Person } from "@fedify/vocab"; + +const federation = createFederation({ + kv: new MemoryKvStore(), + queue: new InProcessMessageQueue(), +}); + +federation.setActorDispatcher("/users/{identifier}", (_ctx, _identifier) => { + return new Person({ + name: "Bad Actor", + }); +}); +`; + +test( + "oxlint plugin: actor-id-required fires on missing id", + { ignore: oxlintUnavailable }, + () => { + const { status, diagnostics, stderr } = runOxlint( + BAD_CODE, + { rules: { "@fedify/lint/actor-id-required": "error" } }, + "federation.ts", + ); + + ok( + status !== 0, + `Expected non-zero exit, got ${status}. stderr: ${stderr}`, + ); + + const codes = diagnostics.map((d) => d.code ?? ""); + const matched = codes.some((code) => + code === "@fedify/lint(actor-id-required)" || + code.includes("actor-id-required") + ); + ok( + matched, + `Expected @fedify/lint(actor-id-required) diagnostic, got: ${ + codes.join(", ") || "(none)" + }`, + ); + }, +); diff --git a/packages/lint/tsdown.config.ts b/packages/lint/tsdown.config.ts index 41c45aead..7312e7fd0 100644 --- a/packages/lint/tsdown.config.ts +++ b/packages/lint/tsdown.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "tsdown"; export default defineConfig({ - entry: ["src/index.ts"], + entry: ["src/index.ts", "src/oxlint.ts"], dts: { compilerOptions: { isolatedDeclarations: true, declaration: true } }, format: ["esm", "cjs"], platform: "node", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04daa6d4d..1f2341172 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,6 +165,9 @@ catalogs: nuxt: specifier: ^4.4.2 version: 4.4.2 + oxlint: + specifier: ^1.66.0 + version: 1.66.0 pkijs: specifier: ^3.3.3 version: 3.3.3 @@ -550,6 +553,22 @@ importers: specifier: ^5.5.4 version: 5.9.2 + examples/lint/oxlint: + dependencies: + '@fedify/fedify': + specifier: workspace:^ + version: link:../../../packages/fedify + '@fedify/vocab': + specifier: workspace:^ + version: link:../../../packages/vocab + devDependencies: + '@fedify/lint': + specifier: workspace:^ + version: link:../../../packages/lint + oxlint: + specifier: 'catalog:' + version: 1.66.0 + examples/next-integration: dependencies: '@fedify/fedify': @@ -719,7 +738,7 @@ importers: version: 1.15.11 nuxt: specifier: 'catalog:' - version: 4.4.2(75d3bb99739572e2ce1a5d32bf0400f3) + version: 4.4.2(9b6f49a83ed1b2fc8889dfd9b7f2c45d) devDependencies: '@types/node': specifier: 'catalog:' @@ -1399,6 +1418,9 @@ importers: specifier: ^8.0.0 version: 8.41.0(eslint@9.32.0(jiti@2.6.1))(typescript@6.0.3) devDependencies: + '@fedify/fixture': + specifier: workspace:* + version: link:../fixture '@types/eslint': specifier: ^9.0.0 version: 9.6.1 @@ -1408,6 +1430,9 @@ importers: eslint: specifier: ^9.0.0 version: 9.32.0(jiti@2.6.1) + oxlint: + specifier: 'catalog:' + version: 1.66.0 tsdown: specifier: 'catalog:' version: 0.22.0(tsx@4.21.0)(typescript@6.0.3)(unrun@0.2.34(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)) @@ -1503,7 +1528,7 @@ importers: version: 1.15.11 nuxt: specifier: 'catalog:' - version: 4.4.2(75d3bb99739572e2ce1a5d32bf0400f3) + version: 4.4.2(9b6f49a83ed1b2fc8889dfd9b7f2c45d) devDependencies: '@fedify/fixture': specifier: workspace:^ @@ -4766,6 +4791,120 @@ packages: cpu: [x64] os: [win32] + '@oxlint/binding-android-arm-eabi@1.66.0': + resolution: {integrity: sha512-f7kq8N51T4phpzqfBpA2qaVTI/KrkCmNwaj3t/97I/WLTDI+UhlP5GL9eER+zVxBhtlx5rKXWByJU1/zDAvyaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.66.0': + resolution: {integrity: sha512-xu6QO71tdDS9mjmLZ3AqhtaVHBvdmsOKkYnReNNDgh+XiwnsipeQOIxbiYOOO0iAXycJ+GK0wdMSZP/2j/AmSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.66.0': + resolution: {integrity: sha512-HZ24VimSOC7mxuEA99e0H2FS0C1yO3+iW13jPRAk+e2njsUs3QeAXsafCDyaIrV/MirdOVez+etQNQsJE43zNQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.66.0': + resolution: {integrity: sha512-awhj8ZvJrrRSnXj7V++rpZvTmnl99L6mi0B7gg7Cp7BN6cKpzuI481bHNLvXGA9GB1/oEgA3ponuyoAc6Md12A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.66.0': + resolution: {integrity: sha512-KQF0oVV21/FjIqkRuL8Q1vh8ECsE5+ocdH5tcqTQ4ZnYuDVoYibQUNfqBjQaUsP6UIIda5Y75Wpm5p4RgQWiWw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.66.0': + resolution: {integrity: sha512-9u1rgwZSEXWb30vbFZzQ78HVXBo0WCKNwJ3a2InRUTNMRng+PUDIoSFmA+m4HdUfBaIqftShq8J8qHc+eE/Vig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.66.0': + resolution: {integrity: sha512-Ynot2HR1bHxUaNWoC280MVTDfZuaWuP3XfSMRDhyuZrVjhzoaBCVFlw8h8qeZjWKVUBhPWFIxB7AQTlK8Z2WWg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.66.0': + resolution: {integrity: sha512-xCbgzciGgo+A4aQZEknsNrNiIwY7sU5SfRuMmRjPIvZAgdF34cIHiKvwOsS5XRLjlTVSFwitmq6YclTtHTfU+g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxlint/binding-linux-arm64-musl@1.66.0': + resolution: {integrity: sha512-hmo+ZB/lHkR1HdDmnziNpzSLmulnUSu10VEqX2Yex7OwvoBAbjJQLvy4gIBRV3AAwWnCvAxKp5Nv1GE6LU1QMg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxlint/binding-linux-ppc64-gnu@1.66.0': + resolution: {integrity: sha512-2Invd4Uyy81mVooQC5FBtfxSNrvcX1OxbMlVQ6M2erRrNI2awFYF26YNW2yFxdVFZ4ffNOWKghtMjhnUPsXsVA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxlint/binding-linux-riscv64-gnu@1.66.0': + resolution: {integrity: sha512-s0iXPDQVdgayE3RGa/N2DZF7tjgg0TwEtD1sGoDxqPDGrIXgo45H0yHknT0f9A0yteASsweYZtDyTuVlM4aSag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxlint/binding-linux-riscv64-musl@1.66.0': + resolution: {integrity: sha512-OekL4XFiu7RPK0JIZi8VeHgtIXPREf42t8Cy/rKEsC+P3gcqDgNAAGiyuUOpdbG4wwbfue1q4CHcCO7spSve6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxlint/binding-linux-s390x-gnu@1.66.0': + resolution: {integrity: sha512-Ga1D0kj1SFslm34ThA/BdkUlyAYEnTsXyRC4pF0C5agZSwtGdHYWMTQWemUfBGp4RCG4QWXgdO+HmmmKqOtlBg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@oxlint/binding-linux-x64-gnu@1.66.0': + resolution: {integrity: sha512-p5jfP1wUZe/IC3qpQO84n9DRnf9g3lKRtLBlQq23ykyrDglHcVx7sWmVTlPuU6SBw8mNnPzyOn022G3XZHnlww==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxlint/binding-linux-x64-musl@1.66.0': + resolution: {integrity: sha512-vUB/sYlYZorDL1ZD+o9mRv7zbsykrrFRtmgS6R8musZqLtrPRQn1gc1eGpuX+sfdccz42STl/AqldY6XRb2upQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxlint/binding-openharmony-arm64@1.66.0': + resolution: {integrity: sha512-yde+6p/F59xRkGR9H1HfngWRif1QRJjynZK349l+UI0H6w9hL3G8/AVaTHFyTtLVQ56qtNbX2/5Dc77n1ovnOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.66.0': + resolution: {integrity: sha512-O9GLucgoTdmOrbBX+EjzNe7o/Ze5TFOvXcib6bzUOtBOmj6cV+zw18NgB+cGKAkDw1Pdqs8vGkfHbbsLuDtXWg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.66.0': + resolution: {integrity: sha512-m3Pjwc2MfTcom4E4gOv7DyuGyt7OfGNCbmqDHd+N7EzXmP+ppHuudm2NjcA3AjV5TSeGxaguVF4SbTKHe1USYA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.66.0': + resolution: {integrity: sha512-/DbBvw8UFBhja6PqudUjV4UtfsJr0Oa7jUjWVKB0g86lj/VwnPrkngn0sFql3c9RDA0O16dh7ozsXb6GjNAzBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@parcel/watcher-android-arm64@2.5.6': resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} engines: {node: '>= 10.0.0'} @@ -10252,6 +10391,16 @@ packages: peerDependencies: oxc-parser: '>=0.98.0' + oxlint@1.66.0: + resolution: {integrity: sha512-N4LLxYLd94KEBqXDMDM5f+2PUpItTjDLreXe2Gn5KhjhCK4Qp2YUXaBi8Yu325ryOgKwt22m45fpD7nPOn69Yw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.22.1' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -14954,7 +15103,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/nitro-server@4.4.2(@babel/core@7.29.0)(better-sqlite3@12.9.0)(db0@0.3.4(better-sqlite3@12.9.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260511.1)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.6.1)(better-sqlite3@12.9.0)(bun-types@1.3.3)(mysql2@3.22.3(@types/node@22.19.1))(postgres@3.4.7))(mysql2@3.22.3(@types/node@22.19.1)))(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260511.1)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.6.1)(better-sqlite3@12.9.0)(bun-types@1.3.3)(mysql2@3.22.3(@types/node@22.19.1))(postgres@3.4.7))(ioredis@5.10.1)(magicast@0.5.2)(mysql2@3.22.3(@types/node@22.19.1))(nuxt@4.4.2(75d3bb99739572e2ce1a5d32bf0400f3))(rolldown@1.0.0)(typescript@6.0.3)': + '@nuxt/nitro-server@4.4.2(@babel/core@7.29.0)(better-sqlite3@12.9.0)(db0@0.3.4(better-sqlite3@12.9.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260511.1)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.6.1)(better-sqlite3@12.9.0)(bun-types@1.3.3)(mysql2@3.22.3(@types/node@22.19.1))(postgres@3.4.7))(mysql2@3.22.3(@types/node@22.19.1)))(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260511.1)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.6.1)(better-sqlite3@12.9.0)(bun-types@1.3.3)(mysql2@3.22.3(@types/node@22.19.1))(postgres@3.4.7))(ioredis@5.10.1)(magicast@0.5.2)(mysql2@3.22.3(@types/node@22.19.1))(nuxt@4.4.2(9b6f49a83ed1b2fc8889dfd9b7f2c45d))(rolldown@1.0.0)(typescript@6.0.3)': dependencies: '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) '@nuxt/devalue': 2.0.2 @@ -14973,7 +15122,7 @@ snapshots: klona: 2.0.6 mocked-exports: 0.1.1 nitropack: 2.13.3(better-sqlite3@12.9.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260511.1)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.6.1)(better-sqlite3@12.9.0)(bun-types@1.3.3)(mysql2@3.22.3(@types/node@22.19.1))(postgres@3.4.7))(mysql2@3.22.3(@types/node@22.19.1))(rolldown@1.0.0) - nuxt: 4.4.2(75d3bb99739572e2ce1a5d32bf0400f3) + nuxt: 4.4.2(9b6f49a83ed1b2fc8889dfd9b7f2c45d) nypm: 0.6.5 ohash: 2.0.11 pathe: 2.0.3 @@ -15040,7 +15189,7 @@ snapshots: rc9: 3.0.1 std-env: 4.1.0 - '@nuxt/vite-builder@4.4.2(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@types/node@22.19.1)(lightningcss@1.30.1)(magicast@0.5.2)(nuxt@4.4.2(75d3bb99739572e2ce1a5d32bf0400f3))(optionator@0.9.4)(rolldown@1.0.0)(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0)(rollup@4.60.2))(rollup@4.60.2)(terser@5.46.1)(tsx@4.21.0)(typescript@6.0.3)(vue@3.5.33(typescript@6.0.3))(yaml@2.9.0)': + '@nuxt/vite-builder@4.4.2(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@types/node@22.19.1)(lightningcss@1.30.1)(magicast@0.5.2)(nuxt@4.4.2(9b6f49a83ed1b2fc8889dfd9b7f2c45d))(optionator@0.9.4)(oxlint@1.66.0)(rolldown@1.0.0)(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0)(rollup@4.60.2))(rollup@4.60.2)(terser@5.46.1)(tsx@4.21.0)(typescript@6.0.3)(vue@3.5.33(typescript@6.0.3))(yaml@2.9.0)': dependencies: '@nuxt/kit': 4.4.2(magicast@0.5.2) '@rollup/plugin-replace': 6.0.3(rollup@4.60.2) @@ -15058,7 +15207,7 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.2 mocked-exports: 0.1.1 - nuxt: 4.4.2(75d3bb99739572e2ce1a5d32bf0400f3) + nuxt: 4.4.2(9b6f49a83ed1b2fc8889dfd9b7f2c45d) nypm: 0.6.5 pathe: 2.0.3 pkg-types: 2.3.0 @@ -15069,7 +15218,7 @@ snapshots: unenv: 2.0.0-rc.24 vite: 7.3.2(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) vite-node: 5.3.0(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0) - vite-plugin-checker: 0.12.0(optionator@0.9.4)(typescript@6.0.3)(vite@7.3.2(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) + vite-plugin-checker: 0.12.0(optionator@0.9.4)(oxlint@1.66.0)(typescript@6.0.3)(vite@7.3.2(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)) vue: 3.5.33(typescript@6.0.3) vue-bundle-renderer: 2.2.0 optionalDependencies: @@ -15877,6 +16026,63 @@ snapshots: '@oxc-transform/binding-win32-x64-msvc@0.117.0': optional: true + '@oxlint/binding-android-arm-eabi@1.66.0': + optional: true + + '@oxlint/binding-android-arm64@1.66.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.66.0': + optional: true + + '@oxlint/binding-darwin-x64@1.66.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.66.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.66.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.66.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.66.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.66.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.66.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.66.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.66.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.66.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.66.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.66.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.66.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.66.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.66.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.66.0': + optional: true + '@parcel/watcher-android-arm64@2.5.6': optional: true @@ -22508,16 +22714,16 @@ snapshots: dependencies: boolbase: 1.0.0 - nuxt@4.4.2(75d3bb99739572e2ce1a5d32bf0400f3): + nuxt@4.4.2(9b6f49a83ed1b2fc8889dfd9b7f2c45d): dependencies: '@dxup/nuxt': 0.4.1(magicast@0.5.2)(typescript@6.0.3) '@nuxt/cli': 3.34.0(@nuxt/schema@4.4.2)(cac@6.7.14)(magicast@0.5.2) '@nuxt/devtools': 3.2.4(vite@7.3.2(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0))(vue@3.5.33(typescript@6.0.3)) '@nuxt/kit': 4.4.2(magicast@0.5.2) - '@nuxt/nitro-server': 4.4.2(@babel/core@7.29.0)(better-sqlite3@12.9.0)(db0@0.3.4(better-sqlite3@12.9.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260511.1)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.6.1)(better-sqlite3@12.9.0)(bun-types@1.3.3)(mysql2@3.22.3(@types/node@22.19.1))(postgres@3.4.7))(mysql2@3.22.3(@types/node@22.19.1)))(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260511.1)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.6.1)(better-sqlite3@12.9.0)(bun-types@1.3.3)(mysql2@3.22.3(@types/node@22.19.1))(postgres@3.4.7))(ioredis@5.10.1)(magicast@0.5.2)(mysql2@3.22.3(@types/node@22.19.1))(nuxt@4.4.2(75d3bb99739572e2ce1a5d32bf0400f3))(rolldown@1.0.0)(typescript@6.0.3) + '@nuxt/nitro-server': 4.4.2(@babel/core@7.29.0)(better-sqlite3@12.9.0)(db0@0.3.4(better-sqlite3@12.9.0)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260511.1)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.6.1)(better-sqlite3@12.9.0)(bun-types@1.3.3)(mysql2@3.22.3(@types/node@22.19.1))(postgres@3.4.7))(mysql2@3.22.3(@types/node@22.19.1)))(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260511.1)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.6.1)(better-sqlite3@12.9.0)(bun-types@1.3.3)(mysql2@3.22.3(@types/node@22.19.1))(postgres@3.4.7))(ioredis@5.10.1)(magicast@0.5.2)(mysql2@3.22.3(@types/node@22.19.1))(nuxt@4.4.2(9b6f49a83ed1b2fc8889dfd9b7f2c45d))(rolldown@1.0.0)(typescript@6.0.3) '@nuxt/schema': 4.4.2 '@nuxt/telemetry': 2.8.0(@nuxt/kit@4.4.2(magicast@0.5.2)) - '@nuxt/vite-builder': 4.4.2(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@types/node@22.19.1)(lightningcss@1.30.1)(magicast@0.5.2)(nuxt@4.4.2(75d3bb99739572e2ce1a5d32bf0400f3))(optionator@0.9.4)(rolldown@1.0.0)(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0)(rollup@4.60.2))(rollup@4.60.2)(terser@5.46.1)(tsx@4.21.0)(typescript@6.0.3)(vue@3.5.33(typescript@6.0.3))(yaml@2.9.0) + '@nuxt/vite-builder': 4.4.2(@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0))(@types/node@22.19.1)(lightningcss@1.30.1)(magicast@0.5.2)(nuxt@4.4.2(9b6f49a83ed1b2fc8889dfd9b7f2c45d))(optionator@0.9.4)(oxlint@1.66.0)(rolldown@1.0.0)(rollup-plugin-visualizer@7.0.1(rolldown@1.0.0)(rollup@4.60.2))(rollup@4.60.2)(terser@5.46.1)(tsx@4.21.0)(typescript@6.0.3)(vue@3.5.33(typescript@6.0.3))(yaml@2.9.0) '@unhead/vue': 2.1.13(vue@3.5.33(typescript@6.0.3)) '@vue/shared': 3.5.33 c12: 3.3.4(magicast@0.5.2) @@ -22871,6 +23077,28 @@ snapshots: magic-regexp: 0.10.0 oxc-parser: 0.117.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + oxlint@1.66.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.66.0 + '@oxlint/binding-android-arm64': 1.66.0 + '@oxlint/binding-darwin-arm64': 1.66.0 + '@oxlint/binding-darwin-x64': 1.66.0 + '@oxlint/binding-freebsd-x64': 1.66.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.66.0 + '@oxlint/binding-linux-arm-musleabihf': 1.66.0 + '@oxlint/binding-linux-arm64-gnu': 1.66.0 + '@oxlint/binding-linux-arm64-musl': 1.66.0 + '@oxlint/binding-linux-ppc64-gnu': 1.66.0 + '@oxlint/binding-linux-riscv64-gnu': 1.66.0 + '@oxlint/binding-linux-riscv64-musl': 1.66.0 + '@oxlint/binding-linux-s390x-gnu': 1.66.0 + '@oxlint/binding-linux-x64-gnu': 1.66.0 + '@oxlint/binding-linux-x64-musl': 1.66.0 + '@oxlint/binding-openharmony-arm64': 1.66.0 + '@oxlint/binding-win32-arm64-msvc': 1.66.0 + '@oxlint/binding-win32-ia32-msvc': 1.66.0 + '@oxlint/binding-win32-x64-msvc': 1.66.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -25403,7 +25631,7 @@ snapshots: - tsx - yaml - vite-plugin-checker@0.12.0(optionator@0.9.4)(typescript@6.0.3)(vite@7.3.2(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)): + vite-plugin-checker@0.12.0(optionator@0.9.4)(oxlint@1.66.0)(typescript@6.0.3)(vite@7.3.2(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)): dependencies: '@babel/code-frame': 7.29.0 chokidar: 4.0.3 @@ -25416,6 +25644,7 @@ snapshots: vscode-uri: 3.1.0 optionalDependencies: optionator: 0.9.4 + oxlint: 1.66.0 typescript: 6.0.3 vite-plugin-inspect@11.3.3(@nuxt/kit@4.4.2(magicast@0.5.2))(vite@7.3.2(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.46.1)(tsx@4.21.0)(yaml@2.9.0)): diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b6b1a1cf3..433b73212 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -37,6 +37,7 @@ packages: - examples/elysia - examples/express - examples/koa +- examples/lint/oxlint - examples/next-integration - examples/fastify - examples/next14-app-router @@ -108,6 +109,7 @@ catalog: pkijs: ^3.3.3 mysql2: ^3.22.3 nuxt: ^4.4.2 + oxlint: ^1.66.0 postgres: ^3.4.7 tsdown: ^0.22.0 typescript: ^6.0.0