From c7cc7f3188c022e909d188619e14afcb59d01fbc Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 18:23:41 +0200 Subject: [PATCH 01/34] Add oxlint plugin for js-toolkit best practices Co-Authored-By: Claude Sonnet 4.6 --- .oxlintrc.json | 14 + package-lock.json | 865 ++++++++++-------- package.json | 4 +- packages/oxlint-plugin-js-toolkit/README.md | 99 ++ packages/oxlint-plugin-js-toolkit/index.ts | 64 ++ .../oxlint-plugin-js-toolkit/package.json | 37 + .../rules/async-lifecycle-methods.test.ts | 40 + .../rules/async-lifecycle-methods.ts | 35 + .../oxlint-plugin-js-toolkit/rules/index.ts | 13 + .../rules/no-create-app.test.ts | 21 + .../rules/no-create-app.ts | 24 + .../rules/no-deprecated-properties.test.ts | 42 + .../rules/no-deprecated-properties.ts | 49 + .../rules/no-dispatch-event.test.ts | 34 + .../rules/no-dispatch-event.ts | 40 + .../rules/no-event-listener-methods.test.ts | 42 + .../rules/no-event-listener-methods.ts | 38 + .../rules/no-shadow-dom.test.ts | 29 + .../rules/no-shadow-dom.ts | 36 + .../rules/on-global-handler-prefix.test.ts | 42 + .../rules/on-global-handler-prefix.ts | 49 + .../rules/on-handler-naming.test.ts | 38 + .../rules/on-handler-naming.ts | 40 + .../rules/options-camel-case.test.ts | 32 + .../rules/options-camel-case.ts | 57 ++ .../rules/refs-camel-case.test.ts | 37 + .../rules/refs-camel-case.ts | 59 ++ .../rules/refs-plural-multiple.test.ts | 38 + .../rules/refs-plural-multiple.ts | 60 ++ .../require-config-name-pascal-case.test.ts | 36 + .../rules/require-config-name-pascal-case.ts | 55 ++ .../rules/require-config.test.ts | 42 + .../rules/require-config.ts | 55 ++ .../oxlint-plugin-js-toolkit/scripts/build.js | 26 + .../oxlint-plugin-js-toolkit/tsconfig.json | 12 + .../oxlint-plugin-js-toolkit/utils/ast.ts | 101 ++ .../utils/rule-tester.ts | 10 + .../oxlint-plugin-js-toolkit/vitest.config.ts | 8 + 38 files changed, 1919 insertions(+), 404 deletions(-) create mode 100644 packages/oxlint-plugin-js-toolkit/README.md create mode 100644 packages/oxlint-plugin-js-toolkit/index.ts create mode 100644 packages/oxlint-plugin-js-toolkit/package.json create mode 100644 packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/index.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/no-create-app.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/require-config.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/rules/require-config.ts create mode 100644 packages/oxlint-plugin-js-toolkit/scripts/build.js create mode 100644 packages/oxlint-plugin-js-toolkit/tsconfig.json create mode 100644 packages/oxlint-plugin-js-toolkit/utils/ast.ts create mode 100644 packages/oxlint-plugin-js-toolkit/utils/rule-tester.ts create mode 100644 packages/oxlint-plugin-js-toolkit/vitest.config.ts diff --git a/.oxlintrc.json b/.oxlintrc.json index ea2bec58e..bf537f094 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,5 +1,19 @@ { + "jsPlugins": [{ "name": "js-toolkit", "specifier": "./packages/oxlint-plugin-js-toolkit" }], "rules": { + "js-toolkit/require-config": "error", + "js-toolkit/require-config-name-pascal-case": "error", + "js-toolkit/refs-camel-case": "error", + "js-toolkit/refs-plural-multiple": "error", + "js-toolkit/options-camel-case": "error", + "js-toolkit/async-lifecycle-methods": "error", + "js-toolkit/on-handler-naming": "error", + "js-toolkit/on-global-handler-prefix": "warn", + "js-toolkit/no-deprecated-properties": "error", + "js-toolkit/no-dispatch-event": "warn", + "js-toolkit/no-shadow-dom": "error", + "js-toolkit/no-create-app": "warn", + "js-toolkit/no-event-listener-methods": "error", "typescript/no-this-alias": [ "error", { diff --git a/package-lock.json b/package-lock.json index f36b43d52..632ef988a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@types/node": "22.19.0", "@typescript/native-preview": "7.0.0-dev.20260326.1", "focus-trap": "^7.8.0", - "oxlint": "1.57.0", + "oxlint": "1.63.0", "prettier": "3.8.1", "vite": "8.0.3" } @@ -1457,7 +1457,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1469,7 +1468,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1480,7 +1478,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2681,7 +2678,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2735,16 +2731,15 @@ "version": "0.122.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" } }, "node_modules/@oxlint/binding-android-arm-eabi": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.57.0.tgz", - "integrity": "sha512-C7EiyfAJG4B70496eV543nKiq5cH0o/xIh/ufbjQz3SIvHhlDDsyn+mRFh+aW8KskTyUpyH2LGWL8p2oN6bl1A==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.63.0.tgz", + "integrity": "sha512-A9xLtQt7i0OA1PoB/meog6kikXI9CdwEp7ZwQqmgnpKn3G3b1orvTDy8CQ6T7w1HvDrgWGB78PkFKcWgibcTCg==", "cpu": [ "arm" ], @@ -2759,9 +2754,9 @@ } }, "node_modules/@oxlint/binding-android-arm64": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.57.0.tgz", - "integrity": "sha512-9i80AresjZ/FZf5xK8tKFbhQnijD4s1eOZw6/FHUwD59HEZbVLRc2C88ADYJfLZrF5XofWDiRX/Ja9KefCLy7w==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.63.0.tgz", + "integrity": "sha512-SQo+ZMvdR9l3CxZp5W5gFNxSiDxclY6lOzzNpKYLF8asESpm3Pwumx0gER5T7aHLF1/2BAAtLD3DiDkdgy4V1A==", "cpu": [ "arm64" ], @@ -2776,9 +2771,9 @@ } }, "node_modules/@oxlint/binding-darwin-arm64": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.57.0.tgz", - "integrity": "sha512-0eUfhRz5L2yKa9I8k3qpyl37XK3oBS5BvrgdVIx599WZK63P8sMbg+0s4IuxmIiZuBK68Ek+Z+gcKgeYf0otsg==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.63.0.tgz", + "integrity": "sha512-6W82XjJDTmMnjg30427l0dufpnyLoq7wEukKdM6/g2VIybRVuQiBVh43EA4b+UxZ3+tLcKm+Or/pXGNgLCEU8g==", "cpu": [ "arm64" ], @@ -2793,9 +2788,9 @@ } }, "node_modules/@oxlint/binding-darwin-x64": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.57.0.tgz", - "integrity": "sha512-UvrSuzBaYOue+QMAcuDITe0k/Vhj6KZGjfnI6x+NkxBTke/VoM7ZisaxgNY0LWuBkTnd1OmeQfEQdQ48fRjkQg==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.63.0.tgz", + "integrity": "sha512-CnWd/YCuVG5W1BYkjJEVbJG11o526O9qAwBEQM+nh8K19CRFUkFdROXCyYkGmroHEYQe4vgQ6+lh3550Lp35Xw==", "cpu": [ "x64" ], @@ -2810,9 +2805,9 @@ } }, "node_modules/@oxlint/binding-freebsd-x64": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.57.0.tgz", - "integrity": "sha512-wtQq0dCoiw4bUwlsNVDJJ3pxJA218fOezpgtLKrbQqUtQJcM9yP8z+I9fu14aHg0uyAxIY+99toL6uBa2r7nxA==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.63.0.tgz", + "integrity": "sha512-a4eZAqrmtajqcxfdAzC+l7g3PaE3V8hpAYqqeD3fTxLXOMFdK3eNTZrU80n4dDEVm0JXy1aL5PqvqWldBl6zYA==", "cpu": [ "x64" ], @@ -2827,9 +2822,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-gnueabihf": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.57.0.tgz", - "integrity": "sha512-qxFWl2BBBFcT4djKa+OtMdnLgoHEJXpqjyGwz8OhW35ImoCwR5qtAGqApNYce5260FQqoAHW8S8eZTjiX67Tsg==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.63.0.tgz", + "integrity": "sha512-tYUtU9TdbU3uXF5D62g5zXJ13iniFGhXQx5vp9cyEjGdbSAY3VdFBSaldYvyoDmgMZ0ZYuwQP1Y4t2Fhejwa0w==", "cpu": [ "arm" ], @@ -2844,9 +2839,9 @@ } }, "node_modules/@oxlint/binding-linux-arm-musleabihf": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.57.0.tgz", - "integrity": "sha512-SQoIsBU7J0bDW15/f0/RvxHfY3Y0+eB/caKBQtNFbuerTiA6JCYx9P1MrrFTwY2dTm/lMgTSgskvCEYk2AtG/Q==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.63.0.tgz", + "integrity": "sha512-I5r3twFf776UZg9dmRo2xbrKt00tTkORXEVe0ctg4vdTkQvJAjiCHxnbAU2HL1AiJ9cqADA76MAliuilsAWnvg==", "cpu": [ "arm" ], @@ -2861,13 +2856,16 @@ } }, "node_modules/@oxlint/binding-linux-arm64-gnu": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.57.0.tgz", - "integrity": "sha512-jqxYd1W6WMeozsCmqe9Rzbu3SRrGTyGDAipRlRggetyYbUksJqJKvUNTQtZR/KFoJPb+grnSm5SHhdWrywv3RQ==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.63.0.tgz", + "integrity": "sha512-t7ltUkg6FFh4b564QyGir8xIj/QZbXu8FlcRkcyW9+ztr/mfRHlvUOFd95pJCXi9s/L5DrUeWWgpXRS+V+6igQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2878,13 +2876,16 @@ } }, "node_modules/@oxlint/binding-linux-arm64-musl": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.57.0.tgz", - "integrity": "sha512-i66WyEPVEvq9bxRUCJ/MP5EBfnTDN3nhwEdFZFTO5MmLLvzngfWEG3NSdXQzTT3vk5B9i6C2XSIYBh+aG6uqyg==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.63.0.tgz", + "integrity": "sha512-Q5mmZy/XWjuYFUuQyYjOvZ5U/JkKEwnpir6hGxhh6HcdP0V/BKxLo8dqkfF/t7r7AguB17dfS/8+go5AQDRR6g==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2895,13 +2896,16 @@ } }, "node_modules/@oxlint/binding-linux-ppc64-gnu": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.57.0.tgz", - "integrity": "sha512-oMZDCwz4NobclZU3pH+V1/upVlJZiZvne4jQP+zhJwt+lmio4XXr4qG47CehvrW1Lx2YZiIHuxM2D4YpkG3KVA==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.63.0.tgz", + "integrity": "sha512-uBGtuZ0TzLB4x5wVa82HGNvYqY8buwDhyCnCP0R0gkk9szqVsP0MeTtD5HX7EsEuFIt+aYmYxuxeVxs3nTSwtQ==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2912,13 +2916,16 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-gnu": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.57.0.tgz", - "integrity": "sha512-uoBnjJ3MMEBbfnWC1jSFr7/nSCkcQYa72NYoNtLl1imshDnWSolYCjzb8LVCwYCCfLJXD+0gBLD7fyC14c0+0g==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.63.0.tgz", + "integrity": "sha512-h4s6FwxE+9MeA181o0dnDwHP32Y/bG8EiB/vrD6Ib+AMt6haigDc/0bUtI/sLmQDBMJnUfaCmtSSrEAqjtEVrA==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2929,13 +2936,16 @@ } }, "node_modules/@oxlint/binding-linux-riscv64-musl": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.57.0.tgz", - "integrity": "sha512-BdrwD7haPZ8a9KrZhKJRSj6jwCor+Z8tHFZ3PT89Y3Jq5v3LfMfEePeAmD0LOTWpiTmzSzdmyw9ijneapiVHKQ==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.63.0.tgz", + "integrity": "sha512-2EaNcCBR8Mcjl5ARtuN3BdEpVkX7KpjSjMGZ/mJMIeaXgTtdz5ytg2VwygMSStA/k0ixfvZFoZOfjDEcouV5vQ==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2946,13 +2956,16 @@ } }, "node_modules/@oxlint/binding-linux-s390x-gnu": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.57.0.tgz", - "integrity": "sha512-BNs+7ZNsRstVg2tpNxAXfMX/Iv5oZh204dVyb8Z37+/gCh+yZqNTlg6YwCLIMPSk5wLWIGOaQjT0GUOahKYImw==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.63.0.tgz", + "integrity": "sha512-p4hlf/fd7TrYYl3QrWWD0GocqJefwMu3cHQhmi2FvEB/YOvFb5DZN3SMBaPi7B1TM5DeypkEtrVib674q1KKPg==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2963,13 +2976,16 @@ } }, "node_modules/@oxlint/binding-linux-x64-gnu": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.57.0.tgz", - "integrity": "sha512-AghS18w+XcENcAX0+BQGLiqjpqpaxKJa4cWWP0OWNLacs27vHBxu7TYkv9LUSGe5w8lOJHeMxcYfZNOAPqw2bg==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.63.0.tgz", + "integrity": "sha512-Vgq9rkRVcPcjbcH+ihYTfpeR7vCXfqpd+z5ItTGc0yYUV59L5ceHYN1iV4H9bKGV7Rn5hkVc7x3mSvHegduENA==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2980,13 +2996,16 @@ } }, "node_modules/@oxlint/binding-linux-x64-musl": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.57.0.tgz", - "integrity": "sha512-E/FV3GB8phu/Rpkhz5T96hAiJlGzn91qX5yj5gU754P5cmVGXY1Jw/VSjDSlZBCY3VHjsVLdzgdkJaomEmcNOg==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.63.0.tgz", + "integrity": "sha512-3/Lkq/ncooA61rorrC+ZQed1Bc4VpGj+WnGsp58zmxKgvZ2vhreu+dcVyr3mX8NUpq7mfZ4gDDTou/yrF1Pd7A==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2997,9 +3016,9 @@ } }, "node_modules/@oxlint/binding-openharmony-arm64": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.57.0.tgz", - "integrity": "sha512-xvZ2yZt0nUVfU14iuGv3V25jpr9pov5N0Wr28RXnHFxHCRxNDMtYPHV61gGLhN9IlXM96gI4pyYpLSJC5ClLCQ==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.63.0.tgz", + "integrity": "sha512-0/EdD/6hDkx5Mfd769PTjvEM8mZ/6Dfukp1dBCL/2PjlIVGEtYdNZyok6ChqYPsT9JcFnlQnUeQzO0/1L/oC9w==", "cpu": [ "arm64" ], @@ -3014,9 +3033,9 @@ } }, "node_modules/@oxlint/binding-win32-arm64-msvc": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.57.0.tgz", - "integrity": "sha512-Z4D8Pd0AyHBKeazhdIXeUUy5sIS3Mo0veOlzlDECg6PhRRKgEsBJCCV1n+keUZtQ04OP+i7+itS3kOykUyNhDg==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.63.0.tgz", + "integrity": "sha512-wb0CUkN8ngwPiRQBjD1Cj0LsHeNvm+Xt6YBHDMtj2DVQVD6Oj8Ri7g6BD+KICf6LaBqZlmzOvy6nF9E/8yyGOg==", "cpu": [ "arm64" ], @@ -3031,9 +3050,9 @@ } }, "node_modules/@oxlint/binding-win32-ia32-msvc": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.57.0.tgz", - "integrity": "sha512-StOZ9nFMVKvevicbQfql6Pouu9pgbeQnu60Fvhz2S6yfMaii+wnueLnqQ5I1JPgNF0Syew4voBlAaHD13wH6tw==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.63.0.tgz", + "integrity": "sha512-BX5iq+ovdNlVYhSn5qPMUIT0uwAwt2lmEnCnzK+Gkhw4DovIvhGb96OFhV8yzQNUnQxn/xGkOR+X+BLrLDNm8w==", "cpu": [ "ia32" ], @@ -3048,9 +3067,9 @@ } }, "node_modules/@oxlint/binding-win32-x64-msvc": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.57.0.tgz", - "integrity": "sha512-6PuxhYgth8TuW0+ABPOIkGdBYw+qYGxgIdXPHSVpiCDm+hqTTWCmC739St1Xni0DJBt8HnSHTG67i1y6gr8qrA==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.63.0.tgz", + "integrity": "sha512-QeN/WELOfsXMeYwxvfgQrl6CbVftYUCZsGXHjXQd5Trccm8+i4gmtxaOui4xbJQaiDlviF8F3yLSBloQUeFsfA==", "cpu": [ "x64" ], @@ -3410,7 +3429,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3427,7 +3445,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3444,7 +3461,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3461,7 +3477,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3478,7 +3493,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3495,7 +3509,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3512,7 +3525,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3529,7 +3541,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3546,7 +3557,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3563,7 +3573,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3580,7 +3589,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3597,7 +3605,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3614,7 +3621,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -3631,7 +3637,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3648,7 +3653,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3672,6 +3676,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3685,6 +3690,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3698,6 +3704,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3711,6 +3718,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3724,6 +3732,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3737,6 +3746,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3750,6 +3760,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3763,6 +3774,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3776,6 +3788,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3789,6 +3802,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3802,6 +3816,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3815,6 +3830,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3828,6 +3844,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3841,6 +3858,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3854,6 +3872,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3867,6 +3886,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3880,6 +3900,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3893,6 +3914,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3906,6 +3928,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3919,6 +3942,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3932,6 +3956,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3945,6 +3970,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3958,6 +3984,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3971,6 +3998,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3984,6 +4012,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4848,6 +4877,10 @@ "resolved": "packages/tests", "link": true }, + "node_modules/@studiometa/oxlint-plugin-js-toolkit": { + "resolved": "packages/oxlint-plugin-js-toolkit", + "link": true + }, "node_modules/@studiometa/prettier-config": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@studiometa/prettier-config/-/prettier-config-4.4.0.tgz", @@ -5382,7 +5415,6 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -5863,6 +5895,154 @@ "vue": "^3.2.25" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.2.tgz", + "integrity": "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==", + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.2", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.2", + "vitest": "4.1.2" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-v8/node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", + "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.2", + "@vitest/utils": "4.1.2", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", + "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.2", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", + "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", + "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.2", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", + "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.2", + "@vitest/utils": "4.1.2", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", + "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", + "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.2", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, "node_modules/@volar/language-core": { "version": "2.4.28", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", @@ -6749,6 +6929,17 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -8530,7 +8721,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -12477,7 +12667,6 @@ "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "devOptional": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -13011,6 +13200,17 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -14392,9 +14592,9 @@ } }, "node_modules/oxlint": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.57.0.tgz", - "integrity": "sha512-DGFsuBX5MFZX9yiDdtKjTrYPq45CZ8Fft6qCltJITYZxfwYjVdGf/6wycGYTACloauwIPxUnYhBVeZbHvleGhw==", + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.63.0.tgz", + "integrity": "sha512-9TGXetdjgIHOJ9OiReomP7nnrMkV9HxC1xM2ramJSLQpzxjsAJtQwa4wqkJN2f/uCrqZuJseFuSlWDdvcruveg==", "dev": true, "license": "MIT", "bin": { @@ -14407,28 +14607,28 @@ "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxlint/binding-android-arm-eabi": "1.57.0", - "@oxlint/binding-android-arm64": "1.57.0", - "@oxlint/binding-darwin-arm64": "1.57.0", - "@oxlint/binding-darwin-x64": "1.57.0", - "@oxlint/binding-freebsd-x64": "1.57.0", - "@oxlint/binding-linux-arm-gnueabihf": "1.57.0", - "@oxlint/binding-linux-arm-musleabihf": "1.57.0", - "@oxlint/binding-linux-arm64-gnu": "1.57.0", - "@oxlint/binding-linux-arm64-musl": "1.57.0", - "@oxlint/binding-linux-ppc64-gnu": "1.57.0", - "@oxlint/binding-linux-riscv64-gnu": "1.57.0", - "@oxlint/binding-linux-riscv64-musl": "1.57.0", - "@oxlint/binding-linux-s390x-gnu": "1.57.0", - "@oxlint/binding-linux-x64-gnu": "1.57.0", - "@oxlint/binding-linux-x64-musl": "1.57.0", - "@oxlint/binding-openharmony-arm64": "1.57.0", - "@oxlint/binding-win32-arm64-msvc": "1.57.0", - "@oxlint/binding-win32-ia32-msvc": "1.57.0", - "@oxlint/binding-win32-x64-msvc": "1.57.0" + "@oxlint/binding-android-arm-eabi": "1.63.0", + "@oxlint/binding-android-arm64": "1.63.0", + "@oxlint/binding-darwin-arm64": "1.63.0", + "@oxlint/binding-darwin-x64": "1.63.0", + "@oxlint/binding-freebsd-x64": "1.63.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.63.0", + "@oxlint/binding-linux-arm-musleabihf": "1.63.0", + "@oxlint/binding-linux-arm64-gnu": "1.63.0", + "@oxlint/binding-linux-arm64-musl": "1.63.0", + "@oxlint/binding-linux-ppc64-gnu": "1.63.0", + "@oxlint/binding-linux-riscv64-gnu": "1.63.0", + "@oxlint/binding-linux-riscv64-musl": "1.63.0", + "@oxlint/binding-linux-s390x-gnu": "1.63.0", + "@oxlint/binding-linux-x64-gnu": "1.63.0", + "@oxlint/binding-linux-x64-musl": "1.63.0", + "@oxlint/binding-openharmony-arm64": "1.63.0", + "@oxlint/binding-win32-arm64-msvc": "1.63.0", + "@oxlint/binding-win32-ia32-msvc": "1.63.0", + "@oxlint/binding-win32-x64-msvc": "1.63.0" }, "peerDependencies": { - "oxlint-tsgolint": ">=0.15.0" + "oxlint-tsgolint": ">=0.22.1" }, "peerDependenciesMeta": { "oxlint-tsgolint": { @@ -18036,7 +18236,6 @@ "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", - "dev": true, "license": "MIT", "dependencies": { "@oxc-project/types": "=0.122.0", @@ -18070,13 +18269,13 @@ "version": "1.0.0-rc.12", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", - "dev": true, "license": "MIT" }, "node_modules/rollup": { "version": "4.60.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -20609,6 +20808,15 @@ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "license": "MIT" }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -20824,7 +21032,6 @@ "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -21228,7 +21435,6 @@ "version": "8.0.3", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", - "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", @@ -21306,7 +21512,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -21414,35 +21619,140 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/vue": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz", - "integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==", - "dev": true, + "node_modules/vitest": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", + "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.31", - "@vue/compiler-sfc": "3.5.31", - "@vue/runtime-dom": "3.5.31", - "@vue/server-renderer": "3.5.31", - "@vue/shared": "3.5.31" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-eslint-parser": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.0.tgz", - "integrity": "sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==", - "dev": true, - "license": "MIT", - "peer": true, + "@vitest/expect": "4.1.2", + "@vitest/mocker": "4.1.2", + "@vitest/pretty-format": "4.1.2", + "@vitest/runner": "4.1.2", + "@vitest/snapshot": "4.1.2", + "@vitest/spy": "4.1.2", + "@vitest/utils": "4.1.2", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.2", + "@vitest/browser-preview": "4.1.2", + "@vitest/browser-webdriverio": "4.1.2", + "@vitest/ui": "4.1.2", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "license": "MIT" + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest/node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz", + "integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.31", + "@vue/compiler-sfc": "3.5.31", + "@vue/runtime-dom": "3.5.31", + "@vue/server-renderer": "3.5.31", + "@vue/shared": "3.5.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.0.tgz", + "integrity": "sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0 || ^9.0.0", @@ -22522,6 +22832,17 @@ "deepmerge": "^4.3.1" } }, + "packages/oxlint-plugin-js-toolkit": { + "name": "@studiometa/oxlint-plugin-js-toolkit", + "version": "3.6.0-beta.0", + "license": "MIT", + "devDependencies": { + "@vitest/coverage-v8": "4.1.2", + "esbuild": "0.27.0", + "typescript": "6.0.2", + "vitest": "4.1.2" + } + }, "packages/tests": { "name": "@studiometa/js-toolkit-tests", "version": "3.6.0-beta.0", @@ -22550,170 +22871,13 @@ "vitest": "^3.2 || ^4" } }, - "packages/tests/node_modules/@vitest/coverage-v8": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.2.tgz", - "integrity": "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==", - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.2", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.1.2", - "vitest": "4.1.2" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "packages/tests/node_modules/@vitest/expect": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", - "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "packages/tests/node_modules/@vitest/mocker": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", - "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.2", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "packages/tests/node_modules/@vitest/pretty-format": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", - "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "packages/tests/node_modules/@vitest/runner": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", - "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.2", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "packages/tests/node_modules/@vitest/snapshot": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", - "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.2", - "@vitest/utils": "4.1.2", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "packages/tests/node_modules/@vitest/spy": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", - "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "packages/tests/node_modules/@vitest/utils": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", - "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.2", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "packages/tests/node_modules/ast-v8-to-istanbul": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", - "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "packages/tests/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "packages/tests/node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "license": "MIT" - }, "packages/tests/node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12.0.0" }, @@ -22726,22 +22890,13 @@ } } }, - "packages/tests/node_modules/magicast": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", - "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" - } - }, "packages/tests/node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -22749,26 +22904,13 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "packages/tests/node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", - "license": "MIT" - }, - "packages/tests/node_modules/tinyexec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "packages/tests/node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -22837,87 +22979,6 @@ "optional": true } } - }, - "packages/tests/node_modules/vitest": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", - "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.2", - "@vitest/mocker": "4.1.2", - "@vitest/pretty-format": "4.1.2", - "@vitest/runner": "4.1.2", - "@vitest/snapshot": "4.1.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.2", - "@vitest/browser-preview": "4.1.2", - "@vitest/browser-webdriverio": "4.1.2", - "@vitest/ui": "4.1.2", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } } } } diff --git a/package.json b/package.json index afcfbafbb..8e55963e0 100644 --- a/package.json +++ b/package.json @@ -34,11 +34,11 @@ "postversion": "npm version --workspaces $npm_package_version" }, "devDependencies": { - "focus-trap": "^7.8.0", "@studiometa/prettier-config": "4.4.0", "@types/node": "22.19.0", "@typescript/native-preview": "7.0.0-dev.20260326.1", - "oxlint": "1.57.0", + "focus-trap": "^7.8.0", + "oxlint": "1.63.0", "prettier": "3.8.1", "vite": "8.0.3" }, diff --git a/packages/oxlint-plugin-js-toolkit/README.md b/packages/oxlint-plugin-js-toolkit/README.md new file mode 100644 index 000000000..ec2ba1869 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/README.md @@ -0,0 +1,99 @@ +# @studiometa/oxlint-plugin-js-toolkit + +Oxlint/ESLint plugin enforcing best practices for [@studiometa/js-toolkit](https://js-toolkit.studiometa.dev). + +## Installation + +```bash +npm install --save-dev @studiometa/oxlint-plugin-js-toolkit +``` + +## Configuration + +### Oxlint + +Add the plugin to your `.oxlintrc.json`: + +```json +{ + "jsPlugins": ["@studiometa/oxlint-plugin-js-toolkit"], + "rules": { + "js-toolkit/require-config": "error", + "js-toolkit/require-config-name-pascal-case": "error", + "js-toolkit/refs-camel-case": "error", + "js-toolkit/refs-plural-multiple": "error", + "js-toolkit/options-camel-case": "error", + "js-toolkit/async-lifecycle-methods": "error", + "js-toolkit/on-handler-naming": "error", + "js-toolkit/on-global-handler-prefix": "warn", + "js-toolkit/no-deprecated-properties": "error", + "js-toolkit/no-dispatch-event": "warn", + "js-toolkit/no-shadow-dom": "error", + "js-toolkit/no-create-app": "warn", + "js-toolkit/no-event-listener-methods": "error" + } +} +``` + +### ESLint + +Add the recommended config to your `eslint.config.js` (ESLint v9 flat config): + +```js +import jsToolkit from '@studiometa/oxlint-plugin-js-toolkit'; + +export default [ + jsToolkit.configs.recommended, + // ...your other config +]; +``` + +To customise individual rule severities, add an override entry after the recommended config: + +```js +import jsToolkit from '@studiometa/oxlint-plugin-js-toolkit'; + +export default [ + jsToolkit.configs.recommended, + { + rules: { + 'js-toolkit/no-create-app': 'error', + }, + }, +]; +``` + +## Rules + +### Class structure + +| Rule | Description | Recommended | +|------|-------------|-------------| +| `js-toolkit/require-config` | Requires a `static config` property with a `name` on every class extending `Base`. | error | +| `js-toolkit/require-config-name-pascal-case` | Requires `config.name` to be PascalCase. | error | +| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | error | +| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | error | +| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | error | + +### Lifecycle methods + +| Rule | Description | Recommended | +|------|-------------|-------------| +| `js-toolkit/async-lifecycle-methods` | Requires lifecycle methods (`mounted`, `destroyed`, `updated`, `terminated`, `ticked`, `scrolled`, `resized`, `moved`, `loaded`, `keyed`) to be declared `async`. | error | + +### Event handlers + +| Rule | Description | Recommended | +|------|-------------|-------------| +| `js-toolkit/on-handler-naming` | Requires event handler methods to follow the `onXxxYyy` camelCase convention. | error | +| `js-toolkit/on-global-handler-prefix` | Requires handlers for window-only events (e.g. `resize`) to use the `onWindow` or `onDocument` prefix. | warn | + +### Forbidden patterns + +| Rule | Description | Recommended | +|------|-------------|-------------| +| `js-toolkit/no-deprecated-properties` | Disallows removed (`$parent`, `$root`) and deprecated (`$children`) properties. Use `$closest()` and `$query()`/`$queryAll()` instead. | error | +| `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | warn | +| `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | error | +| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated since v4). Use `registerComponent()` instead. | warn | +| `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead — the framework handles binding and cleanup automatically. | error | diff --git a/packages/oxlint-plugin-js-toolkit/index.ts b/packages/oxlint-plugin-js-toolkit/index.ts new file mode 100644 index 000000000..73e86298d --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/index.ts @@ -0,0 +1,64 @@ +import { + requireConfig, + requireConfigNamePascalCase, + refsCamelCase, + optionsCamelCase, + asyncLifecycleMethods, + onHandlerNaming, + onGlobalHandlerPrefix, + noDeprecatedProperties, + noDispatchEvent, + noShadowDom, + noCreateApp, + noEventListenerMethods, + refsPluralMultiple, +} from './rules/index.js'; + +const PLUGIN_NAME = 'js-toolkit'; + +const rules = { + 'require-config': requireConfig, + 'require-config-name-pascal-case': requireConfigNamePascalCase, + 'refs-camel-case': refsCamelCase, + 'refs-plural-multiple': refsPluralMultiple, + 'options-camel-case': optionsCamelCase, + 'async-lifecycle-methods': asyncLifecycleMethods, + 'on-handler-naming': onHandlerNaming, + 'on-global-handler-prefix': onGlobalHandlerPrefix, + 'no-deprecated-properties': noDeprecatedProperties, + 'no-dispatch-event': noDispatchEvent, + 'no-shadow-dom': noShadowDom, + 'no-create-app': noCreateApp, + 'no-event-listener-methods': noEventListenerMethods, +}; + +const recommendedRules: Record = { + [`${PLUGIN_NAME}/require-config`]: 'error', + [`${PLUGIN_NAME}/require-config-name-pascal-case`]: 'error', + [`${PLUGIN_NAME}/refs-camel-case`]: 'error', + [`${PLUGIN_NAME}/refs-plural-multiple`]: 'error', + [`${PLUGIN_NAME}/options-camel-case`]: 'error', + [`${PLUGIN_NAME}/async-lifecycle-methods`]: 'error', + [`${PLUGIN_NAME}/on-handler-naming`]: 'error', + [`${PLUGIN_NAME}/on-global-handler-prefix`]: 'warn', + [`${PLUGIN_NAME}/no-deprecated-properties`]: 'error', + [`${PLUGIN_NAME}/no-dispatch-event`]: 'warn', + [`${PLUGIN_NAME}/no-shadow-dom`]: 'error', + [`${PLUGIN_NAME}/no-create-app`]: 'warn', + [`${PLUGIN_NAME}/no-event-listener-methods`]: 'error', +}; + +const plugin: { meta: object; rules: typeof rules; configs: Record } = { + meta: { + name: '@studiometa/oxlint-plugin-js-toolkit', + }, + rules, + configs: {}, +}; + +plugin.configs['recommended'] = { + plugins: { [PLUGIN_NAME]: plugin }, + rules: recommendedRules, +}; + +export default plugin; diff --git a/packages/oxlint-plugin-js-toolkit/package.json b/packages/oxlint-plugin-js-toolkit/package.json new file mode 100644 index 000000000..e5194f3a5 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/package.json @@ -0,0 +1,37 @@ +{ + "name": "@studiometa/oxlint-plugin-js-toolkit", + "version": "3.6.0-beta.0", + "description": "Oxlint/ESLint plugin for @studiometa/js-toolkit best practices", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/studiometa/js-toolkit.git" + }, + "author": "Studio Meta (https://www.studiometa.fr)", + "license": "MIT", + "type": "module", + "sideEffects": false, + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "scripts": { + "build": "node scripts/build.js", + "test": "vitest run", + "test:watch": "vitest" + }, + "devDependencies": { + "@vitest/coverage-v8": "4.1.2", + "esbuild": "0.27.0", + "typescript": "6.0.2", + "vitest": "4.1.2" + } +} diff --git a/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts b/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts new file mode 100644 index 000000000..7ad04e425 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts @@ -0,0 +1,40 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { asyncLifecycleMethods } from './async-lifecycle-methods.js'; + +describe('async-lifecycle-methods', () => { + it('passes and fails correctly', () => { + tester.run('async-lifecycle-methods', asyncLifecycleMethods as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider' }; + async mounted() {} + async destroyed() {} + async ticked() {} + }`, + // Non-Base class — ignored + `class Slider extends Other { + mounted() {} + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + mounted() {} + }`, + errors: [{ messageId: 'notAsync' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + destroyed() {} + scrolled() {} + }`, + errors: [{ messageId: 'notAsync' }, { messageId: 'notAsync' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts b/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts new file mode 100644 index 000000000..865adde7b --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts @@ -0,0 +1,35 @@ +import { isBaseSubclass, LIFECYCLE_METHODS, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.js'; + +export const asyncLifecycleMethods = { + meta: { + type: 'problem', + docs: { + description: 'Require lifecycle methods on Base subclasses to be async', + }, + messages: { + notAsync: 'Lifecycle method "{{name}}" must be declared as async.', + }, + }, + create(context: RuleContext) { + return { + MethodDefinition(node: Node) { + const name = node.key?.name; + if (!name || !LIFECYCLE_METHODS.has(name)) return; + if (node.value?.async) return; + + const ancestors = context.getAncestors + ? context.getAncestors() + : context.sourceCode?.getAncestors?.(node) ?? []; + + const enclosingClass = findEnclosingClass(ancestors); + if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; + + context.report({ + node, + messageId: 'notAsync', + data: { name }, + }); + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/rules/index.ts b/packages/oxlint-plugin-js-toolkit/rules/index.ts new file mode 100644 index 000000000..d7a511b4d --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/index.ts @@ -0,0 +1,13 @@ +export { requireConfig } from './require-config.js'; +export { requireConfigNamePascalCase } from './require-config-name-pascal-case.js'; +export { refsCamelCase } from './refs-camel-case.js'; +export { optionsCamelCase } from './options-camel-case.js'; +export { asyncLifecycleMethods } from './async-lifecycle-methods.js'; +export { onHandlerNaming } from './on-handler-naming.js'; +export { onGlobalHandlerPrefix } from './on-global-handler-prefix.js'; +export { noDeprecatedProperties } from './no-deprecated-properties.js'; +export { noDispatchEvent } from './no-dispatch-event.js'; +export { noShadowDom } from './no-shadow-dom.js'; +export { noCreateApp } from './no-create-app.js'; +export { noEventListenerMethods } from './no-event-listener-methods.js'; +export { refsPluralMultiple } from './refs-plural-multiple.js'; diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-create-app.test.ts b/packages/oxlint-plugin-js-toolkit/rules/no-create-app.test.ts new file mode 100644 index 000000000..3095caa83 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/no-create-app.test.ts @@ -0,0 +1,21 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { noCreateApp } from './no-create-app.js'; + +describe('no-create-app', () => { + it('passes and fails correctly', () => { + tester.run('no-create-app', noCreateApp as any, { + valid: [ + `import { registerComponent } from '@studiometa/js-toolkit'; + registerComponent(Slider);`, + ], + invalid: [ + { + code: `import { createApp } from '@studiometa/js-toolkit'; + createApp(App)();`, + errors: [{ messageId: 'useRegister' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts b/packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts new file mode 100644 index 000000000..bad80d5a1 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts @@ -0,0 +1,24 @@ +import { type Node, type RuleContext } from '../utils/ast.js'; + +export const noCreateApp = { + meta: { + type: 'suggestion', + docs: { + description: 'Disallow createApp() — use registerComponent() instead (v4 migration)', + }, + messages: { + useRegister: + 'createApp() is deprecated. Use registerComponent() from @studiometa/js-toolkit instead.', + }, + }, + create(context: RuleContext) { + return { + CallExpression(node: Node) { + const callee = node.callee; + if (callee.type === 'Identifier' && callee.name === 'createApp') { + context.report({ node, messageId: 'useRegister' }); + } + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts new file mode 100644 index 000000000..db8ae99b0 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts @@ -0,0 +1,42 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { noDeprecatedProperties } from './no-deprecated-properties.js'; + +describe('no-deprecated-properties', () => { + it('passes and fails correctly', () => { + tester.run('no-deprecated-properties', noDeprecatedProperties as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + mounted() { + const parent = this.$closest(Base); + const items = this.$queryAll('.item'); + } + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + mounted() { return this.$children; } + }`, + errors: [{ messageId: 'deprecated' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + mounted() { return this.$parent; } + }`, + errors: [{ messageId: 'removed' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + mounted() { return this.$root; } + }`, + errors: [{ messageId: 'removed' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts new file mode 100644 index 000000000..b5a533bd7 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts @@ -0,0 +1,49 @@ +import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.js'; + +const REMOVED = new Map([ + ['$parent', '$closest()'], + ['$root', '$closest()'], +]); + +const DEPRECATED = new Map([ + ['$children', '$query() or $queryAll()'], +]); + +export const noDeprecatedProperties = { + meta: { + type: 'problem', + docs: { + description: 'Disallow removed or deprecated Base properties ($parent, $children, $root)', + }, + messages: { + removed: '"{{name}}" was removed in v3. Use {{replacement}} instead.', + deprecated: '"{{name}}" is deprecated. Use {{replacement}} instead.', + }, + }, + create(context: RuleContext) { + return { + MemberExpression(node: Node) { + const prop = node.property?.name; + if (!prop || (!REMOVED.has(prop) && !DEPRECATED.has(prop))) return; + + // Only flag `this.$parent` etc. — not arbitrary object access + if (node.object?.type !== 'ThisExpression') return; + + const ancestors = context.getAncestors + ? context.getAncestors() + : context.sourceCode?.getAncestors?.(node) ?? []; + + const enclosingClass = findEnclosingClass(ancestors); + if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; + + const messageId = REMOVED.has(prop) ? 'removed' : 'deprecated'; + const replacement = REMOVED.get(prop) ?? DEPRECATED.get(prop); + context.report({ + node, + messageId, + data: { name: prop, replacement }, + }); + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.test.ts b/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.test.ts new file mode 100644 index 000000000..9568350ae --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.test.ts @@ -0,0 +1,34 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { noDispatchEvent } from './no-dispatch-event.js'; + +describe('no-dispatch-event', () => { + it('passes and fails correctly', () => { + tester.run('no-dispatch-event', noDispatchEvent as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + mounted() { this.$emit('change', { value: 1 }); } + }`, + // dispatchEvent outside a Base class — allowed + `function foo(el) { el.dispatchEvent(new Event('click')); }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + mounted() { this.dispatchEvent(new Event('change')); } + }`, + errors: [{ messageId: 'useEmit' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + mounted() { this.$el.dispatchEvent(new Event('change')); } + }`, + errors: [{ messageId: 'useEmit' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.ts b/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.ts new file mode 100644 index 000000000..4163894b3 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.ts @@ -0,0 +1,40 @@ +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.js'; + +export const noDispatchEvent = { + meta: { + type: 'suggestion', + docs: { + description: 'Disallow dispatchEvent() in Base subclasses — use this.$emit() instead', + }, + messages: { + useEmit: 'Use this.$emit() instead of dispatchEvent() to emit events in a Base component.', + }, + }, + create(context: RuleContext) { + return { + CallExpression(node: Node) { + const callee = node.callee; + + const isDispatchEvent = + // this.dispatchEvent(...) + (callee.type === 'MemberExpression' && + callee.object?.type === 'ThisExpression' && + callee.property?.name === 'dispatchEvent') || + // this.$el.dispatchEvent(...) or el.dispatchEvent(...) + (callee.type === 'MemberExpression' && + callee.property?.name === 'dispatchEvent'); + + if (!isDispatchEvent) return; + + const ancestors = context.getAncestors + ? context.getAncestors() + : context.sourceCode?.getAncestors?.(node) ?? []; + + const enclosingClass = findEnclosingClass(ancestors); + if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; + + context.report({ node, messageId: 'useEmit' }); + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.test.ts b/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.test.ts new file mode 100644 index 000000000..0700386df --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.test.ts @@ -0,0 +1,42 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { noEventListenerMethods } from './no-event-listener-methods.js'; + +describe('no-event-listener-methods', () => { + it('passes and fails correctly', () => { + tester.run('no-event-listener-methods', noEventListenerMethods as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + onClickButton() { /* handled by framework */ } + onWindowResize() {} + }`, + // Outside a Base class — allowed + `function setup(el) { + el.addEventListener('click', handler); + el.removeEventListener('click', handler); + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + async mounted() { + this.$el.addEventListener('click', this.onClick); + } + }`, + errors: [{ messageId: 'useOnMethod' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + async destroyed() { + this.$el.removeEventListener('click', this.onClick); + } + }`, + errors: [{ messageId: 'useOnMethod' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.ts b/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.ts new file mode 100644 index 000000000..f624a167a --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.ts @@ -0,0 +1,38 @@ +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.js'; + +const FORBIDDEN = new Set(['addEventListener', 'removeEventListener']); + +export const noEventListenerMethods = { + meta: { + type: 'problem', + docs: { + description: + 'Disallow addEventListener/removeEventListener in Base subclasses — use on* methods instead', + }, + messages: { + useOnMethod: + '"{{method}}()" should not be used in a Base component. ' + + 'Define an "on()" method instead — the framework handles binding and cleanup automatically.', + }, + }, + create(context: RuleContext) { + return { + CallExpression(node: Node) { + const callee = node.callee; + if (callee.type !== 'MemberExpression') return; + + const method = callee.property?.name; + if (!method || !FORBIDDEN.has(method)) return; + + const ancestors = context.getAncestors + ? context.getAncestors() + : context.sourceCode?.getAncestors?.(node) ?? []; + + const enclosingClass = findEnclosingClass(ancestors); + if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; + + context.report({ node, messageId: 'useOnMethod', data: { method } }); + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.test.ts b/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.test.ts new file mode 100644 index 000000000..d987c6980 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.test.ts @@ -0,0 +1,29 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { noShadowDom } from './no-shadow-dom.js'; + +describe('no-shadow-dom', () => { + it('passes and fails correctly', () => { + tester.run('no-shadow-dom', noShadowDom as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + async mounted() { this.$el.classList.add('ready'); } + }`, + // attachShadow outside a Base class — allowed + `class MyElement extends HTMLElement { + connectedCallback() { this.attachShadow({ mode: 'open' }); } + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + async mounted() { this.$el.attachShadow({ mode: 'open' }); } + }`, + errors: [{ messageId: 'noShadow' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.ts b/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.ts new file mode 100644 index 000000000..4c3981dd5 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.ts @@ -0,0 +1,36 @@ +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.js'; + +export const noShadowDom = { + meta: { + type: 'problem', + docs: { + description: 'Disallow attachShadow() in Base subclasses — js-toolkit uses Light DOM only', + }, + messages: { + noShadow: + 'Do not use attachShadow() in a Base component. @studiometa/js-toolkit uses Light DOM only.', + }, + }, + create(context: RuleContext) { + return { + CallExpression(node: Node) { + const callee = node.callee; + if ( + callee.type !== 'MemberExpression' || + callee.property?.name !== 'attachShadow' + ) { + return; + } + + const ancestors = context.getAncestors + ? context.getAncestors() + : context.sourceCode?.getAncestors?.(node) ?? []; + + const enclosingClass = findEnclosingClass(ancestors); + if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; + + context.report({ node, messageId: 'noShadow' }); + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.test.ts b/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.test.ts new file mode 100644 index 000000000..4dc241dc8 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.test.ts @@ -0,0 +1,42 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { onGlobalHandlerPrefix } from './on-global-handler-prefix.js'; + +describe('on-global-handler-prefix', () => { + it('passes and fails correctly', () => { + tester.run('on-global-handler-prefix', onGlobalHandlerPrefix as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + onWindowResize() {} + onDocumentScroll() {} + onWindowClick() {} + }`, + // onClickButton is fine — not a bare global-only event + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + onClickButton() {} + }`, + // onClick on its own is valid — click can fire on a DOM element + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + onClick() {} + }`, + // onScroll on its own is valid — scroll can fire on a DOM element + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + onScroll() {} + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + onResize() {} + }`, + errors: [{ messageId: 'missingPrefix' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.ts b/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.ts new file mode 100644 index 000000000..95929a71e --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.ts @@ -0,0 +1,49 @@ +import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.js'; + +// Events that only make sense on window/document, never on a DOM element. +// Pointer, keyboard, and form events are intentionally excluded — they can all +// fire on elements too, so onResize/onScroll without a prefix is the only +// unambiguous mistake worth flagging automatically. +const GLOBAL_ONLY_EVENTS = new Set(['Resize']); + +export const onGlobalHandlerPrefix = { + meta: { + type: 'suggestion', + docs: { + description: + 'Require global event handlers (resize, scroll, etc.) to be prefixed with onWindow or onDocument', + }, + messages: { + missingPrefix: + '"{{name}}" looks like a global event handler. Use "onWindow{{event}}" or "onDocument{{event}}" to bind to window/document events.', + }, + }, + create(context: RuleContext) { + return { + MethodDefinition(node: Node) { + const name = node.key?.name; + if (!name || !name.startsWith('on')) return; + + const suffix = name.slice(2); // strip "on" + const event = GLOBAL_ONLY_EVENTS.has(suffix) ? suffix : null; + if (!event) return; + + // Allow onWindow* and onDocument* — those are correct + if (name.startsWith('onWindow') || name.startsWith('onDocument')) return; + + const ancestors = context.getAncestors + ? context.getAncestors() + : context.sourceCode?.getAncestors?.(node) ?? []; + + const enclosingClass = findEnclosingClass(ancestors); + if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; + + context.report({ + node, + messageId: 'missingPrefix', + data: { name, event }, + }); + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.test.ts b/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.test.ts new file mode 100644 index 000000000..606240f91 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.test.ts @@ -0,0 +1,38 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { onHandlerNaming } from './on-handler-naming.js'; + +describe('on-handler-naming', () => { + it('passes and fails correctly', () => { + tester.run('on-handler-naming', onHandlerNaming as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + onClickButton() {} + onWindowResize() {} + onSliderChange() {} + }`, + // Getter starting with "on" — not an event handler + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + get onlyOfficial() { return true; } + }`, + // Method starting with "on" but not followed by uppercase — not an event handler + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + onlyOfficial() {} + }`, + ], + invalid: [ + // Underscore in handler name — not valid camelCase + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + onClick_button() {} + }`, + errors: [{ messageId: 'invalidName' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.ts b/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.ts new file mode 100644 index 000000000..249ad890b --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.ts @@ -0,0 +1,40 @@ +import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.js'; + +// onXxxYyy — the part after "on" must start with an uppercase letter +const ON_HANDLER_RE = /^on[A-Z][a-zA-Z0-9]*$/; + +export const onHandlerNaming = { + meta: { + type: 'problem', + docs: { + description: 'Require event handler methods to follow the onXxxYyy naming convention', + }, + messages: { + invalidName: + 'Event handler "{{name}}" must follow the onXxxYyy camelCase convention (e.g. onClickButton).', + }, + }, + create(context: RuleContext) { + return { + MethodDefinition(node: Node) { + // Skip getters, setters — those are not event handlers + if (node.kind === 'get' || node.kind === 'set') return; + + const name = node.key?.name; + // Only lint methods that start with "on" followed by an uppercase letter — + // that's the unambiguous signal of an intended event handler. + if (!name || !/^on[A-Z]/.test(name)) return; + if (ON_HANDLER_RE.test(name)) return; + + const ancestors = context.getAncestors + ? context.getAncestors() + : context.sourceCode?.getAncestors?.(node) ?? []; + + const enclosingClass = findEnclosingClass(ancestors); + if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; + + context.report({ node, messageId: 'invalidName', data: { name } }); + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts b/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts new file mode 100644 index 000000000..9964cc519 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts @@ -0,0 +1,32 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { optionsCamelCase } from './options-camel-case.js'; + +describe('options-camel-case', () => { + it('passes and fails correctly', () => { + tester.run('options-camel-case', optionsCamelCase as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider', options: { slidesToShow: 3, autoplay: false } }; + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider', options: { 'slides-to-show': 3 } }; + }`, + errors: [{ messageId: 'notCamelCase' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider', options: { SlidesToShow: 3 } }; + }`, + errors: [{ messageId: 'notCamelCase' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts b/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts new file mode 100644 index 000000000..6ca2bffd4 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts @@ -0,0 +1,57 @@ +import { isBaseSubclass, isCamelCase, type Node, type RuleContext } from '../utils/ast.js'; + +export const optionsCamelCase = { + meta: { + type: 'problem', + docs: { + description: 'Require option keys in static config to be camelCase', + }, + messages: { + notCamelCase: 'Option key "{{name}}" must be camelCase.', + }, + }, + create(context: RuleContext) { + return { + ClassDeclaration(node: Node) { + check(node, context); + }, + ClassExpression(node: Node) { + check(node, context); + }, + }; + }, +}; + +function check(node: Node, context: RuleContext) { + if (!isBaseSubclass(node, context)) return; + + const body: Node[] = node.body?.body ?? []; + + const configProp = body.find( + (member: Node) => + member.type === 'PropertyDefinition' && + member.static === true && + member.key?.name === 'config', + ); + + if (!configProp?.value || configProp.value.type !== 'ObjectExpression') return; + + const optionsProp = configProp.value.properties?.find( + (prop: Node) => prop.type === 'Property' && prop.key?.name === 'options', + ); + + if (!optionsProp?.value || optionsProp.value.type !== 'ObjectExpression') return; + + for (const prop of optionsProp.value.properties ?? []) { + if (prop.type !== 'Property') continue; + const name = prop.key?.name ?? prop.key?.value; + if (typeof name !== 'string') continue; + if (!isCamelCase(name)) { + context.report({ + node: prop.key, + messageId: 'notCamelCase', + data: { name }, + }); + } + } +} diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts b/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts new file mode 100644 index 000000000..ac11b3b6f --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts @@ -0,0 +1,37 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { refsCamelCase } from './refs-camel-case.js'; + +describe('refs-camel-case', () => { + it('passes and fails correctly', () => { + tester.run('refs-camel-case', refsCamelCase as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider', refs: ['nextButton', 'previousButton'] }; + }`, + // Multiple ref with [] suffix — base name still camelCase + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider', refs: ['links[]', 'items[]', 'disabledBtn[]'] }; + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider', refs: ['NextButton'] }; + }`, + errors: [{ messageId: 'notCamelCase' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider', refs: ['next-button'] }; + }`, + errors: [{ messageId: 'notCamelCase' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts b/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts new file mode 100644 index 000000000..eaa193ec6 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts @@ -0,0 +1,59 @@ +import { isBaseSubclass, isCamelCase, type Node, type RuleContext } from '../utils/ast.js'; + +export const refsCamelCase = { + meta: { + type: 'problem', + docs: { + description: 'Require ref names in static config to be camelCase', + }, + messages: { + notCamelCase: 'Ref name "{{name}}" must be camelCase.', + }, + }, + create(context: RuleContext) { + return { + ClassDeclaration(node: Node) { + check(node, context); + }, + ClassExpression(node: Node) { + check(node, context); + }, + }; + }, +}; + +function check(node: Node, context: RuleContext) { + if (!isBaseSubclass(node, context)) return; + + const body: Node[] = node.body?.body ?? []; + + const configProp = body.find( + (member: Node) => + member.type === 'PropertyDefinition' && + member.static === true && + member.key?.name === 'config', + ); + + if (!configProp?.value || configProp.value.type !== 'ObjectExpression') return; + + const refsProp = configProp.value.properties?.find( + (prop: Node) => prop.type === 'Property' && prop.key?.name === 'refs', + ); + + if (!refsProp?.value || refsProp.value.type !== 'ArrayExpression') return; + + for (const element of refsProp.value.elements ?? []) { + if (!element || element.type !== 'Literal') continue; + const raw = element.value; + if (typeof raw !== 'string') continue; + // Strip the multiple-ref suffix before checking casing + const name = raw.endsWith('[]') ? raw.slice(0, -2) : raw; + if (!isCamelCase(name)) { + context.report({ + node: element, + messageId: 'notCamelCase', + data: { name }, + }); + } + } +} diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.test.ts b/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.test.ts new file mode 100644 index 000000000..d551ceaab --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.test.ts @@ -0,0 +1,38 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { refsPluralMultiple } from './refs-plural-multiple.js'; + +describe('refs-plural-multiple', () => { + it('passes and fails correctly', () => { + tester.run('refs-plural-multiple', refsPluralMultiple as any, { + valid: [ + // Plural name with [] suffix + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider', refs: ['links[]', 'buttons[]', 'items[]'] }; + }`, + // Single refs without [] are not checked by this rule + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider', refs: ['link', 'button'] }; + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider', refs: ['link[]'] }; + }`, + errors: [{ messageId: 'notPlural' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider', refs: ['navItem[]'] }; + }`, + errors: [{ messageId: 'notPlural' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.ts b/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.ts new file mode 100644 index 000000000..82bb815de --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.ts @@ -0,0 +1,60 @@ +import { isBaseSubclass, type Node, type RuleContext } from '../utils/ast.js'; + +export const refsPluralMultiple = { + meta: { + type: 'problem', + docs: { + description: 'Require multiple-ref names (using [] suffix) to be pluralized', + }, + messages: { + notPlural: 'Multiple ref "{{name}}" must be pluralized (e.g. "{{name}}s[]").', + }, + }, + create(context: RuleContext) { + return { + ClassDeclaration(node: Node) { + check(node, context); + }, + ClassExpression(node: Node) { + check(node, context); + }, + }; + }, +}; + +function check(node: Node, context: RuleContext) { + if (!isBaseSubclass(node, context)) return; + + const body: Node[] = node.body?.body ?? []; + + const configProp = body.find( + (member: Node) => + member.type === 'PropertyDefinition' && + member.static === true && + member.key?.name === 'config', + ); + + if (!configProp?.value || configProp.value.type !== 'ObjectExpression') return; + + const refsProp = configProp.value.properties?.find( + (prop: Node) => prop.type === 'Property' && prop.key?.name === 'refs', + ); + + if (!refsProp?.value || refsProp.value.type !== 'ArrayExpression') return; + + for (const element of refsProp.value.elements ?? []) { + if (!element || element.type !== 'Literal') continue; + const raw = element.value; + if (typeof raw !== 'string') continue; + if (!raw.endsWith('[]')) continue; + + const name = raw.slice(0, -2); + if (!name.endsWith('s')) { + context.report({ + node: element, + messageId: 'notPlural', + data: { name }, + }); + } + } +} diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts b/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts new file mode 100644 index 000000000..b1694c9e1 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts @@ -0,0 +1,36 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { requireConfigNamePascalCase } from './require-config-name-pascal-case.js'; + +describe('require-config-name-pascal-case', () => { + it('passes and fails correctly', () => { + tester.run('require-config-name-pascal-case', requireConfigNamePascalCase as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider' }; + }`, + `import { Base } from '@studiometa/js-toolkit'; + class FigureShopify extends Base { + static config = { name: 'FigureShopify' }; + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'slider' }; + }`, + errors: [{ messageId: 'notPascalCase' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'my-slider' }; + }`, + errors: [{ messageId: 'notPascalCase' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts b/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts new file mode 100644 index 000000000..aabdf299c --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts @@ -0,0 +1,55 @@ +import { isBaseSubclass, isPascalCase, type Node, type RuleContext } from '../utils/ast.js'; + +export const requireConfigNamePascalCase = { + meta: { + type: 'problem', + docs: { + description: 'Require config.name to be PascalCase on classes extending Base', + }, + messages: { + notPascalCase: 'config.name "{{name}}" must be PascalCase.', + }, + }, + create(context: RuleContext) { + return { + ClassDeclaration(node: Node) { + check(node, context); + }, + ClassExpression(node: Node) { + check(node, context); + }, + }; + }, +}; + +function check(node: Node, context: RuleContext) { + if (!isBaseSubclass(node, context)) return; + + const body: Node[] = node.body?.body ?? []; + + const configProp = body.find( + (member: Node) => + member.type === 'PropertyDefinition' && + member.static === true && + member.key?.name === 'config', + ); + + if (!configProp?.value || configProp.value.type !== 'ObjectExpression') return; + + const nameProp = configProp.value.properties?.find( + (prop: Node) => prop.type === 'Property' && prop.key?.name === 'name', + ); + + if (!nameProp) return; + + const nameValue = nameProp.value?.value; + if (typeof nameValue !== 'string') return; + + if (!isPascalCase(nameValue)) { + context.report({ + node: nameProp.value, + messageId: 'notPascalCase', + data: { name: nameValue }, + }); + } +} diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config.test.ts b/packages/oxlint-plugin-js-toolkit/rules/require-config.test.ts new file mode 100644 index 000000000..e8cb963b1 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/require-config.test.ts @@ -0,0 +1,42 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.js'; +import { requireConfig } from './require-config.js'; + +describe('require-config', () => { + it('passes and fails correctly', () => { + tester.run('require-config', requireConfig as any, { + valid: [ + // Has static config with name + `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { name: 'Slider' }; + }`, + // Not a Base subclass — ignored + `class Slider extends SomeOtherClass {}`, + // No superclass — ignored + `class Slider {}`, + ], + invalid: [ + // Missing config entirely + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base {}`, + errors: [{ messageId: 'missingConfig' }], + }, + // Config exists but no name + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + static config = { refs: ['btn'] }; + }`, + errors: [{ messageId: 'missingName' }], + }, + // Bare Base identifier + { + code: `class Slider extends Base {}`, + errors: [{ messageId: 'missingConfig' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config.ts b/packages/oxlint-plugin-js-toolkit/rules/require-config.ts new file mode 100644 index 000000000..282eeb7cd --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/rules/require-config.ts @@ -0,0 +1,55 @@ +import { isBaseSubclass, type Node, type RuleContext } from '../utils/ast.js'; + +export const requireConfig = { + meta: { + type: 'problem', + docs: { + description: 'Require a static config property on classes extending Base', + }, + messages: { + missingConfig: 'Classes extending Base must define a static config property.', + missingName: 'The static config must include a name property.', + }, + }, + create(context: RuleContext) { + return { + ClassDeclaration(node: Node) { + check(node, context); + }, + ClassExpression(node: Node) { + check(node, context); + }, + }; + }, +}; + +function check(node: Node, context: RuleContext) { + if (!isBaseSubclass(node, context)) return; + + const body: Node[] = node.body?.body ?? []; + + const configProp = body.find( + (member: Node) => + member.type === 'PropertyDefinition' && + member.static === true && + member.key?.name === 'config', + ); + + if (!configProp) { + context.report({ node, messageId: 'missingConfig' }); + return; + } + + // Check that config has a `name` property + const value = configProp.value; + if (!value || value.type !== 'ObjectExpression') return; + + const hasName = value.properties?.some( + (prop: Node) => + prop.type === 'Property' && prop.key?.name === 'name' && prop.value?.value, + ); + + if (!hasName) { + context.report({ node: configProp, messageId: 'missingName' }); + } +} diff --git a/packages/oxlint-plugin-js-toolkit/scripts/build.js b/packages/oxlint-plugin-js-toolkit/scripts/build.js new file mode 100644 index 000000000..e59959d72 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/scripts/build.js @@ -0,0 +1,26 @@ +import { resolve, dirname } from 'node:path'; +import { execSync } from 'node:child_process'; +import esbuild from 'esbuild'; + +const root = resolve(dirname(new URL(import.meta.url).pathname), '..'); + +console.log('Building @studiometa/oxlint-plugin-js-toolkit...'); + +const { errors, warnings } = await esbuild.build({ + entryPoints: [resolve(root, 'index.ts')], + bundle: true, + write: true, + outdir: resolve(root, 'dist'), + target: 'esnext', + format: 'esm', + sourcemap: true, + platform: 'node', +}); + +errors.forEach(console.error); +warnings.forEach(console.warn); + +console.log('Emitting types...'); +execSync('tsc --build tsconfig.json', { cwd: root, stdio: 'inherit' }); + +console.log('Done!'); diff --git a/packages/oxlint-plugin-js-toolkit/tsconfig.json b/packages/oxlint-plugin-js-toolkit/tsconfig.json new file mode 100644 index 000000000..030002ecb --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "outDir": "./dist", + "noEmit": false, + "emitDeclarationOnly": true, + "declaration": true + }, + "include": ["./**/*.ts"], + "exclude": ["dist", "node_modules", "**/*.test.ts"] +} diff --git a/packages/oxlint-plugin-js-toolkit/utils/ast.ts b/packages/oxlint-plugin-js-toolkit/utils/ast.ts new file mode 100644 index 000000000..0ac299c1c --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/utils/ast.ts @@ -0,0 +1,101 @@ +/** + * ESLint v9 compatible AST node types used across rules. + * We use loose typings here since oxlint/eslint expose their own node types. + */ + +export type Node = Record; +export type RuleContext = Record; + +export const LIFECYCLE_METHODS = new Set([ + 'mounted', + 'destroyed', + 'updated', + 'terminated', + 'ticked', + 'scrolled', + 'resized', + 'moved', + 'loaded', + 'keyed', +]); + +export const TOOLKIT_PACKAGE = '@studiometa/js-toolkit'; + +/** + * Returns true if a class declaration extends an identifier imported from + * @studiometa/js-toolkit, or extends a bare identifier named `Base`. + * + * This is a heuristic — we cannot do full type resolution in a lint rule. + */ +export function isBaseSubclass(node: Node, context: RuleContext): boolean { + if (node.type !== 'ClassDeclaration' && node.type !== 'ClassExpression') { + return false; + } + + if (!node.superClass) { + return false; + } + + const superName = + node.superClass.type === 'Identifier' ? node.superClass.name : null; + + if (!superName) { + return false; + } + + // Fast path: class extends Base + if (superName === 'Base') { + return true; + } + + // Check if the superclass identifier is imported from @studiometa/js-toolkit + const sourceCode = context.sourceCode ?? context.getSourceCode?.(); + const ast = sourceCode?.ast; + + if (!ast) { + return false; + } + + for (const node of ast.body) { + if (node.type !== 'ImportDeclaration') continue; + if (node.source.value !== TOOLKIT_PACKAGE) continue; + + for (const specifier of node.specifiers) { + if ( + specifier.type === 'ImportSpecifier' && + specifier.local.name === superName + ) { + return true; + } + } + } + + return false; +} + +/** + * Returns true if the string is camelCase (starts lowercase, no separators). + */ +export function isCamelCase(value: string): boolean { + return /^[a-z][a-zA-Z0-9]*$/.test(value); +} + +/** + * Returns true if the string is PascalCase. + */ +export function isPascalCase(value: string): boolean { + return /^[A-Z][a-zA-Z0-9]*$/.test(value); +} + +/** + * Walks up ancestor nodes to find the nearest class declaration/expression. + */ +export function findEnclosingClass(ancestors: Node[]): Node | null { + for (let i = ancestors.length - 1; i >= 0; i--) { + const node = ancestors[i]; + if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') { + return node; + } + } + return null; +} diff --git a/packages/oxlint-plugin-js-toolkit/utils/rule-tester.ts b/packages/oxlint-plugin-js-toolkit/utils/rule-tester.ts new file mode 100644 index 000000000..bf57b7149 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/utils/rule-tester.ts @@ -0,0 +1,10 @@ +import { RuleTester } from 'eslint'; + +export { RuleTester }; + +export const tester = new RuleTester({ + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, +}); diff --git a/packages/oxlint-plugin-js-toolkit/vitest.config.ts b/packages/oxlint-plugin-js-toolkit/vitest.config.ts new file mode 100644 index 000000000..0f2a047b2 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['**/*.test.ts'], + exclude: ['dist', 'node_modules'], + }, +}); From e55b745f235d4b8aa7ff1cee1b53422f989ca682 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 18:27:06 +0200 Subject: [PATCH 02/34] Remove v4 mentions from plugin docs and rule description Co-Authored-By: Claude Sonnet 4.6 --- packages/oxlint-plugin-js-toolkit/README.md | 2 +- packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/oxlint-plugin-js-toolkit/README.md b/packages/oxlint-plugin-js-toolkit/README.md index ec2ba1869..3ee3371a0 100644 --- a/packages/oxlint-plugin-js-toolkit/README.md +++ b/packages/oxlint-plugin-js-toolkit/README.md @@ -95,5 +95,5 @@ export default [ | `js-toolkit/no-deprecated-properties` | Disallows removed (`$parent`, `$root`) and deprecated (`$children`) properties. Use `$closest()` and `$query()`/`$queryAll()` instead. | error | | `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | warn | | `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | error | -| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated since v4). Use `registerComponent()` instead. | warn | +| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | warn | | `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead — the framework handles binding and cleanup automatically. | error | diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts b/packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts index bad80d5a1..10bef8ef3 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts @@ -4,7 +4,7 @@ export const noCreateApp = { meta: { type: 'suggestion', docs: { - description: 'Disallow createApp() — use registerComponent() instead (v4 migration)', + description: 'Disallow createApp() — use registerComponent() instead', }, messages: { useRegister: From 1aadaa059c3c1aa4c45fe63786b34921f2612703 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 18:31:01 +0200 Subject: [PATCH 03/34] Fix oxlint config --- .oxlintrc.json | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index bf537f094..251518b3a 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,24 +1,31 @@ { - "jsPlugins": [{ "name": "js-toolkit", "specifier": "./packages/oxlint-plugin-js-toolkit" }], + "jsPlugins": [{ "name": "js-toolkit", "specifier": "@studiometa/oxlint-plugin-js-toolkit" }], "rules": { - "js-toolkit/require-config": "error", - "js-toolkit/require-config-name-pascal-case": "error", - "js-toolkit/refs-camel-case": "error", - "js-toolkit/refs-plural-multiple": "error", - "js-toolkit/options-camel-case": "error", - "js-toolkit/async-lifecycle-methods": "error", - "js-toolkit/on-handler-naming": "error", - "js-toolkit/on-global-handler-prefix": "warn", - "js-toolkit/no-deprecated-properties": "error", - "js-toolkit/no-dispatch-event": "warn", - "js-toolkit/no-shadow-dom": "error", - "js-toolkit/no-create-app": "warn", - "js-toolkit/no-event-listener-methods": "error", "typescript/no-this-alias": [ "error", { "allowDestructuring": true } ] - } + }, + "overrides": [ + { + "files": ["packages/js-toolkit/**/*.ts", "packages/demo/**/*.{ts,js}"], + "rules": { + "js-toolkit/require-config": "error", + "js-toolkit/require-config-name-pascal-case": "error", + "js-toolkit/refs-camel-case": "error", + "js-toolkit/refs-plural-multiple": "error", + "js-toolkit/options-camel-case": "error", + "js-toolkit/async-lifecycle-methods": "error", + "js-toolkit/on-handler-naming": "error", + "js-toolkit/on-global-handler-prefix": "warn", + "js-toolkit/no-deprecated-properties": "error", + "js-toolkit/no-dispatch-event": "warn", + "js-toolkit/no-shadow-dom": "error", + "js-toolkit/no-create-app": "warn", + "js-toolkit/no-event-listener-methods": "error" + } + } + ] } From 74c9717acfdb70bdb33ba032b2503b61624f7d91 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 18:33:32 +0200 Subject: [PATCH 04/34] Add auto-fix to async-lifecycle-methods, refs-camel-case, options-camel-case and require-config-name-pascal-case rules Co-Authored-By: Claude Sonnet 4.6 --- .../rules/async-lifecycle-methods.test.ts | 23 +++++++++++++------ .../rules/async-lifecycle-methods.ts | 2 ++ .../rules/options-camel-case.test.ts | 20 +++++++++++----- .../rules/options-camel-case.ts | 7 +++++- .../rules/refs-camel-case.test.ts | 20 +++++++++++----- .../rules/refs-camel-case.ts | 5 +++- .../require-config-name-pascal-case.test.ts | 20 +++++++++++----- .../rules/require-config-name-pascal-case.ts | 4 +++- .../oxlint-plugin-js-toolkit/utils/ast.ts | 18 +++++++++++++++ 9 files changed, 91 insertions(+), 28 deletions(-) diff --git a/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts b/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts index 7ad04e425..9d03af7e6 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts @@ -21,18 +21,27 @@ describe('async-lifecycle-methods', () => { invalid: [ { code: `import { Base } from '@studiometa/js-toolkit'; - class Slider extends Base { - mounted() {} - }`, +class Slider extends Base { + mounted() {} +}`, errors: [{ messageId: 'notAsync' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Slider extends Base { + async mounted() {} +}`, }, { code: `import { Base } from '@studiometa/js-toolkit'; - class Slider extends Base { - destroyed() {} - scrolled() {} - }`, +class Slider extends Base { + destroyed() {} + scrolled() {} +}`, errors: [{ messageId: 'notAsync' }, { messageId: 'notAsync' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Slider extends Base { + async destroyed() {} + async scrolled() {} +}`, }, ], }); diff --git a/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts b/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts index 865adde7b..5215315fb 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts @@ -3,6 +3,7 @@ import { isBaseSubclass, LIFECYCLE_METHODS, findEnclosingClass, type Node, type export const asyncLifecycleMethods = { meta: { type: 'problem', + fixable: 'code', docs: { description: 'Require lifecycle methods on Base subclasses to be async', }, @@ -28,6 +29,7 @@ export const asyncLifecycleMethods = { node, messageId: 'notAsync', data: { name }, + fix: (fixer: any) => fixer.insertTextBefore(node.key, 'async '), }); }, }; diff --git a/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts b/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts index 9964cc519..e8950b31b 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts @@ -14,17 +14,25 @@ describe('options-camel-case', () => { invalid: [ { code: `import { Base } from '@studiometa/js-toolkit'; - class Slider extends Base { - static config = { name: 'Slider', options: { 'slides-to-show': 3 } }; - }`, +class Slider extends Base { + static config = { name: 'Slider', options: { 'slides-to-show': 3 } }; +}`, errors: [{ messageId: 'notCamelCase' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Slider extends Base { + static config = { name: 'Slider', options: { 'slidesToShow': 3 } }; +}`, }, { code: `import { Base } from '@studiometa/js-toolkit'; - class Slider extends Base { - static config = { name: 'Slider', options: { SlidesToShow: 3 } }; - }`, +class Slider extends Base { + static config = { name: 'Slider', options: { SlidesToShow: 3 } }; +}`, errors: [{ messageId: 'notCamelCase' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Slider extends Base { + static config = { name: 'Slider', options: { slidesToShow: 3 } }; +}`, }, ], }); diff --git a/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts b/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts index 6ca2bffd4..b0f73cba0 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts @@ -1,8 +1,9 @@ -import { isBaseSubclass, isCamelCase, type Node, type RuleContext } from '../utils/ast.js'; +import { isBaseSubclass, isCamelCase, toCamelCase, type Node, type RuleContext } from '../utils/ast.js'; export const optionsCamelCase = { meta: { type: 'problem', + fixable: 'code', docs: { description: 'Require option keys in static config to be camelCase', }, @@ -47,10 +48,14 @@ function check(node: Node, context: RuleContext) { const name = prop.key?.name ?? prop.key?.value; if (typeof name !== 'string') continue; if (!isCamelCase(name)) { + const fixed = toCamelCase(name); + // Quoted key ('slides-to-show') vs identifier key (SlidesToShow) + const fixedText = prop.key.type === 'Literal' ? `'${fixed}'` : fixed; context.report({ node: prop.key, messageId: 'notCamelCase', data: { name }, + fix: (fixer: any) => fixer.replaceText(prop.key, fixedText), }); } } diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts b/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts index ac11b3b6f..c7b1053e9 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts @@ -19,17 +19,25 @@ describe('refs-camel-case', () => { invalid: [ { code: `import { Base } from '@studiometa/js-toolkit'; - class Slider extends Base { - static config = { name: 'Slider', refs: ['NextButton'] }; - }`, +class Slider extends Base { + static config = { name: 'Slider', refs: ['NextButton'] }; +}`, errors: [{ messageId: 'notCamelCase' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Slider extends Base { + static config = { name: 'Slider', refs: ['nextButton'] }; +}`, }, { code: `import { Base } from '@studiometa/js-toolkit'; - class Slider extends Base { - static config = { name: 'Slider', refs: ['next-button'] }; - }`, +class Slider extends Base { + static config = { name: 'Slider', refs: ['next-button'] }; +}`, errors: [{ messageId: 'notCamelCase' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Slider extends Base { + static config = { name: 'Slider', refs: ['nextButton'] }; +}`, }, ], }); diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts b/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts index eaa193ec6..e3b7d7e64 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts @@ -1,8 +1,9 @@ -import { isBaseSubclass, isCamelCase, type Node, type RuleContext } from '../utils/ast.js'; +import { isBaseSubclass, isCamelCase, toCamelCase, type Node, type RuleContext } from '../utils/ast.js'; export const refsCamelCase = { meta: { type: 'problem', + fixable: 'code', docs: { description: 'Require ref names in static config to be camelCase', }, @@ -49,10 +50,12 @@ function check(node: Node, context: RuleContext) { // Strip the multiple-ref suffix before checking casing const name = raw.endsWith('[]') ? raw.slice(0, -2) : raw; if (!isCamelCase(name)) { + const fixed = toCamelCase(name) + (raw.endsWith('[]') ? '[]' : ''); context.report({ node: element, messageId: 'notCamelCase', data: { name }, + fix: (fixer: any) => fixer.replaceText(element, `'${fixed}'`), }); } } diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts b/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts index b1694c9e1..5087619fe 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts @@ -18,17 +18,25 @@ describe('require-config-name-pascal-case', () => { invalid: [ { code: `import { Base } from '@studiometa/js-toolkit'; - class Slider extends Base { - static config = { name: 'slider' }; - }`, +class Slider extends Base { + static config = { name: 'slider' }; +}`, errors: [{ messageId: 'notPascalCase' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Slider extends Base { + static config = { name: 'Slider' }; +}`, }, { code: `import { Base } from '@studiometa/js-toolkit'; - class Slider extends Base { - static config = { name: 'my-slider' }; - }`, +class Slider extends Base { + static config = { name: 'my-slider' }; +}`, errors: [{ messageId: 'notPascalCase' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Slider extends Base { + static config = { name: 'MySlider' }; +}`, }, ], }); diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts b/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts index aabdf299c..4ccca4bca 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts @@ -1,8 +1,9 @@ -import { isBaseSubclass, isPascalCase, type Node, type RuleContext } from '../utils/ast.js'; +import { isBaseSubclass, isPascalCase, toPascalCase, type Node, type RuleContext } from '../utils/ast.js'; export const requireConfigNamePascalCase = { meta: { type: 'problem', + fixable: 'code', docs: { description: 'Require config.name to be PascalCase on classes extending Base', }, @@ -50,6 +51,7 @@ function check(node: Node, context: RuleContext) { node: nameProp.value, messageId: 'notPascalCase', data: { name: nameValue }, + fix: (fixer: any) => fixer.replaceText(nameProp.value, `'${toPascalCase(nameValue)}'`), }); } } diff --git a/packages/oxlint-plugin-js-toolkit/utils/ast.ts b/packages/oxlint-plugin-js-toolkit/utils/ast.ts index 0ac299c1c..d0e0e8dfa 100644 --- a/packages/oxlint-plugin-js-toolkit/utils/ast.ts +++ b/packages/oxlint-plugin-js-toolkit/utils/ast.ts @@ -87,6 +87,24 @@ export function isPascalCase(value: string): boolean { return /^[A-Z][a-zA-Z0-9]*$/.test(value); } +/** + * Converts a kebab-case, snake_case or PascalCase string to camelCase. + */ +export function toCamelCase(value: string): string { + return value + .replace(/[-_](.)/g, (_, c: string) => c.toUpperCase()) + .replace(/^[A-Z]/, (c) => c.toLowerCase()); +} + +/** + * Converts a kebab-case, snake_case or camelCase string to PascalCase. + */ +export function toPascalCase(value: string): string { + return value + .replace(/[-_](.)/g, (_, c: string) => c.toUpperCase()) + .replace(/^[a-z]/, (c) => c.toUpperCase()); +} + /** * Walks up ancestor nodes to find the nearest class declaration/expression. */ From 77567ed5f75531b873362d1a3c640553a1e108f4 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 18:34:15 +0200 Subject: [PATCH 05/34] Update README to indicate which rules are auto-fixable Co-Authored-By: Claude Sonnet 4.6 --- packages/oxlint-plugin-js-toolkit/README.md | 42 ++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/oxlint-plugin-js-toolkit/README.md b/packages/oxlint-plugin-js-toolkit/README.md index 3ee3371a0..d14e5cbdb 100644 --- a/packages/oxlint-plugin-js-toolkit/README.md +++ b/packages/oxlint-plugin-js-toolkit/README.md @@ -67,33 +67,33 @@ export default [ ### Class structure -| Rule | Description | Recommended | -|------|-------------|-------------| -| `js-toolkit/require-config` | Requires a `static config` property with a `name` on every class extending `Base`. | error | -| `js-toolkit/require-config-name-pascal-case` | Requires `config.name` to be PascalCase. | error | -| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | error | -| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | error | -| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | error | +| Rule | Description | Recommended | Fixable | +|------|-------------|-------------|---------| +| `js-toolkit/require-config` | Requires a `static config` property with a `name` on every class extending `Base`. | error | | +| `js-toolkit/require-config-name-pascal-case` | Requires `config.name` to be PascalCase. | error | 🔧 | +| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | error | 🔧 | +| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | error | | +| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | error | 🔧 | ### Lifecycle methods -| Rule | Description | Recommended | -|------|-------------|-------------| -| `js-toolkit/async-lifecycle-methods` | Requires lifecycle methods (`mounted`, `destroyed`, `updated`, `terminated`, `ticked`, `scrolled`, `resized`, `moved`, `loaded`, `keyed`) to be declared `async`. | error | +| Rule | Description | Recommended | Fixable | +|------|-------------|-------------|---------| +| `js-toolkit/async-lifecycle-methods` | Requires lifecycle methods (`mounted`, `destroyed`, `updated`, `terminated`, `ticked`, `scrolled`, `resized`, `moved`, `loaded`, `keyed`) to be declared `async`. | error | 🔧 | ### Event handlers -| Rule | Description | Recommended | -|------|-------------|-------------| -| `js-toolkit/on-handler-naming` | Requires event handler methods to follow the `onXxxYyy` camelCase convention. | error | -| `js-toolkit/on-global-handler-prefix` | Requires handlers for window-only events (e.g. `resize`) to use the `onWindow` or `onDocument` prefix. | warn | +| Rule | Description | Recommended | Fixable | +|------|-------------|-------------|---------| +| `js-toolkit/on-handler-naming` | Requires event handler methods to follow the `onXxxYyy` camelCase convention. | error | | +| `js-toolkit/on-global-handler-prefix` | Requires handlers for window-only events (e.g. `resize`) to use the `onWindow` or `onDocument` prefix. | warn | | ### Forbidden patterns -| Rule | Description | Recommended | -|------|-------------|-------------| -| `js-toolkit/no-deprecated-properties` | Disallows removed (`$parent`, `$root`) and deprecated (`$children`) properties. Use `$closest()` and `$query()`/`$queryAll()` instead. | error | -| `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | warn | -| `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | error | -| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | warn | -| `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead — the framework handles binding and cleanup automatically. | error | +| Rule | Description | Recommended | Fixable | +|------|-------------|-------------|---------| +| `js-toolkit/no-deprecated-properties` | Disallows removed (`$parent`, `$root`) and deprecated (`$children`) properties. Use `$closest()` and `$query()`/`$queryAll()` instead. | error | | +| `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | warn | | +| `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | error | | +| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | warn | | +| `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead — the framework handles binding and cleanup automatically. | error | | From 2ea98a5fc1699b0e17567a2ed63eca3937274170 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 18:35:50 +0200 Subject: [PATCH 06/34] Add a fix:oxlint script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 8e55963e0..e868b1458 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "lint:docs": "prettier --check 'packages/docs/**/*.{md,js,html,vue}' --cache", "lint:types": "tsgo --build tsconfig.lint.json", "lint:md": "prettier --check '*.md'", - "fix": "npm run fix:docs && npm run fix:md", + "fix": "npm run fix:oxlint && npm run fix:docs && npm run fix:md", + "fix:oxlint": "oxlint . --fix", "fix:docs": "prettier --write 'packages/docs/**/*.{md,js,html,vue}'", "fix:md": "prettier --write '*.md'", "build": "rm -rf dist && npm run build:pkg && npm run build:types && npm run build:cp-files", From 47c4df0ea4493efc8d54b9279ac149efcd59b9c3 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 18:36:05 +0200 Subject: [PATCH 07/34] Fix demo files --- packages/demo/src/js/components/AnimateTest.js | 2 +- .../BreakPointManagerDemo/BreakpointManagerDemoBase.js | 5 +++-- packages/demo/src/js/components/Cursor.js | 4 ++-- packages/demo/src/js/components/ScrollToDemo.js | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/demo/src/js/components/AnimateTest.js b/packages/demo/src/js/components/AnimateTest.js index 8637bbb67..fc63eb96d 100644 --- a/packages/demo/src/js/components/AnimateTest.js +++ b/packages/demo/src/js/components/AnimateTest.js @@ -59,7 +59,7 @@ export default class AnimateTest extends Base { /** * */ - mounted() { + async mounted() { this.animate = animate( this.$refs.target, this.$options.steps.map((step) => { diff --git a/packages/demo/src/js/components/BreakPointManagerDemo/BreakpointManagerDemoBase.js b/packages/demo/src/js/components/BreakPointManagerDemo/BreakpointManagerDemoBase.js index 18e02b987..789014f3a 100644 --- a/packages/demo/src/js/components/BreakPointManagerDemo/BreakpointManagerDemoBase.js +++ b/packages/demo/src/js/components/BreakPointManagerDemo/BreakpointManagerDemoBase.js @@ -5,6 +5,7 @@ import { Base } from '@studiometa/js-toolkit'; */ export default class BreakpointManagerDemoBase extends Base { static config = { + name: 'BreakpointManagerDemoBase', log: false, refs: ['content'], }; @@ -12,7 +13,7 @@ export default class BreakpointManagerDemoBase extends Base { /** * */ - mounted() { + async mounted() { this.$log('mounted'); this.$refs.content.innerHTML = this.$options.name; } @@ -20,7 +21,7 @@ export default class BreakpointManagerDemoBase extends Base { /** * */ - destroyed() { + async destroyed() { this.$log('destroyed'); } diff --git a/packages/demo/src/js/components/Cursor.js b/packages/demo/src/js/components/Cursor.js index 5fcd542ac..104b5f1f0 100644 --- a/packages/demo/src/js/components/Cursor.js +++ b/packages/demo/src/js/components/Cursor.js @@ -24,7 +24,7 @@ export default class Cursor extends Base { * Update position and scale based on pointer state. * @param {import('@studiometa/js-toolkit').PointerServiceProps} props */ - moved({ x, y, isDown }) { + async moved({ x, y, isDown }) { this.x(x); this.y(y); this.scale(isDown ? 0.75 : 1); @@ -35,7 +35,7 @@ export default class Cursor extends Base { * Update the element's transform on each frame. * @returns {() => void} A function to be executed on the "write" step of the DOM Scheduler */ - ticked() { + async ticked() { return () => { transform(this.$el, { x: this.x(), y: this.y(), scale: this.scale() }); }; diff --git a/packages/demo/src/js/components/ScrollToDemo.js b/packages/demo/src/js/components/ScrollToDemo.js index 143db74d6..24eeb72dd 100644 --- a/packages/demo/src/js/components/ScrollToDemo.js +++ b/packages/demo/src/js/components/ScrollToDemo.js @@ -14,7 +14,7 @@ export default class ScrollToDemo extends Base { /** * */ - mounted() { + async mounted() { this.$log('mounted'); } From de8df3e65eec14a0bc3e35b23ac2073ec576ffcd Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 18:39:39 +0200 Subject: [PATCH 08/34] Fix no-deprecated-properties: $parent and $root are deprecated, not removed Co-Authored-By: Claude Sonnet 4.6 --- packages/oxlint-plugin-js-toolkit/README.md | 2 +- .../rules/no-deprecated-properties.test.ts | 4 ++-- .../rules/no-deprecated-properties.ts | 15 +++++---------- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/oxlint-plugin-js-toolkit/README.md b/packages/oxlint-plugin-js-toolkit/README.md index d14e5cbdb..8e7f69400 100644 --- a/packages/oxlint-plugin-js-toolkit/README.md +++ b/packages/oxlint-plugin-js-toolkit/README.md @@ -92,7 +92,7 @@ export default [ | Rule | Description | Recommended | Fixable | |------|-------------|-------------|---------| -| `js-toolkit/no-deprecated-properties` | Disallows removed (`$parent`, `$root`) and deprecated (`$children`) properties. Use `$closest()` and `$query()`/`$queryAll()` instead. | error | | +| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` and `$query()`/`$queryAll()` instead. | error | | | `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | warn | | | `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | error | | | `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | warn | | diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts index db8ae99b0..1d6412079 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts @@ -27,14 +27,14 @@ describe('no-deprecated-properties', () => { class Slider extends Base { mounted() { return this.$parent; } }`, - errors: [{ messageId: 'removed' }], + errors: [{ messageId: 'deprecated' }], }, { code: `import { Base } from '@studiometa/js-toolkit'; class Slider extends Base { mounted() { return this.$root; } }`, - errors: [{ messageId: 'removed' }], + errors: [{ messageId: 'deprecated' }], }, ], }); diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts index b5a533bd7..3aefeba2d 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts @@ -1,11 +1,8 @@ import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.js'; -const REMOVED = new Map([ +const DEPRECATED = new Map([ ['$parent', '$closest()'], ['$root', '$closest()'], -]); - -const DEPRECATED = new Map([ ['$children', '$query() or $queryAll()'], ]); @@ -13,10 +10,9 @@ export const noDeprecatedProperties = { meta: { type: 'problem', docs: { - description: 'Disallow removed or deprecated Base properties ($parent, $children, $root)', + description: 'Disallow deprecated Base properties ($parent, $children, $root)', }, messages: { - removed: '"{{name}}" was removed in v3. Use {{replacement}} instead.', deprecated: '"{{name}}" is deprecated. Use {{replacement}} instead.', }, }, @@ -24,7 +20,7 @@ export const noDeprecatedProperties = { return { MemberExpression(node: Node) { const prop = node.property?.name; - if (!prop || (!REMOVED.has(prop) && !DEPRECATED.has(prop))) return; + if (!prop || !DEPRECATED.has(prop)) return; // Only flag `this.$parent` etc. — not arbitrary object access if (node.object?.type !== 'ThisExpression') return; @@ -36,11 +32,10 @@ export const noDeprecatedProperties = { const enclosingClass = findEnclosingClass(ancestors); if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; - const messageId = REMOVED.has(prop) ? 'removed' : 'deprecated'; - const replacement = REMOVED.get(prop) ?? DEPRECATED.get(prop); + const replacement = DEPRECATED.get(prop); context.report({ node, - messageId, + messageId: 'deprecated', data: { name: prop, replacement }, }); }, From 59c9b3cddb9638cad670a8d372b0beaee3318883 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 18:42:06 +0200 Subject: [PATCH 09/34] Fix no-deprecated-properties: $queryAll() does not exist, use $query() Co-Authored-By: Claude Sonnet 4.6 --- packages/oxlint-plugin-js-toolkit/README.md | 2 +- .../oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/oxlint-plugin-js-toolkit/README.md b/packages/oxlint-plugin-js-toolkit/README.md index 8e7f69400..99ab28232 100644 --- a/packages/oxlint-plugin-js-toolkit/README.md +++ b/packages/oxlint-plugin-js-toolkit/README.md @@ -92,7 +92,7 @@ export default [ | Rule | Description | Recommended | Fixable | |------|-------------|-------------|---------| -| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` and `$query()`/`$queryAll()` instead. | error | | +| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. | error | | | `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | warn | | | `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | error | | | `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | warn | | diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts index 3aefeba2d..5783c39be 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts @@ -3,7 +3,7 @@ import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from const DEPRECATED = new Map([ ['$parent', '$closest()'], ['$root', '$closest()'], - ['$children', '$query() or $queryAll()'], + ['$children', '$query()'], ]); export const noDeprecatedProperties = { From a1e734390abe34d6d925f9003f42e175a08eadda Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 18:59:52 +0200 Subject: [PATCH 10/34] Fix using the oxlint-plugin in CI --- .oxlintrc.json | 2 +- packages/oxlint-plugin-js-toolkit/index.ts | 2 +- .../rules/async-lifecycle-methods.test.ts | 4 +-- .../rules/async-lifecycle-methods.ts | 2 +- .../oxlint-plugin-js-toolkit/rules/index.ts | 26 +++++++++---------- .../rules/no-create-app.test.ts | 4 +-- .../rules/no-create-app.ts | 2 +- .../rules/no-deprecated-properties.test.ts | 4 +-- .../rules/no-deprecated-properties.ts | 2 +- .../rules/no-dispatch-event.test.ts | 4 +-- .../rules/no-dispatch-event.ts | 2 +- .../rules/no-event-listener-methods.test.ts | 4 +-- .../rules/no-event-listener-methods.ts | 2 +- .../rules/no-shadow-dom.test.ts | 4 +-- .../rules/no-shadow-dom.ts | 2 +- .../rules/on-global-handler-prefix.test.ts | 4 +-- .../rules/on-global-handler-prefix.ts | 2 +- .../rules/on-handler-naming.test.ts | 4 +-- .../rules/on-handler-naming.ts | 2 +- .../rules/options-camel-case.test.ts | 4 +-- .../rules/options-camel-case.ts | 2 +- .../rules/refs-camel-case.test.ts | 4 +-- .../rules/refs-camel-case.ts | 2 +- .../rules/refs-plural-multiple.test.ts | 4 +-- .../rules/refs-plural-multiple.ts | 2 +- .../require-config-name-pascal-case.test.ts | 4 +-- .../rules/require-config-name-pascal-case.ts | 2 +- .../rules/require-config.test.ts | 4 +-- .../rules/require-config.ts | 2 +- .../oxlint-plugin-js-toolkit/tsconfig.json | 3 ++- 30 files changed, 56 insertions(+), 55 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 251518b3a..e7306a768 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,5 +1,5 @@ { - "jsPlugins": [{ "name": "js-toolkit", "specifier": "@studiometa/oxlint-plugin-js-toolkit" }], + "jsPlugins": [{ "name": "js-toolkit", "specifier": "./packages/oxlint-plugin-js-toolkit/index.ts" }], "rules": { "typescript/no-this-alias": [ "error", diff --git a/packages/oxlint-plugin-js-toolkit/index.ts b/packages/oxlint-plugin-js-toolkit/index.ts index 73e86298d..e0acf2ed7 100644 --- a/packages/oxlint-plugin-js-toolkit/index.ts +++ b/packages/oxlint-plugin-js-toolkit/index.ts @@ -12,7 +12,7 @@ import { noCreateApp, noEventListenerMethods, refsPluralMultiple, -} from './rules/index.js'; +} from './rules/index.ts'; const PLUGIN_NAME = 'js-toolkit'; diff --git a/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts b/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts index 9d03af7e6..c82043c0d 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { asyncLifecycleMethods } from './async-lifecycle-methods.js'; +import { tester } from '../utils/rule-tester.ts'; +import { asyncLifecycleMethods } from './async-lifecycle-methods.ts'; describe('async-lifecycle-methods', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts b/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts index 5215315fb..5249b23e6 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts @@ -1,4 +1,4 @@ -import { isBaseSubclass, LIFECYCLE_METHODS, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.js'; +import { isBaseSubclass, LIFECYCLE_METHODS, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.ts'; export const asyncLifecycleMethods = { meta: { diff --git a/packages/oxlint-plugin-js-toolkit/rules/index.ts b/packages/oxlint-plugin-js-toolkit/rules/index.ts index d7a511b4d..d2048cb1e 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/index.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/index.ts @@ -1,13 +1,13 @@ -export { requireConfig } from './require-config.js'; -export { requireConfigNamePascalCase } from './require-config-name-pascal-case.js'; -export { refsCamelCase } from './refs-camel-case.js'; -export { optionsCamelCase } from './options-camel-case.js'; -export { asyncLifecycleMethods } from './async-lifecycle-methods.js'; -export { onHandlerNaming } from './on-handler-naming.js'; -export { onGlobalHandlerPrefix } from './on-global-handler-prefix.js'; -export { noDeprecatedProperties } from './no-deprecated-properties.js'; -export { noDispatchEvent } from './no-dispatch-event.js'; -export { noShadowDom } from './no-shadow-dom.js'; -export { noCreateApp } from './no-create-app.js'; -export { noEventListenerMethods } from './no-event-listener-methods.js'; -export { refsPluralMultiple } from './refs-plural-multiple.js'; +export { requireConfig } from './require-config.ts'; +export { requireConfigNamePascalCase } from './require-config-name-pascal-case.ts'; +export { refsCamelCase } from './refs-camel-case.ts'; +export { optionsCamelCase } from './options-camel-case.ts'; +export { asyncLifecycleMethods } from './async-lifecycle-methods.ts'; +export { onHandlerNaming } from './on-handler-naming.ts'; +export { onGlobalHandlerPrefix } from './on-global-handler-prefix.ts'; +export { noDeprecatedProperties } from './no-deprecated-properties.ts'; +export { noDispatchEvent } from './no-dispatch-event.ts'; +export { noShadowDom } from './no-shadow-dom.ts'; +export { noCreateApp } from './no-create-app.ts'; +export { noEventListenerMethods } from './no-event-listener-methods.ts'; +export { refsPluralMultiple } from './refs-plural-multiple.ts'; diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-create-app.test.ts b/packages/oxlint-plugin-js-toolkit/rules/no-create-app.test.ts index 3095caa83..cf390d4a7 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-create-app.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-create-app.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { noCreateApp } from './no-create-app.js'; +import { tester } from '../utils/rule-tester.ts'; +import { noCreateApp } from './no-create-app.ts'; describe('no-create-app', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts b/packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts index 10bef8ef3..5eab00f9a 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts @@ -1,4 +1,4 @@ -import { type Node, type RuleContext } from '../utils/ast.js'; +import { type Node, type RuleContext } from '../utils/ast.ts'; export const noCreateApp = { meta: { diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts index 1d6412079..b6e389e18 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { noDeprecatedProperties } from './no-deprecated-properties.js'; +import { tester } from '../utils/rule-tester.ts'; +import { noDeprecatedProperties } from './no-deprecated-properties.ts'; describe('no-deprecated-properties', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts index 5783c39be..a9ad88c85 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts @@ -1,4 +1,4 @@ -import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.js'; +import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.ts'; const DEPRECATED = new Map([ ['$parent', '$closest()'], diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.test.ts b/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.test.ts index 9568350ae..fa3a97c8a 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { noDispatchEvent } from './no-dispatch-event.js'; +import { tester } from '../utils/rule-tester.ts'; +import { noDispatchEvent } from './no-dispatch-event.ts'; describe('no-dispatch-event', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.ts b/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.ts index 4163894b3..88f3be02f 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.ts @@ -1,4 +1,4 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.js'; +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; export const noDispatchEvent = { meta: { diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.test.ts b/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.test.ts index 0700386df..8610a81cc 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { noEventListenerMethods } from './no-event-listener-methods.js'; +import { tester } from '../utils/rule-tester.ts'; +import { noEventListenerMethods } from './no-event-listener-methods.ts'; describe('no-event-listener-methods', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.ts b/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.ts index f624a167a..88a4db410 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.ts @@ -1,4 +1,4 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.js'; +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; const FORBIDDEN = new Set(['addEventListener', 'removeEventListener']); diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.test.ts b/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.test.ts index d987c6980..21be15cee 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { noShadowDom } from './no-shadow-dom.js'; +import { tester } from '../utils/rule-tester.ts'; +import { noShadowDom } from './no-shadow-dom.ts'; describe('no-shadow-dom', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.ts b/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.ts index 4c3981dd5..448dbc504 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.ts @@ -1,4 +1,4 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.js'; +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; export const noShadowDom = { meta: { diff --git a/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.test.ts b/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.test.ts index 4dc241dc8..1b37c8416 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { onGlobalHandlerPrefix } from './on-global-handler-prefix.js'; +import { tester } from '../utils/rule-tester.ts'; +import { onGlobalHandlerPrefix } from './on-global-handler-prefix.ts'; describe('on-global-handler-prefix', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.ts b/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.ts index 95929a71e..a7af6fcb7 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.ts @@ -1,4 +1,4 @@ -import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.js'; +import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.ts'; // Events that only make sense on window/document, never on a DOM element. // Pointer, keyboard, and form events are intentionally excluded — they can all diff --git a/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.test.ts b/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.test.ts index 606240f91..daa41a590 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { onHandlerNaming } from './on-handler-naming.js'; +import { tester } from '../utils/rule-tester.ts'; +import { onHandlerNaming } from './on-handler-naming.ts'; describe('on-handler-naming', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.ts b/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.ts index 249ad890b..139f98e09 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.ts @@ -1,4 +1,4 @@ -import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.js'; +import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.ts'; // onXxxYyy — the part after "on" must start with an uppercase letter const ON_HANDLER_RE = /^on[A-Z][a-zA-Z0-9]*$/; diff --git a/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts b/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts index e8950b31b..78d2c6220 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { optionsCamelCase } from './options-camel-case.js'; +import { tester } from '../utils/rule-tester.ts'; +import { optionsCamelCase } from './options-camel-case.ts'; describe('options-camel-case', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts b/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts index b0f73cba0..198daad83 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts @@ -1,4 +1,4 @@ -import { isBaseSubclass, isCamelCase, toCamelCase, type Node, type RuleContext } from '../utils/ast.js'; +import { isBaseSubclass, isCamelCase, toCamelCase, type Node, type RuleContext } from '../utils/ast.ts'; export const optionsCamelCase = { meta: { diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts b/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts index c7b1053e9..c46fa8e09 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { refsCamelCase } from './refs-camel-case.js'; +import { tester } from '../utils/rule-tester.ts'; +import { refsCamelCase } from './refs-camel-case.ts'; describe('refs-camel-case', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts b/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts index e3b7d7e64..4f4e2cbe2 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts @@ -1,4 +1,4 @@ -import { isBaseSubclass, isCamelCase, toCamelCase, type Node, type RuleContext } from '../utils/ast.js'; +import { isBaseSubclass, isCamelCase, toCamelCase, type Node, type RuleContext } from '../utils/ast.ts'; export const refsCamelCase = { meta: { diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.test.ts b/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.test.ts index d551ceaab..042276c2a 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { refsPluralMultiple } from './refs-plural-multiple.js'; +import { tester } from '../utils/rule-tester.ts'; +import { refsPluralMultiple } from './refs-plural-multiple.ts'; describe('refs-plural-multiple', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.ts b/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.ts index 82bb815de..41c644a06 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.ts @@ -1,4 +1,4 @@ -import { isBaseSubclass, type Node, type RuleContext } from '../utils/ast.js'; +import { isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; export const refsPluralMultiple = { meta: { diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts b/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts index 5087619fe..bd1962836 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { requireConfigNamePascalCase } from './require-config-name-pascal-case.js'; +import { tester } from '../utils/rule-tester.ts'; +import { requireConfigNamePascalCase } from './require-config-name-pascal-case.ts'; describe('require-config-name-pascal-case', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts b/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts index 4ccca4bca..1c88c3e54 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts @@ -1,4 +1,4 @@ -import { isBaseSubclass, isPascalCase, toPascalCase, type Node, type RuleContext } from '../utils/ast.js'; +import { isBaseSubclass, isPascalCase, toPascalCase, type Node, type RuleContext } from '../utils/ast.ts'; export const requireConfigNamePascalCase = { meta: { diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config.test.ts b/packages/oxlint-plugin-js-toolkit/rules/require-config.test.ts index e8cb963b1..564ca07fc 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/require-config.test.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/require-config.test.ts @@ -1,6 +1,6 @@ import { describe, it } from 'vitest'; -import { tester } from '../utils/rule-tester.js'; -import { requireConfig } from './require-config.js'; +import { tester } from '../utils/rule-tester.ts'; +import { requireConfig } from './require-config.ts'; describe('require-config', () => { it('passes and fails correctly', () => { diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config.ts b/packages/oxlint-plugin-js-toolkit/rules/require-config.ts index 282eeb7cd..bf3b8de28 100644 --- a/packages/oxlint-plugin-js-toolkit/rules/require-config.ts +++ b/packages/oxlint-plugin-js-toolkit/rules/require-config.ts @@ -1,4 +1,4 @@ -import { isBaseSubclass, type Node, type RuleContext } from '../utils/ast.js'; +import { isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; export const requireConfig = { meta: { diff --git a/packages/oxlint-plugin-js-toolkit/tsconfig.json b/packages/oxlint-plugin-js-toolkit/tsconfig.json index 030002ecb..7df88ddbe 100644 --- a/packages/oxlint-plugin-js-toolkit/tsconfig.json +++ b/packages/oxlint-plugin-js-toolkit/tsconfig.json @@ -5,7 +5,8 @@ "outDir": "./dist", "noEmit": false, "emitDeclarationOnly": true, - "declaration": true + "declaration": true, + "allowImportingTsExtensions": true }, "include": ["./**/*.ts"], "exclude": ["dist", "node_modules", "**/*.test.ts"] From bc69b1d93ae6c21ac2295e13153351bedfab6167 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 19:17:08 +0200 Subject: [PATCH 11/34] Move oxlint-plugin source files to src/ --- packages/oxlint-plugin-js-toolkit/scripts/build.js | 4 +++- packages/oxlint-plugin-js-toolkit/{ => src}/index.ts | 0 .../{ => src}/rules/async-lifecycle-methods.test.ts | 0 .../{ => src}/rules/async-lifecycle-methods.ts | 0 packages/oxlint-plugin-js-toolkit/{ => src}/rules/index.ts | 0 .../{ => src}/rules/no-create-app.test.ts | 0 .../{ => src}/rules/no-create-app.ts | 0 .../{ => src}/rules/no-deprecated-properties.test.ts | 0 .../{ => src}/rules/no-deprecated-properties.ts | 0 .../{ => src}/rules/no-dispatch-event.test.ts | 0 .../{ => src}/rules/no-dispatch-event.ts | 0 .../{ => src}/rules/no-event-listener-methods.test.ts | 0 .../{ => src}/rules/no-event-listener-methods.ts | 0 .../{ => src}/rules/no-shadow-dom.test.ts | 0 .../{ => src}/rules/no-shadow-dom.ts | 0 .../{ => src}/rules/on-global-handler-prefix.test.ts | 0 .../{ => src}/rules/on-global-handler-prefix.ts | 0 .../{ => src}/rules/on-handler-naming.test.ts | 0 .../{ => src}/rules/on-handler-naming.ts | 0 .../{ => src}/rules/options-camel-case.test.ts | 0 .../{ => src}/rules/options-camel-case.ts | 0 .../{ => src}/rules/refs-camel-case.test.ts | 0 .../{ => src}/rules/refs-camel-case.ts | 0 .../{ => src}/rules/refs-plural-multiple.test.ts | 0 .../{ => src}/rules/refs-plural-multiple.ts | 0 .../{ => src}/rules/require-config-name-pascal-case.test.ts | 0 .../{ => src}/rules/require-config-name-pascal-case.ts | 0 .../{ => src}/rules/require-config.test.ts | 0 .../{ => src}/rules/require-config.ts | 0 packages/oxlint-plugin-js-toolkit/{ => src}/utils/ast.ts | 0 .../oxlint-plugin-js-toolkit/{ => src}/utils/rule-tester.ts | 0 packages/oxlint-plugin-js-toolkit/tsconfig.json | 4 ++-- packages/oxlint-plugin-js-toolkit/vitest.config.ts | 5 +++++ 33 files changed, 10 insertions(+), 3 deletions(-) rename packages/oxlint-plugin-js-toolkit/{ => src}/index.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/async-lifecycle-methods.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/async-lifecycle-methods.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/index.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/no-create-app.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/no-create-app.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/no-deprecated-properties.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/no-deprecated-properties.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/no-dispatch-event.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/no-dispatch-event.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/no-event-listener-methods.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/no-event-listener-methods.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/no-shadow-dom.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/no-shadow-dom.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/on-global-handler-prefix.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/on-global-handler-prefix.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/on-handler-naming.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/on-handler-naming.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/options-camel-case.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/options-camel-case.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/refs-camel-case.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/refs-camel-case.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/refs-plural-multiple.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/refs-plural-multiple.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/require-config-name-pascal-case.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/require-config-name-pascal-case.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/require-config.test.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/rules/require-config.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/utils/ast.ts (100%) rename packages/oxlint-plugin-js-toolkit/{ => src}/utils/rule-tester.ts (100%) diff --git a/packages/oxlint-plugin-js-toolkit/scripts/build.js b/packages/oxlint-plugin-js-toolkit/scripts/build.js index e59959d72..e48b501d1 100644 --- a/packages/oxlint-plugin-js-toolkit/scripts/build.js +++ b/packages/oxlint-plugin-js-toolkit/scripts/build.js @@ -1,13 +1,15 @@ import { resolve, dirname } from 'node:path'; import { execSync } from 'node:child_process'; +import { rmSync } from 'node:fs'; import esbuild from 'esbuild'; const root = resolve(dirname(new URL(import.meta.url).pathname), '..'); +rmSync(resolve(root, 'dist'), { recursive: true, force: true }); console.log('Building @studiometa/oxlint-plugin-js-toolkit...'); const { errors, warnings } = await esbuild.build({ - entryPoints: [resolve(root, 'index.ts')], + entryPoints: [resolve(root, 'src/index.ts')], bundle: true, write: true, outdir: resolve(root, 'dist'), diff --git a/packages/oxlint-plugin-js-toolkit/index.ts b/packages/oxlint-plugin-js-toolkit/src/index.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/index.ts rename to packages/oxlint-plugin-js-toolkit/src/index.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/async-lifecycle-methods.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/async-lifecycle-methods.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts b/packages/oxlint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/async-lifecycle-methods.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/index.ts b/packages/oxlint-plugin-js-toolkit/src/rules/index.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/index.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/index.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-create-app.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-create-app.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/no-create-app.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/no-create-app.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-create-app.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/no-create-app.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/no-create-app.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-deprecated-properties.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/no-deprecated-properties.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/no-deprecated-properties.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-dispatch-event.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/no-dispatch-event.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-dispatch-event.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/no-dispatch-event.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/no-dispatch-event.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/no-event-listener-methods.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-shadow-dom.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/no-shadow-dom.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-shadow-dom.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/no-shadow-dom.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/no-shadow-dom.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/on-global-handler-prefix.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/on-global-handler-prefix.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.ts b/packages/oxlint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/on-global-handler-prefix.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/on-handler-naming.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/on-handler-naming.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.ts b/packages/oxlint-plugin-js-toolkit/src/rules/on-handler-naming.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/on-handler-naming.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/on-handler-naming.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/options-camel-case.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/options-camel-case.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/options-camel-case.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts b/packages/oxlint-plugin-js-toolkit/src/rules/options-camel-case.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/options-camel-case.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/options-camel-case.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/refs-camel-case.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/refs-camel-case.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts b/packages/oxlint-plugin-js-toolkit/src/rules/refs-camel-case.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/refs-camel-case.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/refs-camel-case.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/refs-plural-multiple.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/refs-plural-multiple.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.ts b/packages/oxlint-plugin-js-toolkit/src/rules/refs-plural-multiple.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/refs-plural-multiple.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/refs-plural-multiple.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts b/packages/oxlint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/require-config-name-pascal-case.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/require-config.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/require-config.test.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/require-config.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/rules/require-config.ts b/packages/oxlint-plugin-js-toolkit/src/rules/require-config.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/rules/require-config.ts rename to packages/oxlint-plugin-js-toolkit/src/rules/require-config.ts diff --git a/packages/oxlint-plugin-js-toolkit/utils/ast.ts b/packages/oxlint-plugin-js-toolkit/src/utils/ast.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/utils/ast.ts rename to packages/oxlint-plugin-js-toolkit/src/utils/ast.ts diff --git a/packages/oxlint-plugin-js-toolkit/utils/rule-tester.ts b/packages/oxlint-plugin-js-toolkit/src/utils/rule-tester.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/utils/rule-tester.ts rename to packages/oxlint-plugin-js-toolkit/src/utils/rule-tester.ts diff --git a/packages/oxlint-plugin-js-toolkit/tsconfig.json b/packages/oxlint-plugin-js-toolkit/tsconfig.json index 7df88ddbe..b99b84255 100644 --- a/packages/oxlint-plugin-js-toolkit/tsconfig.json +++ b/packages/oxlint-plugin-js-toolkit/tsconfig.json @@ -1,13 +1,13 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "rootDir": ".", + "rootDir": "./src", "outDir": "./dist", "noEmit": false, "emitDeclarationOnly": true, "declaration": true, "allowImportingTsExtensions": true }, - "include": ["./**/*.ts"], + "include": ["./src/**/*.ts"], "exclude": ["dist", "node_modules", "**/*.test.ts"] } diff --git a/packages/oxlint-plugin-js-toolkit/vitest.config.ts b/packages/oxlint-plugin-js-toolkit/vitest.config.ts index 0f2a047b2..44cb63826 100644 --- a/packages/oxlint-plugin-js-toolkit/vitest.config.ts +++ b/packages/oxlint-plugin-js-toolkit/vitest.config.ts @@ -4,5 +4,10 @@ export default defineConfig({ test: { include: ['**/*.test.ts'], exclude: ['dist', 'node_modules'], + coverage: { + provider: 'v8', + include: ['src/rules/**/*.ts', 'src/utils/**/*.ts'], + exclude: ['**/*.test.ts'], + }, }, }); From c7c49d1aaec7e4cc38a4b9342a21846d7ccbb1e9 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 19:17:11 +0200 Subject: [PATCH 12/34] Run oxlint-plugin tests in CI with coverage --- .github/workflows/tests.yml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c352123fd..d8c07cab7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,6 +39,21 @@ jobs: unit: runs-on: macos-latest + strategy: + matrix: + include: + - package: js-toolkit + working-directory: . + test-command: npm run test -- -- --retry=3 --coverage.enabled + coverage-file: ./packages/coverage/clover.xml + - package: oxlint-plugin-js-toolkit + working-directory: packages/oxlint-plugin-js-toolkit + test-command: npm test -- --retry=3 --coverage.enabled + coverage-file: packages/oxlint-plugin-js-toolkit/coverage/clover.xml + name: unit (${{ matrix.package }}) + defaults: + run: + working-directory: ${{ matrix.working-directory }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -48,12 +63,12 @@ jobs: - name: Install dependencies run: npm install - name: Run tests - run: npm run test -- -- --retry=3 --coverage.enabled + run: ${{ matrix.test-command }} - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: - files: ./packages/coverage/clover.xml - flags: unittests + files: ${{ matrix.coverage-file }} + flags: ${{ matrix.package }} fail_ci_if_error: false verbose: true env: From d6be0d26f51e6a17a62d081eb2db5267fc95d2b1 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 19:17:14 +0200 Subject: [PATCH 13/34] Publish oxlint-plugin from package root --- .github/workflows/publish.yml | 28 +++++++++++++++++-- .../oxlint-plugin-js-toolkit/package.json | 8 +++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 183c80443..85a3d3465 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -38,6 +38,10 @@ jobs: - run: npm run build + - name: Build oxlint-plugin + run: npm run build + working-directory: packages/oxlint-plugin-js-toolkit + - name: Test run: npm run test -- -- --retry=3 --coverage.enabled @@ -45,8 +49,21 @@ jobs: uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} - directory: ./packages/coverage - flags: unittests + files: ./packages/coverage/clover.xml + flags: js-toolkit + fail_ci_if_error: false + verbose: true + + - name: Test oxlint-plugin + run: npm test -- --retry=3 --coverage.enabled + working-directory: packages/oxlint-plugin-js-toolkit + + - name: Upload oxlint-plugin coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: packages/oxlint-plugin-js-toolkit/coverage/clover.xml + flags: oxlint-plugin-js-toolkit fail_ci_if_error: false verbose: true @@ -69,6 +86,13 @@ jobs: tag: ${{ env.NPM_TAG }} token: ${{ secrets.NPM_TOKEN }} + - uses: JS-DevTools/npm-publish@v3 + with: + provenance: true + package: packages/oxlint-plugin-js-toolkit/ + tag: ${{ env.NPM_TAG }} + token: ${{ secrets.NPM_TOKEN }} + - uses: ncipollo/release-action@v1 with: tag: ${{ github.ref }} diff --git a/packages/oxlint-plugin-js-toolkit/package.json b/packages/oxlint-plugin-js-toolkit/package.json index e5194f3a5..cb2ad1ca5 100644 --- a/packages/oxlint-plugin-js-toolkit/package.json +++ b/packages/oxlint-plugin-js-toolkit/package.json @@ -13,6 +13,11 @@ "license": "MIT", "type": "module", "sideEffects": false, + "files": [ + "dist", + "README.md", + "LICENSE" + ], "main": "./dist/index.js", "module": "./dist/index.js", "types": "./dist/index.d.ts", @@ -26,7 +31,8 @@ "scripts": { "build": "node scripts/build.js", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "lint:types": "tsgo --build tsconfig.json" }, "devDependencies": { "@vitest/coverage-v8": "4.1.2", From 3d14af34d53e2c3566996c4eff4fd7bfdc418958 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 19:22:36 +0200 Subject: [PATCH 14/34] Add names to CI --- .github/workflows/publish.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 85a3d3465..a0191845b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -36,13 +36,14 @@ jobs: - run: npm install --no-audit --no-fund - - run: npm run build + - name: Build js-toolkit + run: npm run build - name: Build oxlint-plugin run: npm run build working-directory: packages/oxlint-plugin-js-toolkit - - name: Test + - name: Test js-toolkit run: npm run test -- -- --retry=3 --coverage.enabled - name: Upload coverage to Codecov From 49ad9a099d02e7cc8b1d6c0017f280534a367b1e Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 19:23:45 +0200 Subject: [PATCH 15/34] Add badges to readme --- packages/oxlint-plugin-js-toolkit/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/oxlint-plugin-js-toolkit/README.md b/packages/oxlint-plugin-js-toolkit/README.md index 99ab28232..2a19796d3 100644 --- a/packages/oxlint-plugin-js-toolkit/README.md +++ b/packages/oxlint-plugin-js-toolkit/README.md @@ -1,5 +1,11 @@ # @studiometa/oxlint-plugin-js-toolkit +[![NPM Version](https://img.shields.io/npm/v/@studiometa/oxlint-plugin-js-toolkit.svg?style=flat&colorB=3e63dd&colorA=414853)](https://www.npmjs.com/package/@studiometa/oxlint-plugin-js-toolkit/) +[![Downloads](https://img.shields.io/npm/dm/@studiometa/oxlint-plugin-js-toolkit?style=flat&colorB=3e63dd&colorA=414853)](https://www.npmjs.com/package/@studiometa/oxlint-plugin-js-toolkit/) +[![Size](https://img.shields.io/bundlephobia/minzip/@studiometa/oxlint-plugin-js-toolkit?style=flat&colorB=3e63dd&colorA=414853&label=size)](https://bundlephobia.com/package/@studiometa/js-toolkit) +[![Dependency Status](https://img.shields.io/librariesio/release/npm/oxlint-plugin-js-toolkit?style=flat&colorB=3e63dd&colorA=414853)](https://david-dm.org/studiometa/js-toolkit) +![Codecov](https://img.shields.io/codecov/c/github/studiometa/js-toolkit?style=flat&colorB=3e63dd&colorA=414853) + Oxlint/ESLint plugin enforcing best practices for [@studiometa/js-toolkit](https://js-toolkit.studiometa.dev). ## Installation From adbd934b8167b8bc4b520cc3b7d799fffaf3aa8e Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 19:34:09 +0200 Subject: [PATCH 16/34] Add oxlint-plugin-js-toolkit to README and docs Co-Authored-By: Claude Sonnet 4.6 --- README.md | 10 ++ packages/docs/.vitepress/config.ts | 1 + packages/docs/guide/going-further/linting.md | 99 ++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 packages/docs/guide/going-further/linting.md diff --git a/README.md b/README.md index f22d6aff7..a9f8d2dcf 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,16 @@ This project is a JavaScript micro-framework (along with its utility functions) Visit [js-toolkit.studiometa.dev](https://js-toolkit.studiometa.dev) to learn more, jump directly to [ui.studiometa.dev](https://ui.studiometa.dev) to discover existing components, or open [the playground](https://ui.studiometa.dev/-/play/) to test it live. +## Linting + +The [`@studiometa/oxlint-plugin-js-toolkit`](./packages/oxlint-plugin-js-toolkit/) package provides an Oxlint/ESLint plugin that enforces best practices when writing components with this framework. + +```bash +npm install --save-dev @studiometa/oxlint-plugin-js-toolkit +``` + +See the [plugin README](./packages/oxlint-plugin-js-toolkit/README.md) or the [linting guide](https://js-toolkit.studiometa.dev/guide/going-further/linting.html) for configuration details. + ## Quick overview This framework lets you define components as classes, and bind them to the DOM with `data-…` attributes. For example, here is how a `Counter` component would be defined in JavaScript: diff --git a/packages/docs/.vitepress/config.ts b/packages/docs/.vitepress/config.ts index 2c9586152..9e32c6926 100644 --- a/packages/docs/.vitepress/config.ts +++ b/packages/docs/.vitepress/config.ts @@ -93,6 +93,7 @@ function getGuideSidebar() { keywords: ['types', 'typings', 'typescript', 'jsdoc'], }, { text: 'Custom Services', link: '/guide/going-further/registering-new-services.html' }, + { text: 'Linting', link: '/guide/going-further/linting.html' }, ], }, { diff --git a/packages/docs/guide/going-further/linting.md b/packages/docs/guide/going-further/linting.md new file mode 100644 index 000000000..1f42d1a54 --- /dev/null +++ b/packages/docs/guide/going-further/linting.md @@ -0,0 +1,99 @@ +# Linting + +The [`@studiometa/oxlint-plugin-js-toolkit`](https://www.npmjs.com/package/@studiometa/oxlint-plugin-js-toolkit) package provides an Oxlint/ESLint plugin that enforces best practices when writing components with this framework. + +## Installation + +```bash +npm install --save-dev @studiometa/oxlint-plugin-js-toolkit +``` + +## Configuration + +### Oxlint + +Add the plugin to your `.oxlintrc.json`: + +```json +{ + "jsPlugins": ["@studiometa/oxlint-plugin-js-toolkit"], + "rules": { + "js-toolkit/require-config": "error", + "js-toolkit/require-config-name-pascal-case": "error", + "js-toolkit/refs-camel-case": "error", + "js-toolkit/refs-plural-multiple": "error", + "js-toolkit/options-camel-case": "error", + "js-toolkit/async-lifecycle-methods": "error", + "js-toolkit/on-handler-naming": "error", + "js-toolkit/on-global-handler-prefix": "warn", + "js-toolkit/no-deprecated-properties": "error", + "js-toolkit/no-dispatch-event": "warn", + "js-toolkit/no-shadow-dom": "error", + "js-toolkit/no-create-app": "warn", + "js-toolkit/no-event-listener-methods": "error" + } +} +``` + +### ESLint + +Add the recommended config to your `eslint.config.js` (ESLint v9 flat config): + +```js +import jsToolkit from '@studiometa/oxlint-plugin-js-toolkit'; + +export default [ + jsToolkit.configs.recommended, + // ...your other config +]; +``` + +To customise individual rule severities, add an override entry after the recommended config: + +```js +import jsToolkit from '@studiometa/oxlint-plugin-js-toolkit'; + +export default [ + jsToolkit.configs.recommended, + { + rules: { + 'js-toolkit/no-create-app': 'error', + }, + }, +]; +``` + +## Rules + +### Class structure + +| Rule | Description | Fixable | +|------|-------------|---------| +| `js-toolkit/require-config` | Requires a `static config` property with a `name` on every class extending `Base`. | ❌ | +| `js-toolkit/require-config-name-pascal-case` | Requires `config.name` to be PascalCase. | 🔧 | +| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | 🔧 | +| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | ❌ | +| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | 🔧 | + +### Lifecycle methods + +| Rule | Description | Fixable | +|------|-------------|---------| +| `js-toolkit/async-lifecycle-methods` | Requires lifecycle methods (`mounted`, `destroyed`, `updated`, `terminated`, `ticked`, `scrolled`, `resized`, `moved`, `loaded`, `keyed`) to be declared `async`. | 🔧 | + +### Event handlers + +| Rule | Description | Fixable | +|------|-------------|---------| +| `js-toolkit/on-handler-naming` | Requires event handler methods to follow the `onXxxYyy` camelCase convention. | ❌ | +| `js-toolkit/on-global-handler-prefix` | Requires handlers for window-only events (e.g. `resize`) to use the `onWindow` or `onDocument` prefix. | ❌ | + +### Forbidden patterns + +| Rule | Description | Fixable | +|------|-------------|---------| +| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. | ❌ | +| `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | ❌ | +| `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | ❌ | +| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | ❌ | +| `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead — the framework handles binding and cleanup automatically. | ❌ | From 62abb79dac230103b688459cbbf34b4c55343515 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 20:05:23 +0200 Subject: [PATCH 17/34] Use npm ci in CI --- .github/workflows/benchmarks.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/tests.yml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 3e3178818..2fd9907d4 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -24,7 +24,7 @@ jobs: node-version: 24 cache: npm - name: Install dependencies - run: npm install + run: npm ci - name: Run the benchmarks uses: CodSpeedHQ/action@v4 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a0191845b..993308527 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -34,7 +34,7 @@ jobs: key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: ${{ runner.os }}-node- - - run: npm install --no-audit --no-fund + - run: npm ci --no-audit --no-fund - name: Build js-toolkit run: npm run build diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d8c07cab7..355bb9312 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: node-version: 24 cache: npm - name: Install dependencies - run: npm install + run: npm ci --no-audit --no-progress --no-fund - name: Build run: npm run build @@ -31,7 +31,7 @@ jobs: node-version: 24 cache: npm - name: Install dependencies - run: npm install + run: npm ci --no-audit --no-progress --no-fund - name: Run code quality tests run: npm run lint:oxlint - name: Run types tests @@ -61,7 +61,7 @@ jobs: node-version: 24 cache: npm - name: Install dependencies - run: npm install + run: npm ci --no-audit --no-progress --no-fund - name: Run tests run: ${{ matrix.test-command }} - name: Upload coverage to Codecov From 210efd933795ce33643ad763de4d72e63b1a297f Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 20:06:27 +0200 Subject: [PATCH 18/34] Fix oxlint config --- .oxlintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index e7306a768..326eb1fd1 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,5 +1,5 @@ { - "jsPlugins": [{ "name": "js-toolkit", "specifier": "./packages/oxlint-plugin-js-toolkit/index.ts" }], + "jsPlugins": [{ "name": "js-toolkit", "specifier": "./packages/oxlint-plugin-js-toolkit/src/index.ts" }], "rules": { "typescript/no-this-alias": [ "error", From 5c721b605ab9a1faa5e79dab55c3cd526afc34d9 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 20:17:53 +0200 Subject: [PATCH 19/34] Add 7 new rules to oxlint-plugin-js-toolkit - no-deep-utils-import (error, fixable) - refs-no-bracket-access (error, fixable) - no-redundant-with-mount-when-in-view (warn) - prefer-ref-over-query-selector (warn) - require-refs-declared-in-config (error) - no-manual-intersection-observer (warn) - no-manual-mutation-observer (warn) Co-Authored-By: Claude Sonnet 4.6 --- packages/oxlint-plugin-js-toolkit/README.md | 21 +++- .../oxlint-plugin-js-toolkit/src/index.ts | 21 ++++ .../src/rules/index.ts | 7 ++ .../src/rules/no-deep-utils-import.test.ts | 32 ++++++ .../src/rules/no-deep-utils-import.ts | 51 ++++++++++ .../no-manual-intersection-observer.test.ts | 39 ++++++++ .../rules/no-manual-intersection-observer.ts | 28 ++++++ .../rules/no-manual-mutation-observer.test.ts | 39 ++++++++ .../src/rules/no-manual-mutation-observer.ts | 28 ++++++ ...-redundant-with-mount-when-in-view.test.ts | 30 ++++++ .../no-redundant-with-mount-when-in-view.ts | 34 +++++++ .../prefer-ref-over-query-selector.test.ts | 41 ++++++++ .../rules/prefer-ref-over-query-selector.ts | 41 ++++++++ .../src/rules/refs-no-bracket-access.test.ts | 32 ++++++ .../src/rules/refs-no-bracket-access.ts | 49 +++++++++ .../require-refs-declared-in-config.test.ts | 50 ++++++++++ .../rules/require-refs-declared-in-config.ts | 99 +++++++++++++++++++ .../tsconfig.tsbuildinfo | 1 + 18 files changed, 642 insertions(+), 1 deletion(-) create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.test.ts create mode 100644 packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts create mode 100644 packages/oxlint-plugin-js-toolkit/tsconfig.tsbuildinfo diff --git a/packages/oxlint-plugin-js-toolkit/README.md b/packages/oxlint-plugin-js-toolkit/README.md index 2a19796d3..cd8aa1572 100644 --- a/packages/oxlint-plugin-js-toolkit/README.md +++ b/packages/oxlint-plugin-js-toolkit/README.md @@ -36,7 +36,14 @@ Add the plugin to your `.oxlintrc.json`: "js-toolkit/no-dispatch-event": "warn", "js-toolkit/no-shadow-dom": "error", "js-toolkit/no-create-app": "warn", - "js-toolkit/no-event-listener-methods": "error" + "js-toolkit/no-event-listener-methods": "error", + "js-toolkit/no-deep-utils-import": "error", + "js-toolkit/refs-no-bracket-access": "error", + "js-toolkit/no-redundant-with-mount-when-in-view": "warn", + "js-toolkit/prefer-ref-over-query-selector": "warn", + "js-toolkit/require-refs-declared-in-config": "error", + "js-toolkit/no-manual-intersection-observer": "warn", + "js-toolkit/no-manual-mutation-observer": "warn" } } ``` @@ -103,3 +110,15 @@ export default [ | `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | error | | | `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | warn | | | `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead — the framework handles binding and cleanup automatically. | error | | +| `js-toolkit/no-deep-utils-import` | Disallows deep imports from `@studiometa/js-toolkit/utils/*`. Use the public entrypoint `@studiometa/js-toolkit/utils` instead. | error | 🔧 | +| `js-toolkit/no-redundant-with-mount-when-in-view` | Disallows wrapping `withMountWhenInView` inside `withScrolledInView` — the latter already includes the former internally. | warn | | +| `js-toolkit/no-manual-intersection-observer` | Disallows `new IntersectionObserver()` inside `Base` subclasses. Use `withIntersectionObserver` or `withMountWhenInView` decorators instead. | warn | | +| `js-toolkit/no-manual-mutation-observer` | Disallows `new MutationObserver()` inside `Base` subclasses. Use the `withMutation` decorator instead. | warn | | + +### Refs + +| Rule | Description | Recommended | Fixable | +|------|-------------|-------------|---------| +| `js-toolkit/refs-no-bracket-access` | Disallows bracket access with a `[]` suffix on `this.$refs` (e.g. `this.$refs['items[]']`). Rewrites to dot notation camelCase. | error | 🔧 | +| `js-toolkit/prefer-ref-over-query-selector` | Warns when `this.$el.querySelector()` or `this.$el.querySelectorAll()` is used inside a `Base` subclass. Declare a ref in `static config` and use `this.$refs` instead. | warn | | +| `js-toolkit/require-refs-declared-in-config` | Requires all `this.$refs.` accesses to be declared in `static config.refs`. | error | | diff --git a/packages/oxlint-plugin-js-toolkit/src/index.ts b/packages/oxlint-plugin-js-toolkit/src/index.ts index e0acf2ed7..2102bf3b2 100644 --- a/packages/oxlint-plugin-js-toolkit/src/index.ts +++ b/packages/oxlint-plugin-js-toolkit/src/index.ts @@ -12,6 +12,13 @@ import { noCreateApp, noEventListenerMethods, refsPluralMultiple, + noDeepUtilsImport, + refsNoBracketAccess, + noRedundantWithMountWhenInView, + preferRefOverQuerySelector, + requireRefsDeclaredInConfig, + noManualIntersectionObserver, + noManualMutationObserver, } from './rules/index.ts'; const PLUGIN_NAME = 'js-toolkit'; @@ -30,6 +37,13 @@ const rules = { 'no-shadow-dom': noShadowDom, 'no-create-app': noCreateApp, 'no-event-listener-methods': noEventListenerMethods, + 'no-deep-utils-import': noDeepUtilsImport, + 'refs-no-bracket-access': refsNoBracketAccess, + 'no-redundant-with-mount-when-in-view': noRedundantWithMountWhenInView, + 'prefer-ref-over-query-selector': preferRefOverQuerySelector, + 'require-refs-declared-in-config': requireRefsDeclaredInConfig, + 'no-manual-intersection-observer': noManualIntersectionObserver, + 'no-manual-mutation-observer': noManualMutationObserver, }; const recommendedRules: Record = { @@ -46,6 +60,13 @@ const recommendedRules: Record = { [`${PLUGIN_NAME}/no-shadow-dom`]: 'error', [`${PLUGIN_NAME}/no-create-app`]: 'warn', [`${PLUGIN_NAME}/no-event-listener-methods`]: 'error', + [`${PLUGIN_NAME}/no-deep-utils-import`]: 'error', + [`${PLUGIN_NAME}/refs-no-bracket-access`]: 'error', + [`${PLUGIN_NAME}/no-redundant-with-mount-when-in-view`]: 'warn', + [`${PLUGIN_NAME}/prefer-ref-over-query-selector`]: 'warn', + [`${PLUGIN_NAME}/require-refs-declared-in-config`]: 'error', + [`${PLUGIN_NAME}/no-manual-intersection-observer`]: 'warn', + [`${PLUGIN_NAME}/no-manual-mutation-observer`]: 'warn', }; const plugin: { meta: object; rules: typeof rules; configs: Record } = { diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/index.ts b/packages/oxlint-plugin-js-toolkit/src/rules/index.ts index d2048cb1e..90002d853 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/index.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/index.ts @@ -11,3 +11,10 @@ export { noShadowDom } from './no-shadow-dom.ts'; export { noCreateApp } from './no-create-app.ts'; export { noEventListenerMethods } from './no-event-listener-methods.ts'; export { refsPluralMultiple } from './refs-plural-multiple.ts'; +export { noDeepUtilsImport } from './no-deep-utils-import.ts'; +export { refsNoBracketAccess } from './refs-no-bracket-access.ts'; +export { noRedundantWithMountWhenInView } from './no-redundant-with-mount-when-in-view.ts'; +export { preferRefOverQuerySelector } from './prefer-ref-over-query-selector.ts'; +export { requireRefsDeclaredInConfig } from './require-refs-declared-in-config.ts'; +export { noManualIntersectionObserver } from './no-manual-intersection-observer.ts'; +export { noManualMutationObserver } from './no-manual-mutation-observer.ts'; diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.test.ts new file mode 100644 index 000000000..e9bc2b7d4 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.test.ts @@ -0,0 +1,32 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { noDeepUtilsImport } from './no-deep-utils-import.ts'; + +describe('no-deep-utils-import', () => { + it('passes and fails correctly', () => { + tester.run('no-deep-utils-import', noDeepUtilsImport as any, { + valid: [ + `import { debounce } from '@studiometa/js-toolkit/utils';`, + `import { Base } from '@studiometa/js-toolkit';`, + `import debounce from 'lodash/debounce';`, + ], + invalid: [ + { + code: `import debounce from '@studiometa/js-toolkit/utils/debounce';`, + errors: [{ messageId: 'noDeepImport' }], + output: `import { debounce } from '@studiometa/js-toolkit/utils';`, + }, + { + code: `import { foo } from '@studiometa/js-toolkit/utils/dom/foo';`, + errors: [{ messageId: 'noDeepImport' }], + output: `import { foo } from '@studiometa/js-toolkit/utils';`, + }, + { + code: `import { foo as bar } from '@studiometa/js-toolkit/utils/dom/foo';`, + errors: [{ messageId: 'noDeepImport' }], + output: `import { foo as bar } from '@studiometa/js-toolkit/utils';`, + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.ts new file mode 100644 index 000000000..8f7a90710 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.ts @@ -0,0 +1,51 @@ +import { type Node, type RuleContext } from '../utils/ast.ts'; + +const DEEP_UTILS_RE = /^@studiometa\/js-toolkit\/utils\/.+/; + +export const noDeepUtilsImport = { + meta: { + type: 'problem', + fixable: 'code', + docs: { + description: 'Disallow deep imports from @studiometa/js-toolkit/utils/*', + }, + messages: { + noDeepImport: + 'Import from "{{source}}" should use the public entrypoint "@studiometa/js-toolkit/utils".', + }, + }, + create(context: RuleContext) { + return { + ImportDeclaration(node: Node) { + const source: string = node.source.value; + if (!DEEP_UTILS_RE.test(source)) return; + + context.report({ + node, + messageId: 'noDeepImport', + data: { source }, + fix(fixer: any) { + const specifiers: Node[] = node.specifiers ?? []; + const parts: string[] = []; + + for (const s of specifiers) { + if (s.type === 'ImportDefaultSpecifier') { + parts.push(s.local.name); + } else if (s.type === 'ImportSpecifier') { + const imported = s.imported.name; + const local = s.local.name; + parts.push(imported === local ? imported : `${imported} as ${local}`); + } + } + + const named = parts.join(', '); + return fixer.replaceText( + node, + `import { ${named} } from '@studiometa/js-toolkit/utils';`, + ); + }, + }); + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.test.ts new file mode 100644 index 000000000..dd8a86eab --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.test.ts @@ -0,0 +1,39 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { noManualIntersectionObserver } from './no-manual-intersection-observer.ts'; + +describe('no-manual-intersection-observer', () => { + it('passes and fails correctly', () => { + tester.run('no-manual-intersection-observer', noManualIntersectionObserver as any, { + valid: [ + // Outside a Base subclass — allowed + `const io = new IntersectionObserver(cb);`, + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + mounted() { /* uses withIntersectionObserver */ } + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + mounted() { + this._io = new IntersectionObserver(cb); + this._io.observe(this.$el); + } +}`, + errors: [{ messageId: 'noManual' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + mounted() { + new IntersectionObserver((entries) => {}).observe(this.$el); + } +}`, + errors: [{ messageId: 'noManual' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts new file mode 100644 index 000000000..7ec3d1efb --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts @@ -0,0 +1,28 @@ +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; + +export const noManualIntersectionObserver = { + meta: { + type: 'suggestion', + docs: { + description: + 'Disallow manual IntersectionObserver inside Base subclasses; use withIntersectionObserver or withMountWhenInView instead', + }, + messages: { + noManual: + 'Avoid manual "new IntersectionObserver()". Use "withIntersectionObserver" or "withMountWhenInView" decorators instead.', + }, + }, + create(context: RuleContext) { + return { + NewExpression(node: Node) { + if (node.callee?.name !== 'IntersectionObserver') return; + + const ancestors = context.getAncestors?.() ?? context.sourceCode?.getAncestors?.(node) ?? []; + const cls = findEnclosingClass(ancestors); + if (!cls || !isBaseSubclass(cls, context)) return; + + context.report({ node, messageId: 'noManual' }); + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.test.ts new file mode 100644 index 000000000..921eb7741 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.test.ts @@ -0,0 +1,39 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { noManualMutationObserver } from './no-manual-mutation-observer.ts'; + +describe('no-manual-mutation-observer', () => { + it('passes and fails correctly', () => { + tester.run('no-manual-mutation-observer', noManualMutationObserver as any, { + valid: [ + // Outside a Base subclass — allowed + `const mo = new MutationObserver(cb);`, + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + mounted() { /* uses withMutation */ } + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + mounted() { + this._mo = new MutationObserver(cb); + this._mo.observe(this.$el, { childList: true }); + } +}`, + errors: [{ messageId: 'noManual' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + mounted() { + new MutationObserver((records) => {}).observe(this.$el, { attributes: true }); + } +}`, + errors: [{ messageId: 'noManual' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts new file mode 100644 index 000000000..c605a570c --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts @@ -0,0 +1,28 @@ +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; + +export const noManualMutationObserver = { + meta: { + type: 'suggestion', + docs: { + description: + 'Disallow manual MutationObserver inside Base subclasses; use withMutation decorator instead', + }, + messages: { + noManual: + 'Avoid manual "new MutationObserver()". Use the "withMutation" decorator instead.', + }, + }, + create(context: RuleContext) { + return { + NewExpression(node: Node) { + if (node.callee?.name !== 'MutationObserver') return; + + const ancestors = context.getAncestors?.() ?? context.sourceCode?.getAncestors?.(node) ?? []; + const cls = findEnclosingClass(ancestors); + if (!cls || !isBaseSubclass(cls, context)) return; + + context.report({ node, messageId: 'noManual' }); + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.test.ts new file mode 100644 index 000000000..9047245d5 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.test.ts @@ -0,0 +1,30 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { noRedundantWithMountWhenInView } from './no-redundant-with-mount-when-in-view.ts'; + +describe('no-redundant-with-mount-when-in-view', () => { + it('passes and fails correctly', () => { + tester.run('no-redundant-with-mount-when-in-view', noRedundantWithMountWhenInView as any, { + valid: [ + `import { Base, withScrolledInView } from '@studiometa/js-toolkit'; + class Foo extends withScrolledInView(Base) {}`, + `import { Base, withMountWhenInView } from '@studiometa/js-toolkit'; + class Foo extends withMountWhenInView(Base) {}`, + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base {}`, + ], + invalid: [ + { + code: `import { Base, withScrolledInView, withMountWhenInView } from '@studiometa/js-toolkit'; +class Foo extends withScrolledInView(withMountWhenInView(Base)) {}`, + errors: [{ messageId: 'redundant' }], + }, + { + code: `import { Base, withScrolledInView, withMountWhenInView } from '@studiometa/js-toolkit'; +class Foo extends withScrolledInView(withMountWhenInView(withScrolledInView(Base))) {}`, + errors: [{ messageId: 'redundant' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts new file mode 100644 index 000000000..2b7622ddb --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts @@ -0,0 +1,34 @@ +import { type Node, type RuleContext } from '../utils/ast.ts'; + +export const noRedundantWithMountWhenInView = { + meta: { + type: 'suggestion', + docs: { + description: + 'Disallow wrapping withMountWhenInView inside withScrolledInView (redundant)', + }, + messages: { + redundant: + 'withScrolledInView already includes withMountWhenInView internally. Remove the inner withMountWhenInView call.', + }, + }, + create(context: RuleContext) { + return { + 'ClassDeclaration, ClassExpression'(node: Node) { + check(node, context); + }, + }; + }, +}; + +function check(node: Node, context: RuleContext) { + const superClass = node.superClass; + if (!superClass || superClass.type !== 'CallExpression') return; + if (superClass.callee?.name !== 'withScrolledInView') return; + + const arg = superClass.arguments?.[0]; + if (!arg || arg.type !== 'CallExpression') return; + if (arg.callee?.name !== 'withMountWhenInView') return; + + context.report({ node: superClass, messageId: 'redundant' }); +} diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.test.ts new file mode 100644 index 000000000..5e50a6b53 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.test.ts @@ -0,0 +1,41 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { preferRefOverQuerySelector } from './prefer-ref-over-query-selector.ts'; + +describe('prefer-ref-over-query-selector', () => { + it('passes and fails correctly', () => { + tester.run('prefer-ref-over-query-selector', preferRefOverQuerySelector as any, { + valid: [ + // querySelector outside a Base subclass is fine + `document.querySelector('.btn');`, + // Using refs properly + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { refs: ['btn'] }; + mounted() { const { btn } = this.$refs; } + }`, + // Not this.$el + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + mounted() { this.$el.classList.add('active'); } + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + mounted() { const btn = this.$el.querySelector('.btn'); } +}`, + errors: [{ messageId: 'preferRef' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + mounted() { const items = this.$el.querySelectorAll('.item'); } +}`, + errors: [{ messageId: 'preferRef' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts b/packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts new file mode 100644 index 000000000..dfe2e9d24 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts @@ -0,0 +1,41 @@ +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; + +const QUERY_METHODS = new Set(['querySelector', 'querySelectorAll']); + +function isThisEl(node: Node): boolean { + return ( + node.type === 'MemberExpression' && + node.object?.type === 'ThisExpression' && + node.property?.name === '$el' + ); +} + +export const preferRefOverQuerySelector = { + meta: { + type: 'suggestion', + docs: { + description: 'Prefer this.$refs over this.$el.querySelector() in Base subclasses', + }, + messages: { + preferRef: + 'Avoid "this.$el.{{method}}()". Declare a ref in static config and use "this.$refs" instead.', + }, + }, + create(context: RuleContext) { + return { + CallExpression(node: Node) { + const callee = node.callee; + if (callee?.type !== 'MemberExpression') return; + if (!isThisEl(callee.object)) return; + const method = callee.property?.name; + if (!QUERY_METHODS.has(method)) return; + + const ancestors = context.getAncestors?.() ?? context.sourceCode?.getAncestors?.(node) ?? []; + const cls = findEnclosingClass(ancestors); + if (!cls || !isBaseSubclass(cls, context)) return; + + context.report({ node, messageId: 'preferRef', data: { method } }); + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.test.ts new file mode 100644 index 000000000..b2cc0b65a --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.test.ts @@ -0,0 +1,32 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { refsNoBracketAccess } from './refs-no-bracket-access.ts'; + +describe('refs-no-bracket-access', () => { + it('passes and fails correctly', () => { + tester.run('refs-no-bracket-access', refsNoBracketAccess as any, { + valid: [ + `this.$refs.items;`, + `this.$refs['items'];`, + `this.$refs['items[]'.replace('[]', '')];`, + ], + invalid: [ + { + code: `this.$refs['items[]'];`, + errors: [{ messageId: 'noBracketAccess' }], + output: `this.$refs.items;`, + }, + { + code: `this.$refs['other-items[]'];`, + errors: [{ messageId: 'noBracketAccess' }], + output: `this.$refs.otherItems;`, + }, + { + code: `this.$refs['my_items[]'];`, + errors: [{ messageId: 'noBracketAccess' }], + output: `this.$refs.myItems;`, + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts b/packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts new file mode 100644 index 000000000..c8ac5d4f5 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts @@ -0,0 +1,49 @@ +import { toCamelCase, type Node, type RuleContext } from '../utils/ast.ts'; + +function isThisRefs(node: Node): boolean { + return ( + node.type === 'MemberExpression' && + node.object?.type === 'ThisExpression' && + node.property?.name === '$refs' + ); +} + +export const refsNoBracketAccess = { + meta: { + type: 'problem', + fixable: 'code', + docs: { + description: 'Disallow bracket access with [] suffix on this.$refs', + }, + messages: { + noBracketAccess: + 'Use "this.$refs.{{fixed}}" instead of "this.$refs[\'{{raw}}\']".', + }, + }, + create(context: RuleContext) { + return { + MemberExpression(node: Node) { + if (!isThisRefs(node.object)) return; + if (!node.computed) return; + const prop = node.property; + if (prop.type !== 'Literal' || typeof prop.value !== 'string') return; + const raw: string = prop.value; + if (!raw.endsWith('[]')) return; + + const stripped = raw.slice(0, -2); + const fixed = toCamelCase(stripped); + + context.report({ + node, + messageId: 'noBracketAccess', + data: { raw, fixed }, + fix(fixer: any) { + const src = context.sourceCode ?? context.getSourceCode?.(); + const objText = src.getText(node.object); + return fixer.replaceText(node, `${objText}.${fixed}`); + }, + }); + }, + }; + }, +}; diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.test.ts new file mode 100644 index 000000000..1a74474f1 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.test.ts @@ -0,0 +1,50 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { requireRefsDeclaredInConfig } from './require-refs-declared-in-config.ts'; + +describe('require-refs-declared-in-config', () => { + it('passes and fails correctly', () => { + tester.run('require-refs-declared-in-config', requireRefsDeclaredInConfig as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { refs: ['button', 'panel'] }; + mounted() { + this.$refs.button.click(); + this.$refs.panel.hidden = false; + } + }`, + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { refs: ['items[]'] }; + mounted() { const { items } = this.$refs; } + }`, + // Outside a Base subclass — not checked + `class Foo { + mounted() { this.$refs.anything; } + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { refs: ['button'] }; + mounted() { this.$refs.panel.hidden = false; } +}`, + errors: [{ messageId: 'undeclared' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { refs: ['button'] }; + mounted() { + this.$refs.button.click(); + this.$refs.missing.focus(); + } +}`, + errors: [{ messageId: 'undeclared' }], + }, + ], + }); + }); +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts b/packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts new file mode 100644 index 000000000..af569d772 --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts @@ -0,0 +1,99 @@ +import { isBaseSubclass, toCamelCase, type Node, type RuleContext } from '../utils/ast.ts'; + +function collectDeclaredRefs(classNode: Node): Set { + const body: Node[] = classNode.body?.body ?? []; + const configProp = body.find( + (m: Node) => + m.type === 'PropertyDefinition' && + m.static === true && + m.key?.name === 'config', + ); + + if (!configProp?.value || configProp.value.type !== 'ObjectExpression') return new Set(); + + const refsProp = configProp.value.properties?.find( + (p: Node) => p.type === 'Property' && p.key?.name === 'refs', + ); + + if (!refsProp?.value || refsProp.value.type !== 'ArrayExpression') return new Set(); + + const names = new Set(); + for (const el of refsProp.value.elements ?? []) { + if (!el || el.type !== 'Literal' || typeof el.value !== 'string') continue; + const raw: string = el.value; + const stripped = raw.endsWith('[]') ? raw.slice(0, -2) : raw; + names.add(toCamelCase(stripped)); + } + return names; +} + +function isThisRefs(node: Node): boolean { + return ( + node.type === 'MemberExpression' && + node.object?.type === 'ThisExpression' && + node.property?.name === '$refs' + ); +} + +export const requireRefsDeclaredInConfig = { + meta: { + type: 'problem', + docs: { + description: 'Require all this.$refs accesses to be declared in static config.refs', + }, + messages: { + undeclared: 'Ref "{{name}}" is not declared in static config.refs.', + }, + }, + create(context: RuleContext) { + return { + 'ClassDeclaration, ClassExpression'(classNode: Node) { + if (!isBaseSubclass(classNode, context)) return; + + const declared = collectDeclaredRefs(classNode); + + // Walk the class body looking for this.$refs. accesses + walkNode(classNode.body, (node: Node) => { + if (node.type !== 'MemberExpression') return; + if (!isThisRefs(node.object)) return; + + let refName: string | null = null; + + if (!node.computed && node.property?.type === 'Identifier') { + refName = node.property.name; + } else if ( + node.computed && + node.property?.type === 'Literal' && + typeof node.property.value === 'string' + ) { + const raw: string = node.property.value; + const stripped = raw.endsWith('[]') ? raw.slice(0, -2) : raw; + refName = toCamelCase(stripped); + } + + if (refName && !declared.has(refName)) { + context.report({ + node, + messageId: 'undeclared', + data: { name: refName }, + }); + } + }); + }, + }; + }, +}; + +function walkNode(node: Node, visit: (n: Node) => void) { + if (!node || typeof node !== 'object') return; + visit(node); + for (const key of Object.keys(node)) { + if (key === 'parent') continue; + const child = node[key]; + if (Array.isArray(child)) { + for (const item of child) walkNode(item, visit); + } else if (child && typeof child === 'object' && child.type) { + walkNode(child, visit); + } + } +} diff --git a/packages/oxlint-plugin-js-toolkit/tsconfig.tsbuildinfo b/packages/oxlint-plugin-js-toolkit/tsconfig.tsbuildinfo new file mode 100644 index 000000000..dd7cf193b --- /dev/null +++ b/packages/oxlint-plugin-js-toolkit/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/index.ts","./src/rules/async-lifecycle-methods.ts","./src/rules/index.ts","./src/rules/no-create-app.ts","./src/rules/no-deep-utils-import.ts","./src/rules/no-deprecated-properties.ts","./src/rules/no-dispatch-event.ts","./src/rules/no-event-listener-methods.ts","./src/rules/no-manual-intersection-observer.ts","./src/rules/no-manual-mutation-observer.ts","./src/rules/no-redundant-with-mount-when-in-view.ts","./src/rules/no-shadow-dom.ts","./src/rules/on-global-handler-prefix.ts","./src/rules/on-handler-naming.ts","./src/rules/options-camel-case.ts","./src/rules/prefer-ref-over-query-selector.ts","./src/rules/refs-camel-case.ts","./src/rules/refs-no-bracket-access.ts","./src/rules/refs-plural-multiple.ts","./src/rules/require-config-name-pascal-case.ts","./src/rules/require-config.ts","./src/rules/require-refs-declared-in-config.ts","./src/utils/ast.ts","./src/utils/rule-tester.ts"],"version":"6.0.2"} \ No newline at end of file From 93bb94a5b106d5e48b2787b1233f0a931f374897 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 20:34:45 +0200 Subject: [PATCH 20/34] Fix false positive and add window/document support in no-event-listener-methods --- .../rules/no-event-listener-methods.test.ts | 26 +++++++++++++++++++ .../src/rules/no-event-listener-methods.ts | 7 +++++ 2 files changed, 33 insertions(+) diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.test.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.test.ts index 8610a81cc..3ad5e8f7a 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.test.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.test.ts @@ -16,6 +16,14 @@ describe('no-event-listener-methods', () => { el.addEventListener('click', handler); el.removeEventListener('click', handler); }`, + // Non-this receiver inside a Base class — allowed (e.g. AbortSignal) + `import { Base } from '@studiometa/js-toolkit'; + class Fetch extends Base { + mounted() { + const ctrl = new AbortController(); + ctrl.signal.addEventListener('abort', () => {}); + } + }`, ], invalid: [ { @@ -36,6 +44,24 @@ describe('no-event-listener-methods', () => { }`, errors: [{ messageId: 'useOnMethod' }], }, + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + mounted() { + document.addEventListener('click', this.onClick); + } + }`, + errors: [{ messageId: 'useOnMethod' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; + class Slider extends Base { + mounted() { + window.addEventListener('resize', this.onResize); + } + }`, + errors: [{ messageId: 'useOnMethod' }], + }, ], }); }); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts index 88a4db410..f70cdd8a1 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts @@ -24,6 +24,13 @@ export const noEventListenerMethods = { const method = callee.property?.name; if (!method || !FORBIDDEN.has(method)) return; + // Only flag calls on `this.*`, `document`, or `window` + let root = callee.object; + while (root?.type === 'MemberExpression') root = root.object; + const isThis = root?.type === 'ThisExpression'; + const isGlobal = root?.type === 'Identifier' && (root.name === 'document' || root.name === 'window'); + if (!isThis && !isGlobal) return; + const ancestors = context.getAncestors ? context.getAncestors() : context.sourceCode?.getAncestors?.(node) ?? []; From 4046bcfe8c608828c4410bb812a53ac46ee94423 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 20:34:47 +0200 Subject: [PATCH 21/34] Downgrade no-deprecated-properties default severity to warn --- packages/oxlint-plugin-js-toolkit/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/oxlint-plugin-js-toolkit/src/index.ts b/packages/oxlint-plugin-js-toolkit/src/index.ts index 2102bf3b2..9a0c7aaa9 100644 --- a/packages/oxlint-plugin-js-toolkit/src/index.ts +++ b/packages/oxlint-plugin-js-toolkit/src/index.ts @@ -55,7 +55,7 @@ const recommendedRules: Record = { [`${PLUGIN_NAME}/async-lifecycle-methods`]: 'error', [`${PLUGIN_NAME}/on-handler-naming`]: 'error', [`${PLUGIN_NAME}/on-global-handler-prefix`]: 'warn', - [`${PLUGIN_NAME}/no-deprecated-properties`]: 'error', + [`${PLUGIN_NAME}/no-deprecated-properties`]: 'warn', [`${PLUGIN_NAME}/no-dispatch-event`]: 'warn', [`${PLUGIN_NAME}/no-shadow-dom`]: 'error', [`${PLUGIN_NAME}/no-create-app`]: 'warn', From d3bf6a66ace09924c59722d3900878674095ea0b Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 20:34:49 +0200 Subject: [PATCH 22/34] Update README to reflect no-deprecated-properties warn severity --- packages/oxlint-plugin-js-toolkit/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/oxlint-plugin-js-toolkit/README.md b/packages/oxlint-plugin-js-toolkit/README.md index cd8aa1572..b6456f754 100644 --- a/packages/oxlint-plugin-js-toolkit/README.md +++ b/packages/oxlint-plugin-js-toolkit/README.md @@ -32,7 +32,7 @@ Add the plugin to your `.oxlintrc.json`: "js-toolkit/async-lifecycle-methods": "error", "js-toolkit/on-handler-naming": "error", "js-toolkit/on-global-handler-prefix": "warn", - "js-toolkit/no-deprecated-properties": "error", + "js-toolkit/no-deprecated-properties": "warn", "js-toolkit/no-dispatch-event": "warn", "js-toolkit/no-shadow-dom": "error", "js-toolkit/no-create-app": "warn", @@ -70,7 +70,7 @@ export default [ jsToolkit.configs.recommended, { rules: { - 'js-toolkit/no-create-app': 'error', + 'js-toolkit/no-create-app': 'error', // override the default 'warn' }, }, ]; @@ -105,7 +105,7 @@ export default [ | Rule | Description | Recommended | Fixable | |------|-------------|-------------|---------| -| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. | error | | +| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. | warn | | | `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | warn | | | `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | error | | | `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | warn | | From 0209c6d523d38f6a58b4457e955cd91e7c4433cf Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 20:35:54 +0200 Subject: [PATCH 23/34] Lint doc files --- packages/docs/guide/going-further/linting.md | 42 ++++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/docs/guide/going-further/linting.md b/packages/docs/guide/going-further/linting.md index 1f42d1a54..96af4d92b 100644 --- a/packages/docs/guide/going-further/linting.md +++ b/packages/docs/guide/going-further/linting.md @@ -67,33 +67,33 @@ export default [ ### Class structure -| Rule | Description | Fixable | -|------|-------------|---------| -| `js-toolkit/require-config` | Requires a `static config` property with a `name` on every class extending `Base`. | ❌ | -| `js-toolkit/require-config-name-pascal-case` | Requires `config.name` to be PascalCase. | 🔧 | -| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | 🔧 | -| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | ❌ | -| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | 🔧 | +| Rule | Description | Fixable | +| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------- | +| `js-toolkit/require-config` | Requires a `static config` property with a `name` on every class extending `Base`. | ❌ | +| `js-toolkit/require-config-name-pascal-case` | Requires `config.name` to be PascalCase. | 🔧 | +| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | 🔧 | +| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | ❌ | +| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | 🔧 | ### Lifecycle methods -| Rule | Description | Fixable | -|------|-------------|---------| -| `js-toolkit/async-lifecycle-methods` | Requires lifecycle methods (`mounted`, `destroyed`, `updated`, `terminated`, `ticked`, `scrolled`, `resized`, `moved`, `loaded`, `keyed`) to be declared `async`. | 🔧 | +| Rule | Description | Fixable | +| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `js-toolkit/async-lifecycle-methods` | Requires lifecycle methods (`mounted`, `destroyed`, `updated`, `terminated`, `ticked`, `scrolled`, `resized`, `moved`, `loaded`, `keyed`) to be declared `async`. | 🔧 | ### Event handlers -| Rule | Description | Fixable | -|------|-------------|---------| -| `js-toolkit/on-handler-naming` | Requires event handler methods to follow the `onXxxYyy` camelCase convention. | ❌ | -| `js-toolkit/on-global-handler-prefix` | Requires handlers for window-only events (e.g. `resize`) to use the `onWindow` or `onDocument` prefix. | ❌ | +| Rule | Description | Fixable | +| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------- | +| `js-toolkit/on-handler-naming` | Requires event handler methods to follow the `onXxxYyy` camelCase convention. | ❌ | +| `js-toolkit/on-global-handler-prefix` | Requires handlers for window-only events (e.g. `resize`) to use the `onWindow` or `onDocument` prefix. | ❌ | ### Forbidden patterns -| Rule | Description | Fixable | -|------|-------------|---------| -| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. | ❌ | -| `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | ❌ | -| `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | ❌ | -| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | ❌ | -| `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead — the framework handles binding and cleanup automatically. | ❌ | +| Rule | Description | Fixable | +| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. | ❌ | +| `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | ❌ | +| `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | ❌ | +| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | ❌ | +| `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead — the framework handles binding and cleanup automatically. | ❌ | From a48ba412557fb2d6bfe1a14f390961c935ea7e24 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 20:38:03 +0200 Subject: [PATCH 24/34] Add a missing dependency for tests --- package-lock.json | 1 + packages/oxlint-plugin-js-toolkit/package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index 632ef988a..0c864619d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22839,6 +22839,7 @@ "devDependencies": { "@vitest/coverage-v8": "4.1.2", "esbuild": "0.27.0", + "eslint": "9.34.0", "typescript": "6.0.2", "vitest": "4.1.2" } diff --git a/packages/oxlint-plugin-js-toolkit/package.json b/packages/oxlint-plugin-js-toolkit/package.json index cb2ad1ca5..40c83c798 100644 --- a/packages/oxlint-plugin-js-toolkit/package.json +++ b/packages/oxlint-plugin-js-toolkit/package.json @@ -37,6 +37,7 @@ "devDependencies": { "@vitest/coverage-v8": "4.1.2", "esbuild": "0.27.0", + "eslint": "9.34.0", "typescript": "6.0.2", "vitest": "4.1.2" } From 72dfd506a79370144f543631228c40bee0879b89 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 21:05:00 +0200 Subject: [PATCH 25/34] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c50bb1b4..009e4e839 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format ## [Unreleased] +### Added + +- Add `@studiometa/oxlint-plugin-js-toolkit` package with 20 rules enforcing best practices for `@studiometa/js-toolkit` projects ([#726](https://github.com/studiometa/js-toolkit/pull/726), [e2477e79](https://github.com/studiometa/js-toolkit/commit/e2477e79)) + ## [v3.6.0-beta.0](https://github.com/studiometa/js-toolkit/compare/3.5.0..3.6.0-beta.0) (2026-04-21) ### Added From 211c9646da69095de4e824104d94dd675fb4f1c1 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 21:22:29 +0200 Subject: [PATCH 26/34] Use oxlint createOnce alternative API for better linting performance Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 39 +++++++------------ .../oxlint-plugin-js-toolkit/package.json | 3 ++ .../oxlint-plugin-js-toolkit/src/index.ts | 8 ++-- .../src/rules/async-lifecycle-methods.ts | 8 ++-- .../src/rules/no-create-app.ts | 8 ++-- .../src/rules/no-deep-utils-import.ts | 8 ++-- .../src/rules/no-deprecated-properties.ts | 8 ++-- .../src/rules/no-dispatch-event.ts | 8 ++-- .../src/rules/no-event-listener-methods.ts | 8 ++-- .../rules/no-manual-intersection-observer.ts | 8 ++-- .../src/rules/no-manual-mutation-observer.ts | 8 ++-- .../no-redundant-with-mount-when-in-view.ts | 8 ++-- .../src/rules/no-shadow-dom.ts | 8 ++-- .../src/rules/on-global-handler-prefix.ts | 8 ++-- .../src/rules/on-handler-naming.ts | 8 ++-- .../src/rules/options-camel-case.ts | 8 ++-- .../rules/prefer-ref-over-query-selector.ts | 8 ++-- .../src/rules/refs-camel-case.ts | 8 ++-- .../src/rules/refs-no-bracket-access.ts | 8 ++-- .../src/rules/refs-plural-multiple.ts | 8 ++-- .../rules/require-config-name-pascal-case.ts | 8 ++-- .../src/rules/require-config.ts | 8 ++-- .../rules/require-refs-declared-in-config.ts | 8 ++-- .../oxlint-plugin-js-toolkit/src/utils/ast.ts | 15 +++++++ .../src/utils/rule-tester.ts | 10 ++++- 25 files changed, 127 insertions(+), 108 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0c864619d..4c12b7297 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2863,9 +2863,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2883,9 +2880,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2903,9 +2897,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2923,9 +2914,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2943,9 +2931,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2963,9 +2948,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2983,9 +2965,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3003,9 +2982,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3083,6 +3059,18 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@oxlint/plugins": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@oxlint/plugins/-/plugins-1.63.0.tgz", + "integrity": "sha512-vZAzaUQkwgdN62RHgPFzfCsiBI6SDJMUdUlBGpJK0V++UHCMUk7UeJseygOhE/wOUSgV3ccE4ORkgab3C3MC6g==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.6", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", @@ -22836,6 +22824,9 @@ "name": "@studiometa/oxlint-plugin-js-toolkit", "version": "3.6.0-beta.0", "license": "MIT", + "dependencies": { + "@oxlint/plugins": "1.63.0" + }, "devDependencies": { "@vitest/coverage-v8": "4.1.2", "esbuild": "0.27.0", diff --git a/packages/oxlint-plugin-js-toolkit/package.json b/packages/oxlint-plugin-js-toolkit/package.json index 40c83c798..0f06b0bc0 100644 --- a/packages/oxlint-plugin-js-toolkit/package.json +++ b/packages/oxlint-plugin-js-toolkit/package.json @@ -40,5 +40,8 @@ "eslint": "9.34.0", "typescript": "6.0.2", "vitest": "4.1.2" + }, + "dependencies": { + "@oxlint/plugins": "1.63.0" } } diff --git a/packages/oxlint-plugin-js-toolkit/src/index.ts b/packages/oxlint-plugin-js-toolkit/src/index.ts index 9a0c7aaa9..bc257b0ba 100644 --- a/packages/oxlint-plugin-js-toolkit/src/index.ts +++ b/packages/oxlint-plugin-js-toolkit/src/index.ts @@ -1,3 +1,4 @@ +import { eslintCompatPlugin } from '@oxlint/plugins'; import { requireConfig, requireConfigNamePascalCase, @@ -69,13 +70,14 @@ const recommendedRules: Record = { [`${PLUGIN_NAME}/no-manual-mutation-observer`]: 'warn', }; -const plugin: { meta: object; rules: typeof rules; configs: Record } = { +const base = eslintCompatPlugin({ meta: { name: '@studiometa/oxlint-plugin-js-toolkit', }, rules, - configs: {}, -}; +}); + +const plugin = Object.assign(base, { configs: {} as Record }); plugin.configs['recommended'] = { plugins: { [PLUGIN_NAME]: plugin }, diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts b/packages/oxlint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts index 5249b23e6..433ee3f2a 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts @@ -1,6 +1,6 @@ -import { isBaseSubclass, LIFECYCLE_METHODS, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.ts'; +import { isBaseSubclass, LIFECYCLE_METHODS, findEnclosingClass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; -export const asyncLifecycleMethods = { +export const asyncLifecycleMethods = createRule({ meta: { type: 'problem', fixable: 'code', @@ -11,7 +11,7 @@ export const asyncLifecycleMethods = { notAsync: 'Lifecycle method "{{name}}" must be declared as async.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { MethodDefinition(node: Node) { const name = node.key?.name; @@ -34,4 +34,4 @@ export const asyncLifecycleMethods = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-create-app.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-create-app.ts index 5eab00f9a..6f3b0240e 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/no-create-app.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-create-app.ts @@ -1,6 +1,6 @@ -import { type Node, type RuleContext } from '../utils/ast.ts'; +import { type Node, type RuleContext, createRule } from '../utils/ast.ts'; -export const noCreateApp = { +export const noCreateApp = createRule({ meta: { type: 'suggestion', docs: { @@ -11,7 +11,7 @@ export const noCreateApp = { 'createApp() is deprecated. Use registerComponent() from @studiometa/js-toolkit instead.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { CallExpression(node: Node) { const callee = node.callee; @@ -21,4 +21,4 @@ export const noCreateApp = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.ts index 8f7a90710..1583b2940 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.ts @@ -1,8 +1,8 @@ -import { type Node, type RuleContext } from '../utils/ast.ts'; +import { type Node, type RuleContext, createRule } from '../utils/ast.ts'; const DEEP_UTILS_RE = /^@studiometa\/js-toolkit\/utils\/.+/; -export const noDeepUtilsImport = { +export const noDeepUtilsImport = createRule({ meta: { type: 'problem', fixable: 'code', @@ -14,7 +14,7 @@ export const noDeepUtilsImport = { 'Import from "{{source}}" should use the public entrypoint "@studiometa/js-toolkit/utils".', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { ImportDeclaration(node: Node) { const source: string = node.source.value; @@ -48,4 +48,4 @@ export const noDeepUtilsImport = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts index a9ad88c85..598e1f408 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts @@ -1,4 +1,4 @@ -import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.ts'; +import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; const DEPRECATED = new Map([ ['$parent', '$closest()'], @@ -6,7 +6,7 @@ const DEPRECATED = new Map([ ['$children', '$query()'], ]); -export const noDeprecatedProperties = { +export const noDeprecatedProperties = createRule({ meta: { type: 'problem', docs: { @@ -16,7 +16,7 @@ export const noDeprecatedProperties = { deprecated: '"{{name}}" is deprecated. Use {{replacement}} instead.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { MemberExpression(node: Node) { const prop = node.property?.name; @@ -41,4 +41,4 @@ export const noDeprecatedProperties = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-dispatch-event.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-dispatch-event.ts index 88f3be02f..588e5f92f 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/no-dispatch-event.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-dispatch-event.ts @@ -1,6 +1,6 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; -export const noDispatchEvent = { +export const noDispatchEvent = createRule({ meta: { type: 'suggestion', docs: { @@ -10,7 +10,7 @@ export const noDispatchEvent = { useEmit: 'Use this.$emit() instead of dispatchEvent() to emit events in a Base component.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { CallExpression(node: Node) { const callee = node.callee; @@ -37,4 +37,4 @@ export const noDispatchEvent = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts index f70cdd8a1..41e2b63fa 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts @@ -1,8 +1,8 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; const FORBIDDEN = new Set(['addEventListener', 'removeEventListener']); -export const noEventListenerMethods = { +export const noEventListenerMethods = createRule({ meta: { type: 'problem', docs: { @@ -15,7 +15,7 @@ export const noEventListenerMethods = { 'Define an "on()" method instead — the framework handles binding and cleanup automatically.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { CallExpression(node: Node) { const callee = node.callee; @@ -42,4 +42,4 @@ export const noEventListenerMethods = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts index 7ec3d1efb..8c5722a0d 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts @@ -1,6 +1,6 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; -export const noManualIntersectionObserver = { +export const noManualIntersectionObserver = createRule({ meta: { type: 'suggestion', docs: { @@ -12,7 +12,7 @@ export const noManualIntersectionObserver = { 'Avoid manual "new IntersectionObserver()". Use "withIntersectionObserver" or "withMountWhenInView" decorators instead.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { NewExpression(node: Node) { if (node.callee?.name !== 'IntersectionObserver') return; @@ -25,4 +25,4 @@ export const noManualIntersectionObserver = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts index c605a570c..50165df71 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts @@ -1,6 +1,6 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; -export const noManualMutationObserver = { +export const noManualMutationObserver = createRule({ meta: { type: 'suggestion', docs: { @@ -12,7 +12,7 @@ export const noManualMutationObserver = { 'Avoid manual "new MutationObserver()". Use the "withMutation" decorator instead.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { NewExpression(node: Node) { if (node.callee?.name !== 'MutationObserver') return; @@ -25,4 +25,4 @@ export const noManualMutationObserver = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts index 2b7622ddb..2c3fda866 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts @@ -1,6 +1,6 @@ -import { type Node, type RuleContext } from '../utils/ast.ts'; +import { type Node, type RuleContext, createRule } from '../utils/ast.ts'; -export const noRedundantWithMountWhenInView = { +export const noRedundantWithMountWhenInView = createRule({ meta: { type: 'suggestion', docs: { @@ -12,14 +12,14 @@ export const noRedundantWithMountWhenInView = { 'withScrolledInView already includes withMountWhenInView internally. Remove the inner withMountWhenInView call.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { 'ClassDeclaration, ClassExpression'(node: Node) { check(node, context); }, }; }, -}; +}); function check(node: Node, context: RuleContext) { const superClass = node.superClass; diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-shadow-dom.ts b/packages/oxlint-plugin-js-toolkit/src/rules/no-shadow-dom.ts index 448dbc504..c8982cc49 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/no-shadow-dom.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/no-shadow-dom.ts @@ -1,6 +1,6 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; -export const noShadowDom = { +export const noShadowDom = createRule({ meta: { type: 'problem', docs: { @@ -11,7 +11,7 @@ export const noShadowDom = { 'Do not use attachShadow() in a Base component. @studiometa/js-toolkit uses Light DOM only.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { CallExpression(node: Node) { const callee = node.callee; @@ -33,4 +33,4 @@ export const noShadowDom = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts b/packages/oxlint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts index a7af6fcb7..403157a25 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts @@ -1,4 +1,4 @@ -import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.ts'; +import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; // Events that only make sense on window/document, never on a DOM element. // Pointer, keyboard, and form events are intentionally excluded — they can all @@ -6,7 +6,7 @@ import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from // unambiguous mistake worth flagging automatically. const GLOBAL_ONLY_EVENTS = new Set(['Resize']); -export const onGlobalHandlerPrefix = { +export const onGlobalHandlerPrefix = createRule({ meta: { type: 'suggestion', docs: { @@ -18,7 +18,7 @@ export const onGlobalHandlerPrefix = { '"{{name}}" looks like a global event handler. Use "onWindow{{event}}" or "onDocument{{event}}" to bind to window/document events.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { MethodDefinition(node: Node) { const name = node.key?.name; @@ -46,4 +46,4 @@ export const onGlobalHandlerPrefix = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/on-handler-naming.ts b/packages/oxlint-plugin-js-toolkit/src/rules/on-handler-naming.ts index 139f98e09..378315927 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/on-handler-naming.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/on-handler-naming.ts @@ -1,9 +1,9 @@ -import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext } from '../utils/ast.ts'; +import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; // onXxxYyy — the part after "on" must start with an uppercase letter const ON_HANDLER_RE = /^on[A-Z][a-zA-Z0-9]*$/; -export const onHandlerNaming = { +export const onHandlerNaming = createRule({ meta: { type: 'problem', docs: { @@ -14,7 +14,7 @@ export const onHandlerNaming = { 'Event handler "{{name}}" must follow the onXxxYyy camelCase convention (e.g. onClickButton).', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { MethodDefinition(node: Node) { // Skip getters, setters — those are not event handlers @@ -37,4 +37,4 @@ export const onHandlerNaming = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/options-camel-case.ts b/packages/oxlint-plugin-js-toolkit/src/rules/options-camel-case.ts index 198daad83..02d198c98 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/options-camel-case.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/options-camel-case.ts @@ -1,6 +1,6 @@ -import { isBaseSubclass, isCamelCase, toCamelCase, type Node, type RuleContext } from '../utils/ast.ts'; +import { isBaseSubclass, isCamelCase, toCamelCase, type Node, type RuleContext, createRule } from '../utils/ast.ts'; -export const optionsCamelCase = { +export const optionsCamelCase = createRule({ meta: { type: 'problem', fixable: 'code', @@ -11,7 +11,7 @@ export const optionsCamelCase = { notCamelCase: 'Option key "{{name}}" must be camelCase.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { ClassDeclaration(node: Node) { check(node, context); @@ -21,7 +21,7 @@ export const optionsCamelCase = { }, }; }, -}; +}); function check(node: Node, context: RuleContext) { if (!isBaseSubclass(node, context)) return; diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts b/packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts index dfe2e9d24..7ff3bf2ba 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts @@ -1,4 +1,4 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; +import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; const QUERY_METHODS = new Set(['querySelector', 'querySelectorAll']); @@ -10,7 +10,7 @@ function isThisEl(node: Node): boolean { ); } -export const preferRefOverQuerySelector = { +export const preferRefOverQuerySelector = createRule({ meta: { type: 'suggestion', docs: { @@ -21,7 +21,7 @@ export const preferRefOverQuerySelector = { 'Avoid "this.$el.{{method}}()". Declare a ref in static config and use "this.$refs" instead.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { CallExpression(node: Node) { const callee = node.callee; @@ -38,4 +38,4 @@ export const preferRefOverQuerySelector = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/refs-camel-case.ts b/packages/oxlint-plugin-js-toolkit/src/rules/refs-camel-case.ts index 4f4e2cbe2..59bc75ef3 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/refs-camel-case.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/refs-camel-case.ts @@ -1,6 +1,6 @@ -import { isBaseSubclass, isCamelCase, toCamelCase, type Node, type RuleContext } from '../utils/ast.ts'; +import { isBaseSubclass, isCamelCase, toCamelCase, type Node, type RuleContext, createRule } from '../utils/ast.ts'; -export const refsCamelCase = { +export const refsCamelCase = createRule({ meta: { type: 'problem', fixable: 'code', @@ -11,7 +11,7 @@ export const refsCamelCase = { notCamelCase: 'Ref name "{{name}}" must be camelCase.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { ClassDeclaration(node: Node) { check(node, context); @@ -21,7 +21,7 @@ export const refsCamelCase = { }, }; }, -}; +}); function check(node: Node, context: RuleContext) { if (!isBaseSubclass(node, context)) return; diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts b/packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts index c8ac5d4f5..2fad20d66 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts @@ -1,4 +1,4 @@ -import { toCamelCase, type Node, type RuleContext } from '../utils/ast.ts'; +import { toCamelCase, type Node, type RuleContext, createRule } from '../utils/ast.ts'; function isThisRefs(node: Node): boolean { return ( @@ -8,7 +8,7 @@ function isThisRefs(node: Node): boolean { ); } -export const refsNoBracketAccess = { +export const refsNoBracketAccess = createRule({ meta: { type: 'problem', fixable: 'code', @@ -20,7 +20,7 @@ export const refsNoBracketAccess = { 'Use "this.$refs.{{fixed}}" instead of "this.$refs[\'{{raw}}\']".', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { MemberExpression(node: Node) { if (!isThisRefs(node.object)) return; @@ -46,4 +46,4 @@ export const refsNoBracketAccess = { }, }; }, -}; +}); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/refs-plural-multiple.ts b/packages/oxlint-plugin-js-toolkit/src/rules/refs-plural-multiple.ts index 41c644a06..0fa170d83 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/refs-plural-multiple.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/refs-plural-multiple.ts @@ -1,6 +1,6 @@ -import { isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; +import { isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; -export const refsPluralMultiple = { +export const refsPluralMultiple = createRule({ meta: { type: 'problem', docs: { @@ -10,7 +10,7 @@ export const refsPluralMultiple = { notPlural: 'Multiple ref "{{name}}" must be pluralized (e.g. "{{name}}s[]").', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { ClassDeclaration(node: Node) { check(node, context); @@ -20,7 +20,7 @@ export const refsPluralMultiple = { }, }; }, -}; +}); function check(node: Node, context: RuleContext) { if (!isBaseSubclass(node, context)) return; diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts b/packages/oxlint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts index 1c88c3e54..02ed7b99c 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts @@ -1,6 +1,6 @@ -import { isBaseSubclass, isPascalCase, toPascalCase, type Node, type RuleContext } from '../utils/ast.ts'; +import { isBaseSubclass, isPascalCase, toPascalCase, type Node, type RuleContext, createRule } from '../utils/ast.ts'; -export const requireConfigNamePascalCase = { +export const requireConfigNamePascalCase = createRule({ meta: { type: 'problem', fixable: 'code', @@ -11,7 +11,7 @@ export const requireConfigNamePascalCase = { notPascalCase: 'config.name "{{name}}" must be PascalCase.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { ClassDeclaration(node: Node) { check(node, context); @@ -21,7 +21,7 @@ export const requireConfigNamePascalCase = { }, }; }, -}; +}); function check(node: Node, context: RuleContext) { if (!isBaseSubclass(node, context)) return; diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/require-config.ts b/packages/oxlint-plugin-js-toolkit/src/rules/require-config.ts index bf3b8de28..b945e6e32 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/require-config.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/require-config.ts @@ -1,6 +1,6 @@ -import { isBaseSubclass, type Node, type RuleContext } from '../utils/ast.ts'; +import { isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; -export const requireConfig = { +export const requireConfig = createRule({ meta: { type: 'problem', docs: { @@ -11,7 +11,7 @@ export const requireConfig = { missingName: 'The static config must include a name property.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { ClassDeclaration(node: Node) { check(node, context); @@ -21,7 +21,7 @@ export const requireConfig = { }, }; }, -}; +}); function check(node: Node, context: RuleContext) { if (!isBaseSubclass(node, context)) return; diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts b/packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts index af569d772..e853ad6f3 100644 --- a/packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts +++ b/packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts @@ -1,4 +1,4 @@ -import { isBaseSubclass, toCamelCase, type Node, type RuleContext } from '../utils/ast.ts'; +import { isBaseSubclass, toCamelCase, type Node, type RuleContext, createRule } from '../utils/ast.ts'; function collectDeclaredRefs(classNode: Node): Set { const body: Node[] = classNode.body?.body ?? []; @@ -35,7 +35,7 @@ function isThisRefs(node: Node): boolean { ); } -export const requireRefsDeclaredInConfig = { +export const requireRefsDeclaredInConfig = createRule({ meta: { type: 'problem', docs: { @@ -45,7 +45,7 @@ export const requireRefsDeclaredInConfig = { undeclared: 'Ref "{{name}}" is not declared in static config.refs.', }, }, - create(context: RuleContext) { + createOnce(context: RuleContext) { return { 'ClassDeclaration, ClassExpression'(classNode: Node) { if (!isBaseSubclass(classNode, context)) return; @@ -82,7 +82,7 @@ export const requireRefsDeclaredInConfig = { }, }; }, -}; +}); function walkNode(node: Node, visit: (n: Node) => void) { if (!node || typeof node !== 'object') return; diff --git a/packages/oxlint-plugin-js-toolkit/src/utils/ast.ts b/packages/oxlint-plugin-js-toolkit/src/utils/ast.ts index d0e0e8dfa..7d101ff1b 100644 --- a/packages/oxlint-plugin-js-toolkit/src/utils/ast.ts +++ b/packages/oxlint-plugin-js-toolkit/src/utils/ast.ts @@ -6,6 +6,21 @@ export type Node = Record; export type RuleContext = Record; +export type RuleMeta = { + type?: 'problem' | 'suggestion' | 'layout'; + fixable?: 'code' | 'whitespace'; + hasSuggestions?: boolean; + docs?: { description?: string }; + messages?: Record; +}; + +export function createRule unknown>>(rule: { + meta?: RuleMeta; + createOnce(context: RuleContext): V; +}): { meta?: RuleMeta; createOnce(context: RuleContext): V } { + return rule; +} + export const LIFECYCLE_METHODS = new Set([ 'mounted', 'destroyed', diff --git a/packages/oxlint-plugin-js-toolkit/src/utils/rule-tester.ts b/packages/oxlint-plugin-js-toolkit/src/utils/rule-tester.ts index bf57b7149..d534d57ac 100644 --- a/packages/oxlint-plugin-js-toolkit/src/utils/rule-tester.ts +++ b/packages/oxlint-plugin-js-toolkit/src/utils/rule-tester.ts @@ -1,10 +1,18 @@ import { RuleTester } from 'eslint'; +import { eslintCompatPlugin } from '@oxlint/plugins'; export { RuleTester }; -export const tester = new RuleTester({ +const _tester = new RuleTester({ languageOptions: { ecmaVersion: 'latest', sourceType: 'module', }, }); + +export const tester = { + run(name: string, rule: Parameters[0]['rules'][string], tests: Parameters['run']>[2]) { + const wrapped = eslintCompatPlugin({ rules: { [name]: rule } }); + _tester.run(name, wrapped.rules[name] as any, tests); + }, +}; From 73f8d991135a7fb49efeb06f89985b978de30841 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 21:30:06 +0200 Subject: [PATCH 27/34] Rename package to @studiometa/eslint-plugin-js-toolkit Co-Authored-By: Claude Sonnet 4.6 --- README.md | 6 +++--- packages/docs/guide/going-further/linting.md | 10 +++++----- .../README.md | 18 +++++++++--------- .../package.json | 2 +- .../scripts/build.js | 2 +- .../src/index.ts | 2 +- .../src/rules/async-lifecycle-methods.test.ts | 0 .../src/rules/async-lifecycle-methods.ts | 0 .../src/rules/index.ts | 0 .../src/rules/no-create-app.test.ts | 0 .../src/rules/no-create-app.ts | 0 .../src/rules/no-deep-utils-import.test.ts | 0 .../src/rules/no-deep-utils-import.ts | 0 .../src/rules/no-deprecated-properties.test.ts | 0 .../src/rules/no-deprecated-properties.ts | 0 .../src/rules/no-dispatch-event.test.ts | 0 .../src/rules/no-dispatch-event.ts | 0 .../rules/no-event-listener-methods.test.ts | 0 .../src/rules/no-event-listener-methods.ts | 0 .../no-manual-intersection-observer.test.ts | 0 .../rules/no-manual-intersection-observer.ts | 0 .../rules/no-manual-mutation-observer.test.ts | 0 .../src/rules/no-manual-mutation-observer.ts | 0 ...o-redundant-with-mount-when-in-view.test.ts | 0 .../no-redundant-with-mount-when-in-view.ts | 0 .../src/rules/no-shadow-dom.test.ts | 0 .../src/rules/no-shadow-dom.ts | 0 .../src/rules/on-global-handler-prefix.test.ts | 0 .../src/rules/on-global-handler-prefix.ts | 0 .../src/rules/on-handler-naming.test.ts | 0 .../src/rules/on-handler-naming.ts | 0 .../src/rules/options-camel-case.test.ts | 0 .../src/rules/options-camel-case.ts | 0 .../prefer-ref-over-query-selector.test.ts | 0 .../rules/prefer-ref-over-query-selector.ts | 0 .../src/rules/refs-camel-case.test.ts | 0 .../src/rules/refs-camel-case.ts | 0 .../src/rules/refs-no-bracket-access.test.ts | 0 .../src/rules/refs-no-bracket-access.ts | 0 .../src/rules/refs-plural-multiple.test.ts | 0 .../src/rules/refs-plural-multiple.ts | 0 .../require-config-name-pascal-case.test.ts | 0 .../rules/require-config-name-pascal-case.ts | 0 .../src/rules/require-config.test.ts | 0 .../src/rules/require-config.ts | 0 .../require-refs-declared-in-config.test.ts | 0 .../rules/require-refs-declared-in-config.ts | 0 .../src/utils/ast.ts | 0 .../src/utils/rule-tester.ts | 0 .../tsconfig.json | 0 .../tsconfig.tsbuildinfo | 0 .../vitest.config.ts | 0 52 files changed, 20 insertions(+), 20 deletions(-) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/README.md (89%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/package.json (95%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/scripts/build.js (92%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/index.ts (98%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/async-lifecycle-methods.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/async-lifecycle-methods.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/index.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-create-app.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-create-app.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-deep-utils-import.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-deep-utils-import.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-deprecated-properties.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-deprecated-properties.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-dispatch-event.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-dispatch-event.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-event-listener-methods.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-event-listener-methods.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-manual-intersection-observer.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-manual-intersection-observer.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-manual-mutation-observer.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-manual-mutation-observer.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-redundant-with-mount-when-in-view.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-redundant-with-mount-when-in-view.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-shadow-dom.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/no-shadow-dom.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/on-global-handler-prefix.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/on-global-handler-prefix.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/on-handler-naming.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/on-handler-naming.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/options-camel-case.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/options-camel-case.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/prefer-ref-over-query-selector.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/prefer-ref-over-query-selector.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/refs-camel-case.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/refs-camel-case.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/refs-no-bracket-access.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/refs-no-bracket-access.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/refs-plural-multiple.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/refs-plural-multiple.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/require-config-name-pascal-case.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/require-config-name-pascal-case.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/require-config.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/require-config.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/require-refs-declared-in-config.test.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/rules/require-refs-declared-in-config.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/utils/ast.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/src/utils/rule-tester.ts (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/tsconfig.json (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/tsconfig.tsbuildinfo (100%) rename packages/{oxlint-plugin-js-toolkit => eslint-plugin-js-toolkit}/vitest.config.ts (100%) diff --git a/README.md b/README.md index a9f8d2dcf..de9c67d6e 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ Visit [js-toolkit.studiometa.dev](https://js-toolkit.studiometa.dev) to learn mo ## Linting -The [`@studiometa/oxlint-plugin-js-toolkit`](./packages/oxlint-plugin-js-toolkit/) package provides an Oxlint/ESLint plugin that enforces best practices when writing components with this framework. +The [`@studiometa/eslint-plugin-js-toolkit`](./packages/eslint-plugin-js-toolkit/) package provides an Oxlint/ESLint plugin that enforces best practices when writing components with this framework. ```bash -npm install --save-dev @studiometa/oxlint-plugin-js-toolkit +npm install --save-dev @studiometa/eslint-plugin-js-toolkit ``` -See the [plugin README](./packages/oxlint-plugin-js-toolkit/README.md) or the [linting guide](https://js-toolkit.studiometa.dev/guide/going-further/linting.html) for configuration details. +See the [plugin README](./packages/eslint-plugin-js-toolkit/README.md) or the [linting guide](https://js-toolkit.studiometa.dev/guide/going-further/linting.html) for configuration details. ## Quick overview diff --git a/packages/docs/guide/going-further/linting.md b/packages/docs/guide/going-further/linting.md index 96af4d92b..d86e1a7cd 100644 --- a/packages/docs/guide/going-further/linting.md +++ b/packages/docs/guide/going-further/linting.md @@ -1,11 +1,11 @@ # Linting -The [`@studiometa/oxlint-plugin-js-toolkit`](https://www.npmjs.com/package/@studiometa/oxlint-plugin-js-toolkit) package provides an Oxlint/ESLint plugin that enforces best practices when writing components with this framework. +The [`@studiometa/eslint-plugin-js-toolkit`](https://www.npmjs.com/package/@studiometa/eslint-plugin-js-toolkit) package provides an Oxlint/ESLint plugin that enforces best practices when writing components with this framework. ## Installation ```bash -npm install --save-dev @studiometa/oxlint-plugin-js-toolkit +npm install --save-dev @studiometa/eslint-plugin-js-toolkit ``` ## Configuration @@ -16,7 +16,7 @@ Add the plugin to your `.oxlintrc.json`: ```json { - "jsPlugins": ["@studiometa/oxlint-plugin-js-toolkit"], + "jsPlugins": ["@studiometa/eslint-plugin-js-toolkit"], "rules": { "js-toolkit/require-config": "error", "js-toolkit/require-config-name-pascal-case": "error", @@ -40,7 +40,7 @@ Add the plugin to your `.oxlintrc.json`: Add the recommended config to your `eslint.config.js` (ESLint v9 flat config): ```js -import jsToolkit from '@studiometa/oxlint-plugin-js-toolkit'; +import jsToolkit from '@studiometa/eslint-plugin-js-toolkit'; export default [ jsToolkit.configs.recommended, @@ -51,7 +51,7 @@ export default [ To customise individual rule severities, add an override entry after the recommended config: ```js -import jsToolkit from '@studiometa/oxlint-plugin-js-toolkit'; +import jsToolkit from '@studiometa/eslint-plugin-js-toolkit'; export default [ jsToolkit.configs.recommended, diff --git a/packages/oxlint-plugin-js-toolkit/README.md b/packages/eslint-plugin-js-toolkit/README.md similarity index 89% rename from packages/oxlint-plugin-js-toolkit/README.md rename to packages/eslint-plugin-js-toolkit/README.md index b6456f754..dc4a6940b 100644 --- a/packages/oxlint-plugin-js-toolkit/README.md +++ b/packages/eslint-plugin-js-toolkit/README.md @@ -1,9 +1,9 @@ -# @studiometa/oxlint-plugin-js-toolkit +# @studiometa/eslint-plugin-js-toolkit -[![NPM Version](https://img.shields.io/npm/v/@studiometa/oxlint-plugin-js-toolkit.svg?style=flat&colorB=3e63dd&colorA=414853)](https://www.npmjs.com/package/@studiometa/oxlint-plugin-js-toolkit/) -[![Downloads](https://img.shields.io/npm/dm/@studiometa/oxlint-plugin-js-toolkit?style=flat&colorB=3e63dd&colorA=414853)](https://www.npmjs.com/package/@studiometa/oxlint-plugin-js-toolkit/) -[![Size](https://img.shields.io/bundlephobia/minzip/@studiometa/oxlint-plugin-js-toolkit?style=flat&colorB=3e63dd&colorA=414853&label=size)](https://bundlephobia.com/package/@studiometa/js-toolkit) -[![Dependency Status](https://img.shields.io/librariesio/release/npm/oxlint-plugin-js-toolkit?style=flat&colorB=3e63dd&colorA=414853)](https://david-dm.org/studiometa/js-toolkit) +[![NPM Version](https://img.shields.io/npm/v/@studiometa/eslint-plugin-js-toolkit.svg?style=flat&colorB=3e63dd&colorA=414853)](https://www.npmjs.com/package/@studiometa/eslint-plugin-js-toolkit/) +[![Downloads](https://img.shields.io/npm/dm/@studiometa/eslint-plugin-js-toolkit?style=flat&colorB=3e63dd&colorA=414853)](https://www.npmjs.com/package/@studiometa/eslint-plugin-js-toolkit/) +[![Size](https://img.shields.io/bundlephobia/minzip/@studiometa/eslint-plugin-js-toolkit?style=flat&colorB=3e63dd&colorA=414853&label=size)](https://bundlephobia.com/package/@studiometa/js-toolkit) +[![Dependency Status](https://img.shields.io/librariesio/release/npm/eslint-plugin-js-toolkit?style=flat&colorB=3e63dd&colorA=414853)](https://david-dm.org/studiometa/js-toolkit) ![Codecov](https://img.shields.io/codecov/c/github/studiometa/js-toolkit?style=flat&colorB=3e63dd&colorA=414853) Oxlint/ESLint plugin enforcing best practices for [@studiometa/js-toolkit](https://js-toolkit.studiometa.dev). @@ -11,7 +11,7 @@ Oxlint/ESLint plugin enforcing best practices for [@studiometa/js-toolkit](https ## Installation ```bash -npm install --save-dev @studiometa/oxlint-plugin-js-toolkit +npm install --save-dev @studiometa/eslint-plugin-js-toolkit ``` ## Configuration @@ -22,7 +22,7 @@ Add the plugin to your `.oxlintrc.json`: ```json { - "jsPlugins": ["@studiometa/oxlint-plugin-js-toolkit"], + "jsPlugins": ["@studiometa/eslint-plugin-js-toolkit"], "rules": { "js-toolkit/require-config": "error", "js-toolkit/require-config-name-pascal-case": "error", @@ -53,7 +53,7 @@ Add the plugin to your `.oxlintrc.json`: Add the recommended config to your `eslint.config.js` (ESLint v9 flat config): ```js -import jsToolkit from '@studiometa/oxlint-plugin-js-toolkit'; +import jsToolkit from '@studiometa/eslint-plugin-js-toolkit'; export default [ jsToolkit.configs.recommended, @@ -64,7 +64,7 @@ export default [ To customise individual rule severities, add an override entry after the recommended config: ```js -import jsToolkit from '@studiometa/oxlint-plugin-js-toolkit'; +import jsToolkit from '@studiometa/eslint-plugin-js-toolkit'; export default [ jsToolkit.configs.recommended, diff --git a/packages/oxlint-plugin-js-toolkit/package.json b/packages/eslint-plugin-js-toolkit/package.json similarity index 95% rename from packages/oxlint-plugin-js-toolkit/package.json rename to packages/eslint-plugin-js-toolkit/package.json index 0f06b0bc0..a9af8c711 100644 --- a/packages/oxlint-plugin-js-toolkit/package.json +++ b/packages/eslint-plugin-js-toolkit/package.json @@ -1,5 +1,5 @@ { - "name": "@studiometa/oxlint-plugin-js-toolkit", + "name": "@studiometa/eslint-plugin-js-toolkit", "version": "3.6.0-beta.0", "description": "Oxlint/ESLint plugin for @studiometa/js-toolkit best practices", "publishConfig": { diff --git a/packages/oxlint-plugin-js-toolkit/scripts/build.js b/packages/eslint-plugin-js-toolkit/scripts/build.js similarity index 92% rename from packages/oxlint-plugin-js-toolkit/scripts/build.js rename to packages/eslint-plugin-js-toolkit/scripts/build.js index e48b501d1..3eba4dc46 100644 --- a/packages/oxlint-plugin-js-toolkit/scripts/build.js +++ b/packages/eslint-plugin-js-toolkit/scripts/build.js @@ -6,7 +6,7 @@ import esbuild from 'esbuild'; const root = resolve(dirname(new URL(import.meta.url).pathname), '..'); rmSync(resolve(root, 'dist'), { recursive: true, force: true }); -console.log('Building @studiometa/oxlint-plugin-js-toolkit...'); +console.log('Building @studiometa/eslint-plugin-js-toolkit...'); const { errors, warnings } = await esbuild.build({ entryPoints: [resolve(root, 'src/index.ts')], diff --git a/packages/oxlint-plugin-js-toolkit/src/index.ts b/packages/eslint-plugin-js-toolkit/src/index.ts similarity index 98% rename from packages/oxlint-plugin-js-toolkit/src/index.ts rename to packages/eslint-plugin-js-toolkit/src/index.ts index bc257b0ba..e947a125e 100644 --- a/packages/oxlint-plugin-js-toolkit/src/index.ts +++ b/packages/eslint-plugin-js-toolkit/src/index.ts @@ -72,7 +72,7 @@ const recommendedRules: Record = { const base = eslintCompatPlugin({ meta: { - name: '@studiometa/oxlint-plugin-js-toolkit', + name: '@studiometa/eslint-plugin-js-toolkit', }, rules, }); diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/async-lifecycle-methods.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/async-lifecycle-methods.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/async-lifecycle-methods.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/async-lifecycle-methods.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts b/packages/eslint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts rename to packages/eslint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/index.ts b/packages/eslint-plugin-js-toolkit/src/rules/index.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/index.ts rename to packages/eslint-plugin-js-toolkit/src/rules/index.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-create-app.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-create-app.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-create-app.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-create-app.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-create-app.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-create-app.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-create-app.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-create-app.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-deep-utils-import.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-deep-utils-import.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-deep-utils-import.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-deep-utils-import.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-deep-utils-import.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-deprecated-properties.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-deprecated-properties.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-deprecated-properties.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-deprecated-properties.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-dispatch-event.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-dispatch-event.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-dispatch-event.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-dispatch-event.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-dispatch-event.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-dispatch-event.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-dispatch-event.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-dispatch-event.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-event-listener-methods.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-event-listener-methods.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-shadow-dom.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-shadow-dom.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-shadow-dom.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-shadow-dom.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/no-shadow-dom.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-shadow-dom.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/no-shadow-dom.ts rename to packages/eslint-plugin-js-toolkit/src/rules/no-shadow-dom.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/on-global-handler-prefix.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/on-global-handler-prefix.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/on-global-handler-prefix.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/on-global-handler-prefix.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts b/packages/eslint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts rename to packages/eslint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/on-handler-naming.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/on-handler-naming.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/on-handler-naming.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/on-handler-naming.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/on-handler-naming.ts b/packages/eslint-plugin-js-toolkit/src/rules/on-handler-naming.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/on-handler-naming.ts rename to packages/eslint-plugin-js-toolkit/src/rules/on-handler-naming.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/options-camel-case.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/options-camel-case.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/options-camel-case.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/options-camel-case.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/options-camel-case.ts b/packages/eslint-plugin-js-toolkit/src/rules/options-camel-case.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/options-camel-case.ts rename to packages/eslint-plugin-js-toolkit/src/rules/options-camel-case.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts b/packages/eslint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts rename to packages/eslint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/refs-camel-case.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/refs-camel-case.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/refs-camel-case.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/refs-camel-case.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/refs-camel-case.ts b/packages/eslint-plugin-js-toolkit/src/rules/refs-camel-case.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/refs-camel-case.ts rename to packages/eslint-plugin-js-toolkit/src/rules/refs-camel-case.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/refs-no-bracket-access.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/refs-no-bracket-access.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts b/packages/eslint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts rename to packages/eslint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/refs-plural-multiple.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/refs-plural-multiple.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/refs-plural-multiple.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/refs-plural-multiple.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/refs-plural-multiple.ts b/packages/eslint-plugin-js-toolkit/src/rules/refs-plural-multiple.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/refs-plural-multiple.ts rename to packages/eslint-plugin-js-toolkit/src/rules/refs-plural-multiple.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts rename to packages/eslint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/require-config.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-config.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/require-config.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/require-config.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/require-config.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-config.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/require-config.ts rename to packages/eslint-plugin-js-toolkit/src/rules/require-config.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.test.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.test.ts rename to packages/eslint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.test.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts rename to packages/eslint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/utils/ast.ts b/packages/eslint-plugin-js-toolkit/src/utils/ast.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/utils/ast.ts rename to packages/eslint-plugin-js-toolkit/src/utils/ast.ts diff --git a/packages/oxlint-plugin-js-toolkit/src/utils/rule-tester.ts b/packages/eslint-plugin-js-toolkit/src/utils/rule-tester.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/src/utils/rule-tester.ts rename to packages/eslint-plugin-js-toolkit/src/utils/rule-tester.ts diff --git a/packages/oxlint-plugin-js-toolkit/tsconfig.json b/packages/eslint-plugin-js-toolkit/tsconfig.json similarity index 100% rename from packages/oxlint-plugin-js-toolkit/tsconfig.json rename to packages/eslint-plugin-js-toolkit/tsconfig.json diff --git a/packages/oxlint-plugin-js-toolkit/tsconfig.tsbuildinfo b/packages/eslint-plugin-js-toolkit/tsconfig.tsbuildinfo similarity index 100% rename from packages/oxlint-plugin-js-toolkit/tsconfig.tsbuildinfo rename to packages/eslint-plugin-js-toolkit/tsconfig.tsbuildinfo diff --git a/packages/oxlint-plugin-js-toolkit/vitest.config.ts b/packages/eslint-plugin-js-toolkit/vitest.config.ts similarity index 100% rename from packages/oxlint-plugin-js-toolkit/vitest.config.ts rename to packages/eslint-plugin-js-toolkit/vitest.config.ts From b9b287543f81cf211f5b3f3a56c8428848fccc56 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 21:32:15 +0200 Subject: [PATCH 28/34] Update changelog to use new package name Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 009e4e839..520438f65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file. The format ### Added -- Add `@studiometa/oxlint-plugin-js-toolkit` package with 20 rules enforcing best practices for `@studiometa/js-toolkit` projects ([#726](https://github.com/studiometa/js-toolkit/pull/726), [e2477e79](https://github.com/studiometa/js-toolkit/commit/e2477e79)) +- Add `@studiometa/eslint-plugin-js-toolkit` package with 20 rules enforcing best practices for `@studiometa/js-toolkit` projects ([#726](https://github.com/studiometa/js-toolkit/pull/726), [e2477e79](https://github.com/studiometa/js-toolkit/commit/e2477e79)) ## [v3.6.0-beta.0](https://github.com/studiometa/js-toolkit/compare/3.5.0..3.6.0-beta.0) (2026-04-21) From 9c0811f15ced48a2d76e1472d073df3ca8a45e25 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 21:37:58 +0200 Subject: [PATCH 29/34] Fix oxlintrc specifier path after package rename Co-Authored-By: Claude Sonnet 4.6 --- .oxlintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 326eb1fd1..91b9af274 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,5 +1,5 @@ { - "jsPlugins": [{ "name": "js-toolkit", "specifier": "./packages/oxlint-plugin-js-toolkit/src/index.ts" }], + "jsPlugins": [{ "name": "js-toolkit", "specifier": "./packages/eslint-plugin-js-toolkit/src/index.ts" }], "rules": { "typescript/no-this-alias": [ "error", From 7e5b185e8f65757954d4109cedd9fd638c17a1f8 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 21:42:42 +0200 Subject: [PATCH 30/34] Fix oxlint config snippet to use explicit plugin alias Co-Authored-By: Claude Sonnet 4.6 --- packages/docs/guide/going-further/linting.md | 2 +- packages/eslint-plugin-js-toolkit/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/docs/guide/going-further/linting.md b/packages/docs/guide/going-further/linting.md index d86e1a7cd..23b9b2f35 100644 --- a/packages/docs/guide/going-further/linting.md +++ b/packages/docs/guide/going-further/linting.md @@ -16,7 +16,7 @@ Add the plugin to your `.oxlintrc.json`: ```json { - "jsPlugins": ["@studiometa/eslint-plugin-js-toolkit"], + "jsPlugins": [{ "name": "js-toolkit", "specifier": "@studiometa/eslint-plugin-js-toolkit" }], "rules": { "js-toolkit/require-config": "error", "js-toolkit/require-config-name-pascal-case": "error", diff --git a/packages/eslint-plugin-js-toolkit/README.md b/packages/eslint-plugin-js-toolkit/README.md index dc4a6940b..ff3cc6868 100644 --- a/packages/eslint-plugin-js-toolkit/README.md +++ b/packages/eslint-plugin-js-toolkit/README.md @@ -22,7 +22,7 @@ Add the plugin to your `.oxlintrc.json`: ```json { - "jsPlugins": ["@studiometa/eslint-plugin-js-toolkit"], + "jsPlugins": [{ "name": "js-toolkit", "specifier": "@studiometa/eslint-plugin-js-toolkit" }], "rules": { "js-toolkit/require-config": "error", "js-toolkit/require-config-name-pascal-case": "error", From 43640b11ee0f644114dfd609a8ad0f5857ab8403 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 21:54:14 +0200 Subject: [PATCH 31/34] Fix npm ci install --- package-lock.json | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4c12b7297..f90d0867a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4849,6 +4849,10 @@ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, + "node_modules/@studiometa/eslint-plugin-js-toolkit": { + "resolved": "packages/eslint-plugin-js-toolkit", + "link": true + }, "node_modules/@studiometa/js-toolkit": { "resolved": "packages/js-toolkit", "link": true @@ -4865,10 +4869,6 @@ "resolved": "packages/tests", "link": true }, - "node_modules/@studiometa/oxlint-plugin-js-toolkit": { - "resolved": "packages/oxlint-plugin-js-toolkit", - "link": true - }, "node_modules/@studiometa/prettier-config": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@studiometa/prettier-config/-/prettier-config-4.4.0.tgz", @@ -22811,6 +22811,21 @@ } } }, + "packages/eslint-plugin-js-toolkit": { + "name": "@studiometa/eslint-plugin-js-toolkit", + "version": "3.6.0-beta.0", + "license": "MIT", + "dependencies": { + "@oxlint/plugins": "1.63.0" + }, + "devDependencies": { + "@vitest/coverage-v8": "4.1.2", + "esbuild": "0.27.0", + "eslint": "9.34.0", + "typescript": "6.0.2", + "vitest": "4.1.2" + } + }, "packages/js-toolkit": { "name": "@studiometa/js-toolkit", "version": "3.6.0-beta.0", @@ -22823,6 +22838,7 @@ "packages/oxlint-plugin-js-toolkit": { "name": "@studiometa/oxlint-plugin-js-toolkit", "version": "3.6.0-beta.0", + "extraneous": true, "license": "MIT", "dependencies": { "@oxlint/plugins": "1.63.0" From e5fe2367b06312d03491279df47b3ff685a36782 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 22:17:35 +0200 Subject: [PATCH 32/34] Fix linting and formatting scripts --- .github/workflows/tests.yml | 4 +++- package-lock.json | 1 - package.json | 14 ++++++-------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 355bb9312..e4ea7164d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,7 +33,9 @@ jobs: - name: Install dependencies run: npm ci --no-audit --no-progress --no-fund - name: Run code quality tests - run: npm run lint:oxlint + run: npm run lint:static + - name: Run format tests + run: npm run lint:fmt - name: Run types tests run: npm run lint:types diff --git a/package-lock.json b/package-lock.json index f90d0867a..db0a67291 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22821,7 +22821,6 @@ "devDependencies": { "@vitest/coverage-v8": "4.1.2", "esbuild": "0.27.0", - "eslint": "9.34.0", "typescript": "6.0.2", "vitest": "4.1.2" } diff --git a/package.json b/package.json index e868b1458..eae726c6e 100644 --- a/package.json +++ b/package.json @@ -18,15 +18,13 @@ "test:watch": "npm run test:watch --workspace=@studiometa/js-toolkit-tests", "bench": "npm run bench --workspace=@studiometa/js-toolkit-tests", "bench:run": "npm run bench:run --workspace=@studiometa/js-toolkit-tests", - "lint": "npm run lint:oxlint && npm run lint:types && npm run lint:docs", - "lint:oxlint": "oxlint .", - "lint:docs": "prettier --check 'packages/docs/**/*.{md,js,html,vue}' --cache", + "lint": "npm run lint:static && npm run lint:fmt && npm run lint:types", + "lint:static": "oxlint .", + "lint:fmt": "prettier --check '**/*.{yml,md,js,ts,html,vue}' --cache", "lint:types": "tsgo --build tsconfig.lint.json", - "lint:md": "prettier --check '*.md'", - "fix": "npm run fix:oxlint && npm run fix:docs && npm run fix:md", - "fix:oxlint": "oxlint . --fix", - "fix:docs": "prettier --write 'packages/docs/**/*.{md,js,html,vue}'", - "fix:md": "prettier --write '*.md'", + "fix": "npm run fix:static && npm run fix:fmt", + "fix:static": "npm run lint:static -- --fix", + "fix:fmt": "npm run lint:fmt -- --write", "build": "rm -rf dist && npm run build:pkg && npm run build:types && npm run build:cp-files", "build:cp-files": "cp packages/js-toolkit/package.json dist/ && node -e \"const f='dist/package.json';require('fs').writeFileSync(f,require('fs').readFileSync(f,'utf8').replace(/index\\.ts/g,'index.js'))\" && cat dist/package.json && cp LICENSE dist/ && cp README.md dist", "build:types": "tsgo --build tsconfig.build.json", From a1f0a4b177937bc2899267b148ce86ef73e573c4 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 7 May 2026 22:23:03 +0200 Subject: [PATCH 33/34] Format files --- .oxlintrc.json | 4 +- packages/docs/.vitepress/config.ts | 10 +++- .../theme/composables/useAllLinks.ts | 16 ++--- packages/docs/guide/going-further/linting.md | 7 ++- packages/docs/vite.config.ts | 5 +- packages/eslint-plugin-js-toolkit/README.md | 60 +++++++++---------- .../src/rules/async-lifecycle-methods.ts | 11 +++- .../src/rules/no-deprecated-properties.ts | 10 +++- .../src/rules/no-dispatch-event.ts | 13 ++-- .../src/rules/no-event-listener-methods.ts | 13 +++- .../rules/no-manual-intersection-observer.ts | 11 +++- .../src/rules/no-manual-mutation-observer.ts | 14 +++-- .../no-redundant-with-mount-when-in-view.ts | 3 +- .../src/rules/no-shadow-dom.ts | 15 +++-- .../src/rules/on-global-handler-prefix.ts | 10 +++- .../src/rules/on-handler-naming.ts | 10 +++- .../src/rules/options-camel-case.ts | 9 ++- .../rules/prefer-ref-over-query-selector.ts | 11 +++- .../src/rules/refs-camel-case.ts | 9 ++- .../src/rules/refs-no-bracket-access.ts | 3 +- .../rules/require-config-name-pascal-case.ts | 9 ++- .../src/rules/require-config.ts | 3 +- .../rules/require-refs-declared-in-config.ts | 13 ++-- .../eslint-plugin-js-toolkit/src/utils/ast.ts | 8 +-- .../src/utils/rule-tester.ts | 6 +- packages/js-toolkit/Base/Base.ts | 4 +- packages/js-toolkit/Base/types.ts | 14 +++-- packages/js-toolkit/Base/utils.ts | 15 +++-- .../decorators/withBreakpointManager.ts | 2 +- .../decorators/withBreakpointObserver.ts | 2 +- packages/js-toolkit/decorators/withDrag.ts | 2 +- .../js-toolkit/decorators/withExtraConfig.ts | 2 +- .../decorators/withFreezedOptions.ts | 2 +- packages/js-toolkit/decorators/withGroup.ts | 3 +- .../decorators/withIntersectionObserver.ts | 2 +- .../decorators/withMountOnMediaQuery.ts | 2 +- .../decorators/withMountWhenInView.ts | 2 +- .../js-toolkit/decorators/withMutation.ts | 2 +- .../decorators/withRelativePointer.ts | 2 +- .../decorators/withResponsiveOptions.ts | 2 +- .../withScrolledInView/withScrolledInView.ts | 2 +- packages/js-toolkit/helpers/createApp.ts | 8 +-- .../js-toolkit/helpers/getClosestParent.ts | 2 +- .../js-toolkit/helpers/getDirectChildren.ts | 4 +- .../helpers/getInstanceFromElement.ts | 2 +- .../js-toolkit/helpers/importOnInteraction.ts | 2 +- .../js-toolkit/helpers/importOnMediaQuery.ts | 2 +- packages/js-toolkit/helpers/importWhenIdle.ts | 2 +- .../helpers/importWhenPrefersMotion.ts | 2 +- .../js-toolkit/helpers/importWhenVisible.ts | 2 +- packages/js-toolkit/helpers/queryComponent.ts | 6 +- packages/js-toolkit/services/DragService.ts | 2 +- packages/js-toolkit/services/KeyService.ts | 2 +- packages/js-toolkit/services/LoadService.ts | 2 +- .../js-toolkit/services/MutationService.ts | 2 +- .../js-toolkit/services/PointerService.ts | 2 +- packages/js-toolkit/services/RafService.ts | 2 +- packages/js-toolkit/services/ResizeService.ts | 2 +- packages/js-toolkit/services/ScrollService.ts | 2 +- packages/js-toolkit/utils/cache.ts | 2 +- .../utils/collide/boundingRectToCircle.ts | 2 +- .../utils/collide/collideCircleCircle.ts | 2 +- .../utils/collide/collideCircleRect.ts | 2 +- .../utils/collide/collidePointCircle.ts | 2 +- .../utils/collide/collidePointRect.ts | 2 +- .../utils/collide/collideRectRect.ts | 2 +- packages/js-toolkit/utils/css/animate.ts | 38 ++++-------- packages/js-toolkit/utils/css/classes.ts | 6 +- .../js-toolkit/utils/css/getOffsetSizes.ts | 2 +- packages/js-toolkit/utils/css/matrix.ts | 2 +- packages/js-toolkit/utils/css/styles.ts | 4 +- packages/js-toolkit/utils/css/transform.ts | 4 +- packages/js-toolkit/utils/css/transition.ts | 2 +- packages/js-toolkit/utils/debounce.ts | 2 +- packages/js-toolkit/utils/history.ts | 6 +- packages/js-toolkit/utils/is.ts | 20 +++---- packages/js-toolkit/utils/loadElement.ts | 10 ++-- packages/js-toolkit/utils/math/clamp.ts | 2 +- packages/js-toolkit/utils/math/clamp01.ts | 2 +- packages/js-toolkit/utils/math/createEases.ts | 4 +- packages/js-toolkit/utils/math/createRange.ts | 2 +- packages/js-toolkit/utils/math/damp.ts | 9 +-- .../utils/math/inertiaFinalValue.ts | 2 +- packages/js-toolkit/utils/math/lerp.ts | 2 +- packages/js-toolkit/utils/math/map.ts | 2 +- packages/js-toolkit/utils/math/round.ts | 2 +- packages/js-toolkit/utils/math/wrap.ts | 2 +- packages/js-toolkit/utils/memo.ts | 2 +- packages/js-toolkit/utils/memoize.ts | 2 +- packages/js-toolkit/utils/nextFrame.ts | 2 +- packages/js-toolkit/utils/nextMicrotask.ts | 2 +- packages/js-toolkit/utils/nextTick.ts | 2 +- packages/js-toolkit/utils/random.ts | 6 +- packages/js-toolkit/utils/scheduler.ts | 4 +- packages/js-toolkit/utils/scrollTo.ts | 2 +- .../js-toolkit/utils/storage/createStorage.ts | 6 +- .../storage/createUrlSearchParamsProvider.ts | 4 +- .../utils/storage/getGlobalStorage.ts | 4 +- packages/js-toolkit/utils/storage/index.ts | 8 +-- packages/js-toolkit/utils/storage/types.ts | 5 +- .../js-toolkit/utils/string/changeCase.ts | 12 ++-- .../utils/string/withLeadingCharacters.ts | 2 +- .../utils/string/withLeadingSlash.ts | 2 +- .../utils/string/withTrailingCharacters.ts | 2 +- .../utils/string/withTrailingSlash.ts | 2 +- .../utils/string/withoutLeadingCharacters.ts | 2 +- .../withoutLeadingCharactersRecursive.ts | 2 +- .../utils/string/withoutLeadingSlash.ts | 2 +- .../utils/string/withoutTrailingCharacters.ts | 2 +- .../withoutTrailingCharactersRecursive.ts | 2 +- .../utils/string/withoutTrailingSlash.ts | 2 +- packages/js-toolkit/utils/throttle.ts | 2 +- packages/js-toolkit/utils/trapFocus.ts | 4 +- .../tests/__benchmarks__/animate.bench.ts | 25 ++++++-- packages/tests/helpers/logTree.spec.ts | 38 ++++++------ packages/tests/helpers/queryComponent.spec.ts | 2 +- packages/tests/services/DragService.spec.ts | 2 +- packages/tests/services/RafService.spec.ts | 10 ++-- .../tests/utils/dom/createElement.spec.ts | 7 ++- packages/tests/utils/math/spring.spec.ts | 2 +- packages/tests/utils/random.spec.ts | 4 +- packages/tests/utils/storage.spec.ts | 5 +- packages/tests/utils/tween.spec.ts | 48 +++++++-------- tsconfig.build.json | 5 +- tsconfig.json | 18 ++++-- tsconfig.lint.json | 5 +- 126 files changed, 436 insertions(+), 352 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 91b9af274..7015df1f7 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,5 +1,7 @@ { - "jsPlugins": [{ "name": "js-toolkit", "specifier": "./packages/eslint-plugin-js-toolkit/src/index.ts" }], + "jsPlugins": [ + { "name": "js-toolkit", "specifier": "./packages/eslint-plugin-js-toolkit/src/index.ts" } + ], "rules": { "typescript/no-this-alias": [ "error", diff --git a/packages/docs/.vitepress/config.ts b/packages/docs/.vitepress/config.ts index 9e32c6926..f7deb1f22 100644 --- a/packages/docs/.vitepress/config.ts +++ b/packages/docs/.vitepress/config.ts @@ -371,8 +371,14 @@ function getUtilsSidebar() { { text: 'createStorage', link: '/utils/storage/createStorage.html' }, { text: 'createLocalStorage', link: '/utils/storage/createLocalStorage.html' }, { text: 'createSessionStorage', link: '/utils/storage/createSessionStorage.html' }, - { text: 'createUrlSearchParamsStorage', link: '/utils/storage/createUrlSearchParamsStorage.html' }, - { text: 'createUrlSearchParamsInHashStorage', link: '/utils/storage/createUrlSearchParamsInHashStorage.html' }, + { + text: 'createUrlSearchParamsStorage', + link: '/utils/storage/createUrlSearchParamsStorage.html', + }, + { + text: 'createUrlSearchParamsInHashStorage', + link: '/utils/storage/createUrlSearchParamsInHashStorage.html', + }, { text: 'Providers', link: '/utils/storage/providers.html' }, ], }, diff --git a/packages/docs/.vitepress/theme/composables/useAllLinks.ts b/packages/docs/.vitepress/theme/composables/useAllLinks.ts index 32e70d1d1..360d1125e 100644 --- a/packages/docs/.vitepress/theme/composables/useAllLinks.ts +++ b/packages/docs/.vitepress/theme/composables/useAllLinks.ts @@ -14,7 +14,7 @@ interface VitepressLink { text: string; link?: string; items?: VitepressLink[]; - keywords?: string[] + keywords?: string[]; } /** @@ -31,21 +31,23 @@ function addLinks( let { text, link, keywords = [] } = item; if (!linksSet.has(link)) { - const newLink:Link = { - text, link, keywords + const newLink: Link = { + text, + link, + keywords, }; if (parent) { newLink.parent = { text: parent.text, - link: parent.link - } + link: parent.link, + }; } if (root) { newLink.root = { text: root.text, - link: root.link + link: root.link, }; } @@ -69,7 +71,7 @@ export function useAllLinks() { nav.forEach((item) => addLinks(links, linkSet, item)); Object.entries(sidebar).forEach(([name, item]) => { - const parent = nav.find(item => name.startsWith(item.link)); + const parent = nav.find((item) => name.startsWith(item.link)); item.forEach((link) => addLinks(links, linkSet, link, parent)); }); diff --git a/packages/docs/guide/going-further/linting.md b/packages/docs/guide/going-further/linting.md index 23b9b2f35..bd1ea589b 100644 --- a/packages/docs/guide/going-further/linting.md +++ b/packages/docs/guide/going-further/linting.md @@ -16,7 +16,12 @@ Add the plugin to your `.oxlintrc.json`: ```json { - "jsPlugins": [{ "name": "js-toolkit", "specifier": "@studiometa/eslint-plugin-js-toolkit" }], + "jsPlugins": [ + { + "name": "js-toolkit", + "specifier": "@studiometa/eslint-plugin-js-toolkit" + } + ], "rules": { "js-toolkit/require-config": "error", "js-toolkit/require-config-name-pascal-case": "error", diff --git a/packages/docs/vite.config.ts b/packages/docs/vite.config.ts index b336438b1..b58b66a9b 100644 --- a/packages/docs/vite.config.ts +++ b/packages/docs/vite.config.ts @@ -4,10 +4,7 @@ import tailwindcss from '@tailwindcss/vite'; import llmstxt from 'vitepress-plugin-llms'; export default defineConfig({ - plugins: [ - tailwindcss(), - llmstxt(), - ], + plugins: [tailwindcss(), llmstxt()], resolve: { alias: { '@studiometa/js-toolkit/utils': resolve('../js-toolkit/utils/index.ts'), diff --git a/packages/eslint-plugin-js-toolkit/README.md b/packages/eslint-plugin-js-toolkit/README.md index ff3cc6868..abb610b28 100644 --- a/packages/eslint-plugin-js-toolkit/README.md +++ b/packages/eslint-plugin-js-toolkit/README.md @@ -80,45 +80,45 @@ export default [ ### Class structure -| Rule | Description | Recommended | Fixable | -|------|-------------|-------------|---------| -| `js-toolkit/require-config` | Requires a `static config` property with a `name` on every class extending `Base`. | error | | -| `js-toolkit/require-config-name-pascal-case` | Requires `config.name` to be PascalCase. | error | 🔧 | -| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | error | 🔧 | -| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | error | | -| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | error | 🔧 | +| Rule | Description | Recommended | Fixable | +| -------------------------------------------- | ------------------------------------------------------------------------------------------------ | ----------- | ------- | +| `js-toolkit/require-config` | Requires a `static config` property with a `name` on every class extending `Base`. | error | | +| `js-toolkit/require-config-name-pascal-case` | Requires `config.name` to be PascalCase. | error | 🔧 | +| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | error | 🔧 | +| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | error | | +| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | error | 🔧 | ### Lifecycle methods -| Rule | Description | Recommended | Fixable | -|------|-------------|-------------|---------| -| `js-toolkit/async-lifecycle-methods` | Requires lifecycle methods (`mounted`, `destroyed`, `updated`, `terminated`, `ticked`, `scrolled`, `resized`, `moved`, `loaded`, `keyed`) to be declared `async`. | error | 🔧 | +| Rule | Description | Recommended | Fixable | +| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- | +| `js-toolkit/async-lifecycle-methods` | Requires lifecycle methods (`mounted`, `destroyed`, `updated`, `terminated`, `ticked`, `scrolled`, `resized`, `moved`, `loaded`, `keyed`) to be declared `async`. | error | 🔧 | ### Event handlers -| Rule | Description | Recommended | Fixable | -|------|-------------|-------------|---------| -| `js-toolkit/on-handler-naming` | Requires event handler methods to follow the `onXxxYyy` camelCase convention. | error | | -| `js-toolkit/on-global-handler-prefix` | Requires handlers for window-only events (e.g. `resize`) to use the `onWindow` or `onDocument` prefix. | warn | | +| Rule | Description | Recommended | Fixable | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------ | ----------- | ------- | +| `js-toolkit/on-handler-naming` | Requires event handler methods to follow the `onXxxYyy` camelCase convention. | error | | +| `js-toolkit/on-global-handler-prefix` | Requires handlers for window-only events (e.g. `resize`) to use the `onWindow` or `onDocument` prefix. | warn | | ### Forbidden patterns -| Rule | Description | Recommended | Fixable | -|------|-------------|-------------|---------| -| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. | warn | | -| `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | warn | | -| `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | error | | -| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | warn | | -| `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead — the framework handles binding and cleanup automatically. | error | | -| `js-toolkit/no-deep-utils-import` | Disallows deep imports from `@studiometa/js-toolkit/utils/*`. Use the public entrypoint `@studiometa/js-toolkit/utils` instead. | error | 🔧 | -| `js-toolkit/no-redundant-with-mount-when-in-view` | Disallows wrapping `withMountWhenInView` inside `withScrolledInView` — the latter already includes the former internally. | warn | | -| `js-toolkit/no-manual-intersection-observer` | Disallows `new IntersectionObserver()` inside `Base` subclasses. Use `withIntersectionObserver` or `withMountWhenInView` decorators instead. | warn | | -| `js-toolkit/no-manual-mutation-observer` | Disallows `new MutationObserver()` inside `Base` subclasses. Use the `withMutation` decorator instead. | warn | | +| Rule | Description | Recommended | Fixable | +| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- | +| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. | warn | | +| `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | warn | | +| `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | error | | +| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | warn | | +| `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead — the framework handles binding and cleanup automatically. | error | | +| `js-toolkit/no-deep-utils-import` | Disallows deep imports from `@studiometa/js-toolkit/utils/*`. Use the public entrypoint `@studiometa/js-toolkit/utils` instead. | error | 🔧 | +| `js-toolkit/no-redundant-with-mount-when-in-view` | Disallows wrapping `withMountWhenInView` inside `withScrolledInView` — the latter already includes the former internally. | warn | | +| `js-toolkit/no-manual-intersection-observer` | Disallows `new IntersectionObserver()` inside `Base` subclasses. Use `withIntersectionObserver` or `withMountWhenInView` decorators instead. | warn | | +| `js-toolkit/no-manual-mutation-observer` | Disallows `new MutationObserver()` inside `Base` subclasses. Use the `withMutation` decorator instead. | warn | | ### Refs -| Rule | Description | Recommended | Fixable | -|------|-------------|-------------|---------| -| `js-toolkit/refs-no-bracket-access` | Disallows bracket access with a `[]` suffix on `this.$refs` (e.g. `this.$refs['items[]']`). Rewrites to dot notation camelCase. | error | 🔧 | -| `js-toolkit/prefer-ref-over-query-selector` | Warns when `this.$el.querySelector()` or `this.$el.querySelectorAll()` is used inside a `Base` subclass. Declare a ref in `static config` and use `this.$refs` instead. | warn | | -| `js-toolkit/require-refs-declared-in-config` | Requires all `this.$refs.` accesses to be declared in `static config.refs`. | error | | +| Rule | Description | Recommended | Fixable | +| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- | +| `js-toolkit/refs-no-bracket-access` | Disallows bracket access with a `[]` suffix on `this.$refs` (e.g. `this.$refs['items[]']`). Rewrites to dot notation camelCase. | error | 🔧 | +| `js-toolkit/prefer-ref-over-query-selector` | Warns when `this.$el.querySelector()` or `this.$el.querySelectorAll()` is used inside a `Base` subclass. Declare a ref in `static config` and use `this.$refs` instead. | warn | | +| `js-toolkit/require-refs-declared-in-config` | Requires all `this.$refs.` accesses to be declared in `static config.refs`. | error | | diff --git a/packages/eslint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts b/packages/eslint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts index 433ee3f2a..22e2b3eec 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/async-lifecycle-methods.ts @@ -1,4 +1,11 @@ -import { isBaseSubclass, LIFECYCLE_METHODS, findEnclosingClass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + isBaseSubclass, + LIFECYCLE_METHODS, + findEnclosingClass, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; export const asyncLifecycleMethods = createRule({ meta: { @@ -20,7 +27,7 @@ export const asyncLifecycleMethods = createRule({ const ancestors = context.getAncestors ? context.getAncestors() - : context.sourceCode?.getAncestors?.(node) ?? []; + : (context.sourceCode?.getAncestors?.(node) ?? []); const enclosingClass = findEnclosingClass(ancestors); if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; diff --git a/packages/eslint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts index 598e1f408..a9d78e86f 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/no-deprecated-properties.ts @@ -1,4 +1,10 @@ -import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + isBaseSubclass, + findEnclosingClass, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; const DEPRECATED = new Map([ ['$parent', '$closest()'], @@ -27,7 +33,7 @@ export const noDeprecatedProperties = createRule({ const ancestors = context.getAncestors ? context.getAncestors() - : context.sourceCode?.getAncestors?.(node) ?? []; + : (context.sourceCode?.getAncestors?.(node) ?? []); const enclosingClass = findEnclosingClass(ancestors); if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; diff --git a/packages/eslint-plugin-js-toolkit/src/rules/no-dispatch-event.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-dispatch-event.ts index 588e5f92f..e77f1093e 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/no-dispatch-event.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/no-dispatch-event.ts @@ -1,4 +1,10 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + findEnclosingClass, + isBaseSubclass, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; export const noDispatchEvent = createRule({ meta: { @@ -21,14 +27,13 @@ export const noDispatchEvent = createRule({ callee.object?.type === 'ThisExpression' && callee.property?.name === 'dispatchEvent') || // this.$el.dispatchEvent(...) or el.dispatchEvent(...) - (callee.type === 'MemberExpression' && - callee.property?.name === 'dispatchEvent'); + (callee.type === 'MemberExpression' && callee.property?.name === 'dispatchEvent'); if (!isDispatchEvent) return; const ancestors = context.getAncestors ? context.getAncestors() - : context.sourceCode?.getAncestors?.(node) ?? []; + : (context.sourceCode?.getAncestors?.(node) ?? []); const enclosingClass = findEnclosingClass(ancestors); if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; diff --git a/packages/eslint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts index 41e2b63fa..09d9d4e71 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/no-event-listener-methods.ts @@ -1,4 +1,10 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + findEnclosingClass, + isBaseSubclass, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; const FORBIDDEN = new Set(['addEventListener', 'removeEventListener']); @@ -28,12 +34,13 @@ export const noEventListenerMethods = createRule({ let root = callee.object; while (root?.type === 'MemberExpression') root = root.object; const isThis = root?.type === 'ThisExpression'; - const isGlobal = root?.type === 'Identifier' && (root.name === 'document' || root.name === 'window'); + const isGlobal = + root?.type === 'Identifier' && (root.name === 'document' || root.name === 'window'); if (!isThis && !isGlobal) return; const ancestors = context.getAncestors ? context.getAncestors() - : context.sourceCode?.getAncestors?.(node) ?? []; + : (context.sourceCode?.getAncestors?.(node) ?? []); const enclosingClass = findEnclosingClass(ancestors); if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; diff --git a/packages/eslint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts index 8c5722a0d..91bc992d0 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/no-manual-intersection-observer.ts @@ -1,4 +1,10 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + findEnclosingClass, + isBaseSubclass, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; export const noManualIntersectionObserver = createRule({ meta: { @@ -17,7 +23,8 @@ export const noManualIntersectionObserver = createRule({ NewExpression(node: Node) { if (node.callee?.name !== 'IntersectionObserver') return; - const ancestors = context.getAncestors?.() ?? context.sourceCode?.getAncestors?.(node) ?? []; + const ancestors = + context.getAncestors?.() ?? context.sourceCode?.getAncestors?.(node) ?? []; const cls = findEnclosingClass(ancestors); if (!cls || !isBaseSubclass(cls, context)) return; diff --git a/packages/eslint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts index 50165df71..a530188c7 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/no-manual-mutation-observer.ts @@ -1,4 +1,10 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + findEnclosingClass, + isBaseSubclass, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; export const noManualMutationObserver = createRule({ meta: { @@ -8,8 +14,7 @@ export const noManualMutationObserver = createRule({ 'Disallow manual MutationObserver inside Base subclasses; use withMutation decorator instead', }, messages: { - noManual: - 'Avoid manual "new MutationObserver()". Use the "withMutation" decorator instead.', + noManual: 'Avoid manual "new MutationObserver()". Use the "withMutation" decorator instead.', }, }, createOnce(context: RuleContext) { @@ -17,7 +22,8 @@ export const noManualMutationObserver = createRule({ NewExpression(node: Node) { if (node.callee?.name !== 'MutationObserver') return; - const ancestors = context.getAncestors?.() ?? context.sourceCode?.getAncestors?.(node) ?? []; + const ancestors = + context.getAncestors?.() ?? context.sourceCode?.getAncestors?.(node) ?? []; const cls = findEnclosingClass(ancestors); if (!cls || !isBaseSubclass(cls, context)) return; diff --git a/packages/eslint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts index 2c3fda866..c839101b5 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/no-redundant-with-mount-when-in-view.ts @@ -4,8 +4,7 @@ export const noRedundantWithMountWhenInView = createRule({ meta: { type: 'suggestion', docs: { - description: - 'Disallow wrapping withMountWhenInView inside withScrolledInView (redundant)', + description: 'Disallow wrapping withMountWhenInView inside withScrolledInView (redundant)', }, messages: { redundant: diff --git a/packages/eslint-plugin-js-toolkit/src/rules/no-shadow-dom.ts b/packages/eslint-plugin-js-toolkit/src/rules/no-shadow-dom.ts index c8982cc49..dc8a2b76b 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/no-shadow-dom.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/no-shadow-dom.ts @@ -1,4 +1,10 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + findEnclosingClass, + isBaseSubclass, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; export const noShadowDom = createRule({ meta: { @@ -15,16 +21,13 @@ export const noShadowDom = createRule({ return { CallExpression(node: Node) { const callee = node.callee; - if ( - callee.type !== 'MemberExpression' || - callee.property?.name !== 'attachShadow' - ) { + if (callee.type !== 'MemberExpression' || callee.property?.name !== 'attachShadow') { return; } const ancestors = context.getAncestors ? context.getAncestors() - : context.sourceCode?.getAncestors?.(node) ?? []; + : (context.sourceCode?.getAncestors?.(node) ?? []); const enclosingClass = findEnclosingClass(ancestors); if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; diff --git a/packages/eslint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts b/packages/eslint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts index 403157a25..56b1aed7c 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/on-global-handler-prefix.ts @@ -1,4 +1,10 @@ -import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + isBaseSubclass, + findEnclosingClass, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; // Events that only make sense on window/document, never on a DOM element. // Pointer, keyboard, and form events are intentionally excluded — they can all @@ -33,7 +39,7 @@ export const onGlobalHandlerPrefix = createRule({ const ancestors = context.getAncestors ? context.getAncestors() - : context.sourceCode?.getAncestors?.(node) ?? []; + : (context.sourceCode?.getAncestors?.(node) ?? []); const enclosingClass = findEnclosingClass(ancestors); if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; diff --git a/packages/eslint-plugin-js-toolkit/src/rules/on-handler-naming.ts b/packages/eslint-plugin-js-toolkit/src/rules/on-handler-naming.ts index 378315927..7797692f4 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/on-handler-naming.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/on-handler-naming.ts @@ -1,4 +1,10 @@ -import { isBaseSubclass, findEnclosingClass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + isBaseSubclass, + findEnclosingClass, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; // onXxxYyy — the part after "on" must start with an uppercase letter const ON_HANDLER_RE = /^on[A-Z][a-zA-Z0-9]*$/; @@ -28,7 +34,7 @@ export const onHandlerNaming = createRule({ const ancestors = context.getAncestors ? context.getAncestors() - : context.sourceCode?.getAncestors?.(node) ?? []; + : (context.sourceCode?.getAncestors?.(node) ?? []); const enclosingClass = findEnclosingClass(ancestors); if (!enclosingClass || !isBaseSubclass(enclosingClass, context)) return; diff --git a/packages/eslint-plugin-js-toolkit/src/rules/options-camel-case.ts b/packages/eslint-plugin-js-toolkit/src/rules/options-camel-case.ts index 02d198c98..b39b5f14d 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/options-camel-case.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/options-camel-case.ts @@ -1,4 +1,11 @@ -import { isBaseSubclass, isCamelCase, toCamelCase, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + isBaseSubclass, + isCamelCase, + toCamelCase, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; export const optionsCamelCase = createRule({ meta: { diff --git a/packages/eslint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts b/packages/eslint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts index 7ff3bf2ba..acb233e9f 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/prefer-ref-over-query-selector.ts @@ -1,4 +1,10 @@ -import { findEnclosingClass, isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + findEnclosingClass, + isBaseSubclass, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; const QUERY_METHODS = new Set(['querySelector', 'querySelectorAll']); @@ -30,7 +36,8 @@ export const preferRefOverQuerySelector = createRule({ const method = callee.property?.name; if (!QUERY_METHODS.has(method)) return; - const ancestors = context.getAncestors?.() ?? context.sourceCode?.getAncestors?.(node) ?? []; + const ancestors = + context.getAncestors?.() ?? context.sourceCode?.getAncestors?.(node) ?? []; const cls = findEnclosingClass(ancestors); if (!cls || !isBaseSubclass(cls, context)) return; diff --git a/packages/eslint-plugin-js-toolkit/src/rules/refs-camel-case.ts b/packages/eslint-plugin-js-toolkit/src/rules/refs-camel-case.ts index 59bc75ef3..215ce1faf 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/refs-camel-case.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/refs-camel-case.ts @@ -1,4 +1,11 @@ -import { isBaseSubclass, isCamelCase, toCamelCase, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + isBaseSubclass, + isCamelCase, + toCamelCase, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; export const refsCamelCase = createRule({ meta: { diff --git a/packages/eslint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts b/packages/eslint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts index 2fad20d66..238e3b0fa 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/refs-no-bracket-access.ts @@ -16,8 +16,7 @@ export const refsNoBracketAccess = createRule({ description: 'Disallow bracket access with [] suffix on this.$refs', }, messages: { - noBracketAccess: - 'Use "this.$refs.{{fixed}}" instead of "this.$refs[\'{{raw}}\']".', + noBracketAccess: 'Use "this.$refs.{{fixed}}" instead of "this.$refs[\'{{raw}}\']".', }, }, createOnce(context: RuleContext) { diff --git a/packages/eslint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts index 02ed7b99c..c6f7210de 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/require-config-name-pascal-case.ts @@ -1,4 +1,11 @@ -import { isBaseSubclass, isPascalCase, toPascalCase, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + isBaseSubclass, + isPascalCase, + toPascalCase, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; export const requireConfigNamePascalCase = createRule({ meta: { diff --git a/packages/eslint-plugin-js-toolkit/src/rules/require-config.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-config.ts index b945e6e32..88d70ec11 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/require-config.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/require-config.ts @@ -45,8 +45,7 @@ function check(node: Node, context: RuleContext) { if (!value || value.type !== 'ObjectExpression') return; const hasName = value.properties?.some( - (prop: Node) => - prop.type === 'Property' && prop.key?.name === 'name' && prop.value?.value, + (prop: Node) => prop.type === 'Property' && prop.key?.name === 'name' && prop.value?.value, ); if (!hasName) { diff --git a/packages/eslint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts index e853ad6f3..4170e41a8 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/require-refs-declared-in-config.ts @@ -1,12 +1,15 @@ -import { isBaseSubclass, toCamelCase, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + isBaseSubclass, + toCamelCase, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; function collectDeclaredRefs(classNode: Node): Set { const body: Node[] = classNode.body?.body ?? []; const configProp = body.find( - (m: Node) => - m.type === 'PropertyDefinition' && - m.static === true && - m.key?.name === 'config', + (m: Node) => m.type === 'PropertyDefinition' && m.static === true && m.key?.name === 'config', ); if (!configProp?.value || configProp.value.type !== 'ObjectExpression') return new Set(); diff --git a/packages/eslint-plugin-js-toolkit/src/utils/ast.ts b/packages/eslint-plugin-js-toolkit/src/utils/ast.ts index 7d101ff1b..731dee903 100644 --- a/packages/eslint-plugin-js-toolkit/src/utils/ast.ts +++ b/packages/eslint-plugin-js-toolkit/src/utils/ast.ts @@ -51,8 +51,7 @@ export function isBaseSubclass(node: Node, context: RuleContext): boolean { return false; } - const superName = - node.superClass.type === 'Identifier' ? node.superClass.name : null; + const superName = node.superClass.type === 'Identifier' ? node.superClass.name : null; if (!superName) { return false; @@ -76,10 +75,7 @@ export function isBaseSubclass(node: Node, context: RuleContext): boolean { if (node.source.value !== TOOLKIT_PACKAGE) continue; for (const specifier of node.specifiers) { - if ( - specifier.type === 'ImportSpecifier' && - specifier.local.name === superName - ) { + if (specifier.type === 'ImportSpecifier' && specifier.local.name === superName) { return true; } } diff --git a/packages/eslint-plugin-js-toolkit/src/utils/rule-tester.ts b/packages/eslint-plugin-js-toolkit/src/utils/rule-tester.ts index d534d57ac..a9f6bf556 100644 --- a/packages/eslint-plugin-js-toolkit/src/utils/rule-tester.ts +++ b/packages/eslint-plugin-js-toolkit/src/utils/rule-tester.ts @@ -11,7 +11,11 @@ const _tester = new RuleTester({ }); export const tester = { - run(name: string, rule: Parameters[0]['rules'][string], tests: Parameters['run']>[2]) { + run( + name: string, + rule: Parameters[0]['rules'][string], + tests: Parameters['run']>[2], + ) { const wrapped = eslintCompatPlugin({ rules: { [name]: rule } }); _tester.run(name, wrapped.rules[name] as any, tests); }, diff --git a/packages/js-toolkit/Base/Base.ts b/packages/js-toolkit/Base/Base.ts index dc844094e..7d1ab18f7 100644 --- a/packages/js-toolkit/Base/Base.ts +++ b/packages/js-toolkit/Base/Base.ts @@ -172,7 +172,7 @@ export class Base { * @deprecated Use `$closest(name)` instead. Will be removed in v4. * @link https://js-toolkit.studiometa.dev/api/instance-properties.html#parent */ - get $parent(): T['$parent'] & Base | null { + get $parent(): (T['$parent'] & Base) | null { if (isDev) { console.warn( `[${this.$id}] $parent is deprecated and will be removed in v4. Use $closest(name) instead.`, @@ -186,7 +186,7 @@ export class Base { * Internal parent resolution without deprecation warning. * @internal */ - get __parent(): T['$parent'] & Base | null { + get __parent(): (T['$parent'] & Base) | null { const parents = new Set(); for (const instance of getInstances()) { diff --git a/packages/js-toolkit/Base/types.ts b/packages/js-toolkit/Base/types.ts index a2d930477..67d5d5a38 100644 --- a/packages/js-toolkit/Base/types.ts +++ b/packages/js-toolkit/Base/types.ts @@ -15,7 +15,7 @@ export interface BaseInterface { * Trigger the `mounted` callback. * @link https://js-toolkit.studiometa.dev/api/instance-methods.html#mount */ - $mount?(): Promise + $mount?(): Promise; /** * Update the instance children. @@ -62,7 +62,7 @@ export interface BaseInterface { event: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions, - ): void + ): void; /** * Emits an event. @@ -1231,7 +1231,9 @@ export interface BaseInterface { * Hook for the `securitypolicyviolation` event emitted on `document`. * @link https://js-toolkit.studiometa.dev/api/methods-hooks-events.html#on-event */ - onDocumentSecuritypolicyviolation?(context: BaseEventHookParams): void; + onDocumentSecuritypolicyviolation?( + context: BaseEventHookParams, + ): void; /** * Hook for the `seeked` event emitted on `document`. @@ -1389,7 +1391,7 @@ export interface BaseInterface { */ onDocumentWheel?(context: BaseEventHookParams): void; - /** + /** * Hook for the `abort` event emitted on `window`. * @link https://js-toolkit.studiometa.dev/api/methods-hooks-events.html#on-event */ @@ -1861,7 +1863,9 @@ export interface BaseInterface { * Hook for the `securitypolicyviolation` event emitted on `window`. * @link https://js-toolkit.studiometa.dev/api/methods-hooks-events.html#on-event */ - onWindowSecuritypolicyviolation?(context: BaseEventHookParams): void; + onWindowSecuritypolicyviolation?( + context: BaseEventHookParams, + ): void; /** * Hook for the `seeked` event emitted on `window`. diff --git a/packages/js-toolkit/Base/utils.ts b/packages/js-toolkit/Base/utils.ts index b556b59a2..3d6685ff2 100644 --- a/packages/js-toolkit/Base/utils.ts +++ b/packages/js-toolkit/Base/utils.ts @@ -121,8 +121,10 @@ function getInstancesStorage(): Set { * Get the global elements storage. * It will hold reference to all elements with at least one component attached to it. */ -function getElementsStorage(): Set}> { - return (globalThis.__JS_TOOLKIT_ELEMENTS__ ??= new Set}>()); +function getElementsStorage(): Set }> { + return (globalThis.__JS_TOOLKIT_ELEMENTS__ ??= new Set< + HTMLElement & { __base__: Map } + >()); } /** @@ -155,12 +157,12 @@ export function getElements() { export function addInstance(instance: Base) { getInstancesStorage().add(instance); - getElementsStorage().add(instance.$el as HTMLElement & { __base__: Map}); + getElementsStorage().add(instance.$el as HTMLElement & { __base__: Map }); } export function deleteInstance(instance: Base) { getInstancesStorage().delete(instance); - getElementsStorage().delete(instance.$el as HTMLElement & { __base__: Map}); + getElementsStorage().delete(instance.$el as HTMLElement & { __base__: Map }); } const registryKey = '__JS_TOOLKIT_REGISTRY__'; @@ -214,7 +216,10 @@ export function addToRegistry(nameOrSelector: string, ctor: BaseConstructor) { /** * Find the closest instance from the given element in the given set of Base instances. */ -export function findClosestInstance(element: HTMLElement, instances: Set): T { +export function findClosestInstance( + element: HTMLElement, + instances: Set, +): T { let closest = null; let minDepth = Infinity; diff --git a/packages/js-toolkit/decorators/withBreakpointManager.ts b/packages/js-toolkit/decorators/withBreakpointManager.ts index 2acaf9a71..5b2304599 100644 --- a/packages/js-toolkit/decorators/withBreakpointManager.ts +++ b/packages/js-toolkit/decorators/withBreakpointManager.ts @@ -39,7 +39,7 @@ const instances: WeakMap> = new WeakMap(); /** * BreakpointManager class. * @link https://js-toolkit.studiometa.dev/api/decorators/withBreakpointManager.html -*/ + */ export function withBreakpointManager( BaseClass: typeof Base, breakpoints: Array<[string, BaseConstructor]>, diff --git a/packages/js-toolkit/decorators/withBreakpointObserver.ts b/packages/js-toolkit/decorators/withBreakpointObserver.ts index 099235c67..c2a4399fa 100644 --- a/packages/js-toolkit/decorators/withBreakpointObserver.ts +++ b/packages/js-toolkit/decorators/withBreakpointObserver.ts @@ -108,7 +108,7 @@ function addToResize(key, instance) { /** * BreakpointObserver class. * @link https://js-toolkit.studiometa.dev/api/decorators/withBreakpointObserver.html -*/ + */ export function withBreakpointObserver( BaseClass: typeof Base, ): BaseDecorator { diff --git a/packages/js-toolkit/decorators/withDrag.ts b/packages/js-toolkit/decorators/withDrag.ts index 8d0e20a08..be6be6342 100644 --- a/packages/js-toolkit/decorators/withDrag.ts +++ b/packages/js-toolkit/decorators/withDrag.ts @@ -14,7 +14,7 @@ export interface WithDragInterface extends BaseInterface { /** * Add dragging capabilities to a component. * @link https://js-toolkit.studiometa.dev/api/decorators/withDrag.html -*/ + */ export function withDrag( BaseClass: typeof Base, { target = (instance) => instance.$el, ...options }: DragDecoratorOptions = {}, diff --git a/packages/js-toolkit/decorators/withExtraConfig.ts b/packages/js-toolkit/decorators/withExtraConfig.ts index ac52d6e71..0a1858d60 100644 --- a/packages/js-toolkit/decorators/withExtraConfig.ts +++ b/packages/js-toolkit/decorators/withExtraConfig.ts @@ -6,7 +6,7 @@ import type { Base, BaseProps, BaseConfig } from '../Base/index.js'; /** * Extends the configuration of an existing class. * @link https://js-toolkit.studiometa.dev/api/decorators/withExtraConfig.html -*/ + */ export function withExtraConfig( BaseClass: typeof Base, config: Partial, diff --git a/packages/js-toolkit/decorators/withFreezedOptions.ts b/packages/js-toolkit/decorators/withFreezedOptions.ts index ef615c949..e8389e157 100644 --- a/packages/js-toolkit/decorators/withFreezedOptions.ts +++ b/packages/js-toolkit/decorators/withFreezedOptions.ts @@ -8,7 +8,7 @@ export interface WithFreezedOptionsInterface extends BaseInterface { /** * Freeze the `$options` property to improve performance. * @link https://js-toolkit.studiometa.dev/api/decorators/withFreezedOptions.html -*/ + */ export function withFreezedOptions( BaseClass: typeof Base, ): BaseDecorator { diff --git a/packages/js-toolkit/decorators/withGroup.ts b/packages/js-toolkit/decorators/withGroup.ts index 41206db36..d4a1ef912 100644 --- a/packages/js-toolkit/decorators/withGroup.ts +++ b/packages/js-toolkit/decorators/withGroup.ts @@ -41,7 +41,8 @@ export function withGroup( */ get $group() { const group = `${namespace}${this.$options.group}`; - const instances = groups().get(group) ?? groups().set(group, new Set()).get(group); + const instances = + groups().get(group) ?? groups().set(group, new Set()).get(group); for (const instance of instances) { if (!instance.$el.isConnected) { diff --git a/packages/js-toolkit/decorators/withIntersectionObserver.ts b/packages/js-toolkit/decorators/withIntersectionObserver.ts index 7120d8721..624047b69 100644 --- a/packages/js-toolkit/decorators/withIntersectionObserver.ts +++ b/packages/js-toolkit/decorators/withIntersectionObserver.ts @@ -17,7 +17,7 @@ export interface WithIntersectionObserverInterface extends BaseInterface { /** * IntersectionObserver decoration. * @link https://js-toolkit.studiometa.dev/api/decorators/withIntersectionObserver.html -*/ + */ export function withIntersectionObserver( BaseClass: typeof Base, // eslint-disable-next-line unicorn/no-object-as-default-parameter diff --git a/packages/js-toolkit/decorators/withMountOnMediaQuery.ts b/packages/js-toolkit/decorators/withMountOnMediaQuery.ts index fda539117..9b21588c8 100644 --- a/packages/js-toolkit/decorators/withMountOnMediaQuery.ts +++ b/packages/js-toolkit/decorators/withMountOnMediaQuery.ts @@ -18,7 +18,7 @@ export interface withMountOnMediaQueryInterface extends BaseInterface { /** * IntersectionObserver decoration. * @link https://js-toolkit.studiometa.dev/api/decorators/withMountOnMediaQuery.html -*/ + */ export function withMountOnMediaQuery( BaseClass: typeof Base, media, diff --git a/packages/js-toolkit/decorators/withMountWhenInView.ts b/packages/js-toolkit/decorators/withMountWhenInView.ts index 39e5a1494..7ebe04b41 100644 --- a/packages/js-toolkit/decorators/withMountWhenInView.ts +++ b/packages/js-toolkit/decorators/withMountWhenInView.ts @@ -21,7 +21,7 @@ export interface WithMountWhenInViewInterface extends BaseInterface { /** * IntersectionObserver decoration. * @link https://js-toolkit.studiometa.dev/api/decorators/withMountWhenInView.html -*/ + */ export function withMountWhenInView( BaseClass: typeof Base, // eslint-disable-next-line unicorn/no-object-as-default-parameter diff --git a/packages/js-toolkit/decorators/withMutation.ts b/packages/js-toolkit/decorators/withMutation.ts index 1f38a2fe6..6e83b4f17 100644 --- a/packages/js-toolkit/decorators/withMutation.ts +++ b/packages/js-toolkit/decorators/withMutation.ts @@ -14,7 +14,7 @@ export interface WithMutationInterface extends BaseInterface { /** * Add a mutation observer to a component. * @link https://js-toolkit.studiometa.dev/api/decorators/withMutation.html -*/ + */ export function withMutation( BaseClass: typeof Base, { target = (instance) => instance.$el, ...options }: MutationDecoratorOptions = {}, diff --git a/packages/js-toolkit/decorators/withRelativePointer.ts b/packages/js-toolkit/decorators/withRelativePointer.ts index 4f963d23b..6edada392 100644 --- a/packages/js-toolkit/decorators/withRelativePointer.ts +++ b/packages/js-toolkit/decorators/withRelativePointer.ts @@ -14,7 +14,7 @@ export interface RelativePointerInterface extends BaseInterface { /** * Add dragging capabilities to a component. * @link https://js-toolkit.studiometa.dev/api/decorators/withRelativePointer.html -*/ + */ export function withRelativePointer( BaseClass: typeof Base, { target = (instance) => instance.$el }: RelativePointerDecoratorOptions = {}, diff --git a/packages/js-toolkit/decorators/withResponsiveOptions.ts b/packages/js-toolkit/decorators/withResponsiveOptions.ts index 85b9e94aa..0d217b031 100644 --- a/packages/js-toolkit/decorators/withResponsiveOptions.ts +++ b/packages/js-toolkit/decorators/withResponsiveOptions.ts @@ -10,7 +10,7 @@ import { isDefined, isObject } from '../utils/index.js'; /** * Extends the configuration of an existing class. * @link https://js-toolkit.studiometa.dev/api/decorators/withResponsiveOptions.html -*/ + */ export function withResponsiveOptions( BaseClass: typeof Base, { responsiveOptions = [] } = {}, diff --git a/packages/js-toolkit/decorators/withScrolledInView/withScrolledInView.ts b/packages/js-toolkit/decorators/withScrolledInView/withScrolledInView.ts index 4e9836eda..425f26c30 100644 --- a/packages/js-toolkit/decorators/withScrolledInView/withScrolledInView.ts +++ b/packages/js-toolkit/decorators/withScrolledInView/withScrolledInView.ts @@ -107,7 +107,7 @@ export interface WithScrolledInViewInterface extends BaseInterface { /** * Add scrolled in view capabilities to a component. * @link https://js-toolkit.studiometa.dev/api/decorators/withScrolledInView.html -*/ + */ export function withScrolledInView( BaseClass: typeof Base, options: WithScrolledInViewOptions = {}, diff --git a/packages/js-toolkit/helpers/createApp.ts b/packages/js-toolkit/helpers/createApp.ts index 8f051f0e5..75c637ec2 100644 --- a/packages/js-toolkit/helpers/createApp.ts +++ b/packages/js-toolkit/helpers/createApp.ts @@ -13,16 +13,14 @@ export type CreateAppOptions = Partial & { * Instantiate and mount the given component on the given root element when the page has been loaded * and return a function to use the app instance when it is ready. * @link https://js-toolkit.studiometa.dev/api/helpers/createApp.html -*/ + */ export function createApp, T extends BaseProps = BaseProps>( App: S, options: HTMLElement | CreateAppOptions = {}, ): () => Promise> { let app: S & Base; - const { - root = document.body, - ...featureOptions - } = options instanceof HTMLElement ? { root: options } : options; + const { root = document.body, ...featureOptions } = + options instanceof HTMLElement ? { root: options } : options; defineFeatures(featureOptions); diff --git a/packages/js-toolkit/helpers/getClosestParent.ts b/packages/js-toolkit/helpers/getClosestParent.ts index 93a0205ec..609e56eb3 100644 --- a/packages/js-toolkit/helpers/getClosestParent.ts +++ b/packages/js-toolkit/helpers/getClosestParent.ts @@ -5,7 +5,7 @@ import { getAncestorWhere } from '../utils/index.js'; /** * Get the closest parent of a component. * @link https://js-toolkit.studiometa.dev/api/helpers/getClosestParent.html -*/ + */ export function getClosestParent( childInstance: Base, ParentConstructor: T, diff --git a/packages/js-toolkit/helpers/getDirectChildren.ts b/packages/js-toolkit/helpers/getDirectChildren.ts index 296cc4e77..80e2bb4db 100644 --- a/packages/js-toolkit/helpers/getDirectChildren.ts +++ b/packages/js-toolkit/helpers/getDirectChildren.ts @@ -10,7 +10,7 @@ import { isArray } from '../utils/index.js'; * @param {string} childrenName * @return {T[]} * @link https://js-toolkit.studiometa.dev/api/helpers/getDirectChildren.html -*/ + */ export function getDirectChildren( parentInstance: Base, parentName: string, @@ -54,7 +54,7 @@ export function getDirectChildren( * @param {Base} childInstance * @return {boolean} * @link https://js-toolkit.studiometa.dev/api/helpers/isDirectChild.html -*/ + */ export function isDirectChild(parentInstance, parentName, childrenName, childInstance) { return getDirectChildren(parentInstance, parentName, childrenName).includes(childInstance); } diff --git a/packages/js-toolkit/helpers/getInstanceFromElement.ts b/packages/js-toolkit/helpers/getInstanceFromElement.ts index e2080b890..1c73f7872 100644 --- a/packages/js-toolkit/helpers/getInstanceFromElement.ts +++ b/packages/js-toolkit/helpers/getInstanceFromElement.ts @@ -3,7 +3,7 @@ import type { BaseConstructor, BaseEl } from '../Base/index.js'; /** * Get a component instance from a DOM element. * @link https://js-toolkit.studiometa.dev/api/helpers/getInstanceFromElement.html -*/ + */ export function getInstanceFromElement( element: BaseEl, Constructor: T, diff --git a/packages/js-toolkit/helpers/importOnInteraction.ts b/packages/js-toolkit/helpers/importOnInteraction.ts index 14825b8c4..d7f35adb4 100644 --- a/packages/js-toolkit/helpers/importOnInteraction.ts +++ b/packages/js-toolkit/helpers/importOnInteraction.ts @@ -16,7 +16,7 @@ import { isString, getComponentResolver } from '../utils/index.js'; * The parent component. * @return {Promise>} * @link https://js-toolkit.studiometa.dev/api/helpers/importOnInteraction.html -*/ + */ export function importOnInteraction( fn: () => Promise, nameOrSelectorOrElement: string | HTMLElement | HTMLElement[], diff --git a/packages/js-toolkit/helpers/importOnMediaQuery.ts b/packages/js-toolkit/helpers/importOnMediaQuery.ts index 33216e629..e01428309 100644 --- a/packages/js-toolkit/helpers/importOnMediaQuery.ts +++ b/packages/js-toolkit/helpers/importOnMediaQuery.ts @@ -11,7 +11,7 @@ import { getComponentResolver } from '../utils/index.js'; * The media query name and value (see https://developer.mozilla.org/en-US/docs/Web/CSS/@media#media_features) * @return {Promise} * @link https://js-toolkit.studiometa.dev/api/helpers/importOnMediaQuery.html -*/ + */ export function importOnMediaQuery( fn: () => Promise, media: string, diff --git a/packages/js-toolkit/helpers/importWhenIdle.ts b/packages/js-toolkit/helpers/importWhenIdle.ts index 7e29f5b2b..76ecc0eaa 100644 --- a/packages/js-toolkit/helpers/importWhenIdle.ts +++ b/packages/js-toolkit/helpers/importWhenIdle.ts @@ -15,7 +15,7 @@ type ImportWhenIdleOptions = { * The time to wait before triggering the callback if never idle. * @return {Promise} * @link https://js-toolkit.studiometa.dev/api/helpers/importWhenIdle.html -*/ + */ export function importWhenIdle( fn: () => Promise, { timeout = 1 }: ImportWhenIdleOptions = {}, diff --git a/packages/js-toolkit/helpers/importWhenPrefersMotion.ts b/packages/js-toolkit/helpers/importWhenPrefersMotion.ts index d3be29105..cb485591d 100644 --- a/packages/js-toolkit/helpers/importWhenPrefersMotion.ts +++ b/packages/js-toolkit/helpers/importWhenPrefersMotion.ts @@ -8,7 +8,7 @@ import { importOnMediaQuery } from './importOnMediaQuery.js'; * @param {() => Promise} fn * @return {Promise} * @link https://js-toolkit.studiometa.dev/api/helpers/importWhenPrefersMotion.html -*/ + */ export function importWhenPrefersMotion( fn: () => Promise, ): Promise { diff --git a/packages/js-toolkit/helpers/importWhenVisible.ts b/packages/js-toolkit/helpers/importWhenVisible.ts index 73fd1bb76..a5546ac4a 100644 --- a/packages/js-toolkit/helpers/importWhenVisible.ts +++ b/packages/js-toolkit/helpers/importWhenVisible.ts @@ -16,7 +16,7 @@ import { getComponentResolver } from '../utils/index.js'; * Options for the `IntersectionObserver` instance. * @return {Promise} * @link https://js-toolkit.studiometa.dev/api/helpers/importWhenVisible.html -*/ + */ export function importWhenVisible( fn: () => Promise, nameOrSelectorOrElement: string | HTMLElement | HTMLElement[], diff --git a/packages/js-toolkit/helpers/queryComponent.ts b/packages/js-toolkit/helpers/queryComponent.ts index 83300e77f..0688955b7 100644 --- a/packages/js-toolkit/helpers/queryComponent.ts +++ b/packages/js-toolkit/helpers/queryComponent.ts @@ -106,7 +106,11 @@ export function closestComponent( if (!baseMap) return false; const instance = baseMap.get(parsedQuery.name) as T; - if (instance && instance !== ('terminated' as unknown) && instanceIsMatching(instance, parsedQuery)) { + if ( + instance && + instance !== ('terminated' as unknown) && + instanceIsMatching(instance, parsedQuery) + ) { closestInstance = instance; return true; } diff --git a/packages/js-toolkit/services/DragService.ts b/packages/js-toolkit/services/DragService.ts index 915cdbcb9..15df6b22c 100644 --- a/packages/js-toolkit/services/DragService.ts +++ b/packages/js-toolkit/services/DragService.ts @@ -329,7 +329,7 @@ export class DragService extends AbstractService { * props(); * ``` * @link https://js-toolkit.studiometa.dev/api/services/useDrag.html -*/ + */ export function useDrag(target: HTMLElement, options: DragServiceOptions): DragServiceInterface { return DragService.getInstance( [target, JSON.stringify(options)], diff --git a/packages/js-toolkit/services/KeyService.ts b/packages/js-toolkit/services/KeyService.ts index 02019f499..e6549c99f 100644 --- a/packages/js-toolkit/services/KeyService.ts +++ b/packages/js-toolkit/services/KeyService.ts @@ -77,7 +77,7 @@ export class KeyService extends AbstractService { /** * Use the keyboard service. * @link https://js-toolkit.studiometa.dev/api/services/useKey.html -*/ + */ export function useKey(): KeyServiceInterface { return KeyService.getInstance(); } diff --git a/packages/js-toolkit/services/LoadService.ts b/packages/js-toolkit/services/LoadService.ts index be41e53df..f40480370 100644 --- a/packages/js-toolkit/services/LoadService.ts +++ b/packages/js-toolkit/services/LoadService.ts @@ -23,7 +23,7 @@ export class LoadService extends AbstractService { /** * Use the load service. * @link https://js-toolkit.studiometa.dev/api/services/useLoad.html -*/ + */ export function useLoad(): LoadServiceInterface { return LoadService.getInstance(); } diff --git a/packages/js-toolkit/services/MutationService.ts b/packages/js-toolkit/services/MutationService.ts index e852edf0e..cd5100cb9 100644 --- a/packages/js-toolkit/services/MutationService.ts +++ b/packages/js-toolkit/services/MutationService.ts @@ -44,7 +44,7 @@ export class MutationService extends AbstractService { /** * Use the mutation service. * @link https://js-toolkit.studiometa.dev/api/services/useMutation.html -*/ + */ export function useMutation( target?: Node, options?: MutationObserverInit, diff --git a/packages/js-toolkit/services/PointerService.ts b/packages/js-toolkit/services/PointerService.ts index 0e4976f5b..20ef8a564 100644 --- a/packages/js-toolkit/services/PointerService.ts +++ b/packages/js-toolkit/services/PointerService.ts @@ -169,7 +169,7 @@ export class PointerService extends AbstractService { /** * Use the pointer service. * @link https://js-toolkit.studiometa.dev/api/services/usePointer.html -*/ + */ export function usePointer(target: HTMLElement | Window = window): PointerServiceInterface { return PointerService.getInstance([target], target); } diff --git a/packages/js-toolkit/services/RafService.ts b/packages/js-toolkit/services/RafService.ts index fc8f9f996..35ce0c671 100644 --- a/packages/js-toolkit/services/RafService.ts +++ b/packages/js-toolkit/services/RafService.ts @@ -73,7 +73,7 @@ export class RafService extends AbstractService { /** * Use the RequestAnimationFrame (raf) service. * @link https://js-toolkit.studiometa.dev/api/services/useRaf.html -*/ + */ export function useRaf(): RafServiceInterface { return RafService.getInstance(); } diff --git a/packages/js-toolkit/services/ResizeService.ts b/packages/js-toolkit/services/ResizeService.ts index c32f29e91..fd019ee4c 100644 --- a/packages/js-toolkit/services/ResizeService.ts +++ b/packages/js-toolkit/services/ResizeService.ts @@ -97,7 +97,7 @@ export class ResizeService< /** * Use the resize service. * @link https://js-toolkit.studiometa.dev/api/services/useResize.html -*/ + */ export function useResize( breakpoints?: T, ): ResizeServiceInterface { diff --git a/packages/js-toolkit/services/ScrollService.ts b/packages/js-toolkit/services/ScrollService.ts index 02748c938..577ec74e7 100644 --- a/packages/js-toolkit/services/ScrollService.ts +++ b/packages/js-toolkit/services/ScrollService.ts @@ -189,7 +189,7 @@ export class ScrollService extends AbstractService { * props(); * ``` * @link https://js-toolkit.studiometa.dev/api/services/useScroll.html -*/ + */ export function useScroll(): ScrollServiceInterface { return ScrollService.getInstance(); } diff --git a/packages/js-toolkit/utils/cache.ts b/packages/js-toolkit/utils/cache.ts index 852802b01..b5a2a314f 100644 --- a/packages/js-toolkit/utils/cache.ts +++ b/packages/js-toolkit/utils/cache.ts @@ -3,7 +3,7 @@ import { isArray } from './is.js'; /** * Cache the result of a callback in map instances. * @link https://js-toolkit.studiometa.dev/utils/cache.html -*/ + */ export function cache(keys: any | any[], callback: () => T): T { const normalizedKeys = isArray(keys) ? keys : [keys]; let value = (globalThis.__JS_TOOLKIT_CACHE__ ??= new Map()); diff --git a/packages/js-toolkit/utils/collide/boundingRectToCircle.ts b/packages/js-toolkit/utils/collide/boundingRectToCircle.ts index 2476d0e33..5ce38bae8 100644 --- a/packages/js-toolkit/utils/collide/boundingRectToCircle.ts +++ b/packages/js-toolkit/utils/collide/boundingRectToCircle.ts @@ -12,7 +12,7 @@ * @param {boolean} force Force usage of non-square DOMElements * @return {Circle} Circle object that can be used in collides functions * @link https://js-toolkit.studiometa.dev/utils/collision/boundingRectToCircle.html -*/ + */ export function boundingRectToCircle({ x, y, width, height }, force = false) { if (width !== height && !force) { throw new Error('Initial DOMElement is not a square. Please use the force mode.'); diff --git a/packages/js-toolkit/utils/collide/collideCircleCircle.ts b/packages/js-toolkit/utils/collide/collideCircleCircle.ts index a693ba8d6..d124fe0ad 100644 --- a/packages/js-toolkit/utils/collide/collideCircleCircle.ts +++ b/packages/js-toolkit/utils/collide/collideCircleCircle.ts @@ -13,7 +13,7 @@ * @param {Circle} circle2 Circle 2 * @return {boolean} Are the sides of one circle touching the other ? * @link https://js-toolkit.studiometa.dev/utils/collision/collideCircleCircle.html -*/ + */ export function collideCircleCircle(circle1, circle2) { // get distance between the circle's centers // use the Pythagorean Theorem to compute the distance diff --git a/packages/js-toolkit/utils/collide/collideCircleRect.ts b/packages/js-toolkit/utils/collide/collideCircleRect.ts index 8a9c22492..11013a80e 100644 --- a/packages/js-toolkit/utils/collide/collideCircleRect.ts +++ b/packages/js-toolkit/utils/collide/collideCircleRect.ts @@ -21,7 +21,7 @@ * @param {Rect} rect Rectangle * @return {boolean} Are the sides of the circle touching the rectangle ? * @link https://js-toolkit.studiometa.dev/utils/collision/collideCircleRect.html -*/ + */ export function collideCircleRect(circle, rect) { // temporary variables to set edges for testing let testX = circle.x; diff --git a/packages/js-toolkit/utils/collide/collidePointCircle.ts b/packages/js-toolkit/utils/collide/collidePointCircle.ts index de35d82ff..43c2f173b 100644 --- a/packages/js-toolkit/utils/collide/collidePointCircle.ts +++ b/packages/js-toolkit/utils/collide/collidePointCircle.ts @@ -19,7 +19,7 @@ * @param {Circle} circle Circle * @return {boolean} Is the point inside the circle's bounds ? * @link https://js-toolkit.studiometa.dev/utils/collision/collidePointCircle.html -*/ + */ export function collidePointCircle(point, circle) { // get distance between the point and circle's center // using the Pythagorean Theorem diff --git a/packages/js-toolkit/utils/collide/collidePointRect.ts b/packages/js-toolkit/utils/collide/collidePointRect.ts index ffd595e95..21974f15b 100644 --- a/packages/js-toolkit/utils/collide/collidePointRect.ts +++ b/packages/js-toolkit/utils/collide/collidePointRect.ts @@ -20,7 +20,7 @@ * @param {Rect} rect Rectangle * @return {boolean} Is the point inside the rectangle's bounds ? * @link https://js-toolkit.studiometa.dev/utils/collision/collidePointRect.html -*/ + */ export function collidePointRect(point, rect) { return ( point.x >= rect.x && // right of the left edge AND diff --git a/packages/js-toolkit/utils/collide/collideRectRect.ts b/packages/js-toolkit/utils/collide/collideRectRect.ts index 464892c8f..af98a7b21 100644 --- a/packages/js-toolkit/utils/collide/collideRectRect.ts +++ b/packages/js-toolkit/utils/collide/collideRectRect.ts @@ -14,7 +14,7 @@ * @param {Rect} rect2 Rectangle 2 * @return {boolean} Are the sides of one rectangle touching the other ? * @link https://js-toolkit.studiometa.dev/utils/collision/collideRectRect.html -*/ + */ export function collideRectRect(rect1, rect2) { return ( rect1.x + rect1.width >= rect2.x && // rect1 right edge past rect2 left AND diff --git a/packages/js-toolkit/utils/css/animate.ts b/packages/js-toolkit/utils/css/animate.ts index 267bdcc22..9fca0e039 100644 --- a/packages/js-toolkit/utils/css/animate.ts +++ b/packages/js-toolkit/utils/css/animate.ts @@ -30,7 +30,6 @@ export type NormalizedKeyframe = Keyframe & { vars: string[]; }; - /** * A pre-compiled segment between two keyframes. * All constant parts (deltas, offsets, writers) are resolved once at creation. @@ -122,9 +121,7 @@ function resolveUnitValue(val: number | [number, string], elementSize: number): /** * Non-translate transform property definitions: [keyframeProp, cssFn, unit, defaultValue]. */ -const SIMPLE_TRANSFORM_DEFS: ReadonlyArray< - readonly [string, string, string, number] -> = [ +const SIMPLE_TRANSFORM_DEFS: ReadonlyArray = [ ['rotate', 'rotate', 'deg', 0], ['rotateX', 'rotateX', 'deg', 0], ['rotateY', 'rotateY', 'deg', 0], @@ -142,9 +139,7 @@ const SIMPLE_TRANSFORM_DEFS: ReadonlyArray< * Translate axis definitions: [keyframeProp, defaultValue, sizeRef]. * These are grouped into a single translate3d() call during rendering. */ -const TRANSLATE_DEFS: ReadonlyArray< - readonly [string, number, string] -> = [ +const TRANSLATE_DEFS: ReadonlyArray = [ ['x', 0, 'offsetWidth'], ['y', 0, 'offsetHeight'], ['z', 0, 'offsetWidth'], @@ -154,10 +149,7 @@ const TRANSLATE_DEFS: ReadonlyArray< * Compile a pair of keyframes into a pre-computed segment. * All constant work (deltas, property filtering) is done here once. */ -function compileSegment( - from: NormalizedKeyframe, - to: NormalizedKeyframe, -): CompiledSegment { +function compileSegment(from: NormalizedKeyframe, to: NormalizedKeyframe): CompiledSegment { const transformStarts: number[] = []; const transformDeltas: number[] = []; const transformCssFunctions: string[] = []; @@ -248,14 +240,8 @@ function compileSegment( * Render an element using a pre-compiled segment and a progress value. * The hot path: only multiply-add, string concatenation, and DOM writes. */ -function renderSegment( - element: HTMLElement, - segment: CompiledSegment, - progress: number, -) { - const easedProgress = segment.easing( - map(progress, segment.fromOffset, segment.toOffset, 0, 1), - ); +function renderSegment(element: HTMLElement, segment: CompiledSegment, progress: number) { + const easedProgress = segment.easing(map(progress, segment.fromOffset, segment.toOffset, 0, 1)); scheduler.read(() => { // Resolve dynamic (CSS-unit) transforms that need element size @@ -264,7 +250,8 @@ function renderSegment( const elementSize = element[dynamic.sizeRef]; const startValue = resolveUnitValue(dynamic.from, elementSize); segment.transformStarts[dynamic.index] = startValue; - segment.transformDeltas[dynamic.index] = resolveUnitValue(dynamic.to, elementSize) - startValue; + segment.transformDeltas[dynamic.index] = + resolveUnitValue(dynamic.to, elementSize) - startValue; } // Build transform string: translate3d grouped, then individual transforms @@ -316,7 +303,10 @@ function renderSegment( for (let i = 0; i < segment.customPropertyNames.length; i++) { element.style.setProperty( segment.customPropertyNames[i], - (segment.customPropertyStarts[i] + segment.customPropertyDeltas[i] * easedProgress).toString(), + ( + segment.customPropertyStarts[i] + + segment.customPropertyDeltas[i] * easedProgress + ).toString(), ); } } @@ -399,7 +389,7 @@ type DurationWithDelay = number; /** * Animate one or more elements. * @link https://js-toolkit.studiometa.dev/utils/css/animate.html -*/ + */ export function animate( elementOrElements: HTMLElement | HTMLElement[] | NodeListOf, keyframes: Keyframe[], @@ -464,9 +454,7 @@ export function animate( if (newProgress !== undefined) { const newTime = lerp(0, duration, newProgress); for (let i = 0; i < controls.length; i++) { - const controlProgress = clamp01( - map(newTime, timings[i][1], timings[i][2], 0, 1), - ); + const controlProgress = clamp01(map(newTime, timings[i][1], timings[i][2], 0, 1)); controls[i].progress(controlProgress); } } diff --git a/packages/js-toolkit/utils/css/classes.ts b/packages/js-toolkit/utils/css/classes.ts index 92e1329e1..60de53762 100644 --- a/packages/js-toolkit/utils/css/classes.ts +++ b/packages/js-toolkit/utils/css/classes.ts @@ -30,7 +30,7 @@ function setClasses( /** * Add class names to an element. * @link https://js-toolkit.studiometa.dev/utils/css/addClass.html -*/ + */ export function add( element: Element | Element[] | NodeListOf, classNames: string | string[], @@ -41,7 +41,7 @@ export function add( /** * Remove class names from an element. * @link https://js-toolkit.studiometa.dev/utils/css/removeClass.html -*/ + */ export function remove( element: Element | Element[] | NodeListOf, classNames: string | string[], @@ -52,7 +52,7 @@ export function remove( /** * Toggle class names from an element. * @link https://js-toolkit.studiometa.dev/utils/css/toggleClass.html -*/ + */ export function toggle( element: Element | Element[] | NodeListOf, classNames: string | string[], diff --git a/packages/js-toolkit/utils/css/getOffsetSizes.ts b/packages/js-toolkit/utils/css/getOffsetSizes.ts index 83c7e22d8..f5134eccd 100644 --- a/packages/js-toolkit/utils/css/getOffsetSizes.ts +++ b/packages/js-toolkit/utils/css/getOffsetSizes.ts @@ -1,7 +1,7 @@ /** * Get a `DOMRect` like `Object` for an element, without its transforms. * @link https://js-toolkit.studiometa.dev/utils/css/getOffsetSizes.html -*/ + */ export function getOffsetSizes(element: HTMLElement) { let parent = element; let x = -window.scrollX; diff --git a/packages/js-toolkit/utils/css/matrix.ts b/packages/js-toolkit/utils/css/matrix.ts index f7c9a8c70..fcbe3a209 100644 --- a/packages/js-toolkit/utils/css/matrix.ts +++ b/packages/js-toolkit/utils/css/matrix.ts @@ -24,7 +24,7 @@ type MatrixTransform = { * // matrix(0.5, 0, 0, 0.5, 0, 0) * ``` * @link https://js-toolkit.studiometa.dev/utils/css/matrix.html -*/ + */ export function matrix(transform?: MatrixTransform): string { // eslint-disable-next-line no-param-reassign transform = transform || {}; diff --git a/packages/js-toolkit/utils/css/styles.ts b/packages/js-toolkit/utils/css/styles.ts index 0e9048288..cb7e17a77 100644 --- a/packages/js-toolkit/utils/css/styles.ts +++ b/packages/js-toolkit/utils/css/styles.ts @@ -24,7 +24,7 @@ function setStyles( /** * Add styles to an element. * @link https://js-toolkit.studiometa.dev/utils/css/addStyle.html -*/ + */ export function add( elementOrElements: HTMLElement | HTMLElement[] | NodeListOf, styles: Partial, @@ -35,7 +35,7 @@ export function add( /** * Remove class names from an element. * @link https://js-toolkit.studiometa.dev/utils/css/removeStyle.html -*/ + */ export function remove( elementOrElements: HTMLElement | HTMLElement[] | NodeListOf, styles: Partial, diff --git a/packages/js-toolkit/utils/css/transform.ts b/packages/js-toolkit/utils/css/transform.ts index bda070242..aa824e39e 100644 --- a/packages/js-toolkit/utils/css/transform.ts +++ b/packages/js-toolkit/utils/css/transform.ts @@ -38,12 +38,12 @@ export const TRANSFORM_PROPS = [ /** * Generate a CSS transform. * @link https://js-toolkit.studiometa.dev/utils/css/transform.html -*/ + */ export function transform( elementOrElements: HTMLElement | HTMLElement[] | NodeListOf, props: TransformProps, ): string { - if (!elementOrElements) return + if (!elementOrElements) return; let value = ''; diff --git a/packages/js-toolkit/utils/css/transition.ts b/packages/js-toolkit/utils/css/transition.ts index cd58dc196..dd6d6b30f 100644 --- a/packages/js-toolkit/utils/css/transition.ts +++ b/packages/js-toolkit/utils/css/transition.ts @@ -183,7 +183,7 @@ async function singleTransition( * @param {string} endMode Whether to remove or keep the `to` classes/styles * @return {Promise} A promise resolving at the end of the transition. * @link https://js-toolkit.studiometa.dev/utils/css/transition.html -*/ + */ export async function transition( elementOrElements: HTMLElement | HTMLElement[] | NodeListOf, name: string | TransitionStyles, diff --git a/packages/js-toolkit/utils/debounce.ts b/packages/js-toolkit/utils/debounce.ts index 97c39dc43..39ae59e9e 100644 --- a/packages/js-toolkit/utils/debounce.ts +++ b/packages/js-toolkit/utils/debounce.ts @@ -7,7 +7,7 @@ * @param {number} [delay=300] The delay in ms to wait before calling the function. * @return {(...args:unknown[]) => void} The debounced function. * @link https://js-toolkit.studiometa.dev/utils/debounce.html -*/ + */ export function debounce( fn: (...args: unknown[]) => void, delay = 300, diff --git a/packages/js-toolkit/utils/history.ts b/packages/js-toolkit/utils/history.ts index de2c234c3..09752316b 100644 --- a/packages/js-toolkit/utils/history.ts +++ b/packages/js-toolkit/utils/history.ts @@ -57,7 +57,7 @@ function updateUrlSearchParam( * @param {string} defaultSearch A string of defaults search params. * @return {URLSearchParams} * @link https://js-toolkit.studiometa.dev/utils/history/objectToURLSearchParams.html -*/ + */ export function objectToURLSearchParams( obj: unknown, defaultSearch = hasWindow() ? window.location.search : '', @@ -123,7 +123,7 @@ function updateHistory( * @param {string} [title] The title for the new state. * @return {void} * @link https://js-toolkit.studiometa.dev/utils/history/historyPush.html -*/ + */ export function push(options: HistoryOptions, data: unknown = {}, title = '') { updateHistory('push', options, data, title); } @@ -136,7 +136,7 @@ export function push(options: HistoryOptions, data: unknown = {}, title = '') { * @param {string} [title] The title for the new state. * @return {void} * @link https://js-toolkit.studiometa.dev/utils/history/historyReplace.html -*/ + */ export function replace(options: HistoryOptions, data: unknown = {}, title = '') { updateHistory('replace', options, data, title); } diff --git a/packages/js-toolkit/utils/is.ts b/packages/js-toolkit/utils/is.ts index bdc6b467b..0fdfaf453 100644 --- a/packages/js-toolkit/utils/is.ts +++ b/packages/js-toolkit/utils/is.ts @@ -7,65 +7,65 @@ export const isDev = typeof __DEV__ !== 'undefined' && __DEV__; /** * Test is the given value is null. * @link https://js-toolkit.studiometa.dev/utils/is/isNull.html -*/ + */ export const isNull = (value: unknown): value is null => value === null; /** * Test if the given value is a function. * @link https://js-toolkit.studiometa.dev/utils/is/isFunction.html -*/ + */ // eslint-disable-next-line @typescript-eslint/ban-types export const isFunction = (value: unknown): value is Function => typeof value === 'function'; /** * Test if a value is defined or not. * @link https://js-toolkit.studiometa.dev/utils/is/isDefined.html -*/ + */ export const isDefined = (value: unknown): boolean => typeof value !== 'undefined'; /** * Test if value is a string. * @link https://js-toolkit.studiometa.dev/utils/is/isString.html -*/ + */ export const isString = (value: unknown): value is string => typeof value === 'string'; /** * Test if the given value is an object. * @link https://js-toolkit.studiometa.dev/utils/is/isObject.html -*/ + */ export const isObject = (value: unknown): boolean => typeof value === 'object' && !!value && value.toString() === '[object Object]'; /** * Test if a given value is a number. * @link https://js-toolkit.studiometa.dev/utils/is/isNumber.html -*/ + */ export const isNumber = (value: unknown): value is number => typeof value === 'number' && !Number.isNaN(value); /** * Test if a given value is a boolean. * @link https://js-toolkit.studiometa.dev/utils/is/isBoolean.html -*/ + */ export const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean'; // eslint-disable-next-line prefer-destructuring /** * Test if a given value is an array. * @link https://js-toolkit.studiometa.dev/utils/is/isArray.html -*/ + */ export const isArray = Array.isArray; /** * Test if a given value is an empty string. * @link https://js-toolkit.studiometa.dev/utils/is/isEmptyString.html -*/ + */ export const isEmptyString = (value?: unknown): boolean => isString(value) && value.length === 0; /** * Test if the given value is empty. * @link https://js-toolkit.studiometa.dev/utils/is/isEmpty.html -*/ + */ export const isEmpty = (value?) => { if (isNull(value) || !isDefined(value)) { return true; diff --git a/packages/js-toolkit/utils/loadElement.ts b/packages/js-toolkit/utils/loadElement.ts index 1931660bb..06e76df37 100644 --- a/packages/js-toolkit/utils/loadElement.ts +++ b/packages/js-toolkit/utils/loadElement.ts @@ -25,7 +25,7 @@ export type LoadElementsReturnType = Promise<{ * Load the given source for the given type of element. * @todo manage memo * @link https://js-toolkit.studiometa.dev/utils/loadElement.html -*/ + */ export function loadElement( src: string, type: T, @@ -47,7 +47,7 @@ export function loadElement( /** * Load the given source as an `` element. * @link https://js-toolkit.studiometa.dev/utils/loadImage.html -*/ + */ export function loadImage(src: string, options?: LoadElementsOptions) { return loadElement(src, 'img', options); } @@ -55,7 +55,7 @@ export function loadImage(src: string, options?: LoadElementsOptions) { /** * Load the given source as an `