From a63333a118eff53e7caecca2e1f24cc9df904d51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:47:58 +0000 Subject: [PATCH 1/6] Add merge props ESLint migration rule Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- .github/workflows/migration-status.yml | 13 +++ eslint.config.mjs | 2 + package-lock.json | 20 ++++ packages/eslint-config/package.json | 23 +++++ packages/eslint-config/src/index.test.ts | 9 ++ packages/eslint-config/src/index.ts | 24 +++++ .../src/rules/preferMergeProps.test.ts | 68 +++++++++++++ .../src/rules/preferMergeProps.ts | 95 +++++++++++++++++++ packages/eslint-config/tsconfig.json | 7 ++ packages/eslint-config/vitest.config.ts | 7 ++ script/merge-props-migration-status.mts | 65 +++++++++++++ 11 files changed, 333 insertions(+) create mode 100644 packages/eslint-config/package.json create mode 100644 packages/eslint-config/src/index.test.ts create mode 100644 packages/eslint-config/src/index.ts create mode 100644 packages/eslint-config/src/rules/preferMergeProps.test.ts create mode 100644 packages/eslint-config/src/rules/preferMergeProps.ts create mode 100644 packages/eslint-config/tsconfig.json create mode 100644 packages/eslint-config/vitest.config.ts create mode 100644 script/merge-props-migration-status.mts diff --git a/.github/workflows/migration-status.yml b/.github/workflows/migration-status.yml index 9b74e5e6f78..d2cf2599c1f 100644 --- a/.github/workflows/migration-status.yml +++ b/.github/workflows/migration-status.yml @@ -5,6 +5,19 @@ on: workflow_dispatch: jobs: + merge-props: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 + with: + node-version-file: '.nvmrc' + cache: 'npm' + - name: install dependencies + run: npm ci + - name: run migration script + run: node script/merge-props-migration-status.mts >> $GITHUB_STEP_SUMMARY + react-compiler: runs-on: ubuntu-latest steps: diff --git a/eslint.config.mjs b/eslint.config.mjs index 22647924d8e..7092b2f8d56 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,6 +3,7 @@ import {fileURLToPath} from 'node:url' import {fixupConfigRules, fixupPluginRules} from '@eslint/compat' import {FlatCompat} from '@eslint/eslintrc' import js from '@eslint/js' +import primerConfig from '@primer/eslint-config' import eslintReact from '@eslint-react/eslint-plugin' import vitest from '@vitest/eslint-plugin' import {defineConfig, globalIgnores} from 'eslint/config' @@ -58,6 +59,7 @@ const config = defineConfig([ ]), js.configs.recommended, + primerConfig, ...fixupConfigRules([react.configs.flat.recommended, react.configs.flat['jsx-runtime']]), reactHooks.configs.flat['recommended-latest'], diff --git a/package-lock.json b/package-lock.json index 055b4792707..27849dc14ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6627,6 +6627,10 @@ "resolved": "packages/doc-gen", "link": true }, + "node_modules/@primer/eslint-config": { + "resolved": "packages/eslint-config", + "link": true + }, "node_modules/@primer/mcp": { "resolved": "packages/mcp", "link": true @@ -28958,6 +28962,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/eslint-config": { + "name": "@primer/eslint-config", + "version": "0.0.0", + "dependencies": { + "@typescript-eslint/utils": "^8.59.1" + }, + "devDependencies": { + "@primer/vitest-config": "^0.0.0", + "eslint": "^10.7.0", + "typescript": "^6.0.3", + "vitest": "^4.1.9" + }, + "peerDependencies": { + "eslint": "^10.7.0" + } + }, "packages/mcp": { "name": "@primer/mcp", "version": "1.0.0", diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json new file mode 100644 index 00000000000..1cdac91ed43 --- /dev/null +++ b/packages/eslint-config/package.json @@ -0,0 +1,23 @@ +{ + "name": "@primer/eslint-config", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": "./src/index.ts", + "scripts": { + "test": "vitest --run", + "type-check": "tsc --noEmit" + }, + "peerDependencies": { + "eslint": "^10.7.0" + }, + "dependencies": { + "@typescript-eslint/utils": "^8.59.1" + }, + "devDependencies": { + "@primer/vitest-config": "^0.0.0", + "eslint": "^10.7.0", + "typescript": "^6.0.3", + "vitest": "^4.1.9" + } +} diff --git a/packages/eslint-config/src/index.test.ts b/packages/eslint-config/src/index.test.ts new file mode 100644 index 00000000000..a85314c3b18 --- /dev/null +++ b/packages/eslint-config/src/index.test.ts @@ -0,0 +1,9 @@ +import {describe, expect, test} from 'vitest' +import {config, plugin} from './index.ts' + +describe('@primer/eslint-config', () => { + test('exports the prefer-merge-props rule disabled by default', () => { + expect(plugin.rules?.['prefer-merge-props']).toBeDefined() + expect(config.rules?.['primer/prefer-merge-props']).toBe('off') + }) +}) diff --git a/packages/eslint-config/src/index.ts b/packages/eslint-config/src/index.ts new file mode 100644 index 00000000000..c1d31e635e3 --- /dev/null +++ b/packages/eslint-config/src/index.ts @@ -0,0 +1,24 @@ +import type {TSESLint} from '@typescript-eslint/utils' +import {preferMergeProps} from './rules/preferMergeProps.ts' + +const plugin: TSESLint.FlatConfig.Plugin = { + meta: { + name: '@primer/eslint-config', + }, + rules: { + 'prefer-merge-props': preferMergeProps, + }, +} + +const config: TSESLint.FlatConfig.Config = { + name: '@primer/eslint-config', + plugins: { + primer: plugin, + }, + rules: { + 'primer/prefer-merge-props': 'off', + }, +} + +export {config, plugin, preferMergeProps} +export default config diff --git a/packages/eslint-config/src/rules/preferMergeProps.test.ts b/packages/eslint-config/src/rules/preferMergeProps.test.ts new file mode 100644 index 00000000000..d8cdb28987b --- /dev/null +++ b/packages/eslint-config/src/rules/preferMergeProps.test.ts @@ -0,0 +1,68 @@ +import {RuleTester} from 'eslint' +import {describe, it} from 'vitest' +import {preferMergeProps} from './preferMergeProps.ts' + +RuleTester.describe = describe +RuleTester.it = it +RuleTester.itOnly = it + +const ruleTester = new RuleTester({ + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, +}) + +ruleTester.run('prefer-merge-props', preferMergeProps as unknown as Parameters[1], { + valid: [ + { + name: 'accepts outermost props merged with mergeProps', + code: `const example =