From 0667110886b9dd09b411ed59e329fb7c4db0f141 Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Tue, 22 Sep 2026 23:21:19 +0530 Subject: [PATCH] Lint with oxlint instead of ESLint ESLint cannot parse this project's TypeScript: typescript@7 is the native port with no JavaScript compiler API, which every ESLint TypeScript parser needs, and typescript-eslint refuses to install with a >=4.8.4 <6.1.0 peer. oxlint parses TypeScript without the compiler API, so it lints the .ts and the .js together in about 20ms over the whole repo, and it runs the correctness category as errors. It found one real thing on the first run: the drag harness built its fetch options with a body key even for GET. The four opinionated rules it also flagged are off with their reasoning in the README, since oxlint's config takes no comments. lint is back in deploy.sh's checks, and the editor has lint tasks and the oxc extension recommendation. Prettier is unchanged. --- .oxlintrc.json | 21 +++ .vscode/extensions.json | 2 +- .vscode/tasks.json | 12 ++ README.md | 35 ++-- deploy.sh | 3 +- package-lock.json | 397 ++++++++++++++++++++++++++++++++++++++++ package.json | 3 + scripts/check-dnd.mjs | 11 +- 8 files changed, 464 insertions(+), 20 deletions(-) create mode 100644 .oxlintrc.json diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..64c6449 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,21 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "categories": { + "correctness": "error" + }, + "rules": { + "eslint/no-await-in-loop": "off", + "unicorn/consistent-function-scoping": "off", + "unicorn/no-array-sort": "off", + "oxc/no-map-spread": "off" + }, + "ignorePatterns": [ + ".dev", + ".tmp", + ".npm-cache", + "node_modules", + "public/fonts", + "public/prototype", + "docs" + ] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c83e263..c67ec6d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["esbenp.prettier-vscode"] + "recommendations": ["esbenp.prettier-vscode", "oxc.oxc-vscode"] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7e0a9e2..b75e097 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,6 +7,18 @@ "script": "dev:env", "problemMatcher": [] }, + { + "label": "lint", + "type": "npm", + "script": "lint", + "problemMatcher": [] + }, + { + "label": "lint:fix", + "type": "npm", + "script": "lint:fix", + "problemMatcher": [] + }, { "label": "format", "type": "npm", diff --git a/README.md b/README.md index 7ef3e0c..eec990b 100644 --- a/README.md +++ b/README.md @@ -82,26 +82,35 @@ npm run verify # integrity of .dev/data npm run check-site -- http://127.0.0.1:3000 # checks a running site from outside ``` -### Formatting +### Linting and formatting ```sh +npm run lint # oxlint +npm run lint:fix npm run format # prettier --write npm run format:check # what deploy.sh runs ``` -Prettier owns formatting (`.prettierrc.json`); `docs/` is excluded because those -are hand-wrapped notes rather than code. `embeddedLanguageFormatting` is off on -purpose: the renderers build HTML inside `html` tagged template literals, and -letting Prettier reflow those would change the markup customers receive. +**oxlint** lints the TypeScript and the JavaScript together. This project's +`typescript@7` is the native port with no JavaScript compiler API, which every +ESLint TypeScript parser needs — `typescript-eslint` requires a +`>=4.8.4 <6.1.0` peer and refuses to install — and oxlint parses TypeScript +without one, so it is the linter that works here. It runs the correctness +category and takes about 20 ms over the whole repo. `.oxlintrc.json` turns four +opinionated rules off, and oxlint's config takes no comments, so the reasoning +lives here: `no-await-in-loop` because the loops that await are sequential on +purpose (one photo at a time, memory bounded); `unicorn/consistent-function-scoping` +because the helpers close over their route's dependencies; `unicorn/no-array-sort` +because `toSorted()` is worth adopting but that is a refactor rather than a bug; +and `oxc/no-map-spread` because clarity beats the micro-optimisation. Delete the +line to see what a rule would say. `npm run typecheck` remains the +deeper TypeScript gate (strict, with `noUnusedLocals`, `noUncheckedIndexedAccess`, +`exactOptionalPropertyTypes` and `erasableSyntaxOnly`). -**ESLint is deliberately absent.** This project's `typescript@7` is the native -port and ships no JavaScript compiler API, which every ESLint TypeScript parser -needs — `typescript-eslint` requires `typescript >=4.8.4 <6.1.0` and refuses to -install, and forcing it through leaves a parser that cannot read the compiler. -Linting only the JavaScript was not worth the config, so the TypeScript gate is -`npm run typecheck` (strict, with `noUnusedLocals`, `noUncheckedIndexedAccess`, -`exactOptionalPropertyTypes` and `erasableSyntaxOnly`) and ESLint can come back, -with `typescript-eslint`, once it supports TypeScript 7. +**Prettier** owns formatting (`.prettierrc.json`); `docs/` is excluded because +those are hand-wrapped notes rather than code. `embeddedLanguageFormatting` is +off on purpose: the renderers build HTML inside `html` tagged template literals, +and letting Prettier reflow those would change the markup customers receive. Dragging Pages is the one interaction that only a browser can check, so there is a harness for it. It needs `geckodriver` on PATH and a running dev server: diff --git a/deploy.sh b/deploy.sh index 5018615..71651e6 100755 --- a/deploy.sh +++ b/deploy.sh @@ -25,7 +25,8 @@ if ! command -v podman >/dev/null 2>&1; then fi if [[ -z "${SKIP_CHECKS:-}" ]]; then - echo "==> format, types and tests" + echo "==> lint, format, types and tests" + npm run lint npm run format:check npm run typecheck npm test diff --git a/package-lock.json b/package-lock.json index e0cd7bc..48b3ce8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ }, "devDependencies": { "@types/node": "^26.6.2", + "oxlint": "^1.85.0", "prettier": "^3.9.8", "typescript": "^7.0.2" }, @@ -851,6 +852,353 @@ "node": ">=8" } }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.85.0.tgz", + "integrity": "sha512-q2KO/Zso9UT+OMn0NF9ywn4E4t0MI3yxiDhNyhsQ7DyQJrC4FhFE4TXOi4bktFnOWXTMds8qZSbpv2XwRaNOBg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.85.0.tgz", + "integrity": "sha512-SxLN3ALjoT9NNdvpjEevGeHvfzTAFrF0NBYB5tzK7/GtCKMze3j1e/m/X2ozqGj2U9hfGG/dg/OG8vpVK4PiDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.85.0.tgz", + "integrity": "sha512-Y/Sup/J4f0f9UGsSd/xyCNTeWL+gepO63GBdEDAfue9nBsnk9zMmnIXx1O6b1V8C90vB5nucYNZ0pbMXAp8zJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.85.0.tgz", + "integrity": "sha512-ApOSNC04ynpDTwvBD+//0wyfODRSbEzvRoKpX8teffmc27z8AockwSNeMXGJXn5KP85eahDgR/2llICWLkzcnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.85.0.tgz", + "integrity": "sha512-bNrVrCOA/kHky3Tu79IXWXe5bhIgLXfUuUEDHlAGOHUk96MkvDZ1ecaQF19rwstrnaqfP1o9nBTqzIr9+ZHkUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.85.0.tgz", + "integrity": "sha512-NUrzOJ1s/EqsVvfn2L/1D8Wro2LPIZUbihL8kOJLh5fEdGEN3rdOGUYq3HwnUIL8sjpoP+4N6RaGrgmMJnaMPw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.85.0.tgz", + "integrity": "sha512-UJXrAT3E/RWkEqXLIs2ehETja1qfgkPb+5gwLIIS+o/6cf+grHvoOXTa5997a/YNQfcJS0DRBTOfZt95cvOI1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.85.0.tgz", + "integrity": "sha512-lK40QLjI0HxigO7CjDDshEtfYIeiYS0020v5BHFPqN4uuQBQxd2K9LNom2dW15o9F1937quSCRVp4ZsVhdbYdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.85.0.tgz", + "integrity": "sha512-c2zbdBwGKreHXwRx3gWBuFGJxLhxgsg6YlZ+3H+RgRusU/UEV9jNwJ3HGYK+nRo0LvBa7mt6Kj86xoVotUo8cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.85.0.tgz", + "integrity": "sha512-tlt/Hy8lZ97/lCPmCgw/B3k/mwh+BzaIPbPkldZEly7TwLmx0xe2CQcaW2g/rR0dOgS9JNGCZsMEqLhUNMGvaw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.85.0.tgz", + "integrity": "sha512-3tNR9Xey82X0zKuY1d8hJ6Rc9gwRDurmqGLnQZa5xqOXy8/YyiqFXjAtugkKLY82obOlpK1eSiDRlgcNPuxtIg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.85.0.tgz", + "integrity": "sha512-wbGRd5PqCcjkJFHhZuZ2OBSUQY9czlQsoA/cQQB9JK/L9mC5MQgGoKAh+xd8QjA5V+0D3j+Qd1lAWn1I8zlelA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.85.0.tgz", + "integrity": "sha512-3Sn0kSrE4DPZCWV/8o+n4x3aFZxI9ulMnkYlwCbJ8eUVkwRK2IerohE/A/z3SNbCwoPFOCJmGE5Avrq0rrvdvQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.85.0.tgz", + "integrity": "sha512-JY2pxxYfB62bAGfejljVCqc44etItehPuAyaeSAdMuEMtwNA00ggMnS66lC1oIhos6oOXUkuU6mZ9bpFh3BqWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.85.0.tgz", + "integrity": "sha512-5k74vZ6qJBjBHEOlBk9B/iv68Yu0F1Afw/vvT2ar6OGCqEeXLaSjXz2n/IPCbhLG22UoKoYEJTzpYraRdcp6PA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.85.0.tgz", + "integrity": "sha512-GbAl5qt5TCkPLXTaIISZJnugrcBhra6rodcXc9jYt620UtdsTt71NlNmJmm0frxzFpd54x/G+MkitEJA8I/BoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.85.0.tgz", + "integrity": "sha512-kjmws5MK0et2swk4ND85D7NVQyDHw162i6whtZDLUA/lo6FQyBZDcmMRCMcVZcNrAhIaftVb00x9ChGDOjjNJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.85.0.tgz", + "integrity": "sha512-eSsIJx9n4yxvOqYTZyPEMyEXRmE60XH7xGAU7i0Qbsn1lf6Za3CWJ9aRd82oSFKXaxhp+sA6/yMJVRIpLpna6A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.85.0.tgz", + "integrity": "sha512-pBebIPUpKKhWrhSMWhy8TdAZBewiXnfxmaAGxhzxM1068GagqFaTwgKlU6e+UyJ2sPR+VoHouhXuGJkQjsrDvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@pinojs/redact": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", @@ -1685,6 +2033,55 @@ "node": ">=14.0.0" } }, + "node_modules/oxlint": { + "version": "1.85.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.85.0.tgz", + "integrity": "sha512-bc26s97nuvPj1ViyPsqmKecVkUWFMEdtayO8MaQ6oiLfs1pj94cQlZZhrh4BPNlr9HQosjhIlwgZKsfcwmcNgg==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.85.0", + "@oxlint/binding-android-arm64": "1.85.0", + "@oxlint/binding-darwin-arm64": "1.85.0", + "@oxlint/binding-darwin-x64": "1.85.0", + "@oxlint/binding-freebsd-x64": "1.85.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.85.0", + "@oxlint/binding-linux-arm-musleabihf": "1.85.0", + "@oxlint/binding-linux-arm64-gnu": "1.85.0", + "@oxlint/binding-linux-arm64-musl": "1.85.0", + "@oxlint/binding-linux-ppc64-gnu": "1.85.0", + "@oxlint/binding-linux-riscv64-gnu": "1.85.0", + "@oxlint/binding-linux-riscv64-musl": "1.85.0", + "@oxlint/binding-linux-s390x-gnu": "1.85.0", + "@oxlint/binding-linux-x64-gnu": "1.85.0", + "@oxlint/binding-linux-x64-musl": "1.85.0", + "@oxlint/binding-openharmony-arm64": "1.85.0", + "@oxlint/binding-win32-arm64-msvc": "1.85.0", + "@oxlint/binding-win32-ia32-msvc": "1.85.0", + "@oxlint/binding-win32-x64-msvc": "1.85.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=7.0.2001", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, "node_modules/path-scurry": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", diff --git a/package.json b/package.json index ed498ad..fca042a 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "verify": "node src/cli/verify.ts", "repair": "node src/cli/repair.ts", "typecheck": "tsc --noEmit", + "lint": "oxlint", + "lint:fix": "oxlint --fix", "format": "prettier --write .", "format:check": "prettier --check .", "test": "node --test test/*.test.ts", @@ -31,6 +33,7 @@ }, "devDependencies": { "@types/node": "^26.6.2", + "oxlint": "^1.85.0", "prettier": "^3.9.8", "typescript": "^7.0.2" } diff --git a/scripts/check-dnd.mjs b/scripts/check-dnd.mjs index 06e9692..0d4c149 100644 --- a/scripts/check-dnd.mjs +++ b/scripts/check-dnd.mjs @@ -24,11 +24,12 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const KEY = 'element-6066-11e4-a52e-4f735466cecf'; async function cmd(method, path, body) { - const response = await fetch(GECKO + path, { - method, - headers: body ? { 'content-type': 'application/json' } : {}, - body: body ? JSON.stringify(body) : undefined, - }); + const init = { method }; + if (body) { + init.headers = { 'content-type': 'application/json' }; + init.body = JSON.stringify(body); + } + const response = await fetch(GECKO + path, init); const json = JSON.parse(await response.text()); if (json.value && json.value.error) throw new Error(`${json.value.error}: ${json.value.message}`); return json.value;