diff --git a/.github/workflows/compiler_discord_notify.yml b/.github/workflows/compiler_discord_notify.yml index 3eeb009a78aab..b9cc3e9b9fa83 100644 --- a/.github/workflows/compiler_discord_notify.yml +++ b/.github/workflows/compiler_discord_notify.yml @@ -8,8 +8,12 @@ on: - .github/workflows/compiler_**.yml jobs: + check_maintainer: + uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main + notify: - if: ${{ github.event.label.name == 'React Core Team' }} + if: ${{ needs.check_maintainer.outputs.is_core_team }} + needs: check_maintainer runs-on: ubuntu-latest steps: - name: Discord Webhook Action diff --git a/.github/workflows/runtime_discord_notify.yml b/.github/workflows/runtime_discord_notify.yml index 93606cd549873..abf7970a1e634 100644 --- a/.github/workflows/runtime_discord_notify.yml +++ b/.github/workflows/runtime_discord_notify.yml @@ -8,8 +8,12 @@ on: - .github/workflows/compiler_**.yml jobs: + check_maintainer: + uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main + notify: - if: ${{ github.event.label.name == 'React Core Team' }} + if: ${{ needs.check_maintainer.outputs.is_core_team }} + needs: check_maintainer runs-on: ubuntu-latest steps: - name: Discord Webhook Action diff --git a/.github/workflows/shared_check_maintainer.yml b/.github/workflows/shared_check_maintainer.yml new file mode 100644 index 0000000000000..53f4c7a8af046 --- /dev/null +++ b/.github/workflows/shared_check_maintainer.yml @@ -0,0 +1,35 @@ +name: (Shared) Check maintainer + +on: + workflow_call: + outputs: + is_core_team: + value: ${{ jobs.check_maintainer.outputs.is_core_team }} + +env: + TZ: /usr/share/zoneinfo/America/Los_Angeles + # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout + SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1 + +jobs: + check_maintainer: + runs-on: ubuntu-latest + outputs: + is_core_team: ${{ steps.check_if_actor_is_maintainer.outputs.result }} + steps: + - uses: actions/checkout@v4 + - name: Check if actor is maintainer + id: check_if_actor_is_maintainer + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const actor = '${{ github.actor }}'; + const data = await fs.readFileSync('./MAINTAINERS', { encoding: 'utf8' }); + const maintainers = new Set(data.split('\n')); + if (maintainers.has(actor)) { + console.log(`🟢 ${actor} is a maintainer`); + return true; + } + console.log(`🔴 ${actor} is NOT a maintainer`); + return null; diff --git a/.github/workflows/shared_label_core_team_prs.yml b/.github/workflows/shared_label_core_team_prs.yml new file mode 100644 index 0000000000000..b96aea88054fa --- /dev/null +++ b/.github/workflows/shared_label_core_team_prs.yml @@ -0,0 +1,29 @@ +name: (Shared) Label Core Team PRs + +on: + pull_request_target: + +env: + TZ: /usr/share/zoneinfo/America/Los_Angeles + # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout + SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1 + +jobs: + check_maintainer: + uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main + + label: + if: ${{ needs.check_maintainer.outputs.is_core_team }} + runs-on: ubuntu-latest + needs: check_maintainer + steps: + - name: Label PR as React Core Team + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ github.event.number }}, + labels: ['React Core Team'] + }); diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000000000..f204a5ca66def --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,25 @@ +acdlite +eps1lon +gaearon +gnoff +gsathya +hoxyq +jackpope +jbonta +jbrown215 +josephsavona +kassens +lunaleaps +mattcarrollcode +mofeiZ +mvitousek +noahlemen +pieterv +poteto +rickhanlonii +sebmarkbage +sethwebster +sophiebits +TheSavior +tyao1 +yuzhi diff --git a/packages/eslint-plugin-react-hooks/README.md b/packages/eslint-plugin-react-hooks/README.md index d7a3e7d39cdee..36f63722f7268 100644 --- a/packages/eslint-plugin-react-hooks/README.md +++ b/packages/eslint-plugin-react-hooks/README.md @@ -18,21 +18,38 @@ npm install eslint-plugin-react-hooks --save-dev yarn add eslint-plugin-react-hooks --dev ``` -Then extend the recommended eslint config: +### Legacy Config (.eslintrc) + +If you are still using ESLint below 9.0.0, please continue to use `recommended-legacy`. To avoid breaking changes, we still support `recommended` as well, but note that this will be changed to alias the flat recommended config in v6. ```js { "extends": [ // ... - "plugin:react-hooks/recommended" + "plugin:react-hooks/recommended-legacy" ] } ``` +### Flat Config (eslint.config.js) + +For [ESLint 9.0.0 and above](https://eslint.org/blog/2024/04/eslint-v9.0.0-released/) users, add the `recommended-latest` config. + +```js +import reactHooks from 'eslint-plugin-react-hooks'; + +export default [ + // ... + reactHooks.configs['recommended-latest'], +]; +``` + ### Custom Configuration If you want more fine-grained configuration, you can instead add a snippet like this to your ESLint configuration file: +#### Legacy Config (.eslintrc) + ```js { "plugins": [ @@ -47,6 +64,23 @@ If you want more fine-grained configuration, you can instead add a snippet like } ``` +#### Flat Config (eslint.config.js) + +```js +import reactHooks from 'eslint-plugin-react-hooks'; + +export default [ + { + files: ['**/*.{js,jsx}'], + plugins: { 'react-hooks': reactHooks }, + // ... + rules: { + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + } + }, +]; +``` ## Advanced Configuration diff --git a/packages/eslint-plugin-react-hooks/src/index.js b/packages/eslint-plugin-react-hooks/src/index.js index d8ff02e7b144f..c9da639a4fff6 100644 --- a/packages/eslint-plugin-react-hooks/src/index.js +++ b/packages/eslint-plugin-react-hooks/src/index.js @@ -10,17 +10,54 @@ import RulesOfHooks from './RulesOfHooks'; import ExhaustiveDeps from './ExhaustiveDeps'; -export const configs = { - recommended: { - plugins: ['react-hooks'], - rules: { - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'warn', - }, - }, -}; +const {name, version} = require('../package.json'); +// All rules export const rules = { 'rules-of-hooks': RulesOfHooks, 'exhaustive-deps': ExhaustiveDeps, }; + +// Config rules +const configRules = { + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', +}; + +// Legacy config +const legacyRecommendedConfig = { + plugins: ['react-hooks'], + rules: configRules, +}; + +// Base plugin object +const reactHooksPlugin = { + meta: {name, version}, + rules, +}; + +// Flat config +const flatRecommendedConfig = { + name: 'react-hooks/recommended', + plugins: {'react-hooks': reactHooksPlugin}, + rules: configRules, +}; + +export const configs = { + /** Legacy recommended config, to be used with rc-based configurations */ + 'recommended-legacy': legacyRecommendedConfig, + + /** Latest recommended config, to be used with flat configurations */ + 'recommended-latest': flatRecommendedConfig, + + /** + * 'recommended' is currently aliased to the legacy / rc recommended config) to maintain backwards compatibility. + * This is deprecated and in v6, it will switch to alias the flat recommended config. + */ + recommended: legacyRecommendedConfig, +}; + +export default { + ...reactHooksPlugin, + configs, +};