├── .changeset ├── README.md └── config.json ├── .github └── workflows │ ├── publish.yml │ ├── test.yml │ └── version.yml ├── .gitignore ├── .node-version ├── .prettierignore ├── README.md ├── eslint.config.ts ├── package.json ├── packages └── eslint-config │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ ├── __snapshots__ │ │ ├── next.rules.json │ │ └── recommended.rules.json │ ├── config.ts │ ├── index.test.ts │ └── index.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── renovate.json └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { "repo": "pixiv/frontend-config" } 6 | ], 7 | "commit": false, 8 | "fixed": [], 9 | "linked": [], 10 | "access": "restricted", 11 | "baseBranch": "main", 12 | "updateInternalDependencies": "patch", 13 | "ignore": [] 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npmjs 2 | 3 | on: 4 | release: 5 | types: [created] 6 | workflow_dispatch: 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | id-token: write 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: pnpm/action-setup@v4 16 | name: Install pnpm 17 | with: 18 | version: 9 19 | - uses: actions/setup-node@v4 20 | with: 21 | cache: pnpm 22 | node-version: "20.x" 23 | registry-url: "https://registry.npmjs.org" 24 | - name: publish 25 | run: | 26 | pnpm install 27 | pnpm -r build 28 | pnpm -r publish --access public --no-git-checks 29 | env: 30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | NPM_CONFIG_PROVENANCE: true 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: push 4 | 5 | defaults: 6 | run: 7 | shell: bash 8 | 9 | jobs: 10 | test-and-build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: pnpm/action-setup@v4 15 | name: Install pnpm 16 | with: 17 | version: 9 18 | - uses: actions/setup-node@v4 19 | with: 20 | cache: pnpm 21 | node-version: "20.x" 22 | registry-url: "https://registry.npmjs.org" 23 | - name: build 24 | run: | 25 | pnpm install 26 | pnpm lint 27 | pnpm fmt --check 28 | pnpm -r build 29 | -------------------------------------------------------------------------------- /.github/workflows/version.yml: -------------------------------------------------------------------------------- 1 | name: Version packages 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_type: 7 | description: "Release type" 8 | required: true 9 | default: "patch" 10 | type: choice 11 | options: 12 | - patch 13 | - minor 14 | - major 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: write 21 | id-token: write 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: pnpm/action-setup@v4 25 | name: Install pnpm 26 | with: 27 | version: 9 28 | - uses: actions/setup-node@v4 29 | with: 30 | cache: pnpm 31 | node-version: "20.x" 32 | registry-url: "https://registry.npmjs.org" 33 | - name: version 34 | run: | 35 | RELEASE_TYPE=${{ github.event.inputs.release_type }} 36 | echo -e "---\n\"@pixiv/eslint-config\": $RELEASE_TYPE\n---\n\n[changelog](https://github.com/pixiv/frontend-config/releases)" > .changeset/$RELEASE_TYPE.md 37 | pnpm install 38 | pnpm changeset version 39 | git config --local user.email "action@github.com" 40 | git config --local user.name "actions-user" 41 | git add . 42 | git commit -m 'feat: bump versions' 43 | git push origin ${{ github.ref_name }} 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp 3 | dist 4 | 5 | .npmrc -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | lts 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/__snapshots__ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # frontend-config 2 | 3 | > [!WARNING] 4 | > This repository does not actively accept external contributions. 5 | -------------------------------------------------------------------------------- /eslint.config.ts: -------------------------------------------------------------------------------- 1 | import type { Linter } from "eslint"; 2 | import pixiv from "./packages/eslint-config/src"; 3 | 4 | export default [ 5 | { ignores: ["**/dist"] }, 6 | ...pixiv.configs.vanillaTs, 7 | ] satisfies Linter.Config[]; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "config", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "private": true, 8 | "keywords": [], 9 | "engines": { 10 | "pnpm": ">= 9" 11 | }, 12 | "scripts": { 13 | "lint": "eslint", 14 | "fmt": "prettier .github packages" 15 | }, 16 | "devDependencies": { 17 | "@babel/preset-typescript": "7.27.1", 18 | "@changesets/changelog-github": "0.5.1", 19 | "@changesets/cli": "2.29.4", 20 | "@types/node": "22.15.19", 21 | "eslint": "9.27.0", 22 | "jiti": "2.4.2", 23 | "prettier": "3.5.3", 24 | "typescript": "5.8.3", 25 | "vitest": "3.1.3" 26 | }, 27 | "resolutions": { 28 | "eslint": "9.27.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/eslint-config/.npmignore: -------------------------------------------------------------------------------- 1 | __snapshots__ 2 | -------------------------------------------------------------------------------- /packages/eslint-config/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @pixiv/eslint-config 2 | 3 | ## 0.0.17 4 | 5 | ### Patch Changes 6 | 7 | - [changelog](https://github.com/pixiv/frontend-config/releases) 8 | 9 | ## 0.0.16 10 | 11 | ### Patch Changes 12 | 13 | - [changelog](https://github.com/pixiv/frontend-config/releases) 14 | 15 | ## 0.0.15 16 | 17 | ### Patch Changes 18 | 19 | - [changelog](https://github.com/pixiv/frontend-config/releases) 20 | 21 | ## 0.0.14 22 | 23 | ### Patch Changes 24 | 25 | - [changelog](https://github.com/pixiv/frontend-config/releases) 26 | 27 | ## 0.0.13 28 | 29 | ### Patch Changes 30 | 31 | - [changelog](https://github.com/pixiv/frontend-config/releases) 32 | 33 | ## 0.0.12 34 | 35 | ### Patch Changes 36 | 37 | - [changelog](https://github.com/pixiv/frontend-config/releases) 38 | 39 | ## 0.0.11 40 | 41 | ### Patch Changes 42 | 43 | - ([679c4e0](https://github.com/pixiv/frontend-config/commit/679c4e0)) chore(deps): update devdependencies 44 | - ([e108b32](https://github.com/pixiv/frontend-config/commit/e108b32)) fix(deps): update dependency globals to v16 45 | - ([745286c](https://github.com/pixiv/frontend-config/commit/745286c)) fix(deps): update dependencies 46 | 47 | ## 0.0.10 48 | 49 | ### Patch Changes 50 | 51 | - ([3c89b1d](https://github.com/pixiv/frontend-config/commit/3c89b1dac641d3b17773f1c0edec7ddf83c9fe17)) chore(deps): update dependency eslint to v9.20.0 52 | - ([f723850](https://github.com/pixiv/frontend-config/commit/f72385053e07ae21050e6ec64d8975eb0af9c692)) chore(deps): update devdependencies 53 | - ([ab8d3f4](https://github.com/pixiv/frontend-config/commit/ab8d3f46d40350955a73e81bf8a5db92e57871dc)) fix(deps): update dependencies 54 | 55 | ## 0.0.9 56 | 57 | ### Patch Changes 58 | 59 | - ([6139c62](https://github.com/pixiv/frontend-config/commit/6139c62)) chore(deps): update dependency eslint to v9.19.0 60 | - ([6004363](https://github.com/pixiv/frontend-config/commit/6004363)) chore(deps): update devdependencies 61 | - ([7356809](https://github.com/pixiv/frontend-config/commit/7356809)) fix(deps): update dependency eslint-config-prettier to v10 62 | - ([bfceb6a](https://github.com/pixiv/frontend-config/commit/bfceb6a)) fix(deps): update dependencies 63 | 64 | ## 0.0.8 65 | 66 | ### Patch Changes 67 | 68 | - ([69f97cf](https://github.com/pixiv/frontend-config/commit/69f97cf)) chore(deps): update devdependencies 69 | - ([61b7d4b](https://github.com/pixiv/frontend-config/commit/61b7d4b)) fix(deps): update dependencies 70 | 71 | ## 0.0.7 72 | 73 | ### Patch Changes 74 | 75 | - (35d4a33) chore(deps): update devdependencies 76 | - (79cdeb1) fix(deps): update dependencies 77 | - (8931f60) feat: try add version workflow 78 | - (586b632) chore(deps): pin dependency @changesets/changelog-github to 0.5.0 79 | 80 | ## 0.0.6 81 | 82 | ### Patch Changes 83 | 84 | - [Bump dependencies](https://github.com/pixiv/frontend-config/pull/32) 85 | 86 | ## 0.0.5 87 | 88 | ### Patch Changes 89 | 90 | - Bump dependencies and [support TypeScript 5.7](https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.16.0) 91 | 92 | ## 0.0.4 93 | 94 | ### Patch Changes 95 | 96 | - Bump dependencies and update `eslint-plugin-react-compiler` to beta 97 | - Enforce eslint and prettier for self dev 98 | - Expose `vanillaTs` config 99 | 100 | ## 0.0.3 101 | 102 | ### Patch Changes 103 | 104 | - Bump dependencies and [add varsIgnorePattern to @typescript-eslint/no-unused-vars](https://github.com/pixiv/frontend-config/pull/11) 105 | 106 | ## 0.0.2 107 | 108 | ### Patch Changes 109 | 110 | - [Bump deps](https://github.com/pixiv/frontend-config/pull/3) to support TypeScript 5.6 and [update eslint rules](https://github.com/pixiv/frontend-config/pull/4) 111 | 112 | ## 0.0.1 113 | 114 | ### Patch Changes 115 | 116 | - Release 0.0.1 (version bump only) 117 | 118 | ## 0.0.0-experimental.5 119 | 120 | ### Patch Changes 121 | 122 | - BREAKING: update eslint-config-react-hooks to v5, see https://github.com/facebook/react/releases/tag/eslint-plugin-react-hooks%405.0.0 123 | - chore: update deps 124 | 125 | ## 0.0.0-experimental.4 126 | 127 | ### Patch Changes 128 | 129 | - chore: bump deps 130 | 131 | ## 0.0.0-experimental.3 132 | 133 | ### Patch Changes 134 | 135 | - feat: use @next/eslint-plugin-next directly 136 | 137 | ## 0.0.0-experimental.2 138 | 139 | ### Patch Changes 140 | 141 | - chore: bump deps 142 | 143 | ## 0.0.0-experimental.1 144 | 145 | ### Patch Changes 146 | 147 | - fix: lazy init config options 148 | 149 | ## 0.0.0-experimental.0 150 | 151 | ### Patch Changes 152 | 153 | - feat: init release 154 | -------------------------------------------------------------------------------- /packages/eslint-config/README.md: -------------------------------------------------------------------------------- 1 | # @pixiv/eslint-config 2 | 3 | > [!WARNING] 4 | > This package does not follow semver before 1.0 5 | 6 | `pnpm add eslint @pixiv/eslint-config` 7 | 8 | eslint.config.mjs 9 | 10 | ```js 11 | import pixiv from "@pixiv/eslint-config"; 12 | 13 | export default pixiv.configs.recommended; 14 | ``` 15 | 16 | また 17 | 18 | ```js 19 | import pixiv from "@pixiv/eslint-config"; 20 | 21 | export default [ 22 | { 23 | ignores: ["tmp", "vendor", "public/packs"], 24 | }, 25 | ...pixiv.configs.recommended, 26 | { 27 | rules: { 28 | "react/self-closing-comp": "warn", 29 | "import/first": "warn", 30 | "object-shorthand": "warn", 31 | curly: "off", 32 | }, 33 | }, 34 | ]; 35 | ``` 36 | 37 | ## Goals and non-goals 38 | 39 | ### Goals 40 | 41 | - Reduce the cost for managing eslint related dependencies 42 | 43 | ### Non-goals 44 | 45 | - Managing extremely opinionated lint rules 46 | 47 | ## Known issues 48 | 49 | - This package contains many deps that might not be used 50 | - In some condition it's required to set `public-hoist-pattern[]=eslint-*` in .npmrc 51 | -------------------------------------------------------------------------------- /packages/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pixiv/eslint-config", 3 | "version": "0.0.17", 4 | "description": "pixiv's base eslint config", 5 | "author": "pixiv", 6 | "publishConfig": { 7 | "provenance": true 8 | }, 9 | "type": "module", 10 | "main": "dist/index.js", 11 | "types": "dist/index.d.ts", 12 | "scripts": { 13 | "build": "tsup src/index.ts --clean --dts --format esm", 14 | "prepublish": "pnpm build" 15 | }, 16 | "exports": { 17 | ".": { 18 | "default": "./dist/index.js" 19 | } 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/pixiv/frontend-config.git", 24 | "directory": "packages/eslint-config" 25 | }, 26 | "files": [ 27 | "README.md", 28 | "CHANGELOG.md", 29 | "src", 30 | "dist" 31 | ], 32 | "peerDependencies": { 33 | "eslint": "^9.0.0" 34 | }, 35 | "dependencies": { 36 | "@eslint/compat": "1.2.9", 37 | "@eslint/eslintrc": "3.3.1", 38 | "@eslint/js": "9.27.0", 39 | "@next/eslint-plugin-next": "15.3.2", 40 | "@typescript-eslint/eslint-plugin": "8.32.1", 41 | "@typescript-eslint/parser": "8.32.1", 42 | "eslint-config-prettier": "10.1.5", 43 | "eslint-plugin-import": "2.31.0", 44 | "eslint-plugin-jsx-a11y": "6.10.2", 45 | "eslint-plugin-react": "7.37.5", 46 | "eslint-plugin-react-compiler": "19.0.0-beta-ebf51a3-20250411", 47 | "eslint-plugin-react-hooks": "5.2.0", 48 | "eslint-plugin-storybook": "0.12.0", 49 | "typescript-eslint": "8.32.1", 50 | "globals": "16.1.0" 51 | }, 52 | "devDependencies": { 53 | "tsup": "8.5.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/eslint-config/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: "9.0" 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | .: {} 9 | -------------------------------------------------------------------------------- /packages/eslint-config/src/__snapshots__/next.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "curly": "error", 3 | "no-empty-function": "error", 4 | "no-fallthrough": "error", 5 | "no-constant-condition": "error", 6 | "object-shorthand": "error", 7 | "@typescript-eslint/no-unused-vars": [ 8 | "error", 9 | { 10 | "argsIgnorePattern": "^_", 11 | "varsIgnorePattern": "^_" 12 | } 13 | ], 14 | "@typescript-eslint/consistent-type-imports": [ 15 | "error", 16 | { 17 | "disallowTypeAnnotations": false, 18 | "fixStyle": "inline-type-imports" 19 | } 20 | ], 21 | "@typescript-eslint/no-import-type-side-effects": "error", 22 | "@typescript-eslint/no-deprecated": "warn", 23 | "react/display-name": "warn", 24 | "react/jsx-key": 2, 25 | "react/jsx-no-comment-textnodes": 2, 26 | "react/jsx-no-duplicate-props": 2, 27 | "react/jsx-no-undef": 2, 28 | "react/jsx-uses-react": 2, 29 | "react/jsx-uses-vars": 2, 30 | "react/no-children-prop": 2, 31 | "react/no-danger-with-children": 2, 32 | "react/no-deprecated": 2, 33 | "react/no-direct-mutation-state": 2, 34 | "react/no-find-dom-node": 2, 35 | "react/no-is-mounted": 2, 36 | "react/no-render-return-value": 2, 37 | "react/no-string-refs": 2, 38 | "react/no-unescaped-entities": 2, 39 | "react/no-unknown-property": [ 40 | "error", 41 | { 42 | "ignore": [ 43 | "css" 44 | ] 45 | } 46 | ], 47 | "react/require-render-return": 2, 48 | "react/self-closing-comp": "error", 49 | "react/jsx-boolean-value": [ 50 | "error", 51 | "never" 52 | ], 53 | "react-hooks/rules-of-hooks": "error", 54 | "react-hooks/exhaustive-deps": "error", 55 | "react-compiler/react-compiler": "warn", 56 | "jsx-a11y/aria-props": "warn", 57 | "jsx-a11y/aria-proptypes": "warn", 58 | "jsx-a11y/aria-unsupported-elements": "warn", 59 | "jsx-a11y/role-has-required-aria-props": "warn", 60 | "jsx-a11y/role-supports-aria-props": "warn", 61 | "@next/next/no-html-link-for-pages": "error", 62 | "@next/next/no-sync-scripts": "error", 63 | "storybook/await-interactions": "error", 64 | "storybook/context-in-play-function": "error", 65 | "storybook/default-exports": "error", 66 | "storybook/hierarchy-separator": "warn", 67 | "storybook/no-redundant-story-name": "warn", 68 | "storybook/prefer-pascal-case": "warn", 69 | "storybook/story-exports": "error", 70 | "storybook/use-storybook-expect": "error", 71 | "storybook/use-storybook-testing-library": "error", 72 | "storybook/no-uninstalled-addons": "error", 73 | "import/no-unresolved": "error", 74 | "import/named": "error", 75 | "import/namespace": "error", 76 | "import/default": "error", 77 | "import/export": "error", 78 | "import/no-named-as-default": "warn", 79 | "import/no-named-as-default-member": "warn", 80 | "import/no-duplicates": "error", 81 | "import/first": "error", 82 | "import/order": [ 83 | "warn", 84 | { 85 | "alphabetize": { 86 | "order": "asc", 87 | "orderImportKind": "asc" 88 | } 89 | } 90 | ] 91 | } -------------------------------------------------------------------------------- /packages/eslint-config/src/__snapshots__/recommended.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "curly": "error", 3 | "no-empty-function": "error", 4 | "no-fallthrough": "error", 5 | "no-constant-condition": "error", 6 | "object-shorthand": "error", 7 | "@typescript-eslint/no-unused-vars": [ 8 | "error", 9 | { 10 | "argsIgnorePattern": "^_", 11 | "varsIgnorePattern": "^_" 12 | } 13 | ], 14 | "@typescript-eslint/consistent-type-imports": [ 15 | "error", 16 | { 17 | "disallowTypeAnnotations": false, 18 | "fixStyle": "inline-type-imports" 19 | } 20 | ], 21 | "@typescript-eslint/no-import-type-side-effects": "error", 22 | "@typescript-eslint/no-deprecated": "warn", 23 | "react/display-name": "warn", 24 | "react/jsx-key": 2, 25 | "react/jsx-no-comment-textnodes": 2, 26 | "react/jsx-no-duplicate-props": 2, 27 | "react/jsx-no-undef": 2, 28 | "react/jsx-uses-react": 2, 29 | "react/jsx-uses-vars": 2, 30 | "react/no-children-prop": 2, 31 | "react/no-danger-with-children": 2, 32 | "react/no-deprecated": 2, 33 | "react/no-direct-mutation-state": 2, 34 | "react/no-find-dom-node": 2, 35 | "react/no-is-mounted": 2, 36 | "react/no-render-return-value": 2, 37 | "react/no-string-refs": 2, 38 | "react/no-unescaped-entities": 2, 39 | "react/no-unknown-property": [ 40 | "error", 41 | { 42 | "ignore": [ 43 | "css" 44 | ] 45 | } 46 | ], 47 | "react/require-render-return": 2, 48 | "react/self-closing-comp": "error", 49 | "react/jsx-boolean-value": [ 50 | "error", 51 | "never" 52 | ], 53 | "react-hooks/rules-of-hooks": "error", 54 | "react-hooks/exhaustive-deps": "error", 55 | "react-compiler/react-compiler": "warn", 56 | "jsx-a11y/aria-props": "warn", 57 | "jsx-a11y/aria-proptypes": "warn", 58 | "jsx-a11y/aria-unsupported-elements": "warn", 59 | "jsx-a11y/role-has-required-aria-props": "warn", 60 | "jsx-a11y/role-supports-aria-props": "warn", 61 | "storybook/await-interactions": "error", 62 | "storybook/context-in-play-function": "error", 63 | "storybook/default-exports": "error", 64 | "storybook/hierarchy-separator": "warn", 65 | "storybook/no-redundant-story-name": "warn", 66 | "storybook/prefer-pascal-case": "warn", 67 | "storybook/story-exports": "error", 68 | "storybook/use-storybook-expect": "error", 69 | "storybook/use-storybook-testing-library": "error", 70 | "storybook/no-uninstalled-addons": "error", 71 | "import/no-unresolved": "error", 72 | "import/named": "error", 73 | "import/namespace": "error", 74 | "import/default": "error", 75 | "import/export": "error", 76 | "import/no-named-as-default": "warn", 77 | "import/no-named-as-default-member": "warn", 78 | "import/no-duplicates": "error", 79 | "import/first": "error", 80 | "import/order": [ 81 | "warn", 82 | { 83 | "alphabetize": { 84 | "order": "asc", 85 | "orderImportKind": "asc" 86 | } 87 | } 88 | ] 89 | } -------------------------------------------------------------------------------- /packages/eslint-config/src/config.ts: -------------------------------------------------------------------------------- 1 | import * as compat from "@eslint/compat"; 2 | import * as eslintrc from "@eslint/eslintrc"; 3 | import eslintJs from "@eslint/js"; 4 | // @ts-expect-error no types for this 5 | import pluginNext from "@next/eslint-plugin-next"; 6 | import pluginTypescriptEslint from "@typescript-eslint/eslint-plugin"; 7 | import tsParser from "@typescript-eslint/parser"; 8 | import type { ESLint, Linter } from "eslint"; 9 | import configPrettier from "eslint-config-prettier/flat"; 10 | // @ts-expect-error no types for this 11 | import pluginImport from "eslint-plugin-import"; 12 | // @ts-expect-error no types for this 13 | import pluginJsxA11y from "eslint-plugin-jsx-a11y"; 14 | import pluginReact from "eslint-plugin-react"; 15 | import pluginReactCompiler from "eslint-plugin-react-compiler"; 16 | import pluginReactHooks from "eslint-plugin-react-hooks"; 17 | import pluginStorybook from "eslint-plugin-storybook"; 18 | import globals from "globals"; 19 | import tseslint from "typescript-eslint"; 20 | 21 | // re-export 22 | export { 23 | eslintrc, 24 | eslintJs, 25 | globals, 26 | compat, 27 | tseslint, 28 | tsParser, 29 | configPrettier, 30 | pluginReact, 31 | pluginReactCompiler, 32 | pluginReactHooks, 33 | pluginTypescriptEslint, 34 | pluginJsxA11y, 35 | pluginNext, 36 | pluginStorybook, 37 | pluginImport, 38 | }; 39 | 40 | // No longer required but keep for a while just in case we are adding new plugins. 41 | // const flatCompat = new eslintrc.FlatCompat(); 42 | 43 | export const files = () => 44 | [ 45 | { 46 | ignores: ["tmp", "dist", ".storybook", "storybook-static"], 47 | files: ["**/*.{js,mjs,cjs,jsx,ts,tsx}"], 48 | }, 49 | ] satisfies Linter.Config[]; 50 | 51 | export const js = () => 52 | [ 53 | // TODO: eslintJs.configs.recommended, 54 | { 55 | rules: { 56 | ...configPrettier.rules, 57 | "no-empty-function": "error", 58 | curly: "error", 59 | "no-fallthrough": "error", 60 | "no-constant-condition": "error", 61 | "object-shorthand": "error", 62 | }, 63 | }, 64 | ] satisfies Linter.Config[]; 65 | 66 | export const typescript = () => 67 | [ 68 | { 69 | plugins: { 70 | // @ts-expect-error pluginTypescriptEslint export is not compat with ESLint.Plugin 71 | "@typescript-eslint": pluginTypescriptEslint as ESLint.Plugin, 72 | }, 73 | languageOptions: { 74 | globals: { 75 | ...globals.browser, 76 | }, 77 | 78 | parser: tsParser, 79 | ecmaVersion: 8, 80 | sourceType: "module", 81 | 82 | parserOptions: { 83 | project: "./tsconfig.json", 84 | tsconfigRootDir: process.cwd(), 85 | ecmaFeatures: { 86 | jsx: true, 87 | }, 88 | }, 89 | }, 90 | 91 | rules: { 92 | "no-unused-vars": "off", 93 | "@typescript-eslint/no-unused-vars": [ 94 | "error", 95 | { 96 | argsIgnorePattern: "^_", 97 | varsIgnorePattern: "^_", 98 | }, 99 | ], 100 | "@typescript-eslint/consistent-type-imports": [ 101 | "error", 102 | { 103 | disallowTypeAnnotations: false, 104 | fixStyle: "inline-type-imports", 105 | }, 106 | ], 107 | "@typescript-eslint/no-import-type-side-effects": "error", 108 | "@typescript-eslint/no-deprecated": "warn", 109 | }, 110 | }, 111 | { 112 | files: ["**/*.{mjs,js}"], 113 | ...(tseslint.configs.disableTypeChecked as Linter.Config), 114 | }, 115 | ] satisfies Linter.Config[]; 116 | 117 | export const react = () => 118 | [ 119 | { 120 | settings: { 121 | react: { 122 | version: "detect", 123 | }, 124 | }, 125 | plugins: { 126 | // https://github.com/jsx-eslint/eslint-plugin-react/issues/3838#issuecomment-2395472758 127 | react: pluginReact as ESLint.Plugin, 128 | "react-hooks": pluginReactHooks, 129 | "react-compiler": pluginReactCompiler, 130 | "jsx-a11y": pluginJsxA11y, 131 | }, 132 | rules: { 133 | ...(pluginReact.configs.flat!.recommended.rules as Linter.RulesRecord), 134 | "react/self-closing-comp": "error", 135 | "react/react-in-jsx-scope": "off", 136 | "react/jsx-no-target-blank": "off", 137 | "react/prop-types": "off", 138 | "react/jsx-boolean-value": ["error", "never"], 139 | "react/display-name": "warn", 140 | "react/no-unknown-property": [ 141 | "error", 142 | { 143 | ignore: ["css"], 144 | }, 145 | ], 146 | "react-hooks/rules-of-hooks": "error", 147 | "react-hooks/exhaustive-deps": "error", 148 | "react-compiler/react-compiler": "warn", 149 | 150 | // from next config, comment below is used to track diff 151 | 152 | // 'jsx-a11y/alt-text': [ 153 | // 'warn', 154 | // { 155 | // elements: ['img'], 156 | // img: ['Image'], 157 | // }, 158 | // ], 159 | "jsx-a11y/aria-props": "warn", 160 | "jsx-a11y/aria-proptypes": "warn", 161 | "jsx-a11y/aria-unsupported-elements": "warn", 162 | "jsx-a11y/role-has-required-aria-props": "warn", 163 | "jsx-a11y/role-supports-aria-props": "warn", 164 | }, 165 | }, 166 | ] satisfies Linter.Config[]; 167 | 168 | /** 169 | * NOTE: eslint-plugin-nextのリリースが頻繁ではないので@next/eslint-plugin-nextを利用して回避する 170 | */ 171 | export const nextJs = () => { 172 | return [ 173 | { 174 | plugins: { 175 | "@next/next": pluginNext, 176 | }, 177 | rules: { 178 | ...( 179 | pluginNext.configs as Record< 180 | string, 181 | Linter.Config 182 | > 183 | )["core-web-vitals"].rules, 184 | "@next/next/no-duplicate-head": "off", 185 | "@next/next/no-img-element": "off", 186 | }, 187 | }, 188 | ] satisfies Linter.Config[]; 189 | }; 190 | 191 | export const storybook = () => [...pluginStorybook.configs["flat/recommended"]]; 192 | 193 | export const imports = () => 194 | [ 195 | pluginImport.flatConfigs.recommended, 196 | { 197 | rules: { 198 | "import/no-duplicates": "error", 199 | "import/first": "error", 200 | "import/named": "off", 201 | "import/export": "off", 202 | "import/no-unresolved": "off", 203 | "import/namespace": "off", 204 | "import/default": "off", 205 | "import/no-named-as-default": "off", 206 | "import/no-named-as-default-member": "off", 207 | "import/order": [ 208 | "warn", 209 | { 210 | alphabetize: { 211 | order: "asc", 212 | orderImportKind: "asc", 213 | }, 214 | }, 215 | ], 216 | }, 217 | }, 218 | ] satisfies Linter.Config[]; 219 | -------------------------------------------------------------------------------- /packages/eslint-config/src/index.test.ts: -------------------------------------------------------------------------------- 1 | import type { Linter } from "eslint"; 2 | import { expect, test } from "vitest"; 3 | import pixiv from "."; 4 | 5 | const rules = (config: Linter.Config[]) => 6 | JSON.stringify( 7 | Object.fromEntries( 8 | config 9 | .flatMap((r) => (r.rules ? Object.entries(r.rules) : [])) 10 | .filter((r) => r[1] !== 0 && r[1] !== "off"), 11 | ), 12 | null, 13 | 2, 14 | ); 15 | 16 | test("recommended rules", () => { 17 | expect(rules(pixiv.configs.recommended)).toMatchFileSnapshot( 18 | "./__snapshots__/recommended.rules.json", 19 | ); 20 | }); 21 | 22 | test("Next.js rules", () => { 23 | expect(rules(pixiv.configs.nextJs)).toMatchFileSnapshot( 24 | "./__snapshots__/next.rules.json", 25 | ); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/eslint-config/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { Linter } from "eslint"; 2 | 3 | // TODO: optional peer depsに移動してinstallサイズを減らす 4 | import { 5 | files, 6 | js, 7 | typescript, 8 | react, 9 | nextJs, 10 | storybook, 11 | imports, 12 | } from "./config"; 13 | 14 | export * from "./config"; 15 | 16 | export default { 17 | configs: { 18 | /** 19 | * フレームワーク使用しない場合 20 | * 21 | * 内容: files + js + typescript 22 | */ 23 | get vanillaTs() { 24 | return [ 25 | ...files(), 26 | ...js(), 27 | ...typescript(), 28 | ...imports(), 29 | ] satisfies Linter.Config[]; 30 | }, 31 | /** 32 | * できるだけそのまま使いたい場合 33 | * 34 | * 内容: files + js + typescript + react + storybook + imports 35 | */ 36 | get recommended() { 37 | return [ 38 | ...files(), 39 | ...js(), 40 | ...typescript(), 41 | ...react(), 42 | ...storybook(), 43 | ...imports(), 44 | ] satisfies Linter.Config[]; 45 | }, 46 | 47 | /** 48 | * 内容: js + typescript + react 49 | */ 50 | get react() { 51 | return [ 52 | ...js(), // 53 | ...typescript(), 54 | ...react(), 55 | ] satisfies Linter.Config[]; 56 | }, 57 | 58 | /** 59 | * 内容: js + typescript + react + next.js + storybook + imports 60 | * 61 | * NOTE: 62 | * 1. 中身はeslint-config-nextではなく @next/eslint-plugin-next 63 | */ 64 | get nextJs() { 65 | return [ 66 | ...js(), 67 | ...typescript(), 68 | ...react(), 69 | ...nextJs(), 70 | ...storybook(), 71 | ...imports(), 72 | ] satisfies Linter.Config[]; 73 | }, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /packages/eslint-config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["./src"], 4 | "compilerOptions": { 5 | "outDir": "./dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base", ":disableDependencyDashboard"], 4 | "platform": "github", 5 | "repositories": ["pixiv/frontend-config"], 6 | "timezone": "Asia/Tokyo", 7 | "enabledManagers": ["npm"], 8 | "fetchReleaseNotes": "off", 9 | "prHourlyLimit": 0, 10 | "prConcurrentLimit": 0, 11 | "platformAutomerge": true, 12 | "npm": { 13 | "fileMatch": ["^package\\.json$"] 14 | }, 15 | "lockFileMaintenance": { 16 | "enabled": false 17 | }, 18 | "rangeStrategy": "pin", 19 | "ignoreDeps": ["pnpm"], 20 | "pin": { 21 | "labels": ["dependencies", "pin"] 22 | }, 23 | "patch": { 24 | "labels": ["dependencies", "patch"] 25 | }, 26 | "minor": { 27 | "labels": ["dependencies", "minor"] 28 | }, 29 | "major": { 30 | "labels": ["dependencies", "major"] 31 | }, 32 | "minimumReleaseAge": "1 week", 33 | "packageRules": [ 34 | { 35 | "depTypeList": ["peerDependencies"], 36 | "enabled": false 37 | }, 38 | { 39 | "groupName": "pin dependencies", 40 | "description": "packageのpinでまとめる", 41 | "matchUpdateTypes": ["pin"] 42 | }, 43 | { 44 | "groupName": "patch dependencies", 45 | "description": "patchのアップデートはまとめる", 46 | "matchUpdateTypes": ["patch"] 47 | }, 48 | { 49 | "groupName": "minor dependencies", 50 | "description": "minorのアップデートはまとめる", 51 | "matchUpdateTypes": ["minor"] 52 | }, 53 | { 54 | "groupName": "dependencies", 55 | "matchDepTypes": ["dependencies"] 56 | }, 57 | { 58 | "groupName": "devDependencies", 59 | "matchDepTypes": ["devDependencies"] 60 | } 61 | ], 62 | "baseBranches": ["main"], 63 | "assignees": ["yue4u"], 64 | "onboarding": false 65 | } 66 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "Preserve" /* Specify what module code is generated. */, 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | "moduleResolution": "Bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ 43 | // "resolveJsonModule": true, /* Enable importing .json files. */ 44 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 45 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 46 | 47 | /* JavaScript Support */ 48 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 49 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 50 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 51 | 52 | /* Emit */ 53 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 54 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 55 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 56 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 57 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 58 | // "noEmit": true, /* Disable emitting files from a compilation. */ 59 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 60 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 61 | // "removeComments": true, /* Disable emitting comments. */ 62 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | 75 | /* Interop Constraints */ 76 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 77 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 78 | // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 83 | 84 | /* Type Checking */ 85 | "strict": true /* Enable all strict type-checking options. */, 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ 92 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 93 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 94 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 95 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 96 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 97 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 98 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 99 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 100 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 101 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 102 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 103 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 104 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 105 | 106 | /* Completeness */ 107 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 108 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 109 | } 110 | } 111 | --------------------------------------------------------------------------------