diff --git a/.changeset/react-compiler-linting.md b/.changeset/react-compiler-linting.md new file mode 100644 index 00000000000..9c79cf1d2b4 --- /dev/null +++ b/.changeset/react-compiler-linting.md @@ -0,0 +1,5 @@ +--- +'@primer/react': patch +--- + +chore: add eslint-plugin-react-compiler for lint-time compiler validation diff --git a/eslint.config.mjs b/eslint.config.mjs index bc5b452bd94..d4f5d7c967d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -10,6 +10,8 @@ import githubPlugin from 'eslint-plugin-github' import storybook from 'eslint-plugin-storybook' import react from 'eslint-plugin-react' import reactHooks from 'eslint-plugin-react-hooks' +import reactCompiler from 'eslint-plugin-react-compiler' +import {unsupportedPatterns as reactCompilerUnsupported} from './packages/react/script/react-compiler.mjs' import playwright from 'eslint-plugin-playwright' import prettierRecommended from 'eslint-plugin-prettier/recommended' import primerReact from 'eslint-plugin-primer-react' @@ -62,6 +64,21 @@ const config = defineConfig([ react.configs.flat['jsx-runtime'], reactHooks.configs.flat['recommended-latest'], + // React Compiler + { + plugins: {'react-compiler': reactCompiler}, + rules: { + 'react-compiler/react-compiler': 'warn', + }, + }, + // Disable react-compiler rule for files not yet migrated + { + files: reactCompilerUnsupported.map(p => `packages/react/${p}`), + rules: { + 'react-compiler/react-compiler': 'off', + }, + }, + github.browser, github.recommended, github.react, diff --git a/package-lock.json b/package-lock.json index 5c18a285e11..3300c08a934 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-primer-react": "^8.5.1", "eslint-plugin-react": "^7.35.5", + "eslint-plugin-react-compiler": "^19.1.0-rc.2", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-ssr-friendly": "1.3.0", "eslint-plugin-storybook": "^10.2.0", @@ -812,6 +813,24 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "dev": true, @@ -13816,6 +13835,37 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, + "node_modules/eslint-plugin-react-compiler": { + "version": "19.1.0-rc.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.1.0-rc.2.tgz", + "integrity": "sha512-oKalwDGcD+RX9mf3NEO4zOoUMeLvjSvcbbEOpquzmzqEEM2MQdp7/FY/Hx9NzmUwFzH1W9SKTz5fihfMldpEYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "hermes-parser": "^0.25.1", + "zod": "^3.22.4", + "zod-validation-error": "^3.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" + }, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-plugin-react-compiler/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/eslint-plugin-react-debug": { "version": "1.52.6", "dev": true, diff --git a/package.json b/package.json index 87287a04928..597862771d6 100644 --- a/package.json +++ b/package.json @@ -51,11 +51,11 @@ "@eslint/compat": "^2.0.1", "@eslint/eslintrc": "^3.3.3", "@eslint/js": "^9.39.2", + "@github-ui/storybook-addon-performance-panel": "0.1.2", "@github/axe-github": "0.7.0", "@github/markdownlint-github": "^0.8.0", "@github/mini-throttle": "2.1.1", "@github/prettier-config": "0.0.6", - "@github-ui/storybook-addon-performance-panel": "0.1.2", "@mdx-js/react": "1.6.22", "@playwright/test": "^1.56.1", "@prettier/sync": "0.5.5", @@ -77,6 +77,7 @@ "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-primer-react": "^8.5.1", "eslint-plugin-react": "^7.35.5", + "eslint-plugin-react-compiler": "^19.1.0-rc.2", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-ssr-friendly": "1.3.0", "eslint-plugin-storybook": "^10.2.0", diff --git a/packages/react/script/react-compiler.mjs b/packages/react/script/react-compiler.mjs index b365d389ed2..8de6af2b943 100644 --- a/packages/react/script/react-compiler.mjs +++ b/packages/react/script/react-compiler.mjs @@ -11,20 +11,41 @@ const files = glob .map(match => { return path.join(PACKAGE_DIR, match) }) +const unsupportedPatterns = [ + 'src/ActionList/**/*.tsx', + 'src/ActionMenu/**/*.tsx', + 'src/Autocomplete/**/*.tsx', + 'src/AvatarStack/**/*.tsx', + 'src/Banner/**/*.tsx', + 'src/Button/**/*.tsx', + 'src/Checkbox/**/*.tsx', + 'src/ConfirmationDialog/**/*.tsx', + 'src/Dialog/**/*.tsx', + 'src/Heading/**/*.tsx', + 'src/Link/**/*.tsx', + 'src/Pagehead/**/*.tsx', + 'src/PageLayout/**/*.tsx', + 'src/Pagination/**/*.tsx', + 'src/Portal/**/*.tsx', + 'src/SelectPanel/**/*.tsx', + 'src/SideNav.tsx', + 'src/UnderlineNav/**/*.tsx', + 'src/experimental/SelectPanel2/**/*.tsx', + 'src/hooks/useAnchoredPosition.ts', + 'src/hooks/useFocusTrap.ts', + 'src/hooks/useFocusZone.ts', + 'src/hooks/useMenuInitialFocus.ts', + 'src/hooks/useOnEscapePress.ts', + 'src/hooks/useResizeObserver.ts', + 'src/hooks/useSafeTimeout.ts', + 'src/hooks/useScrollFlash.ts', + 'src/internal/components/CheckboxOrRadioGroup/**/*.tsx', + 'src/internal/hooks/useMergedRefs.ts', + 'src/TooltipV2/**/*.tsx', +] + const unsupported = new Set( - [ - 'src/ActionList/**/*.tsx', - 'src/ActionMenu/**/*.tsx', - 'src/AvatarStack/**/*.tsx', - 'src/Button/**/*.tsx', - 'src/ConfirmationDialog/**/*.tsx', - 'src/Pagehead/**/*.tsx', - 'src/Pagination/**/*.tsx', - 'src/SelectPanel/**/*.tsx', - 'src/SideNav.tsx', - 'src/internal/components/CheckboxOrRadioGroup/**/*.tsx', - 'src/TooltipV2/**/*.tsx', - ].flatMap(pattern => { + unsupportedPatterns.flatMap(pattern => { if (glob.isDynamicPattern(pattern)) { const matches = glob.sync(pattern, {cwd: PACKAGE_DIR}) if (matches) { @@ -43,4 +64,4 @@ function isSupported(filepath) { const notMigrated = Array.from(unsupported) -export {files, notMigrated, isSupported} +export {files, notMigrated, isSupported, unsupportedPatterns}