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..b98d8ac49a0 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,23 @@ "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", + "@typescript-eslint/parser": "8.59.1", + "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..3bfbee50bb4 --- /dev/null +++ b/packages/eslint-config/package.json @@ -0,0 +1,24 @@ +{ + "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", + "@typescript-eslint/parser": "8.59.1", + "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..7fccc3aa386 --- /dev/null +++ b/packages/eslint-config/src/rules/preferMergeProps.test.ts @@ -0,0 +1,214 @@ +import {RuleTester} from 'eslint' +import tsParser from '@typescript-eslint/parser' +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', + parser: tsParser, + sourceType: 'module', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, +}) + +ruleTester.run('prefer-merge-props', preferMergeProps as unknown as Parameters[1], { + valid: [ + { + name: 'accepts component root props merged with mergeProps', + code: `function Example() { return