diff --git a/.changepacks/changepack_log__L_t84RVYpiOqQEWV7oib.json b/.changepacks/changepack_log__L_t84RVYpiOqQEWV7oib.json new file mode 100644 index 0000000..a27f8fe --- /dev/null +++ b/.changepacks/changepack_log__L_t84RVYpiOqQEWV7oib.json @@ -0,0 +1 @@ +{"changes":{"package.json":"Patch"},"note":"Support oxlint","date":"2026-01-22T15:25:57.906162800Z"} \ No newline at end of file diff --git a/.changepacks/config.json b/.changepacks/config.json index 990abf7..f74d516 100644 --- a/.changepacks/config.json +++ b/.changepacks/config.json @@ -4,4 +4,4 @@ "latestPackage": "package.json", "publish": {}, "updateOn": {} -} \ No newline at end of file +} diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index ddd64e2..826025c 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -1,21 +1,21 @@ -name: Check Pull Request -on: - pull_request: - branches: - - main -jobs: - check: - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - - run: bun i - - run: | - bun test - bun run build - # eslint project can not lint before build - bun lint +name: Check Pull Request +on: + pull_request: + branches: + - main +jobs: + check: + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - run: bun i + - run: | + bun test + bun run build + # eslint project can not lint before build + bun lint diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bbb1dfd..ab1fdc3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,37 +1,37 @@ -name: Publish Package to npm - -on: - push: - branches: - - main - -jobs: - publish: - runs-on: ubuntu-latest - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - permissions: - # create pull request comments - pull-requests: write - - # Actions > General > Workflow permissions for creating pull request - # Create brench to create pull request - contents: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - - run: bun i - - run: | - bun test - bun run build - # eslint project can not lint before build - bun lint - - uses: changepacks/action@main - id: changepacks - with: - publish: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} +name: Publish Package to npm + +on: + push: + branches: + - main + +jobs: + publish: + runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + permissions: + # create pull request comments + pull-requests: write + + # Actions > General > Workflow permissions for creating pull request + # Create brench to create pull request + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - run: bun i + - run: | + bun test + bun run build + # eslint project can not lint before build + bun lint + - uses: changepacks/action@main + id: changepacks + with: + publish: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..e9d2223 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json", + "extends": ["./oxlintrc.json"], + "jsPlugins": [ + "./dist/oxlint.mjs", + "eslint-plugin-simple-import-sort", + "eslint-plugin-unused-imports", + "@tanstack/eslint-plugin-query", + "eslint-plugin-eslint-plugin" + ], + "rules": { + "eslint-plugin/require-meta-docs-description": "error", + "eslint-plugin/require-meta-type": "error", + "eslint-plugin/no-unused-message-ids": "error", + "eslint-plugin/no-missing-message-ids": "error", + "eslint-plugin/prefer-message-ids": "error", + "eslint-plugin/no-deprecated-report-api": "error" + } +} diff --git a/README.md b/README.md index 21eba52..fb82430 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,58 @@ -# eslint-plugin-devup - -`devup` is a system for faster and more accurate software development used at DevFive. - -`eslint-plugin-devup` helps you develop software quickly and accurately. - -For details on each rule, please refer to the files in the `src/rules` directory. - -As this was originally used internally and has now been made public, some rules exist for company-specific systems. - -All rules are provided as `named exports` so you can use only the rules you want. - -## Installation - -```bash -bun add -d eslint-plugin-devup -``` - -## Usage - -Create an `eslint.config.mjs` file in your project root. - -```js -import { configs } from 'eslint-plugin-devup' - -export default configs.recommended -``` - -## Test - -Coverage score must be 100%. - -```bash -bun test -``` - -## Contributing - -- When adding or modifying rules, add or modify files in the `src/rules` directory. -- When adding or modifying rules, add a description of the rule in `README.md`. - -All opinions and contributions are welcome. - -## Join the Community - -[Discord](https://discord.gg/8zjcGc7cWh) +# eslint-plugin-devup + +`devup` is a system for faster and more accurate software development used at DevFive. + +`eslint-plugin-devup` helps you develop software quickly and accurately. + +For details on each rule, please refer to the files in the `src/rules` directory. + +As this was originally used internally and has now been made public, some rules exist for company-specific systems. + +All rules are provided as `named exports` so you can use only the rules you want. + +## Installation + +```bash +bun add -d eslint-plugin-devup +``` + +## Usage + +### ESLint + +Create an `eslint.config.mjs` file in your project root. + +```js +import { configs } from "eslint-plugin-devup"; + +export default configs.recommended; +``` + +### Oxlint + +Create an `.oxlintrc.json` file in your project root. + +```json +{ + "extends": ["eslint-plugin-devup/oxlintrc"] +} +``` + +## Test + +Coverage score must be 100%. + +```bash +bun test +``` + +## Contributing + +- When adding or modifying rules, add or modify files in the `src/rules` directory. +- When adding or modifying rules, add a description of the rule in `README.md`. + +All opinions and contributions are welcome. + +## Join the Community + +[Discord](https://discord.gg/8zjcGc7cWh) diff --git a/bun.lock b/bun.lock index 393b1be..7220dd6 100644 --- a/bun.lock +++ b/bun.lock @@ -17,16 +17,19 @@ "eslint-plugin-simple-import-sort": ">=12", "eslint-plugin-unused-imports": ">=4", "prettier": ">=3", - "typescript-eslint": ">=8.51", + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12", + "typescript-eslint": ">=8.53", }, "devDependencies": { "@changesets/cli": "^2.29", "@types/bun": "^1.3", "@types/eslint": "^9.6", - "@typescript-eslint/rule-tester": "^8.51", - "@typescript-eslint/utils": "^8.51", - "eslint-plugin-eslint-plugin": "^7.2", + "@typescript-eslint/rule-tester": "^8.53", + "@typescript-eslint/utils": "^8.53", + "eslint-plugin-eslint-plugin": "^7.3", "husky": "^9.1", + "oxlint": "^1.41.0", "typescript": "^5.9", "vite": "^7.3", "vite-plugin-dts": "^4.5", @@ -159,7 +162,7 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], @@ -233,6 +236,22 @@ "@npmcli/promise-spawn": ["@npmcli/promise-spawn@7.0.2", "", { "dependencies": { "which": "^4.0.0" } }, "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ=="], + "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.41.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-K0Bs0cNW11oWdSrKmrollKF44HMM2HKr4QidZQHMlhJcSX8pozxv0V5FLdqB4sddzCY0J9Wuuw+oRAfR8sdRwA=="], + + "@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.41.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-1LCCXCe9nN8LbrJ1QOGari2HqnxrZrveYKysWDIg8gFsQglIg00XF/8lRbA0kWHMdLgt4X0wfNYhhFz+c3XXLQ=="], + + "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@1.41.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Fow7H84Bs8XxuaK1yfSEWBC8HI7rfEQB9eR2A0J61un1WgCas7jNrt1HbT6+p6KmUH2bhR+r/RDu/6JFAvvj4g=="], + + "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@1.41.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-WoRRDNwgP5W3rjRh42Zdx8ferYnqpKoYCv2QQLenmdrLjRGYwAd52uywfkcS45mKEWHeY1RPwPkYCSROXiGb2w=="], + + "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@1.41.0", "", { "os": "linux", "cpu": "x64" }, "sha512-75k3CKj3fOc/a/2aSgO81s3HsTZOFROthPJ+UI2Oatic1LhvH6eKjKfx3jDDyVpzeDS2qekPlc/y3N33iZz5Og=="], + + "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@1.41.0", "", { "os": "linux", "cpu": "x64" }, "sha512-8r82eBwGPoAPn67ZvdxTlX/Z3gVb+ZtN6nbkyFzwwHWAh8yGutX+VBcVkyrePSl6XgBP4QAaddPnHmkvJjqY0g=="], + + "@oxlint/win32-arm64": ["@oxlint/win32-arm64@1.41.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-aK+DAcckQsNCOXKruatyYuY/ROjNiRejQB1PeJtkZwM21+8rV9ODYbvKNvt0pW+YCws7svftBSFMCpl3ke2unw=="], + + "@oxlint/win32-x64": ["@oxlint/win32-x64@1.41.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dVBXkZ6MGLd3owV7jvuqJsZwiF3qw7kEkDVsYVpS/O96eEvlHcxVbaPjJjrTBgikXqyC22vg3dxBU7MW0utGfw=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], @@ -327,25 +346,25 @@ "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.51.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/type-utils": "8.51.0", "@typescript-eslint/utils": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.51.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.51.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.53.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.51.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.51.0", "@typescript-eslint/types": "^8.51.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.53.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.53.1", "@typescript-eslint/types": "^8.53.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog=="], - "@typescript-eslint/rule-tester": ["@typescript-eslint/rule-tester@8.51.0", "", { "dependencies": { "@typescript-eslint/parser": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/utils": "8.51.0", "ajv": "^6.12.6", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "4.6.2", "semver": "^7.6.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-iMEcUgzOcVhNo68VelLiZNdBGp91bG3CcLvFR0kJokMooRU82PkWT+ISGDHuph2eMnUucYkf6QTr71IFQ6aB+w=="], + "@typescript-eslint/rule-tester": ["@typescript-eslint/rule-tester@8.53.1", "", { "dependencies": { "@typescript-eslint/parser": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1", "@typescript-eslint/utils": "8.53.1", "ajv": "^6.12.6", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "4.6.2", "semver": "^7.7.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-+Xn/2Wd3AxB4LD1AYVLSDNYMCjbFrZAwt0rYgS/KmT7DjJr/TRMXVtS4eK4Gje8r4XUdWbiR/akse6aVYgqcCA=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0" } }, "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.53.1", "", { "dependencies": { "@typescript-eslint/types": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1" } }, "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.51.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.53.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA=="], "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/utils": "8.51.0", "debug": "^4.3.4", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.53.1", "", {}, "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.51.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.51.0", "@typescript-eslint/tsconfig-utils": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.53.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.53.1", "@typescript-eslint/tsconfig-utils": "8.53.1", "@typescript-eslint/types": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.51.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.53.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.53.1", "", { "dependencies": { "@typescript-eslint/types": "8.53.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg=="], "@volar/language-core": ["@volar/language-core@2.4.27", "", { "dependencies": { "@volar/source-map": "2.4.27" } }, "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ=="], @@ -539,7 +558,7 @@ "eslint-mdx": ["eslint-mdx@3.6.2", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "espree": "^9.6.1 || ^10.4.0", "estree-util-visit": "^2.0.0", "remark-mdx": "^3.1.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "synckit": "^0.11.8", "unified": "^11.0.5", "unified-engine": "^11.2.2", "unist-util-visit": "^5.0.0", "uvu": "^0.5.6", "vfile": "^6.0.3" }, "peerDependencies": { "eslint": ">=8.0.0", "remark-lint-file-extension": "*" }, "optionalPeers": ["remark-lint-file-extension"] }, "sha512-5hczn5iSSEcwtNtVXFwCKIk6iLEDaZpwc3vjYDl/B779OzaAAK/ou16J2xVdO6ecOLEO1WZqp7MRCQ/WsKDUig=="], - "eslint-plugin-eslint-plugin": ["eslint-plugin-eslint-plugin@7.2.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "estraverse": "^5.3.0" }, "peerDependencies": { "eslint": ">=9.0.0" } }, "sha512-3WOuoauBlxCItqpIdyajCOVQbCmAlqHNQq82QunpzuGkBNr6OqHRjdPZKpy2Z0rGb005mIO0HEP9aaDCzkApxQ=="], + "eslint-plugin-eslint-plugin": ["eslint-plugin-eslint-plugin@7.3.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "estraverse": "^5.3.0" }, "peerDependencies": { "eslint": ">=9.0.0" } }, "sha512-M9S7ihAFD91+FnSja0Joky+0xrJlgMqmy3WmbOJVNpnUqy49YqEImSdfuVbpnggVz3QinzIVPJh2cPYaJ1Z4TA=="], "eslint-plugin-mdx": ["eslint-plugin-mdx@3.6.2", "", { "dependencies": { "eslint-mdx": "^3.6.2", "mdast-util-from-markdown": "^2.0.2", "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0", "remark-mdx": "^3.1.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "synckit": "^0.11.8", "unified": "^11.0.5", "vfile": "^6.0.3" }, "peerDependencies": { "eslint": ">=8.0.0" } }, "sha512-RfMd5HYD/9+cqANhVWJbuBRg3huWUsAoGJNGmPsyiRD2X6BaG6bvt1omyk1ORlg81GK8ST7Ojt5fNAuwWhWU8A=="], @@ -945,6 +964,8 @@ "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], + "oxlint": ["oxlint@1.41.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.41.0", "@oxlint/darwin-x64": "1.41.0", "@oxlint/linux-arm64-gnu": "1.41.0", "@oxlint/linux-arm64-musl": "1.41.0", "@oxlint/linux-x64-gnu": "1.41.0", "@oxlint/linux-x64-musl": "1.41.0", "@oxlint/win32-arm64": "1.41.0", "@oxlint/win32-x64": "1.41.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.11.1" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-Dyaoup82uhgAgp5xLNt4dPdvl5eSJTIzqzL7DcKbkooUE4PDViWURIPlSUF8hu5a+sCnNIp/LlQMDsKoyaLTBA=="], + "p-filter": ["p-filter@2.1.0", "", { "dependencies": { "p-map": "^2.0.0" } }, "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw=="], "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], @@ -1129,7 +1150,7 @@ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - "synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="], + "synckit": ["synckit@0.11.12", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="], "term-size": ["term-size@2.2.1", "", {}, "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg=="], @@ -1139,7 +1160,7 @@ "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], - "ts-api-utils": ["ts-api-utils@2.3.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg=="], + "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], @@ -1157,7 +1178,7 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "typescript-eslint": ["typescript-eslint@8.51.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.51.0", "@typescript-eslint/parser": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/utils": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA=="], + "typescript-eslint": ["typescript-eslint@8.53.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.53.1", "@typescript-eslint/parser": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1", "@typescript-eslint/utils": "8.53.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg=="], "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], @@ -1251,6 +1272,10 @@ "@changesets/write/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="], + "@devup-ui/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.51.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA=="], + + "@devup-ui/eslint-plugin/typescript-eslint": ["typescript-eslint@8.51.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.51.0", "@typescript-eslint/parser": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/utils": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], @@ -1301,14 +1326,40 @@ "@rushstack/terminal/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + "@tanstack/eslint-plugin-query/@typescript-eslint/utils": ["@typescript-eslint/utils@8.51.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0" } }, "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.51.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "@typescript-eslint/eslint-plugin/ts-api-utils": ["ts-api-utils@2.3.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg=="], + + "@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], + + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.51.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.51.0", "@typescript-eslint/tsconfig-utils": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng=="], + + "@typescript-eslint/type-utils/@typescript-eslint/utils": ["@typescript-eslint/utils@8.51.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA=="], + + "@typescript-eslint/type-utils/ts-api-utils": ["ts-api-utils@2.3.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg=="], + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "@vue/language-core/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "ajv-formats/ajv": ["ajv@8.13.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.4.1" } }, "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA=="], + "eslint/@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + + "eslint-mdx/synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="], + + "eslint-plugin-mdx/synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="], + + "eslint-plugin-prettier/synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="], + "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], @@ -1337,6 +1388,8 @@ "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "typescript-eslint/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.53.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/type-utils": "8.53.1", "@typescript-eslint/utils": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.53.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag=="], + "unified-engine/ignore": ["ignore@6.0.2", "", {}, "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A=="], "uvu/diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="], @@ -1353,6 +1406,18 @@ "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "@devup-ui/eslint-plugin/@typescript-eslint/utils/@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + + "@devup-ui/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0" } }, "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA=="], + + "@devup-ui/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], + + "@devup-ui/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.51.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.51.0", "@typescript-eslint/tsconfig-utils": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng=="], + + "@devup-ui/eslint-plugin/typescript-eslint/@typescript-eslint/parser": ["@typescript-eslint/parser@8.51.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A=="], + + "@devup-ui/eslint-plugin/typescript-eslint/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.51.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.51.0", "@typescript-eslint/tsconfig-utils": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng=="], + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], @@ -1373,24 +1438,128 @@ "@rushstack/node-core-library/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + "@tanstack/eslint-plugin-query/@typescript-eslint/utils/@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + + "@tanstack/eslint-plugin-query/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0" } }, "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA=="], + + "@tanstack/eslint-plugin-query/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], + + "@tanstack/eslint-plugin-query/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.51.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.51.0", "@typescript-eslint/tsconfig-utils": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.51.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.51.0", "@typescript-eslint/tsconfig-utils": "8.51.0", "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.2.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], + + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.51.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.51.0", "@typescript-eslint/types": "^8.51.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ=="], + + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.51.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw=="], + + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg=="], + + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/type-utils/@typescript-eslint/utils/@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + + "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0" } }, "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "@vue/language-core/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "eslint/@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "typescript-eslint/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.53.1", "", { "dependencies": { "@typescript-eslint/types": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1", "@typescript-eslint/utils": "8.53.1", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w=="], + + "typescript-eslint/@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "wrap-ansi/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "@devup-ui/eslint-plugin/@typescript-eslint/utils/@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@devup-ui/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg=="], + + "@devup-ui/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.51.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.51.0", "@typescript-eslint/types": "^8.51.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ=="], + + "@devup-ui/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.51.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw=="], + + "@devup-ui/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg=="], + + "@devup-ui/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@devup-ui/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.3.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg=="], + + "@devup-ui/eslint-plugin/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "@typescript-eslint/visitor-keys": "8.51.0" } }, "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA=="], + + "@devup-ui/eslint-plugin/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], + + "@devup-ui/eslint-plugin/typescript-eslint/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg=="], + + "@devup-ui/eslint-plugin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.51.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.51.0", "@typescript-eslint/types": "^8.51.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ=="], + + "@devup-ui/eslint-plugin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.51.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw=="], + + "@devup-ui/eslint-plugin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/types": ["@typescript-eslint/types@8.51.0", "", {}, "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag=="], + + "@devup-ui/eslint-plugin/typescript-eslint/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg=="], + + "@devup-ui/eslint-plugin/typescript-eslint/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@devup-ui/eslint-plugin/typescript-eslint/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.3.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg=="], + "@manypkg/find-root/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "@tanstack/eslint-plugin-query/@typescript-eslint/utils/@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@tanstack/eslint-plugin-query/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg=="], + + "@tanstack/eslint-plugin-query/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.51.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.51.0", "@typescript-eslint/types": "^8.51.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ=="], + + "@tanstack/eslint-plugin-query/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.51.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw=="], + + "@tanstack/eslint-plugin-query/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg=="], + + "@tanstack/eslint-plugin-query/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@tanstack/eslint-plugin-query/@typescript-eslint/utils/@typescript-eslint/typescript-estree/ts-api-utils": ["ts-api-utils@2.3.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.51.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.51.0", "@typescript-eslint/types": "^8.51.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.51.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "@typescript-eslint/type-utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "@typescript-eslint/type-utils/@typescript-eslint/utils/@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@typescript-eslint/type-utils/@typescript-eslint/utils/@typescript-eslint/scope-manager/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.51.0", "", { "dependencies": { "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg=="], + + "@devup-ui/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "@devup-ui/eslint-plugin/typescript-eslint/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "@tanstack/eslint-plugin-query/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], } } diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index 5a98dc7..0000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import eslintPlugin from 'eslint-plugin-eslint-plugin' - -import { configs } from './dist/index.mjs' -export default [ - ...configs.recommended, - { - ...eslintPlugin.configs['flat/recommended'], - files: ['src/**'], - }, -] diff --git a/oxlintrc.json b/oxlintrc.json new file mode 100644 index 0000000..fd4f6cc --- /dev/null +++ b/oxlintrc.json @@ -0,0 +1,202 @@ +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json", + "plugins": [ + "react", + "typescript", + "unicorn", + "import", + "jsx-a11y", + "nextjs", + "promise", + "oxc" + ], + "jsPlugins": [ + "eslint-plugin-devup/oxlint", + "eslint-plugin-simple-import-sort", + "eslint-plugin-unused-imports", + "@tanstack/eslint-plugin-query" + ], + "env": { + "browser": true, + "es2024": true, + "node": true + }, + + "ignorePatterns": [ + "**/node_modules/", + "**/build/", + "**/storybook-static/", + "**/dist/", + "**/next-env.d.ts", + "**/out/", + "**/public/", + "**/df/", + "**/coverage/", + "**/target/", + "**/venv/", + "**/__snapshots__/", + "**/.*/", + "!**/src/**", + "!vite.config.ts" + ], + "rules": { + "devup/app-page": "error", + "devup/component": "error", + "devup/component-interface": "error", + + "devup/prettier": [ + "error", + { + "endOfLine": "auto", + "trailingComma": "all", + "singleQuote": true, + "semi": false + } + ], + + "devup/ui/css-utils-literal-only": "error", + "devup/ui/no-duplicate-value": "error", + "devup/ui/no-useless-responsive": "error", + "devup/ui/no-useless-tailing-nulls": "error", + "devup/ui/style-order-range": "error", + + "devup/mdx/remark": "error", + + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", + + "unused-imports/no-unused-imports": "error", + "unused-imports/no-unused-vars": "off", + + "@tanstack/query/exhaustive-deps": "error", + + "no-console": ["error", { "allow": ["info", "debug", "warn", "error"] }], + "no-constant-condition": ["error", { "checkLoops": false }], + "no-debugger": "error", + "no-empty": "error", + "no-empty-pattern": "error", + "no-trailing-spaces": "error", + "no-unused-vars": [ + "error", + { + "args": "all", + "argsIgnorePattern": "^_", + "caughtErrors": "all", + "caughtErrorsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "ignoreRestSiblings": true + } + ], + "no-unused-expressions": [ + "error", + { + "allowShortCircuit": true, + "allowTernary": true + } + ], + "spaced-comment": ["error", "always", { "markers": ["/"] }], + + "react/react-in-jsx-scope": "off", + "react/prop-types": "off", + "react/jsx-key": "error", + "react/jsx-no-comment-textnodes": "error", + "react/jsx-no-duplicate-props": "error", + "react/jsx-no-target-blank": "error", + "react/jsx-no-undef": "error", + "react/jsx-curly-brace-presence": "error", + "react/jsx-sort-props": [ + "error", + { + "callbacksLast": false, + "shorthandFirst": false, + "shorthandLast": false, + "ignoreCase": false, + "noSortAlphabetically": false, + "reservedFirst": true + } + ], + "react/no-children-prop": "error", + "react/no-danger-with-children": "error", + "react/no-direct-mutation-state": "error", + "react/no-find-dom-node": "error", + "react/no-is-mounted": "error", + "react/no-render-return-value": "error", + "react/no-string-refs": "error", + "react/no-unescaped-entities": "error", + "react/no-unknown-property": "error", + "react/sort-default-props": "error", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": [ + "warn", + { + "additionalHooks": "useSafeEffect" + } + ], + + "typescript/explicit-module-boundary-types": "off", + "typescript/no-explicit-any": "off", + "typescript/no-var-requires": "off", + "typescript/ban-ts-comment": "off", + "typescript/no-duplicate-enum-values": "error", + "typescript/no-empty-object-type": "error", + "typescript/no-extra-non-null-assertion": "error", + "typescript/no-misused-new": "error", + "typescript/no-namespace": "error", + "typescript/no-non-null-asserted-optional-chain": "error", + "typescript/no-require-imports": "error", + "typescript/no-this-alias": "error", + "typescript/no-unnecessary-type-constraint": "error", + "typescript/no-unsafe-declaration-merging": "error", + "typescript/no-unsafe-function-type": "error", + "typescript/no-wrapper-object-types": "error", + "typescript/prefer-as-const": "error", + "typescript/prefer-namespace-keyword": "error", + "typescript/triple-slash-reference": "error", + + "import/no-default-export": "off", + + "jsx-a11y/alt-text": "error", + "jsx-a11y/anchor-has-content": "error", + "jsx-a11y/anchor-is-valid": "error", + "jsx-a11y/click-events-have-key-events": "error", + "jsx-a11y/heading-has-content": "error", + "jsx-a11y/html-has-lang": "error", + "jsx-a11y/lang": "error", + "jsx-a11y/no-autofocus": "warn", + "jsx-a11y/no-redundant-roles": "error", + "jsx-a11y/role-has-required-aria-props": "error", + "jsx-a11y/role-supports-aria-props": "error", + + "nextjs/no-html-link-for-pages": "error", + "nextjs/no-img-element": "warn", + "nextjs/no-sync-scripts": "error", + + "promise/no-new-statics": "error", + "promise/valid-params": "error" + }, + "overrides": [ + { + "files": ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], + "rules": { + "no-var": "error", + "prefer-rest-params": "error", + "prefer-spread": "error" + } + }, + { + "files": ["**/*.test-d.{ts,tsx}"], + "rules": { + "no-unused-expressions": "off" + } + }, + { + "files": ["**/*.{md,mdx}"], + "rules": { + "react/jsx-no-undef": "off", + "no-empty-pattern": "off", + "typescript/no-empty-object-type": "off" + } + } + ] +} diff --git a/package.json b/package.json index 76739e2..e3e830d 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,9 @@ "type": "module", "scripts": { "test": "bun test", - "lint": "eslint", - "build": "tsc && bun build --target node src/index.ts --production --outfile dist/index.cjs --format cjs --packages external && bun build --target node src/index.ts --production --outfile dist/index.mjs --format esm --packages external && cp dist/index.d.ts dist/index.d.cts", + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "build": "tsc && bun build --target node src/index.ts --production --outfile dist/index.cjs --format cjs --packages external && bun build --target node src/index.ts --production --outfile dist/index.mjs --format esm --packages external && bun build --target node src/oxlint.ts --production --outfile dist/oxlint.mjs --format esm --packages external && cp dist/index.d.ts dist/index.d.cts", "prepare": "bun run husky" }, "exports": { @@ -20,11 +21,17 @@ "types": "./dist/index.d.ts", "default": "./dist/index.cjs" } - } + }, + "./oxlint": { + "import": "./dist/oxlint.mjs", + "default": "./dist/oxlint.mjs" + }, + "./oxlintrc": "./oxlintrc.json" }, "types": "./dist/index.d.ts", "files": [ - "dist" + "dist", + "oxlintrc.json" ], "keywords": [ "eslint" @@ -44,7 +51,7 @@ "eslint-plugin-simple-import-sort": ">=12", "eslint-plugin-unused-imports": ">=4", "prettier": ">=3", - "typescript-eslint": ">=8.51" + "typescript-eslint": ">=8.53" }, "peerDependencies": { "eslint": "*" @@ -53,10 +60,11 @@ "@changesets/cli": "^2.29", "@types/bun": "^1.3", "@types/eslint": "^9.6", - "@typescript-eslint/rule-tester": "^8.51", - "@typescript-eslint/utils": "^8.51", - "eslint-plugin-eslint-plugin": "^7.2", + "@typescript-eslint/rule-tester": "^8.53", + "@typescript-eslint/utils": "^8.53", + "eslint-plugin-eslint-plugin": "^7.3", "husky": "^9.1", + "oxlint": "^1.41.0", "typescript": "^5.9", "vite": "^7.3", "vite-plugin-dts": "^4.5" diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 27e5d3e..bef4949 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -1,21 +1,21 @@ -import { describe, expect, it } from 'bun:test' - -import * as index from '../index' - -describe('export index', () => { - it('should export rules object', () => { - expect(index.rules).toBeDefined() - expect(typeof index.rules).toBe('object') - }) - - it('should export configs with recommended', () => { - expect(index.configs).toBeDefined() - expect(index.configs.recommended).toBeDefined() - }) - - it('should export default with configs', () => { - expect(index.default).toBeDefined() - expect(index.default.configs).toBeDefined() - expect(index.default.configs.recommended).toBeDefined() - }) -}) +import { describe, expect, it } from 'bun:test' + +import * as index from '../index' + +describe('export index', () => { + it('should export rules object', () => { + expect(index.rules).toBeDefined() + expect(typeof index.rules).toBe('object') + }) + + it('should export configs with recommended', () => { + expect(index.configs).toBeDefined() + expect(index.configs.recommended).toBeDefined() + }) + + it('should export default with configs', () => { + expect(index.default).toBeDefined() + expect(index.default.configs).toBeDefined() + expect(index.default.configs.recommended).toBeDefined() + }) +}) diff --git a/src/configs/__tests__/recommended.test.ts b/src/configs/__tests__/recommended.test.ts index f7f14f5..18c7824 100644 --- a/src/configs/__tests__/recommended.test.ts +++ b/src/configs/__tests__/recommended.test.ts @@ -1,30 +1,30 @@ -import { describe, expect, it } from 'bun:test' - -import recommended from '../recommended' - -describe('recommended', () => { - it('should export recommended config as array', () => { - expect(Array.isArray(recommended)).toBe(true) - expect(recommended.length).toBeGreaterThan(0) - }) - - it('should have ignores config', () => { - const ignoresConfig = recommended.find( - (config) => 'ignores' in config && !('rules' in config), - ) - expect(ignoresConfig).toBeDefined() - expect(ignoresConfig?.ignores).toContain('**/node_modules/') - expect(ignoresConfig?.ignores).toContain('**/dist/') - }) - - it('should have @devup plugin rules', () => { - const pluginConfig = recommended.find( - (config) => - config.plugins && '@devup' in (config.plugins as Record), - ) - expect(pluginConfig).toBeDefined() - expect(pluginConfig?.rules?.['@devup/component']).toBe('error') - expect(pluginConfig?.rules?.['@devup/app-page']).toBe('error') - expect(pluginConfig?.rules?.['@devup/component-interface']).toBe('error') - }) -}) +import { describe, expect, it } from 'bun:test' + +import recommended from '../recommended' + +describe('recommended', () => { + it('should export recommended config as array', () => { + expect(Array.isArray(recommended)).toBe(true) + expect(recommended.length).toBeGreaterThan(0) + }) + + it('should have ignores config', () => { + const ignoresConfig = recommended.find( + (config) => 'ignores' in config && !('rules' in config), + ) + expect(ignoresConfig).toBeDefined() + expect(ignoresConfig?.ignores).toContain('**/node_modules/') + expect(ignoresConfig?.ignores).toContain('**/dist/') + }) + + it('should have @devup plugin rules', () => { + const pluginConfig = recommended.find( + (config) => + config.plugins && '@devup' in (config.plugins as Record), + ) + expect(pluginConfig).toBeDefined() + expect(pluginConfig?.rules?.['@devup/component']).toBe('error') + expect(pluginConfig?.rules?.['@devup/app-page']).toBe('error') + expect(pluginConfig?.rules?.['@devup/component-interface']).toBe('error') + }) +}) diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts index 9bb20e6..e031e5e 100644 --- a/src/configs/recommended.ts +++ b/src/configs/recommended.ts @@ -1,170 +1,170 @@ -import devupUiEslintPlugin from '@devup-ui/eslint-plugin' -import js from '@eslint/js' -import pluginQuery from '@tanstack/eslint-plugin-query' -import type { Linter } from 'eslint' -import * as mdx from 'eslint-plugin-mdx' -import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' -// @ts-ignore -import react from 'eslint-plugin-react' -// @ts-ignore -import hooksPlugin from 'eslint-plugin-react-hooks' -import simpleImportSort from 'eslint-plugin-simple-import-sort' -import unusedImports from 'eslint-plugin-unused-imports' -import tseslint from 'typescript-eslint' - -import { appPage, component, componentInterface } from '../rules' - -export default [ - { - ignores: [ - '**/node_modules/', - '**/build/', - '**/storybook-static/', - '**/dist/', - '**/next-env.d.ts', - '**/out/', - '**/public/', - '**/df/', - '**/coverage/', - '**/target/', - '**/venv/', - '!**/src/**', - '!vite.config.ts', - '**/__snapshots__/', - '**/.*/', - ], - }, - ...devupUiEslintPlugin.configs.recommended, - react.configs.flat!.recommended, - js.configs.recommended, - eslintPluginPrettierRecommended, - ...tseslint.configs.recommended, - ...pluginQuery.configs['flat/recommended'], - { - settings: { - react: { - version: 'detect', - defaultVersion: '19', - }, - }, - plugins: { - 'react-hooks': hooksPlugin, - 'unused-imports': unusedImports, - 'simple-import-sort': simpleImportSort, - '@devup': { - rules: { - component, - 'app-page': appPage, - 'component-interface': componentInterface, - }, - }, - }, - rules: { - 'react/react-in-jsx-scope': 'off', - 'require-jsdoc': 'off', - 'valid-jsdoc': 'off', - 'prettier/prettier': [ - 'error', - { - endOfLine: 'auto', - trailingComma: 'all', - singleQuote: true, - semi: false, - }, - ], - 'no-trailing-spaces': 'error', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - 'no-constant-condition': ['error', { checkLoops: false }], - 'react/jsx-curly-brace-presence': 'error', - camelcase: 'off', - 'simple-import-sort/imports': 'error', - 'simple-import-sort/exports': 'error', - 'react/jsx-sort-props': [ - 'error', - { - callbacksLast: false, - shorthandFirst: false, - shorthandLast: false, - ignoreCase: false, - noSortAlphabetically: false, - reservedFirst: true, - }, - ], - '@typescript-eslint/no-unused-vars': [ - 'error', - { - args: 'all', - argsIgnorePattern: '^_', - caughtErrors: 'all', - caughtErrorsIgnorePattern: '^_', - destructuredArrayIgnorePattern: '^_', - varsIgnorePattern: '^_', - ignoreRestSiblings: true, - }, - ], - 'react/sort-default-props': 'error', - 'unused-imports/no-unused-imports': 'error', - 'unused-imports/no-unused-vars': 'off', - 'comma-dangle': 'off', - 'react/prop-types': 'off', - 'no-console': [ - 'error', - { - allow: ['info', 'debug', 'warn', 'error'], - }, - ], - 'spaced-comment': [ - 'error', - 'always', - { - markers: ['/'], - }, - ], - '@devup/component-interface': 'error', - '@devup/app-page': 'error', - '@devup/component': 'error', - ...hooksPlugin.configs.recommended.rules, - 'react-hooks/exhaustive-deps': [ - 'warn', - { - additionalHooks: 'useSafeEffect', - }, - ], - '@typescript-eslint/no-unused-expressions': [ - 'error', - { - allowShortCircuit: true, - allowTernary: true, - }, - ], - }, - }, - { - files: ['**/*.test-d.{ts,tsx}'], - rules: { - '@typescript-eslint/no-unused-expressions': 'off', - }, - }, - // md, mdx rules - { - ...mdx.flat, - files: ['**/*.{md,mdx}'], - processor: mdx.createRemarkProcessor({ - lintCodeBlocks: true, - }), - }, - { - ...mdx.flatCodeBlocks, - files: ['**/*.{md,mdx}/*.{js,jsx,ts,tsx}'], - rules: { - ...mdx.flatCodeBlocks.rules, - 'react/jsx-no-undef': 'off', - 'react/jsx-tag-spacing': ['error', { beforeClosing: 'never' }], - 'no-empty-pattern': 'off', - '@typescript-eslint/no-empty-object-type': 'off', - }, - }, -] as Linter.Config[] +import devupUiEslintPlugin from '@devup-ui/eslint-plugin' +import js from '@eslint/js' +import pluginQuery from '@tanstack/eslint-plugin-query' +import type { Linter } from 'eslint' +import * as mdx from 'eslint-plugin-mdx' +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended' +// @ts-ignore +import react from 'eslint-plugin-react' +// @ts-ignore +import hooksPlugin from 'eslint-plugin-react-hooks' +import simpleImportSort from 'eslint-plugin-simple-import-sort' +import unusedImports from 'eslint-plugin-unused-imports' +import tseslint from 'typescript-eslint' + +import { appPage, component, componentInterface } from '../rules' + +export default [ + { + ignores: [ + '**/node_modules/', + '**/build/', + '**/storybook-static/', + '**/dist/', + '**/next-env.d.ts', + '**/out/', + '**/public/', + '**/df/', + '**/coverage/', + '**/target/', + '**/venv/', + '!**/src/**', + '!vite.config.ts', + '**/__snapshots__/', + '**/.*/', + ], + }, + ...devupUiEslintPlugin.configs.recommended, + react.configs.flat!.recommended, + js.configs.recommended, + eslintPluginPrettierRecommended, + ...tseslint.configs.recommended, + ...pluginQuery.configs['flat/recommended'], + { + settings: { + react: { + version: 'detect', + defaultVersion: '19', + }, + }, + plugins: { + 'react-hooks': hooksPlugin, + 'unused-imports': unusedImports, + 'simple-import-sort': simpleImportSort, + '@devup': { + rules: { + component, + 'app-page': appPage, + 'component-interface': componentInterface, + }, + }, + }, + rules: { + 'react/react-in-jsx-scope': 'off', + 'require-jsdoc': 'off', + 'valid-jsdoc': 'off', + 'prettier/prettier': [ + 'error', + { + endOfLine: 'auto', + trailingComma: 'all', + singleQuote: true, + semi: false, + }, + ], + 'no-trailing-spaces': 'error', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + 'no-constant-condition': ['error', { checkLoops: false }], + 'react/jsx-curly-brace-presence': 'error', + camelcase: 'off', + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'error', + 'react/jsx-sort-props': [ + 'error', + { + callbacksLast: false, + shorthandFirst: false, + shorthandLast: false, + ignoreCase: false, + noSortAlphabetically: false, + reservedFirst: true, + }, + ], + '@typescript-eslint/no-unused-vars': [ + 'error', + { + args: 'all', + argsIgnorePattern: '^_', + caughtErrors: 'all', + caughtErrorsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', + varsIgnorePattern: '^_', + ignoreRestSiblings: true, + }, + ], + 'react/sort-default-props': 'error', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': 'off', + 'comma-dangle': 'off', + 'react/prop-types': 'off', + 'no-console': [ + 'error', + { + allow: ['info', 'debug', 'warn', 'error'], + }, + ], + 'spaced-comment': [ + 'error', + 'always', + { + markers: ['/'], + }, + ], + '@devup/component-interface': 'error', + '@devup/app-page': 'error', + '@devup/component': 'error', + ...hooksPlugin.configs.recommended.rules, + 'react-hooks/exhaustive-deps': [ + 'warn', + { + additionalHooks: 'useSafeEffect', + }, + ], + '@typescript-eslint/no-unused-expressions': [ + 'error', + { + allowShortCircuit: true, + allowTernary: true, + }, + ], + }, + }, + { + files: ['**/*.test-d.{ts,tsx}'], + rules: { + '@typescript-eslint/no-unused-expressions': 'off', + }, + }, + // md, mdx rules + { + ...mdx.flat, + files: ['**/*.{md,mdx}'], + processor: mdx.createRemarkProcessor({ + lintCodeBlocks: true, + }), + }, + { + ...mdx.flatCodeBlocks, + files: ['**/*.{md,mdx}/*.{js,jsx,ts,tsx}'], + rules: { + ...mdx.flatCodeBlocks.rules, + 'react/jsx-no-undef': 'off', + 'react/jsx-tag-spacing': ['error', { beforeClosing: 'never' }], + 'no-empty-pattern': 'off', + '@typescript-eslint/no-empty-object-type': 'off', + }, + }, +] as Linter.Config[] diff --git a/src/oxlint.ts b/src/oxlint.ts new file mode 100644 index 0000000..906fc92 --- /dev/null +++ b/src/oxlint.ts @@ -0,0 +1,93 @@ +/** + * Oxlint plugin for devup + * Exports the same rules as ESLint plugin for oxlint JS plugins + * + * Usage in .oxlintrc.json: + * { + * "extends": ["eslint-plugin-devup/oxlintrc"] + * } + */ + +// @ts-ignore - named export for rules +import { rules as devupUiRules } from '@devup-ui/eslint-plugin' +import type { Rule } from 'eslint' +import { rules as mdxRules } from 'eslint-plugin-mdx' +import eslintPluginPrettier from 'eslint-plugin-prettier' + +import { appPage, component, componentInterface } from './rules' + +/** + * Wrap a rule to handle unsupported context properties in oxlint + * (e.g., context.parserPath throws in oxlint) + */ +function wrapRuleForOxlint(rule: Rule.RuleModule): Rule.RuleModule { + return { + ...rule, + create(context) { + const proxiedContext = new Proxy(context, { + get(target, prop) { + // Return undefined for unsupported properties instead of throwing + if (prop === 'parserPath') { + return undefined + } + // Pass through all other properties directly to preserve Proxy invariants + // (non-configurable properties must return their actual value) + return target[prop as keyof typeof target] + }, + }) + return rule.create(proxiedContext) + }, + } +} + +/** + * Convert camelCase to kebab-case + * e.g., "cssUtilsLiteralOnly" -> "css-utils-literal-only" + */ +function toKebabCase(str: string): string { + return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() +} + +/** + * Build wrapped rules from external plugin with prefix + */ +function buildWrappedRules( + externalRules: Record, + prefix: string, +): Record { + const rules: Record = {} + for (const [name, rule] of Object.entries(externalRules)) { + const kebabName = toKebabCase(name) + rules[`${prefix}/${kebabName}`] = wrapRuleForOxlint( + rule as unknown as Rule.RuleModule, + ) + } + return rules +} + +// ============================================================================ +// Plugin Export +// ============================================================================ + +const plugin = { + meta: { + name: 'devup', + }, + rules: { + // devup custom rules + 'app-page': appPage, + component: component, + 'component-interface': componentInterface, + + // prettier (wrapped for oxlint compatibility) + prettier: wrapRuleForOxlint(eslintPluginPrettier.rules!.prettier), + + // @devup-ui/eslint-plugin rules (auto-wrapped for oxlint compatibility) + ...buildWrappedRules(devupUiRules, 'ui'), + + // eslint-plugin-mdx rules (auto-wrapped for oxlint compatibility) + ...buildWrappedRules(mdxRules, 'mdx'), + }, +} + +export default plugin diff --git a/src/rules/__tests__/index.test.ts b/src/rules/__tests__/index.test.ts index f5ed555..f583183 100644 --- a/src/rules/__tests__/index.test.ts +++ b/src/rules/__tests__/index.test.ts @@ -1,20 +1,20 @@ -import { describe, expect, it } from 'bun:test' - -import * as index from '../index' - -describe('export index', () => { - it('should export component rule', () => { - expect(index.component).toBeDefined() - expect(typeof index.component).toBe('object') - }) - - it('should export componentInterface rule', () => { - expect(index.componentInterface).toBeDefined() - expect(typeof index.componentInterface).toBe('object') - }) - - it('should export appPage rule', () => { - expect(index.appPage).toBeDefined() - expect(typeof index.appPage).toBe('object') - }) -}) +import { describe, expect, it } from 'bun:test' + +import * as index from '../index' + +describe('export index', () => { + it('should export component rule', () => { + expect(index.component).toBeDefined() + expect(typeof index.component).toBe('object') + }) + + it('should export componentInterface rule', () => { + expect(index.componentInterface).toBeDefined() + expect(typeof index.componentInterface).toBe('object') + }) + + it('should export appPage rule', () => { + expect(index.appPage).toBeDefined() + expect(typeof index.appPage).toBe('object') + }) +}) diff --git a/src/rules/app-page/README.md b/src/rules/app-page/README.md index e53639b..4104e2a 100644 --- a/src/rules/app-page/README.md +++ b/src/rules/app-page/README.md @@ -1,21 +1,21 @@ -# app-page - -## Description - -When creating a page.tsx or layout.tsx file inside the app directory, this rule warns if the file is empty and automatically generates a component. - -## Example - -```jsx -// app/hello/page.tsx -export default function HelloPage() { - return
...
-} -``` - -```jsx -// app/page.tsx -export default function IndexPage() { - return
...
-} -``` +# app-page + +## Description + +When creating a page.tsx or layout.tsx file inside the app directory, this rule warns if the file is empty and automatically generates a component. + +## Example + +```jsx +// app/hello/page.tsx +export default function HelloPage() { + return
...
; +} +``` + +```jsx +// app/page.tsx +export default function IndexPage() { + return
...
; +} +``` diff --git a/src/rules/app-page/__tests__/index.test.ts b/src/rules/app-page/__tests__/index.test.ts index 71ec53e..f568660 100644 --- a/src/rules/app-page/__tests__/index.test.ts +++ b/src/rules/app-page/__tests__/index.test.ts @@ -1,147 +1,147 @@ -import { RuleTester } from '@typescript-eslint/rule-tester' - -import { appPage } from '../index' - -const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 'latest', - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - }, -}) - -ruleTester.run('app-page rule', appPage, { - valid: [ - { - code: 'export default function IndexPage(){return }', - filename: 'src/app/page.tsx', - }, - { - code: 'export default function IndexPage(){return }', - filename: 'src/components/page.tsx', - }, - { - code: 'export default function IndexLayout(){return <>}', - filename: 'src/app/layout.tsx', - }, - { - code: 'export default function NotFoundPage(){return <>}', - filename: 'src/app/404.tsx', - }, - { - code: 'const a=1;\nexport default a', - filename: 'src/app/a/page.tsx', - }, - { - code: 'export default function TestPage(){return <>}', - filename: 'src/app/[locale]/page.tsx', - }, - { - code: 'export default function TestPage({ params }: { params: Promise<{ locale:string }>}){return <>}', - filename: 'src/app/[locale]/page.tsx', - }, - { - code: '', - filename: 'src/app/Comp.tsx', - }, - ], - invalid: [ - { - code: 'export default function Index(){return <>}', - output: 'export default function IndexLayout(){return <>}', - filename: 'src/app/layout.tsx', - errors: [ - { - messageId: 'nameOfPageOrLayoutComponentShouldHaveSuffix', - }, - ], - }, - { - code: 'export default function Index(){return <>}', - output: 'export default function IndexPage(){return <>}', - filename: 'src/app/page.tsx', - errors: [ - { - messageId: 'nameOfPageOrLayoutComponentShouldHaveSuffix', - }, - ], - }, - { - code: '', - filename: 'src/app/page.tsx', - output: 'export default function Page(){return <>}', - errors: [ - { - messageId: 'pageOrLayoutComponentShouldDefaultExport', - }, - ], - }, - { - code: '', - filename: 'src/app/[a]/page.tsx', - output: - 'export default function Page({params}:{params:Promise<{a:string}>}){return <>}', - errors: [ - { - messageId: 'pageOrLayoutComponentShouldDefaultExport', - }, - ], - }, - { - code: '', - filename: 'src/app/404.tsx', - output: 'export default function NotFoundPage(){return <>}', - errors: [ - { - messageId: 'pageOrLayoutComponentShouldDefaultExport', - }, - ], - }, - { - code: 'export const A=1', - filename: 'src/app/page.tsx', - output: 'export const A=1\nexport default function Page(){return <>}', - errors: [ - { - messageId: 'pageOrLayoutComponentShouldDefaultExport', - }, - ], - }, - { - code: 'export default function Page(){return <>}', - filename: 'src/app/[a]/[b]/page.tsx', - output: - 'export default function Page({params}:{params:Promise<{a:string;b:string}>}){return <>}', - errors: [ - { - messageId: 'pathParamsShouldExist', - }, - ], - }, - { - code: 'export default function Page():any{return <>}', - filename: 'src/app/[a]/[b]/page.tsx', - output: - 'export default function Page({params}:{params:Promise<{a:string;b:string}>}):any{return <>}', - errors: [ - { - messageId: 'pathParamsShouldExist', - }, - ], - }, - { - code: '', - filename: 'src/app/[locale]/page.tsx', - output: - 'export default function Page({params}:{params:Promise<{locale:string}>}){return <>}', - errors: [ - { - messageId: 'pageOrLayoutComponentShouldDefaultExport', - }, - ], - }, - ], -}) +import { RuleTester } from '@typescript-eslint/rule-tester' + +import { appPage } from '../index' + +const ruleTester = new RuleTester({ + languageOptions: { + ecmaVersion: 'latest', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, +}) + +ruleTester.run('app-page rule', appPage, { + valid: [ + { + code: 'export default function IndexPage(){return }', + filename: 'src/app/page.tsx', + }, + { + code: 'export default function IndexPage(){return }', + filename: 'src/components/page.tsx', + }, + { + code: 'export default function IndexLayout(){return <>}', + filename: 'src/app/layout.tsx', + }, + { + code: 'export default function NotFoundPage(){return <>}', + filename: 'src/app/404.tsx', + }, + { + code: 'const a=1;\nexport default a', + filename: 'src/app/a/page.tsx', + }, + { + code: 'export default function TestPage(){return <>}', + filename: 'src/app/[locale]/page.tsx', + }, + { + code: 'export default function TestPage({ params }: { params: Promise<{ locale:string }>}){return <>}', + filename: 'src/app/[locale]/page.tsx', + }, + { + code: '', + filename: 'src/app/Comp.tsx', + }, + ], + invalid: [ + { + code: 'export default function Index(){return <>}', + output: 'export default function IndexLayout(){return <>}', + filename: 'src/app/layout.tsx', + errors: [ + { + messageId: 'nameOfPageOrLayoutComponentShouldHaveSuffix', + }, + ], + }, + { + code: 'export default function Index(){return <>}', + output: 'export default function IndexPage(){return <>}', + filename: 'src/app/page.tsx', + errors: [ + { + messageId: 'nameOfPageOrLayoutComponentShouldHaveSuffix', + }, + ], + }, + { + code: '', + filename: 'src/app/page.tsx', + output: 'export default function Page(){return <>}', + errors: [ + { + messageId: 'pageOrLayoutComponentShouldDefaultExport', + }, + ], + }, + { + code: '', + filename: 'src/app/[a]/page.tsx', + output: + 'export default function Page({params}:{params:Promise<{a:string}>}){return <>}', + errors: [ + { + messageId: 'pageOrLayoutComponentShouldDefaultExport', + }, + ], + }, + { + code: '', + filename: 'src/app/404.tsx', + output: 'export default function NotFoundPage(){return <>}', + errors: [ + { + messageId: 'pageOrLayoutComponentShouldDefaultExport', + }, + ], + }, + { + code: 'export const A=1', + filename: 'src/app/page.tsx', + output: 'export const A=1\nexport default function Page(){return <>}', + errors: [ + { + messageId: 'pageOrLayoutComponentShouldDefaultExport', + }, + ], + }, + { + code: 'export default function Page(){return <>}', + filename: 'src/app/[a]/[b]/page.tsx', + output: + 'export default function Page({params}:{params:Promise<{a:string;b:string}>}){return <>}', + errors: [ + { + messageId: 'pathParamsShouldExist', + }, + ], + }, + { + code: 'export default function Page():any{return <>}', + filename: 'src/app/[a]/[b]/page.tsx', + output: + 'export default function Page({params}:{params:Promise<{a:string;b:string}>}):any{return <>}', + errors: [ + { + messageId: 'pathParamsShouldExist', + }, + ], + }, + { + code: '', + filename: 'src/app/[locale]/page.tsx', + output: + 'export default function Page({params}:{params:Promise<{locale:string}>}){return <>}', + errors: [ + { + messageId: 'pageOrLayoutComponentShouldDefaultExport', + }, + ], + }, + ], +}) diff --git a/src/rules/app-page/index.ts b/src/rules/app-page/index.ts index 2ffccaf..bcfef0f 100644 --- a/src/rules/app-page/index.ts +++ b/src/rules/app-page/index.ts @@ -1,116 +1,116 @@ -import { ESLintUtils } from '@typescript-eslint/utils' -import { relative } from 'path' - -const createRule = ESLintUtils.RuleCreator( - (name) => - `https://github.com/dev-five-git/devup/tree/main/packages/eslint-plugin/src/rules/${name}`, -) - -// Pre-compiled regex patterns -const APP_DIR_PATTERN = /src[/\\]app[/\\]/ -const FILE_TYPE_PATTERN = /(page|layout|404)\.[jt]sx?$/ -const PATH_PARAMS_PATTERN = /\[.*?]/g - -function generateParamsType(pathParams: string[]): string { - return `{params}:{params:Promise<{${pathParams.map((param) => `${param.slice(1, -1)}:string`).join(';')}}>}` -} - -export const appPage = createRule({ - name: 'app-page', - defaultOptions: [], - meta: { - schema: [], - messages: { - pageOrLayoutComponentShouldDefaultExport: - 'Page or layout component must be exported with `export default`.', - nameOfPageOrLayoutComponentShouldHaveSuffix: - 'Page or layout component name must end with `Page` or `Layout`.', - pathParamsShouldExist: - '`params` must exist when path parameters are available.', - }, - type: 'problem', - fixable: 'code', - docs: { - description: - 'Require page or layout component to be exported with export default.', - }, - }, - create(context) { - const filename = relative(context.cwd, context.physicalFilename) - - if (!APP_DIR_PATTERN.test(filename)) return {} - - const fileTypeMatch = FILE_TYPE_PATTERN.exec(filename) - if (!fileTypeMatch) return {} - - const type = fileTypeMatch[1] as 'page' | 'layout' | '404' - - const functionSuffix = type === 'layout' ? 'Layout' : 'Page' - const pathParams = filename.match(PATH_PARAMS_PATTERN) ?? [] - // Ignore when only locale param exists - const onlyLocale = pathParams.length === 1 && pathParams[0] === '[locale]' - - const hasPathParams = pathParams.length > 0 - const requiresParams = hasPathParams && !onlyLocale - const paramsType = hasPathParams ? generateParamsType(pathParams) : '' - - let ok = false - return { - ExportDefaultDeclaration(defaultExport) { - ok = true - const declaration = defaultExport.declaration - if (declaration.type !== 'FunctionDeclaration') return - - if ( - declaration.id?.name && - !declaration.id.name.endsWith(functionSuffix) - ) { - context.report({ - node: declaration.id, - messageId: 'nameOfPageOrLayoutComponentShouldHaveSuffix', - fix: (fixer) => - fixer.replaceText( - declaration.id!, - declaration.id!.name + functionSuffix, - ), - }) - } - - if ( - declaration.id && - requiresParams && - declaration.params.length === 0 - ) { - context.report({ - node: declaration, - messageId: 'pathParamsShouldExist', - fix: (fixer) => - fixer.replaceTextRange( - [ - declaration.id!.range[1], - declaration.returnType?.range[0] ?? declaration.body.range[0], - ], - `(${paramsType})`, - ), - }) - } - }, - 'Program:exit'(program) { - if (ok) return - - const functionName = type === '404' ? 'NotFoundPage' : functionSuffix - const prefix = context.sourceCode.text.trim().length ? '\n' : '' - - context.report({ - node: program, - messageId: 'pageOrLayoutComponentShouldDefaultExport', - fix: (fixer) => - fixer.insertTextAfter( - program, - `${prefix}export default function ${functionName}(${paramsType}){return <>}`, - ), - }) - }, - } - }, -}) +import { ESLintUtils } from '@typescript-eslint/utils' +import { relative } from 'path' + +const createRule = ESLintUtils.RuleCreator( + (name) => + `https://github.com/dev-five-git/devup/tree/main/packages/eslint-plugin/src/rules/${name}`, +) + +// Pre-compiled regex patterns +const APP_DIR_PATTERN = /src[/\\]app[/\\]/ +const FILE_TYPE_PATTERN = /(page|layout|404)\.[jt]sx?$/ +const PATH_PARAMS_PATTERN = /\[.*?]/g + +function generateParamsType(pathParams: string[]): string { + return `{params}:{params:Promise<{${pathParams.map((param) => `${param.slice(1, -1)}:string`).join(';')}}>}` +} + +export const appPage = createRule({ + name: 'app-page', + defaultOptions: [], + meta: { + schema: [], + messages: { + pageOrLayoutComponentShouldDefaultExport: + 'Page or layout component must be exported with `export default`.', + nameOfPageOrLayoutComponentShouldHaveSuffix: + 'Page or layout component name must end with `Page` or `Layout`.', + pathParamsShouldExist: + '`params` must exist when path parameters are available.', + }, + type: 'problem', + fixable: 'code', + docs: { + description: + 'require page or layout component to be exported with export default', + }, + }, + create(context) { + const filename = relative(context.cwd, context.physicalFilename) + + if (!APP_DIR_PATTERN.test(filename)) return {} + + const fileTypeMatch = FILE_TYPE_PATTERN.exec(filename) + if (!fileTypeMatch) return {} + + const type = fileTypeMatch[1] as 'page' | 'layout' | '404' + + const functionSuffix = type === 'layout' ? 'Layout' : 'Page' + const pathParams = filename.match(PATH_PARAMS_PATTERN) ?? [] + // Ignore when only locale param exists + const onlyLocale = pathParams.length === 1 && pathParams[0] === '[locale]' + + const hasPathParams = pathParams.length > 0 + const requiresParams = hasPathParams && !onlyLocale + const paramsType = hasPathParams ? generateParamsType(pathParams) : '' + + let ok = false + return { + ExportDefaultDeclaration(defaultExport) { + ok = true + const declaration = defaultExport.declaration + if (declaration.type !== 'FunctionDeclaration') return + + if ( + declaration.id?.name && + !declaration.id.name.endsWith(functionSuffix) + ) { + context.report({ + node: declaration.id, + messageId: 'nameOfPageOrLayoutComponentShouldHaveSuffix', + fix: (fixer) => + fixer.replaceText( + declaration.id!, + declaration.id!.name + functionSuffix, + ), + }) + } + + if ( + declaration.id && + requiresParams && + declaration.params.length === 0 + ) { + context.report({ + node: declaration, + messageId: 'pathParamsShouldExist', + fix: (fixer) => + fixer.replaceTextRange( + [ + declaration.id!.range[1], + declaration.returnType?.range[0] ?? declaration.body.range[0], + ], + `(${paramsType})`, + ), + }) + } + }, + 'Program:exit'(program) { + if (ok) return + + const functionName = type === '404' ? 'NotFoundPage' : functionSuffix + const prefix = context.sourceCode.text.trim().length ? '\n' : '' + + context.report({ + node: program, + messageId: 'pageOrLayoutComponentShouldDefaultExport', + fix: (fixer) => + fixer.insertTextAfter( + program, + `${prefix}export default function ${functionName}(${paramsType}){return <>}`, + ), + }) + }, + } + }, +}) diff --git a/src/rules/component-interface/README.md b/src/rules/component-interface/README.md index 495b65b..1153854 100644 --- a/src/rules/component-interface/README.md +++ b/src/rules/component-interface/README.md @@ -1,22 +1,22 @@ -# component-interface - -## Description - -Adds an interface when component props is an empty object. - -## Example - -```tsx -// Before -export function Hello({}) { - return
...
-} -``` - -```tsx -// After -interface HelloProps {} -export function Hello({}: HelloProps) { - return
...
-} -``` +# component-interface + +## Description + +Adds an interface when component props is an empty object. + +## Example + +```tsx +// Before +export function Hello({}) { + return
...
; +} +``` + +```tsx +// After +interface HelloProps {} +export function Hello({}: HelloProps) { + return
...
; +} +``` diff --git a/src/rules/component-interface/__tests__/index.test.ts b/src/rules/component-interface/__tests__/index.test.ts index 16bc7b4..d054d53 100644 --- a/src/rules/component-interface/__tests__/index.test.ts +++ b/src/rules/component-interface/__tests__/index.test.ts @@ -1,105 +1,105 @@ -import { RuleTester } from '@typescript-eslint/rule-tester' - -import { componentInterface } from '../index' - -const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 'latest', - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - }, -}) - -ruleTester.run('component-interface rule', componentInterface, { - valid: [ - { - code: 'interface HelloProps{}\ninterface Hello1Props{}\nexport function Hello({}:HelloProps){return <>}', - filename: 'src/components/hello.tsx', - }, - { - code: '', - filename: 'src/components/hello.ts', - }, - ], - invalid: [ - { - code: 'function Hello({}){return <>}', - output: - 'interface HelloProps{}\nfunction Hello({}:HelloProps){return <>}', - filename: 'src/components/hello.tsx', - errors: [ - { - messageId: - 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', - }, - ], - }, - { - code: 'export function Hello({}){return <>}', - output: - 'interface HelloProps{}\nexport function Hello({}:HelloProps){return <>}', - filename: 'src/components/hello.tsx', - errors: [ - { - messageId: - 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', - }, - ], - }, - { - code: 'export default function Hello({}){return <>}', - output: - 'interface HelloProps{}\nexport default function Hello({}:HelloProps){return <>}', - filename: 'src/components/hello.tsx', - errors: [ - { - messageId: - 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', - }, - ], - }, - { - code: 'function Hello({}){return <>}\nfunction Hello2({}){return <>}', - output: - 'interface HelloProps{}\nfunction Hello({}:HelloProps){return <>}\ninterface Hello2Props{}\nfunction Hello2({}:Hello2Props){return <>}', - filename: 'src/components/hello.tsx', - errors: [ - { - messageId: - 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', - }, - { - messageId: - 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', - }, - ], - }, - { - code: 'function Hello({}){return <>}\nfunction Hello2({}){return <>}\nexport function Hello3({}){return <>}\nexport default function Hello4({}){return <>}', - output: - 'interface HelloProps{}\nfunction Hello({}:HelloProps){return <>}\ninterface Hello2Props{}\nfunction Hello2({}:Hello2Props){return <>}\ninterface Hello3Props{}\nexport function Hello3({}:Hello3Props){return <>}\ninterface Hello4Props{}\nexport default function Hello4({}:Hello4Props){return <>}', - filename: 'src/components/hello.tsx', - errors: [ - { - messageId: - 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', - }, - { - messageId: - 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', - }, - { - messageId: - 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', - }, - { - messageId: - 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', - }, - ], - }, - ], -}) +import { RuleTester } from '@typescript-eslint/rule-tester' + +import { componentInterface } from '../index' + +const ruleTester = new RuleTester({ + languageOptions: { + ecmaVersion: 'latest', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, +}) + +ruleTester.run('component-interface rule', componentInterface, { + valid: [ + { + code: 'interface HelloProps{}\ninterface Hello1Props{}\nexport function Hello({}:HelloProps){return <>}', + filename: 'src/components/hello.tsx', + }, + { + code: '', + filename: 'src/components/hello.ts', + }, + ], + invalid: [ + { + code: 'function Hello({}){return <>}', + output: + 'interface HelloProps{}\nfunction Hello({}:HelloProps){return <>}', + filename: 'src/components/hello.tsx', + errors: [ + { + messageId: + 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', + }, + ], + }, + { + code: 'export function Hello({}){return <>}', + output: + 'interface HelloProps{}\nexport function Hello({}:HelloProps){return <>}', + filename: 'src/components/hello.tsx', + errors: [ + { + messageId: + 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', + }, + ], + }, + { + code: 'export default function Hello({}){return <>}', + output: + 'interface HelloProps{}\nexport default function Hello({}:HelloProps){return <>}', + filename: 'src/components/hello.tsx', + errors: [ + { + messageId: + 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', + }, + ], + }, + { + code: 'function Hello({}){return <>}\nfunction Hello2({}){return <>}', + output: + 'interface HelloProps{}\nfunction Hello({}:HelloProps){return <>}\ninterface Hello2Props{}\nfunction Hello2({}:Hello2Props){return <>}', + filename: 'src/components/hello.tsx', + errors: [ + { + messageId: + 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', + }, + { + messageId: + 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', + }, + ], + }, + { + code: 'function Hello({}){return <>}\nfunction Hello2({}){return <>}\nexport function Hello3({}){return <>}\nexport default function Hello4({}){return <>}', + output: + 'interface HelloProps{}\nfunction Hello({}:HelloProps){return <>}\ninterface Hello2Props{}\nfunction Hello2({}:Hello2Props){return <>}\ninterface Hello3Props{}\nexport function Hello3({}:Hello3Props){return <>}\ninterface Hello4Props{}\nexport default function Hello4({}:Hello4Props){return <>}', + filename: 'src/components/hello.tsx', + errors: [ + { + messageId: + 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', + }, + { + messageId: + 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', + }, + { + messageId: + 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', + }, + { + messageId: + 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', + }, + ], + }, + ], +}) diff --git a/src/rules/component-interface/index.ts b/src/rules/component-interface/index.ts index 3448623..a8ac78c 100644 --- a/src/rules/component-interface/index.ts +++ b/src/rules/component-interface/index.ts @@ -1,67 +1,67 @@ -import { ESLintUtils } from '@typescript-eslint/utils' - -const createRule = ESLintUtils.RuleCreator( - (name) => - `https://github.com/dev-five-git/devup/tree/main/packages/eslint-plugin/src/rules/${name}`, -) - -const PASCAL_CASE_PATTERN = /^[A-Z]/ -const EXPORTABLE_PARENT_TYPES = new Set([ - 'Program', - 'ExportNamedDeclaration', - 'ExportDefaultDeclaration', -]) - -export const componentInterface = createRule({ - name: 'component-interface', - defaultOptions: [], - meta: { - schema: [], - messages: { - componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern: - 'Component `props` must have type annotation when empty object pattern.', - }, - type: 'problem', - fixable: 'code', - docs: { - description: - 'required type annotation for component props when empty object pattern', - }, - }, - create(context) { - if (!context.physicalFilename.endsWith('.tsx')) return {} - - return { - FunctionDeclaration(node) { - const funcName = node.id?.name - if (!funcName || !PASCAL_CASE_PATTERN.test(funcName)) return - - const firstParam = node.params[0] - if ( - node.params.length !== 1 || - firstParam.type !== 'ObjectPattern' || - firstParam.typeAnnotation || - firstParam.properties.length !== 0 || - !EXPORTABLE_PARENT_TYPES.has(node.parent.type) - ) { - return - } - - const insertTarget = node.parent.type === 'Program' ? node : node.parent - - context.report({ - node, - messageId: - 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', - fix: (fixer) => [ - fixer.insertTextAfter(firstParam, `:${funcName}Props`), - fixer.insertTextBefore( - insertTarget, - `interface ${funcName}Props{}\n`, - ), - ], - }) - }, - } - }, -}) +import { ESLintUtils } from '@typescript-eslint/utils' + +const createRule = ESLintUtils.RuleCreator( + (name) => + `https://github.com/dev-five-git/devup/tree/main/packages/eslint-plugin/src/rules/${name}`, +) + +const PASCAL_CASE_PATTERN = /^[A-Z]/ +const EXPORTABLE_PARENT_TYPES = new Set([ + 'Program', + 'ExportNamedDeclaration', + 'ExportDefaultDeclaration', +]) + +export const componentInterface = createRule({ + name: 'component-interface', + defaultOptions: [], + meta: { + schema: [], + messages: { + componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern: + 'Component `props` must have type annotation when empty object pattern.', + }, + type: 'problem', + fixable: 'code', + docs: { + description: + 'require type annotation for component props when empty object pattern.', + }, + }, + create(context) { + if (!context.physicalFilename.endsWith('.tsx')) return {} + + return { + FunctionDeclaration(node) { + const funcName = node.id?.name + if (!funcName || !PASCAL_CASE_PATTERN.test(funcName)) return + + const firstParam = node.params[0] + if ( + node.params.length !== 1 || + firstParam.type !== 'ObjectPattern' || + firstParam.typeAnnotation || + firstParam.properties.length !== 0 || + !EXPORTABLE_PARENT_TYPES.has(node.parent.type) + ) { + return + } + + const insertTarget = node.parent.type === 'Program' ? node : node.parent + + context.report({ + node, + messageId: + 'componentPropsShouldHaveTypeAnnotationWhenEmptyObjectPattern', + fix: (fixer) => [ + fixer.insertTextAfter(firstParam, `:${funcName}Props`), + fixer.insertTextBefore( + insertTarget, + `interface ${funcName}Props{}\n`, + ), + ], + }) + }, + } + }, +}) diff --git a/src/rules/component/README.md b/src/rules/component/README.md index 62ccbfb..1c3f563 100644 --- a/src/rules/component/README.md +++ b/src/rules/component/README.md @@ -1,21 +1,21 @@ -# component - -## Description - -Components in the app folder or components folder must be named according to the file name, or the folder name if it's an index.tsx file. - -## Example - -```jsx -// app/hello/index.tsx -export function Hello() { - return
...
-} -``` - -```jsx -// components/app-wrapper/index.tsx -export function AppWrapper() { - return
...
-} -``` +# component + +## Description + +Components in the app folder or components folder must be named according to the file name, or the folder name if it's an index.tsx file. + +## Example + +```jsx +// app/hello/index.tsx +export function Hello() { + return
...
; +} +``` + +```jsx +// components/app-wrapper/index.tsx +export function AppWrapper() { + return
...
; +} +``` diff --git a/src/rules/component/__tests__/index.test.ts b/src/rules/component/__tests__/index.test.ts index 9af35ba..46a8328 100644 --- a/src/rules/component/__tests__/index.test.ts +++ b/src/rules/component/__tests__/index.test.ts @@ -1,257 +1,257 @@ -import { RuleTester } from '@typescript-eslint/rule-tester' - -import { component } from '../index' - -const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 'latest', - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - }, -}) - -ruleTester.run('component rule', component, { - valid: [ - { - code: 'export default function IndexPage(){return }', - filename: 'src/app/page', - }, - { - code: 'export default function IndexPage(){return }', - filename: 'src/app/page.tsx', - }, - { - code: 'export default function Hello(){return }', - filename: 'src/components/Hello.tsx', - }, - { - code: 'export default function IndexPage(){return }', - filename: 'src/app/aaa/bb/cc/page.tsx', - }, - { - code: 'export default function IndexPage(){return }', - filename: 'src/app/aaa/bb/cc/layout.tsx', - }, - { - code: 'export function Page(){return }', - filename: 'src/components/page.tsx', - }, - { - code: 'export class Page extends React.Component{render(){return }}', - filename: 'src/components/page.tsx', - }, - { - code: 'export {a} from "./a"', - filename: 'src/components/index.tsx', - }, - { - code: '', - filename: 'src/app/404.tsx', - }, - { - code: 'export function TestComponent(){return }', - filename: 'src/components/test-component.tsx', - }, - { - code: 'export function NiceComponentAll(){return }\nexport function NiceComponent(){return }', - filename: 'src/components/nice_component.tsx', - }, - { - code: 'export function NiceComponent(){return }', - filename: 'src/components/nice_component.tsx', - }, - { - code: 'export function Number9(){return }', - filename: 'src/components/number9.tsx', - }, - { - code: '"use client"\nexport function Number9(){return }', - filename: 'src/components/Number9.tsx', - }, - { - code: 'export function Number9(){return }', - filename: 'src/components/Number9.tsx', - }, - { - code: 'export function hello(){return }', - filename: 'src/components/utils/Number9.tsx', - }, - { - code: 'export function Dir(){return <>}', - filename: 'src/components/dir/index.tsx', - }, - { - code: 'export const Dir = ()=>{return <>}', - filename: 'src/components/dir/index.tsx', - }, - { - code: 'export const Dir1 = ()=>{return <>}\nexport const Dir = ()=>{return <>}', - filename: 'src/components/dir/index.tsx', - }, - { - code: '\nexport {} from ""\nexport const Dir = ()=><>', - filename: 'src/components/dir/index.tsx', - }, - { - code: 'export const Dir = ()=><>', - filename: 'src/components/__test__/index.test.tsx', - }, - { - code: 'export const Dir = ()=><>', - filename: 'src/components/__tests__/index.test.tsx', - }, - { - code: 'export const Dir = ()=><>', - filename: 'src/components/index.css.tsx', - }, - { - code: 'export const Dir = function(){return <>}\nexport const Dir2 = function(){return <>}', - filename: 'src/components/dir/index.tsx', - }, - { - code: 'export class Dir extends React.Component{render(){return <>}}', - filename: 'src/components/dir/index.tsx', - }, - { - code: 'export function Dir(){return <>}', - filename: 'src/components/dir1/a/b/c/d/dir.tsx', - }, - { - code: 'export function Dir(){return <>}', - filename: 'src/app/dir1/a/b/c/d/dir.tsx', - }, - { - code: 'export function D(){return <>}', - filename: 'src/app/dir1/a/b/c/d/index.tsx', - }, - { - code: '', - filename: 'src/none.tsx', - }, - { - code: 'interface HelloProps{}\ninterface Hello1Props{}\nexport function Hello({}:HelloProps){return <>}', - filename: 'src/components/hello.tsx', - }, - { - // ClassDeclaration matches file name - code: 'export class Hello extends React.Component{render(){return <>}}', - filename: 'src/components/hello.tsx', - }, - { - // ClassDeclaration matches directory name (index file) - code: 'export class MyComponent extends React.Component{render(){return <>}}', - filename: 'src/components/my-component/index.tsx', - }, - { - // export specifiers without declaration (non-index file, but has default export) - code: 'export default function Hello(){return <>}\nexport { a } from "./a"', - filename: 'src/components/hello.tsx', - }, - { - // named export with specifiers in non-index file (no declaration) - code: 'export function Hello(){return <>}\nexport { b } from "./b"', - filename: 'src/components/hello.tsx', - }, - { - // ClassDeclaration without matching name (other export passes) - code: 'export class Wrong extends React.Component{render(){return <>}}\nexport function Hello(){return <>}', - filename: 'src/components/hello.tsx', - }, - ], - invalid: [ - { - code: '', - output: 'export function D(){return <>}', - filename: 'src/app/d.tsx', - errors: [ - { - messageId: 'componentFileShouldExportComponent', - }, - ], - }, - { - code: '', - output: 'export function D(){return <>}', - filename: 'src/app/d/index.tsx', - errors: [ - { - messageId: 'componentFileShouldExportComponent', - }, - ], - }, - { - code: '', - output: 'export function D(){return <>}', - filename: 'src/components/d/index.tsx', - errors: [ - { - messageId: 'componentFileShouldExportComponent', - }, - ], - }, - { - code: 'export function Wrong(){return <>}', - output: 'export function D(){return <>}', - filename: 'src/components/d.tsx', - errors: [ - { - messageId: 'componentNameShouldBeFollowDirectoryStructure', - }, - ], - }, - { - code: 'export function d(){return <>}', - output: 'export function D(){return <>}', - filename: 'src/app/d.tsx', - errors: [ - { - messageId: 'componentNameShouldBeFollowDirectoryStructure', - }, - ], - }, - { - code: 'export function d(){return <>}', - output: 'export function D(){return <>}', - filename: 'src/app/d/index.tsx', - errors: [ - { - messageId: 'componentNameShouldBeFollowDirectoryStructure', - }, - ], - }, - { - code: 'export function d(){return <>}', - output: 'export function D(){return <>}', - filename: 'src/components/d/index.tsx', - errors: [ - { - messageId: 'componentNameShouldBeFollowDirectoryStructure', - }, - ], - }, - { - code: 'export const d=function(){return <>}', - output: 'export const D=function(){return <>}', - filename: 'src/components/d/index.tsx', - errors: [ - { - messageId: 'componentNameShouldBeFollowDirectoryStructure', - }, - ], - }, - { - code: 'export const num=1\nexport const {num1}={num1:1}', - output: - 'export const num=1\nexport const {num1}={num1:1}\nexport function Just(){return <>}', - filename: 'src/components/just.tsx', - errors: [ - { - messageId: 'componentFileShouldExportComponent', - }, - ], - }, - ], -}) +import { RuleTester } from '@typescript-eslint/rule-tester' + +import { component } from '../index' + +const ruleTester = new RuleTester({ + languageOptions: { + ecmaVersion: 'latest', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, +}) + +ruleTester.run('component rule', component, { + valid: [ + { + code: 'export default function IndexPage(){return }', + filename: 'src/app/page', + }, + { + code: 'export default function IndexPage(){return }', + filename: 'src/app/page.tsx', + }, + { + code: 'export default function Hello(){return }', + filename: 'src/components/Hello.tsx', + }, + { + code: 'export default function IndexPage(){return }', + filename: 'src/app/aaa/bb/cc/page.tsx', + }, + { + code: 'export default function IndexPage(){return }', + filename: 'src/app/aaa/bb/cc/layout.tsx', + }, + { + code: 'export function Page(){return }', + filename: 'src/components/page.tsx', + }, + { + code: 'export class Page extends React.Component{render(){return }}', + filename: 'src/components/page.tsx', + }, + { + code: 'export {a} from "./a"', + filename: 'src/components/index.tsx', + }, + { + code: '', + filename: 'src/app/404.tsx', + }, + { + code: 'export function TestComponent(){return }', + filename: 'src/components/test-component.tsx', + }, + { + code: 'export function NiceComponentAll(){return }\nexport function NiceComponent(){return }', + filename: 'src/components/nice_component.tsx', + }, + { + code: 'export function NiceComponent(){return }', + filename: 'src/components/nice_component.tsx', + }, + { + code: 'export function Number9(){return }', + filename: 'src/components/number9.tsx', + }, + { + code: '"use client"\nexport function Number9(){return }', + filename: 'src/components/Number9.tsx', + }, + { + code: 'export function Number9(){return }', + filename: 'src/components/Number9.tsx', + }, + { + code: 'export function hello(){return }', + filename: 'src/components/utils/Number9.tsx', + }, + { + code: 'export function Dir(){return <>}', + filename: 'src/components/dir/index.tsx', + }, + { + code: 'export const Dir = ()=>{return <>}', + filename: 'src/components/dir/index.tsx', + }, + { + code: 'export const Dir1 = ()=>{return <>}\nexport const Dir = ()=>{return <>}', + filename: 'src/components/dir/index.tsx', + }, + { + code: '\nexport {} from ""\nexport const Dir = ()=><>', + filename: 'src/components/dir/index.tsx', + }, + { + code: 'export const Dir = ()=><>', + filename: 'src/components/__test__/index.test.tsx', + }, + { + code: 'export const Dir = ()=><>', + filename: 'src/components/__tests__/index.test.tsx', + }, + { + code: 'export const Dir = ()=><>', + filename: 'src/components/index.css.tsx', + }, + { + code: 'export const Dir = function(){return <>}\nexport const Dir2 = function(){return <>}', + filename: 'src/components/dir/index.tsx', + }, + { + code: 'export class Dir extends React.Component{render(){return <>}}', + filename: 'src/components/dir/index.tsx', + }, + { + code: 'export function Dir(){return <>}', + filename: 'src/components/dir1/a/b/c/d/dir.tsx', + }, + { + code: 'export function Dir(){return <>}', + filename: 'src/app/dir1/a/b/c/d/dir.tsx', + }, + { + code: 'export function D(){return <>}', + filename: 'src/app/dir1/a/b/c/d/index.tsx', + }, + { + code: '', + filename: 'src/none.tsx', + }, + { + code: 'interface HelloProps{}\ninterface Hello1Props{}\nexport function Hello({}:HelloProps){return <>}', + filename: 'src/components/hello.tsx', + }, + { + // ClassDeclaration matches file name + code: 'export class Hello extends React.Component{render(){return <>}}', + filename: 'src/components/hello.tsx', + }, + { + // ClassDeclaration matches directory name (index file) + code: 'export class MyComponent extends React.Component{render(){return <>}}', + filename: 'src/components/my-component/index.tsx', + }, + { + // export specifiers without declaration (non-index file, but has default export) + code: 'export default function Hello(){return <>}\nexport { a } from "./a"', + filename: 'src/components/hello.tsx', + }, + { + // named export with specifiers in non-index file (no declaration) + code: 'export function Hello(){return <>}\nexport { b } from "./b"', + filename: 'src/components/hello.tsx', + }, + { + // ClassDeclaration without matching name (other export passes) + code: 'export class Wrong extends React.Component{render(){return <>}}\nexport function Hello(){return <>}', + filename: 'src/components/hello.tsx', + }, + ], + invalid: [ + { + code: '', + output: 'export function D(){return <>}', + filename: 'src/app/d.tsx', + errors: [ + { + messageId: 'componentFileShouldExportComponent', + }, + ], + }, + { + code: '', + output: 'export function D(){return <>}', + filename: 'src/app/d/index.tsx', + errors: [ + { + messageId: 'componentFileShouldExportComponent', + }, + ], + }, + { + code: '', + output: 'export function D(){return <>}', + filename: 'src/components/d/index.tsx', + errors: [ + { + messageId: 'componentFileShouldExportComponent', + }, + ], + }, + { + code: 'export function Wrong(){return <>}', + output: 'export function D(){return <>}', + filename: 'src/components/d.tsx', + errors: [ + { + messageId: 'componentNameShouldBeFollowDirectoryStructure', + }, + ], + }, + { + code: 'export function d(){return <>}', + output: 'export function D(){return <>}', + filename: 'src/app/d.tsx', + errors: [ + { + messageId: 'componentNameShouldBeFollowDirectoryStructure', + }, + ], + }, + { + code: 'export function d(){return <>}', + output: 'export function D(){return <>}', + filename: 'src/app/d/index.tsx', + errors: [ + { + messageId: 'componentNameShouldBeFollowDirectoryStructure', + }, + ], + }, + { + code: 'export function d(){return <>}', + output: 'export function D(){return <>}', + filename: 'src/components/d/index.tsx', + errors: [ + { + messageId: 'componentNameShouldBeFollowDirectoryStructure', + }, + ], + }, + { + code: 'export const d=function(){return <>}', + output: 'export const D=function(){return <>}', + filename: 'src/components/d/index.tsx', + errors: [ + { + messageId: 'componentNameShouldBeFollowDirectoryStructure', + }, + ], + }, + { + code: 'export const num=1\nexport const {num1}={num1:1}', + output: + 'export const num=1\nexport const {num1}={num1:1}\nexport function Just(){return <>}', + filename: 'src/components/just.tsx', + errors: [ + { + messageId: 'componentFileShouldExportComponent', + }, + ], + }, + ], +}) diff --git a/src/rules/component/index.ts b/src/rules/component/index.ts index 627ae45..da8a44e 100644 --- a/src/rules/component/index.ts +++ b/src/rules/component/index.ts @@ -1,139 +1,139 @@ -import { ESLintUtils, TSESTree } from '@typescript-eslint/utils' -import { relative } from 'path' - -function toPascal(str: string) { - return str - .replace(/[-_](.)/g, (_, c: string) => c.toUpperCase()) - .replace(/^[a-z]/, (c) => c.toUpperCase()) -} - -const createRule = ESLintUtils.RuleCreator( - (name) => - `https://github.com/dev-five-git/devup/tree/main/packages/eslint-plugin/src/rules/${name}`, -) - -// Combined regex patterns for better performance (single regex test instead of multiple) -const EXCLUDE_PATTERN = - /[\\/]utils[\\/]|[\\/](__)?tests?(__)?[\\/]|\.(test|css|stories)\.[jt]sx$/ -const INCLUDE_PATTERN = - /(src[/\\])?(app[/\\](?!.*[\\/]?(page|layout|404)\.[jt]sx$)|components[/\\]).*\.[jt]sx$/ -const COMPONENT_NAME_PATTERN = /([^/\\]+)[/\\]([^/\\]+)\.[jt]sx$/i - -export const component = createRule({ - name: 'component', - defaultOptions: [], - meta: { - schema: [], - messages: { - componentNameShouldBeFollowDirectoryStructure: - 'Component name should follow directory or file name.', - componentFileShouldExportComponent: - 'Component file should export a component. (Component name: {targetComponentName})', - }, - type: 'problem', - fixable: 'code', - docs: { - description: 'Require component name to follow directory or file name.', - }, - }, - create(context) { - const filename = relative(context.cwd, context.physicalFilename) - - // Early return: check exclusion first (more likely to match, faster exit) - if (EXCLUDE_PATTERN.test(filename) || !INCLUDE_PATTERN.test(filename)) { - return {} - } - - // Extract component name from file path - const targetComponentRegex = COMPONENT_NAME_PATTERN.exec(filename)! - - const isIndex = targetComponentRegex[2].startsWith('index') - const targetComponentName = isIndex - ? toPascal(targetComponentRegex[1]) - : toPascal(targetComponentRegex[2]) - - const exportFunc: TSESTree.Identifier[] = [] - let ok = false - - return { - ExportDefaultDeclaration() { - ok = true - }, - ExportNamedDeclaration(namedExport) { - if (ok) return - - // Pass check if there are export specifiers in index file - if (namedExport.specifiers.length && isIndex) { - ok = true - return - } - - const declaration = namedExport.declaration - if (!declaration) return - - switch (declaration.type) { - case 'FunctionDeclaration': - if (declaration.id) { - if (declaration.id.name === targetComponentName) { - ok = true - } else { - exportFunc.push(declaration.id) - } - } - break - case 'ClassDeclaration': - if (declaration.id?.name === targetComponentName) { - ok = true - } - break - case 'VariableDeclaration': - for (const el of declaration.declarations) { - if (el.id.type !== 'Identifier') continue - - if (el.id.name === targetComponentName) { - ok = true - return - } - - if ( - el.init?.type === 'ArrowFunctionExpression' || - el.init?.type === 'FunctionExpression' - ) { - exportFunc.push(el.id) - } - } - break - } - }, - 'Program:exit'(program) { - if (ok) return - - // Suggest fix when component name does not match - if (exportFunc.length) { - for (const exported of exportFunc) { - context.report({ - node: exported, - messageId: 'componentNameShouldBeFollowDirectoryStructure', - fix: (fixer) => fixer.replaceText(exported, targetComponentName), - }) - } - return - } - - // Suggest adding default component when no component is exported - context.report({ - node: program, - messageId: 'componentFileShouldExportComponent', - data: { targetComponentName }, - fix: (fixer) => - fixer.insertTextAfter( - context.sourceCode.ast, - context.sourceCode.text.trim().length - ? `\nexport function ${targetComponentName}(){return <>}` - : `export function ${targetComponentName}(){return <>}`, - ), - }) - }, - } - }, -}) +import { ESLintUtils, TSESTree } from '@typescript-eslint/utils' +import { relative } from 'path' + +function toPascal(str: string) { + return str + .replace(/[-_](.)/g, (_, c: string) => c.toUpperCase()) + .replace(/^[a-z]/, (c) => c.toUpperCase()) +} + +const createRule = ESLintUtils.RuleCreator( + (name) => + `https://github.com/dev-five-git/devup/tree/main/packages/eslint-plugin/src/rules/${name}`, +) + +// Combined regex patterns for better performance (single regex test instead of multiple) +const EXCLUDE_PATTERN = + /[\\/]utils[\\/]|[\\/](__)?tests?(__)?[\\/]|\.(test|css|stories)\.[jt]sx$/ +const INCLUDE_PATTERN = + /(src[/\\])?(app[/\\](?!.*[\\/]?(page|layout|404)\.[jt]sx$)|components[/\\]).*\.[jt]sx$/ +const COMPONENT_NAME_PATTERN = /([^/\\]+)[/\\]([^/\\]+)\.[jt]sx$/i + +export const component = createRule({ + name: 'component', + defaultOptions: [], + meta: { + schema: [], + messages: { + componentNameShouldBeFollowDirectoryStructure: + 'Component name should follow directory or file name.', + componentFileShouldExportComponent: + 'Component file should export a component. (Component name: {targetComponentName})', + }, + type: 'problem', + fixable: 'code', + docs: { + description: 'require component name to follow directory or file name.', + }, + }, + create(context) { + const filename = relative(context.cwd, context.physicalFilename) + + // Early return: check exclusion first (more likely to match, faster exit) + if (EXCLUDE_PATTERN.test(filename) || !INCLUDE_PATTERN.test(filename)) { + return {} + } + + // Extract component name from file path + const targetComponentRegex = COMPONENT_NAME_PATTERN.exec(filename)! + + const isIndex = targetComponentRegex[2].startsWith('index') + const targetComponentName = isIndex + ? toPascal(targetComponentRegex[1]) + : toPascal(targetComponentRegex[2]) + + const exportFunc: TSESTree.Identifier[] = [] + let ok = false + + return { + ExportDefaultDeclaration() { + ok = true + }, + ExportNamedDeclaration(namedExport) { + if (ok) return + + // Pass check if there are export specifiers in index file + if (namedExport.specifiers.length && isIndex) { + ok = true + return + } + + const declaration = namedExport.declaration + if (!declaration) return + + switch (declaration.type) { + case 'FunctionDeclaration': + if (declaration.id) { + if (declaration.id.name === targetComponentName) { + ok = true + } else { + exportFunc.push(declaration.id) + } + } + break + case 'ClassDeclaration': + if (declaration.id?.name === targetComponentName) { + ok = true + } + break + case 'VariableDeclaration': + for (const el of declaration.declarations) { + if (el.id.type !== 'Identifier') continue + + if (el.id.name === targetComponentName) { + ok = true + return + } + + if ( + el.init?.type === 'ArrowFunctionExpression' || + el.init?.type === 'FunctionExpression' + ) { + exportFunc.push(el.id) + } + } + break + } + }, + 'Program:exit'(program) { + if (ok) return + + // Suggest fix when component name does not match + if (exportFunc.length) { + for (const exported of exportFunc) { + context.report({ + node: exported, + messageId: 'componentNameShouldBeFollowDirectoryStructure', + fix: (fixer) => fixer.replaceText(exported, targetComponentName), + }) + } + return + } + + // Suggest adding default component when no component is exported + context.report({ + node: program, + messageId: 'componentFileShouldExportComponent', + data: { targetComponentName }, + fix: (fixer) => + fixer.insertTextAfter( + context.sourceCode.ast, + context.sourceCode.text.trim().length + ? `\nexport function ${targetComponentName}(){return <>}` + : `export function ${targetComponentName}(){return <>}`, + ), + }) + }, + } + }, +}) diff --git a/tsconfig.json b/tsconfig.json index 4acc9bf..f1f3b1a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,26 +1,26 @@ -{ - "compilerOptions": { - "strict": true, - "target": "ESNext", - "declaration": true, - "declarationMap": true, - "removeComments": true, - "sourceMap": true, - "useDefineForClassFields": true, - "allowJs": false, - "skipLibCheck": true, - "noFallthroughCasesInSwitch": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true, - "strictFunctionTypes": true, - "module": "ESNext", - "moduleResolution": "Bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "emitDeclarationOnly": true, - "outDir": "dist", - "baseUrl": ".", - "jsx": "react-jsx" - } -} +{ + "compilerOptions": { + "strict": true, + "target": "ESNext", + "declaration": true, + "declarationMap": true, + "removeComments": true, + "sourceMap": true, + "useDefineForClassFields": true, + "allowJs": false, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strictFunctionTypes": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "emitDeclarationOnly": true, + "outDir": "dist", + "baseUrl": ".", + "jsx": "react-jsx" + } +}