From e1977f32c193cbb43ad7d02651cadfe608d2d7bf Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Mon, 4 May 2026 17:25:49 +0300 Subject: [PATCH 1/3] Eslint: create eslint rule to disencourage using deprecated JP components --- tools/eslint-excludelist.json | 267 +++++++++++++++++- tools/eslint/eslint-plugin-jetpack/index.cjs | 5 + .../__tests__/fixtures/empty-denylist.json | 4 + ...se-recommended-jetpack-components.test.cjs | 53 ++++ .../use-recommended-jetpack-components.cjs | 190 +++++++++++++ tools/eslint/jetpack-components-denylist.json | 80 ++++++ tools/eslint/jetpack-components-denylist.md | 34 +++ tools/js-tools/eslintrc/base.mjs | 50 +++- 8 files changed, 680 insertions(+), 3 deletions(-) create mode 100644 tools/eslint/eslint-plugin-jetpack/index.cjs create mode 100644 tools/eslint/eslint-plugin-jetpack/rules/__tests__/fixtures/empty-denylist.json create mode 100644 tools/eslint/eslint-plugin-jetpack/rules/__tests__/use-recommended-jetpack-components.test.cjs create mode 100644 tools/eslint/eslint-plugin-jetpack/rules/use-recommended-jetpack-components.cjs create mode 100644 tools/eslint/jetpack-components-denylist.json create mode 100644 tools/eslint/jetpack-components-denylist.md diff --git a/tools/eslint-excludelist.json b/tools/eslint-excludelist.json index fe51488c7066..b15abfd2e8e7 100644 --- a/tools/eslint-excludelist.json +++ b/tools/eslint-excludelist.json @@ -1 +1,266 @@ -[] +[ + "projects/js-packages/connection/components/connect-screen/basic/visual.tsx", + "projects/js-packages/connection/components/connect-screen/required-plan/visual.jsx", + "projects/js-packages/connection/components/disconnect-dialog/steps/step-disconnect-confirm.jsx", + "projects/js-packages/connection/components/disconnect-dialog/steps/step-thank-you.jsx", + "projects/js-packages/licensing/components/golden-token-modal/index.jsx", + "projects/js-packages/scan/src/components/threat-fixer-button/index.tsx", + "projects/js-packages/scan/src/components/threat-modal/index.tsx", + "projects/js-packages/scan/src/components/threat-modal/stories/index.stories.tsx", + "projects/js-packages/scan/src/components/threat-modal/threat-actions.tsx", + "projects/js-packages/scan/src/components/threat-modal/threat-fix-details.tsx", + "projects/js-packages/scan/src/components/threat-modal/threat-notice.tsx", + "projects/js-packages/scan/src/components/threat-modal/threat-summary.tsx", + "projects/js-packages/scan/src/components/threat-modal/threat-technical-details.tsx", + "projects/js-packages/storybook/storybook/preview.jsx", + "projects/packages/activity-log/src/js/index.js", + "projects/packages/backup/src/dashboard/components/dashboard-layout/index.tsx", + "projects/packages/backup/src/js/components/Admin/index.js", + "projects/packages/backup/src/js/components/Admin/no-backup-capabilities.js", + "projects/packages/backup/src/js/components/Backups.js", + "projects/packages/backup/src/js/components/backup-connection-screen/index.js", + "projects/packages/backup/src/js/components/backup-connection-screen/secondary-admin.js", + "projects/packages/backup/src/js/components/backup-storage-space/storage-addon-upsell-prompt/index.jsx", + "projects/packages/backup/src/js/components/backup-storage-space/storage-help-popover/index.jsx", + "projects/packages/backup/src/js/components/backup-video-section/index.jsx", + "projects/packages/backup/src/js/components/next-scheduled-backup.tsx", + "projects/packages/backup/src/js/components/review-request/index.tsx", + "projects/packages/backup/src/js/index.js", + "projects/packages/external-media/src/shared/sources/google-photos/filter-option.js", + "projects/packages/external-media/src/shared/sources/jetpack-app-media/index.js", + "projects/packages/forms/routes/responses/stage.tsx", + "projects/packages/forms/src/blocks/contact-form/components/jetpack-contact-form-skeleton-loader.js", + "projects/packages/forms/src/blocks/contact-form/edit.tsx", + "projects/packages/forms/src/dashboard/components/inbox-status-toggle/index.tsx", + "projects/packages/forms/src/dashboard/components/inspector/response-meta/index.tsx", + "projects/packages/forms/src/dashboard/components/layout/index.tsx", + "projects/packages/forms/src/dashboard/hooks/use-export-responses.ts", + "projects/packages/forms/src/dashboard/inbox/stage/index.js", + "projects/packages/forms/src/dashboard/wp-build/components/page/index.tsx", + "projects/packages/forms/src/dashboard/wp-build/hooks/use-page-header-details.tsx", + "projects/packages/my-jetpack/_inc/components/action-button/index.tsx", + "projects/packages/my-jetpack/_inc/components/action-button/secondary-button.tsx", + "projects/packages/my-jetpack/_inc/components/add-license-screen/index.jsx", + "projects/packages/my-jetpack/_inc/components/card/index.jsx", + "projects/packages/my-jetpack/_inc/components/connection-screen/index.tsx", + "projects/packages/my-jetpack/_inc/components/evaluation-recommendations/index.tsx", + "projects/packages/my-jetpack/_inc/components/golden-token/tooltip/index.tsx", + "projects/packages/my-jetpack/_inc/components/module-toggle/index.tsx", + "projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx", + "projects/packages/my-jetpack/_inc/components/my-jetpack-tab-panel/overview/content.tsx", + "projects/packages/my-jetpack/_inc/components/my-jetpack-tab-panel/products/filters.tsx", + "projects/packages/my-jetpack/_inc/components/my-jetpack-tab-panel/products/skeleton.tsx", + "projects/packages/my-jetpack/_inc/components/onboarding-screen/connection-form/connection-form.tsx", + "projects/packages/my-jetpack/_inc/components/onboarding-screen/index.tsx", + "projects/packages/my-jetpack/_inc/components/product-cards-section/boost-card/boost-speed-score.tsx", + "projects/packages/my-jetpack/_inc/components/product-cards-section/contextual-card-info.jsx", + "projects/packages/my-jetpack/_inc/components/product-cards-section/index.tsx", + "projects/packages/my-jetpack/_inc/components/product-cards-section/unowned-products-card.tsx", + "projects/packages/my-jetpack/_inc/components/product-detail-card/index.jsx", + "projects/packages/my-jetpack/_inc/components/product-detail-table/index.jsx", + "projects/packages/my-jetpack/_inc/components/product-interstitial-modal/product-interstifial-feature-list.tsx", + "projects/packages/my-jetpack/_inc/components/product-interstitial-modal/product-interstitial-modal.tsx", + "projects/packages/my-jetpack/_inc/components/product-interstitial-modal/product-interstitial-my-jetpack.tsx", + "projects/packages/my-jetpack/_inc/components/product-interstitial-modal/stories/index.stories.jsx", + "projects/packages/my-jetpack/_inc/components/product-interstitial/jetpack-ai/more-requests.jsx", + "projects/packages/my-jetpack/_inc/components/product-interstitial/jetpack-ai/product-page.jsx", + "projects/packages/my-jetpack/_inc/components/product-interstitial/pricing-interstitial.jsx", + "projects/packages/my-jetpack/_inc/components/product-interstitial/product-interstitial.jsx", + "projects/packages/my-jetpack/_inc/components/product-interstitial/protect/product-page.tsx", + "projects/packages/my-jetpack/_inc/components/testimonials/index.tsx", + "projects/packages/my-jetpack/_inc/data/products/use-activate-plugins.ts", + "projects/packages/my-jetpack/_inc/data/products/use-deactivate-plugins.ts", + "projects/packages/my-jetpack/_inc/data/products/use-install-plugins.ts", + "projects/packages/my-jetpack/_inc/hooks/use-notification-watcher/use-backup-needs-attention-notice.tsx", + "projects/packages/my-jetpack/_inc/hooks/use-notification-watcher/use-connection-errors-notice.tsx", + "projects/packages/my-jetpack/_inc/hooks/use-notification-watcher/use-deprecate-feature-notice.tsx", + "projects/packages/my-jetpack/_inc/hooks/use-notification-watcher/use-get-expiring-notice-content.tsx", + "projects/packages/my-jetpack/_inc/hooks/use-notification-watcher/use-paid-plan-needs-plugin-install-activation-notice.tsx", + "projects/packages/my-jetpack/_inc/hooks/use-notification-watcher/use-protect-threats-detected-notice.tsx", + "projects/packages/my-jetpack/_inc/hooks/use-notification-watcher/use-site-connection-notice.tsx", + "projects/packages/my-jetpack/_inc/providers.tsx", + "projects/packages/newsletter/_inc/subscribers/components/cells/subscriber-identity.tsx", + "projects/packages/newsletter/_inc/subscribers/components/detail/subscriber-detail-content.tsx", + "projects/packages/newsletter/src/settings/index.tsx", + "projects/packages/podcast/src/dashboard/locked-preview/index.tsx", + "projects/packages/podcast/src/dashboard/welcome/index.tsx", + "projects/packages/publicize/_inc/components/admin-page/connection-screen/index.js", + "projects/packages/publicize/_inc/components/admin-page/header/index.js", + "projects/packages/publicize/_inc/components/admin-page/index.tsx", + "projects/packages/publicize/_inc/components/admin-page/info-section/index.js", + "projects/packages/publicize/_inc/components/admin-page/message-template-section/index.tsx", + "projects/packages/publicize/_inc/components/admin-page/pricing-page/index.tsx", + "projects/packages/publicize/_inc/components/admin-page/toggles/social-image-generator-toggle/index.tsx", + "projects/packages/publicize/_inc/components/admin-page/toggles/social-module-toggle/index.tsx", + "projects/packages/publicize/_inc/components/admin-page/toggles/social-notes-toggle/index.tsx", + "projects/packages/publicize/_inc/components/admin-page/toggles/toggle-section.tsx", + "projects/packages/publicize/_inc/components/admin-page/toggles/utm-toggle/index.tsx", + "projects/packages/publicize/_inc/components/block-editor/post-publish-panels.tsx", + "projects/packages/publicize/_inc/components/block-editor/social-panels.tsx", + "projects/packages/publicize/_inc/components/block-editor/social-settings.tsx", + "projects/packages/publicize/_inc/components/connection-management/connection-info.tsx", + "projects/packages/publicize/_inc/components/manage-connections-modal/confirmation-form/index.tsx", + "projects/packages/publicize/_inc/components/manage-connections-modal/index.tsx", + "projects/packages/publicize/_inc/components/manual-sharing/index.tsx", + "projects/packages/publicize/_inc/components/manual-sharing/info.tsx", + "projects/packages/publicize/_inc/components/media-picker/index.tsx", + "projects/packages/publicize/_inc/components/media-section-v2/index.tsx", + "projects/packages/publicize/_inc/components/services/service-connection-info.tsx", + "projects/packages/publicize/_inc/components/services/service-item-details.tsx", + "projects/packages/publicize/_inc/components/services/service-item.tsx", + "projects/packages/publicize/_inc/components/services/use-request-access.ts", + "projects/packages/publicize/_inc/components/share-buttons/share-buttons.tsx", + "projects/packages/publicize/_inc/components/share-status/retry.tsx", + "projects/packages/publicize/_inc/components/share-status/share-status-label.tsx", + "projects/packages/publicize/_inc/components/social-image-generator/template-picker/modal/index.tsx", + "projects/packages/publicize/_inc/components/unified-modal/index.tsx", + "projects/packages/publicize/_inc/components/unified-modal/types.ts", + "projects/packages/publicize/_inc/entry-points/social-admin-page.tsx", + "projects/packages/publicize/_inc/social-store/actions/connection-data.ts", + "projects/packages/search/src/dashboard/components/donut-meter-container/index.jsx", + "projects/packages/search/src/dashboard/components/module-control/index.jsx", + "projects/packages/search/src/dashboard/components/pages/connection-page.jsx", + "projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx", + "projects/packages/search/src/dashboard/components/pages/sections/first-run-section.jsx", + "projects/packages/search/src/dashboard/components/pages/upsell-page/index.jsx", + "projects/packages/search/src/dashboard/components/price/index.jsx", + "projects/packages/search/src/dashboard/components/record-meter/index.jsx", + "projects/packages/search/src/dashboard/hooks/use-product-checkout-workflow.jsx", + "projects/packages/videopress/routes/library/stage.tsx", + "projects/packages/videopress/routes/video/stage.tsx", + "projects/packages/videopress/src/client/admin/components/admin-page/index.tsx", + "projects/packages/videopress/src/client/admin/components/admin-page/libraries.tsx", + "projects/packages/videopress/src/client/admin/components/clipboard-button-input/index.tsx", + "projects/packages/videopress/src/client/admin/components/delete-video-confirmation-modal/index.tsx", + "projects/packages/videopress/src/client/admin/components/edit-video-details/index.tsx", + "projects/packages/videopress/src/client/admin/components/input/index.tsx", + "projects/packages/videopress/src/client/admin/components/pagination/index.tsx", + "projects/packages/videopress/src/client/admin/components/pricing-section/index.tsx", + "projects/packages/videopress/src/client/admin/components/publish-first-video-popover/index.tsx", + "projects/packages/videopress/src/client/admin/components/site-settings-section/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-card/error.tsx", + "projects/packages/videopress/src/client/admin/components/video-card/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-details-actions/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-details/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-filter/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-grid/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-list/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-quick-actions/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-row/error.tsx", + "projects/packages/videopress/src/client/admin/components/video-row/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-row/stats.tsx", + "projects/packages/videopress/src/client/admin/components/video-stats-group/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-storage-meter/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-thumbnail-selector-modal/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-thumbnail/index.tsx", + "projects/packages/videopress/src/client/admin/components/video-upload-area/index.tsx", + "projects/packages/videopress/src/client/admin/index.js", + "projects/packages/videopress/src/client/components/chapters-learn-more-helper/index.tsx", + "projects/packages/videopress/src/client/components/incomplete-chapters-notice/index.tsx", + "projects/packages/videopress/src/dashboard/components/connection-gate/pricing-upsell.tsx", + "projects/packages/videopress/src/dashboard/components/video-details/thumbnail-card.tsx", + "projects/plugins/automattic-for-agencies-client/src/js/components/admin-page/index.jsx", + "projects/plugins/automattic-for-agencies-client/src/js/components/branded-card/index.jsx", + "projects/plugins/automattic-for-agencies-client/src/js/components/connected-card/index.jsx", + "projects/plugins/automattic-for-agencies-client/src/js/components/disconnect-site-link/index.jsx", + "projects/plugins/automattic-for-agencies-client/src/js/index.js", + "projects/plugins/boost/app/assets/src/js/features/boost-pricing-table/boost-pricing-table.tsx", + "projects/plugins/boost/app/assets/src/js/features/cornerstone-pages/meta/meta.tsx", + "projects/plugins/boost/app/assets/src/js/features/cornerstone-pages/prerender/prerender.tsx", + "projects/plugins/boost/app/assets/src/js/features/critical-css/folding-element/folding-element.tsx", + "projects/plugins/boost/app/assets/src/js/features/critical-css/status/status.tsx", + "projects/plugins/boost/app/assets/src/js/features/image-cdn/quality-control/quality-control.tsx", + "projects/plugins/boost/app/assets/src/js/features/image-cdn/quality-settings/quality-settings.tsx", + "projects/plugins/boost/app/assets/src/js/features/lcp/lcp.tsx", + "projects/plugins/boost/app/assets/src/js/features/minify-meta/minify-meta.tsx", + "projects/plugins/boost/app/assets/src/js/features/module/module.tsx", + "projects/plugins/boost/app/assets/src/js/features/page-cache/meta/meta.tsx", + "projects/plugins/boost/app/assets/src/js/features/page-cache/switch-to-boost/switch-to-boost.tsx", + "projects/plugins/boost/app/assets/src/js/features/performance-history/graph-component/graph-component.tsx", + "projects/plugins/boost/app/assets/src/js/features/performance-history/performance-history.tsx", + "projects/plugins/boost/app/assets/src/js/features/premium-tooltip/premium-tooltip.tsx", + "projects/plugins/boost/app/assets/src/js/features/speed-score/context-tooltip/context-tooltip.tsx", + "projects/plugins/boost/app/assets/src/js/features/speed-score/pop-out/pop-out.tsx", + "projects/plugins/boost/app/assets/src/js/features/speed-score/speed-score.tsx", + "projects/plugins/boost/app/assets/src/js/features/ui/back-button/back-button.tsx", + "projects/plugins/boost/app/assets/src/js/features/ui/collapsible-meta/collapsible-meta.tsx", + "projects/plugins/boost/app/assets/src/js/layout/settings-page/support/support.tsx", + "projects/plugins/boost/app/assets/src/js/pages/cache-debug-log/cache-debug-log.tsx", + "projects/plugins/boost/app/assets/src/js/pages/critical-css-advanced/critical-css-advanced.tsx", + "projects/plugins/boost/app/assets/src/js/pages/purchase-success/purchase-success.tsx", + "projects/plugins/classic-theme-helper-plugin/src/js/components/admin-page/index.jsx", + "projects/plugins/classic-theme-helper-plugin/src/js/index.js", + "projects/plugins/jetpack/_inc/client/at-a-glance/backup-upgrade/bar-chart.tsx", + "projects/plugins/jetpack/_inc/client/at-a-glance/boost/index.jsx", + "projects/plugins/jetpack/_inc/client/at-a-glance/index.jsx", + "projects/plugins/jetpack/_inc/client/components/contextualized-connection/index.jsx", + "projects/plugins/jetpack/_inc/client/components/footer/index.jsx", + "projects/plugins/jetpack/_inc/client/components/jetpack-product-card/index.jsx", + "projects/plugins/jetpack/_inc/client/network-admin.tsx", + "projects/plugins/jetpack/_inc/client/recommendations/product-card-upsell/index.jsx", + "projects/plugins/jetpack/_inc/client/recommendations/product-purchased/index.jsx", + "projects/plugins/jetpack/_inc/client/recommendations/prompts/feature-prompt/index.jsx", + "projects/plugins/jetpack/_inc/client/recommendations/prompts/resource-prompt/index.jsx", + "projects/plugins/jetpack/_inc/client/recommendations/prompts/site-type/index.jsx", + "projects/plugins/jetpack/_inc/client/recommendations/step-progress-bar/index.tsx", + "projects/plugins/jetpack/_inc/client/settings/index.jsx", + "projects/plugins/jetpack/extensions/blocks/subscriptions/edit.js", + "projects/plugins/jetpack/extensions/blocks/subscriptions/email-preview.js", + "projects/plugins/jetpack/extensions/blocks/voice-to-content/edit.tsx", + "projects/plugins/jetpack/extensions/blocks/wordads/components/jetpack-wordads-skeleton-loader.js", + "projects/plugins/jetpack/extensions/blocks/wordads/edit.js", + "projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/usage-bar/index.tsx", + "projects/plugins/jetpack/extensions/plugins/likes/components/skeleton-loader.js", + "projects/plugins/jetpack/extensions/plugins/post-publish-qr-post-panel/components/qr-post.js", + "projects/plugins/jetpack/extensions/plugins/seo/components/skeleton-loader.js", + "projects/plugins/jetpack/extensions/plugins/sharing/components/skeleton-loader.js", + "projects/plugins/jetpack/extensions/shared/clipboard-input.js", + "projects/plugins/jetpack/extensions/shared/components/loading-posts-grid/index.js", + "projects/plugins/protect/src/js/components/admin-section-hero/admin-section-hero-notices.tsx", + "projects/plugins/protect/src/js/components/admin-section-hero/index.tsx", + "projects/plugins/protect/src/js/components/admin-section-hero/stories/index.stories.jsx", + "projects/plugins/protect/src/js/components/button-group/index.jsx", + "projects/plugins/protect/src/js/components/button-group/stories/index.stories.jsx", + "projects/plugins/protect/src/js/components/error-admin-section-hero/index.tsx", + "projects/plugins/protect/src/js/components/fix-all-threats-modal/index.jsx", + "projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx", + "projects/plugins/protect/src/js/components/free-accordion/index.jsx", + "projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx", + "projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx", + "projects/plugins/protect/src/js/components/navigation/badge.jsx", + "projects/plugins/protect/src/js/components/navigation/group.jsx", + "projects/plugins/protect/src/js/components/navigation/index.jsx", + "projects/plugins/protect/src/js/components/navigation/label.jsx", + "projects/plugins/protect/src/js/components/onboarding-popover/index.jsx", + "projects/plugins/protect/src/js/components/paid-accordion/index.jsx", + "projects/plugins/protect/src/js/components/pricing-table/index.jsx", + "projects/plugins/protect/src/js/components/scan-button/index.jsx", + "projects/plugins/protect/src/js/components/seventy-five-layout/index.tsx", + "projects/plugins/protect/src/js/components/standalone-mode-modal/index.jsx", + "projects/plugins/protect/src/js/components/threat-fix-header/index.jsx", + "projects/plugins/protect/src/js/components/threats-list/empty.jsx", + "projects/plugins/protect/src/js/components/threats-list/free-list.jsx", + "projects/plugins/protect/src/js/components/threats-list/index.jsx", + "projects/plugins/protect/src/js/components/threats-list/navigation.jsx", + "projects/plugins/protect/src/js/components/threats-list/pagination.jsx", + "projects/plugins/protect/src/js/components/threats-list/paid-list.jsx", + "projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx", + "projects/plugins/protect/src/js/components/user-connection-needed-modal/index.jsx", + "projects/plugins/protect/src/js/index.tsx", + "projects/plugins/protect/src/js/routes/firewall/firewall-admin-section-hero.tsx", + "projects/plugins/protect/src/js/routes/firewall/firewall-footer.jsx", + "projects/plugins/protect/src/js/routes/firewall/firewall-statcards.jsx", + "projects/plugins/protect/src/js/routes/firewall/firewall-subheading.jsx", + "projects/plugins/protect/src/js/routes/firewall/index.jsx", + "projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx", + "projects/plugins/protect/src/js/routes/scan/history/index.jsx", + "projects/plugins/protect/src/js/routes/scan/index.jsx", + "projects/plugins/protect/src/js/routes/scan/onboarding-steps.jsx", + "projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx", + "projects/plugins/protect/src/js/routes/scan/scan-footer.jsx", + "projects/plugins/protect/src/js/routes/scan/scanning-admin-section-hero.tsx", + "projects/plugins/protect/src/js/routes/settings/index.jsx", + "projects/plugins/protect/src/js/routes/setup/index.jsx", + "projects/plugins/starter-plugin/src/js/components/admin-page/index.jsx", + "projects/plugins/starter-plugin/src/js/index.js" +] diff --git a/tools/eslint/eslint-plugin-jetpack/index.cjs b/tools/eslint/eslint-plugin-jetpack/index.cjs new file mode 100644 index 000000000000..649236799f88 --- /dev/null +++ b/tools/eslint/eslint-plugin-jetpack/index.cjs @@ -0,0 +1,5 @@ +module.exports = { + rules: { + 'use-recommended-jetpack-components': require( './rules/use-recommended-jetpack-components.cjs' ), + }, +}; diff --git a/tools/eslint/eslint-plugin-jetpack/rules/__tests__/fixtures/empty-denylist.json b/tools/eslint/eslint-plugin-jetpack/rules/__tests__/fixtures/empty-denylist.json new file mode 100644 index 000000000000..f731897bb8e2 --- /dev/null +++ b/tools/eslint/eslint-plugin-jetpack/rules/__tests__/fixtures/empty-denylist.json @@ -0,0 +1,4 @@ +{ + "components": {}, + "subpaths": {} +} diff --git a/tools/eslint/eslint-plugin-jetpack/rules/__tests__/use-recommended-jetpack-components.test.cjs b/tools/eslint/eslint-plugin-jetpack/rules/__tests__/use-recommended-jetpack-components.test.cjs new file mode 100644 index 000000000000..32f813391a89 --- /dev/null +++ b/tools/eslint/eslint-plugin-jetpack/rules/__tests__/use-recommended-jetpack-components.test.cjs @@ -0,0 +1,53 @@ +const { createRequire } = require( 'node:module' ); +const path = require( 'node:path' ); +const requireFromJsTools = createRequire( + path.join( __dirname, '..', '..', '..', '..', 'js-tools', 'package.json' ) +); +const { RuleTester } = requireFromJsTools( 'eslint' ); +const rule = require( '../use-recommended-jetpack-components.cjs' ); + +const denylistPath = path.resolve( + __dirname, + '..', + '..', + '..', + 'jetpack-components-denylist.json' +); + +const ruleTester = new RuleTester( { + languageOptions: { + sourceType: 'module', + ecmaVersion: 'latest', + }, +} ); + +ruleTester.run( 'use-recommended-jetpack-components', rule, { + valid: [ + "import { getRedirectUrl } from '@automattic/jetpack-api';", + { + code: "import { JetpackLogo } from '@automattic/jetpack-components';", + options: [ { denylistPath: path.join( __dirname, 'fixtures', 'empty-denylist.json' ) } ], + }, + ], + + invalid: [ + { + code: "import { Button } from '@automattic/jetpack-components';", + options: [ { denylistPath } ], + errors: [ + { + message: 'Use `Button` from `@wordpress/ui` instead.', + }, + ], + }, + { + code: "import Button from '@automattic/jetpack-components/button';", + options: [ { denylistPath } ], + errors: [ + { + message: 'Use `Button` from `@wordpress/ui` instead.', + }, + ], + }, + ], +} ); diff --git a/tools/eslint/eslint-plugin-jetpack/rules/use-recommended-jetpack-components.cjs b/tools/eslint/eslint-plugin-jetpack/rules/use-recommended-jetpack-components.cjs new file mode 100644 index 000000000000..14b6003611bd --- /dev/null +++ b/tools/eslint/eslint-plugin-jetpack/rules/use-recommended-jetpack-components.cjs @@ -0,0 +1,190 @@ +const fs = require( 'node:fs' ); +const path = require( 'node:path' ); + +const PACKAGE_NAME = '@automattic/jetpack-components'; +const DEFAULT_DENYLIST_PATH = path.resolve( + __dirname, + '..', + '..', + 'jetpack-components-denylist.json' +); + +/** + * Interpolate `{{ name }}` and `{{ source }}` placeholders in a lint message. + * + * @param {string|undefined} template - Message template. + * @param {string} name - Component name. + * @param {string} source - Import source. + * @return {string} Resolved message string. + */ +function resolveMessage( template, name, source ) { + if ( ! template ) { + return `\`${ name }\` from \`${ source }\` is not recommended. Prefer \`@wordpress/components\` or \`@wordpress/ui\` when an equivalent exists.`; + } + + return template.replace( /\{\{\s*name\s*\}\}/g, name ).replace( /\{\{\s*source\s*\}\}/g, source ); +} + +/** + * Load the Jetpack components denylist from disk. + * + * @param {string} denylistPath - Path to the denylist JSON file. + * @return {{ components: Record, subpaths: Record }} Denylist data. + */ +function loadDenylist( denylistPath ) { + const raw = JSON.parse( fs.readFileSync( denylistPath, 'utf8' ) ); + + return { + components: normalizeEntries( raw.components ), + subpaths: normalizeEntries( raw.subpaths ), + }; +} + +/** + * Normalize denylist entries to a flat component-name → message map. + * + * @param {Record|undefined} entries - Denylist entries. + * @return {Record} Normalized message map. + */ +function normalizeEntries( entries ) { + if ( ! entries ) { + return {}; + } + + return Object.fromEntries( + Object.entries( entries ).map( ( [ key, value ] ) => { + if ( typeof value === 'string' ) { + return [ key, value ]; + } + + return [ key, value.message ]; + } ) + ); +} + +/** @type {import('eslint').Rule.RuleModule} */ +const rule = { + meta: { + type: 'suggestion', + docs: { + description: + 'Encourage the use of WordPress components instead of discouraged Jetpack components.', + }, + schema: [ + { + type: 'object', + properties: { + denylistPath: { + type: 'string', + }, + }, + additionalProperties: false, + }, + ], + }, + create( context ) { + const options = context.options[ 0 ] ?? {}; + const configuredPath = options.denylistPath ?? DEFAULT_DENYLIST_PATH; + const denylistPath = path.isAbsolute( configuredPath ) + ? configuredPath + : path.resolve( context.cwd ?? process.cwd(), configuredPath ); + const { components, subpaths } = loadDenylist( denylistPath ); + + return { + /** + * Lint import declarations from Jetpack components. + * + * @param {import('estree').ImportDeclaration} node - Import declaration AST node. + */ + ImportDeclaration( node ) { + if ( typeof node.source.value !== 'string' ) { + return; + } + + const source = node.source.value; + + if ( source === PACKAGE_NAME ) { + reportMainPackageImports( context, node, source, components ); + return; + } + + if ( source.startsWith( `${ PACKAGE_NAME }/` ) ) { + reportSubpathImports( + context, + node, + source, + source.slice( PACKAGE_NAME.length + 1 ), + components, + subpaths + ); + } + }, + }; + }, +}; + +/** + * Report discouraged imports from `@automattic/jetpack-components`. + * + * @param {import('eslint').Rule.RuleContext} context - ESLint context. + * @param {import('estree').ImportDeclaration} node - Import declaration node. + * @param {string} source - Import source. + * @param {Record} components - Component denylist. + */ +function reportMainPackageImports( context, node, source, components ) { + node.specifiers.forEach( specifier => { + if ( specifier.type !== 'ImportSpecifier' ) { + return; + } + + const name = specifier.imported.name; + const message = components[ name ]; + + if ( message ) { + context.report( { + node: specifier, + message: resolveMessage( message, name, source ), + } ); + } + } ); +} + +/** + * Report discouraged imports from `@automattic/jetpack-components/*` subpaths. + * + * @param {import('eslint').Rule.RuleContext} context - ESLint context. + * @param {import('estree').ImportDeclaration} node - Import declaration node. + * @param {string} source - Import source. + * @param {string} subpath - Subpath without leading slash. + * @param {Record} components - Component denylist. + * @param {Record} subpaths - Subpath denylist. + */ +function reportSubpathImports( context, node, source, subpath, components, subpaths ) { + const subpathMessage = subpaths[ subpath ]; + + node.specifiers.forEach( specifier => { + if ( specifier.type === 'ImportDefaultSpecifier' && subpathMessage ) { + context.report( { + node: specifier, + message: resolveMessage( subpathMessage, subpath, source ), + } ); + return; + } + + if ( specifier.type !== 'ImportSpecifier' ) { + return; + } + + const name = specifier.imported.name; + const message = components[ name ] ?? subpathMessage; + + if ( message ) { + context.report( { + node: specifier, + message: resolveMessage( message, name, source ), + } ); + } + } ); +} + +module.exports = rule; diff --git a/tools/eslint/jetpack-components-denylist.json b/tools/eslint/jetpack-components-denylist.json new file mode 100644 index 000000000000..de497b5f5f5a --- /dev/null +++ b/tools/eslint/jetpack-components-denylist.json @@ -0,0 +1,80 @@ +{ + "components": { + "ActionButton": "Use `Button` from `@wordpress/ui` instead.", + "ActionPopover": "Use `Popover` from `@wordpress/ui` instead.", + "AdminSection": "Jetpack-specific layout component — remove from denylist to allow.", + "AdminSectionHero": "Jetpack-specific layout component — remove from denylist to allow.", + "Alert": "Use `Notice` from `@wordpress/ui` instead.", + "AutomatticBylineLogo": "Jetpack branding component — remove from denylist to allow.", + "AutomatticForAgenciesLogo": "Jetpack branding component — remove from denylist to allow.", + "AutomatticIconLogo": "Jetpack branding component — remove from denylist to allow.", + "BoostScoreBar": "Jetpack-specific visualization — remove from denylist to allow.", + "BoostScoreGraph": "Jetpack-specific visualization — remove from denylist to allow.", + "Button": "Use `Button` from `@wordpress/ui` instead.", + "Chip": "Use `Badge` from `@wordpress/ui` instead.", + "cleanLocale": "Jetpack utility — remove from denylist to allow.", + "Col": "Jetpack layout component — remove from denylist to allow or use `Stack` from `@wordpress/ui`.", + "Container": "Jetpack layout component — remove from denylist to allow or use `Stack` from `@wordpress/ui`.", + "ContextualUpgradeTrigger": "Jetpack-specific upsell component — remove from denylist to allow.", + "CopyToClipboard": "Use `ClipboardButton` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", + "DecorativeCard": "Use `Card` from `@wordpress/ui` instead.", + "DetailsViewer": "Jetpack-specific component — remove from denylist to allow.", + "Dialog": "Use `Dialog` from `@wordpress/ui` instead.", + "DiffViewer": "Jetpack-specific component — remove from denylist to allow.", + "DonutMeter": "Jetpack-specific visualization — remove from denylist to allow.", + "DotPager": "Jetpack-specific component — remove from denylist to allow.", + "getProductCheckoutUrl": "Jetpack utility — remove from denylist to allow.", + "GlobalNotices": "Use `@wordpress/notices` instead.", + "globalNoticesStore": "Use `@wordpress/notices` instead.", + "Gravatar": "Use `@gravatar-com/hovercards` or `@wordpress/components` when appropriate.", + "H2": "Use `Text` with `variant=\"heading-*\"` from `@wordpress/ui` instead.", + "H3": "Use `Text` with `variant=\"heading-*\"` from `@wordpress/ui` instead.", + "IconTooltip": "Use `Tooltip` from `@wordpress/ui` instead.", + "IconsCard": "Jetpack-specific component — remove from denylist to allow.", + "IndeterminateProgressBar": "Use `Spinner` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", + "isFirstMonthTrial": "Jetpack utility — remove from denylist to allow.", + "JetpackFooter": "Jetpack branding component — remove from denylist to allow.", + "JetpackProtectLogo": "Jetpack branding component — remove from denylist to allow.", + "JetpackSearchLogo": "Jetpack branding component — remove from denylist to allow.", + "JetpackVaultPressBackupLogo": "Jetpack branding component — remove from denylist to allow.", + "JetpackVideoPressLogo": "Jetpack branding component — remove from denylist to allow.", + "LoadingPlaceholder": "Use `Placeholder` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", + "MarkedLines": "Jetpack-specific component — remove from denylist to allow.", + "NavigatorModal": "Use `Dialog` from `@wordpress/ui` and `Navigator` from `@wordpress/components` instead.", + "Notice": "Use `Notice` from `@wordpress/ui` instead.", + "NumberControl": "Use `__experimentalNumberControl` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", + "NumberSlider": "Use `RangeControl` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", + "Popover": "Use `Popover` from `@wordpress/ui` instead.", + "PricingTable": "Jetpack-specific pricing component — remove from denylist to allow.", + "PricingTableColumn": "Jetpack-specific pricing component — remove from denylist to allow.", + "PricingTableHeader": "Jetpack-specific pricing component — remove from denylist to allow.", + "PricingTableItem": "Jetpack-specific pricing component — remove from denylist to allow.", + "ProductOffer": "Jetpack-specific pricing component — remove from denylist to allow.", + "ProductPrice": "Jetpack-specific pricing component — remove from denylist to allow.", + "ProgressBar": "Use `ProgressBar` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", + "QRCode": "Jetpack-specific component — remove from denylist to allow.", + "RadioControl": "Use `RadioControl` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", + "RecordMeterBar": "Jetpack-specific visualization — remove from denylist to allow.", + "Spinner": "Use `Spinner` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", + "SplitButton": "Use `Button` from `@wordpress/ui` (and `Dropdown` from `@wordpress/components` if needed) instead.", + "StatCard": "Jetpack-specific component — remove from denylist to allow.", + "Status": "Use `Badge` from `@wordpress/ui` instead.", + "TermsOfService": "Jetpack-specific component — remove from denylist to allow.", + "Testimonials": "Jetpack-specific component — remove from denylist to allow.", + "Text": "Use `Text` from `@wordpress/ui` instead.", + "ThemeProvider": "Use `@wordpress/theme` instead.", + "Title": "Use `Text` from `@wordpress/ui` instead.", + "ToggleControl": "Use `ToggleControl` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", + "useBreakpointMatch": "Jetpack layout hook — remove from denylist to allow.", + "useGlobalNotices": "Use `@wordpress/notices` instead.", + "ZendeskChat": "Jetpack-specific integration — remove from denylist to allow." + }, + "subpaths": { + "button": "Use `Button` from `@wordpress/ui` instead.", + "global-notices": "Use `@wordpress/notices` instead.", + "gravatar": "Use `@gravatar-com/hovercards` or `@wordpress/components` when appropriate.", + "jetpack-footer": "Jetpack branding component — remove from denylist to allow.", + "pricing-table": "Jetpack-specific pricing component — remove from denylist to allow.", + "product-price": "Jetpack-specific pricing component — remove from denylist to allow." + } +} diff --git a/tools/eslint/jetpack-components-denylist.md b/tools/eslint/jetpack-components-denylist.md new file mode 100644 index 000000000000..3331c6cc8444 --- /dev/null +++ b/tools/eslint/jetpack-components-denylist.md @@ -0,0 +1,34 @@ +# Jetpack components denylist + +Curate discouraged `@automattic/jetpack-components` imports in +[`jetpack-components-denylist.json`](./jetpack-components-denylist.json). + +The ESLint rule `@automattic/jetpack/use-recommended-jetpack-components` reads this +file and reports imports that match entries in `components` or `subpaths`. + +## How to curate + +1. Open `jetpack-components-denylist.json`. +2. **Allow a component:** delete its key from `components` (or `subpaths` for subpath imports). +3. **Change the lint message:** edit the string value or `{ "message": "..." }` object. +4. **Add a component:** add a key under `components` for named imports from the main + package, or under `subpaths` for `@automattic/jetpack-components/` imports. + +When writing messages, prefer **`@wordpress/ui`** over **`@wordpress/components`** +when both packages ship the same primitive (e.g. `Button`, `Text`, `Popover`, +`Notice`, `Dialog`, `Badge`, `Card`). Use `@wordpress/components` only when there +is no `@wordpress/ui` equivalent yet (e.g. `Spinner`, `ToggleControl`). + +Message placeholders: + +- `{{ name }}` — imported component or subpath name +- `{{ source }}` — full import source (e.g. `@automattic/jetpack-components`) + +## Suppressions + +Existing violations are tracked in [`eslint-excludelist.json`](../eslint-excludelist.json). + +## Scope + +The rule is disabled inside `projects/js-packages/components/` so the package can +import its own modules. diff --git a/tools/js-tools/eslintrc/base.mjs b/tools/js-tools/eslintrc/base.mjs index 59e0801ecb8f..81c38f3af60a 100644 --- a/tools/js-tools/eslintrc/base.mjs +++ b/tools/js-tools/eslintrc/base.mjs @@ -11,6 +11,7 @@ // ``` import fs from 'node:fs'; +import { createRequire } from 'node:module'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { fixupPluginRules } from '@eslint/compat'; @@ -48,6 +49,7 @@ export * from './files.mjs'; export { defineConfig, globalIgnores } from 'eslint/config'; const debug = makeDebug( 'eslintrc/base' ); +const require = createRequire( import.meta.url ); const rootdir = fileURLToPath( new URL( '../../..', import.meta.url ) ); @@ -146,6 +148,14 @@ export function makeBaseConfig( configurl, opts = {} ) { path.join( rootdir, 'projects/js-packages/storybook/storybook/main.js' ) ); + const jetpackComponentsDenylistPath = path.join( + rootdir, + 'tools/eslint/jetpack-components-denylist.json' + ); + const jetpackEslintPlugin = fixupPluginRules( + require( path.join( rootdir, 'tools/eslint/eslint-plugin-jetpack/index.cjs' ) ) + ); + return defineConfig( globalIgnores( loadIgnorePatterns( basedir ) ), @@ -195,6 +205,11 @@ export function makeBaseConfig( configurl, opts = {} ) { wordpressEslintPlugin.configs.custom, wordpressEslintPlugin.configs.esnext, wordpressEslintPlugin.configs.i18n, + { + rules: { + '@wordpress/use-recommended-components': 'error', + }, + }, { plugins: { @@ -222,6 +237,22 @@ export function makeBaseConfig( configurl, opts = {} ) { extends: [ eslintPluginPrettierRecommended ], }, + { + name: 'Jetpack recommended components', + files: javascriptFiles, + plugins: { + '@automattic/jetpack': jetpackEslintPlugin, + }, + rules: { + '@automattic/jetpack/use-recommended-jetpack-components': [ + 'error', + { + denylistPath: jetpackComponentsDenylistPath, + }, + ], + }, + }, + // Base config. { name: 'Monorepo base config', @@ -437,9 +468,24 @@ export function makeBaseConfig( configurl, opts = {} ) { }, }, - // Various config files should allow 'node' globals. + // The components package implements Jetpack UI primitives. { - files: [ '**/*.config.?([cm])js', '**/webpack.config.*.?([cm])js' ], + files: [ 'projects/js-packages/components/**/*.{js,jsx,ts,tsx,mjs,cjs,svelte}' ], + rules: { + '@automattic/jetpack/use-recommended-jetpack-components': 'off', + }, + }, + + // Various Node-oriented files should allow 'node' globals. + { + files: [ + '.pnpmfile.cjs', + 'tools/js-tools/eslintrc/get-ts-parser.cjs', + 'tools/eslint/eslint-plugin-jetpack/**/*.cjs', + '**/tests/e2e/config/default.cjs', + '**/*.config.?([cm])js', + '**/webpack.config.*.?([cm])js', + ], languageOptions: { globals: globals.node, }, From 00583fd4c834a7dce04aecb21d5445d83057efcc Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Fri, 5 Jun 2026 15:06:34 +0300 Subject: [PATCH 2/3] Cleaned up component list --- tools/eslint/jetpack-components-denylist.json | 71 ++++--------------- 1 file changed, 14 insertions(+), 57 deletions(-) diff --git a/tools/eslint/jetpack-components-denylist.json b/tools/eslint/jetpack-components-denylist.json index de497b5f5f5a..16bd207bb630 100644 --- a/tools/eslint/jetpack-components-denylist.json +++ b/tools/eslint/jetpack-components-denylist.json @@ -1,80 +1,37 @@ { "components": { - "ActionButton": "Use `Button` from `@wordpress/ui` instead.", - "ActionPopover": "Use `Popover` from `@wordpress/ui` instead.", - "AdminSection": "Jetpack-specific layout component — remove from denylist to allow.", - "AdminSectionHero": "Jetpack-specific layout component — remove from denylist to allow.", "Alert": "Use `Notice` from `@wordpress/ui` instead.", - "AutomatticBylineLogo": "Jetpack branding component — remove from denylist to allow.", - "AutomatticForAgenciesLogo": "Jetpack branding component — remove from denylist to allow.", - "AutomatticIconLogo": "Jetpack branding component — remove from denylist to allow.", - "BoostScoreBar": "Jetpack-specific visualization — remove from denylist to allow.", - "BoostScoreGraph": "Jetpack-specific visualization — remove from denylist to allow.", "Button": "Use `Button` from `@wordpress/ui` instead.", "Chip": "Use `Badge` from `@wordpress/ui` instead.", - "cleanLocale": "Jetpack utility — remove from denylist to allow.", - "Col": "Jetpack layout component — remove from denylist to allow or use `Stack` from `@wordpress/ui`.", - "Container": "Jetpack layout component — remove from denylist to allow or use `Stack` from `@wordpress/ui`.", - "ContextualUpgradeTrigger": "Jetpack-specific upsell component — remove from denylist to allow.", - "CopyToClipboard": "Use `ClipboardButton` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", + "Col": "Use `Stack` from `@wordpress/ui` instead.", + "Container": "Use `Stack` from `@wordpress/ui` instead.", "DecorativeCard": "Use `Card` from `@wordpress/ui` instead.", - "DetailsViewer": "Jetpack-specific component — remove from denylist to allow.", "Dialog": "Use `Dialog` from `@wordpress/ui` instead.", - "DiffViewer": "Jetpack-specific component — remove from denylist to allow.", - "DonutMeter": "Jetpack-specific visualization — remove from denylist to allow.", - "DotPager": "Jetpack-specific component — remove from denylist to allow.", - "getProductCheckoutUrl": "Jetpack utility — remove from denylist to allow.", + "DonutMeter": "Use `PieChart` from `@automattic/charts` instead.", "GlobalNotices": "Use `@wordpress/notices` instead.", "globalNoticesStore": "Use `@wordpress/notices` instead.", - "Gravatar": "Use `@gravatar-com/hovercards` or `@wordpress/components` when appropriate.", "H2": "Use `Text` with `variant=\"heading-*\"` from `@wordpress/ui` instead.", "H3": "Use `Text` with `variant=\"heading-*\"` from `@wordpress/ui` instead.", - "IconTooltip": "Use `Tooltip` from `@wordpress/ui` instead.", - "IconsCard": "Jetpack-specific component — remove from denylist to allow.", - "IndeterminateProgressBar": "Use `Spinner` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", - "isFirstMonthTrial": "Jetpack utility — remove from denylist to allow.", - "JetpackFooter": "Jetpack branding component — remove from denylist to allow.", - "JetpackProtectLogo": "Jetpack branding component — remove from denylist to allow.", - "JetpackSearchLogo": "Jetpack branding component — remove from denylist to allow.", - "JetpackVaultPressBackupLogo": "Jetpack branding component — remove from denylist to allow.", - "JetpackVideoPressLogo": "Jetpack branding component — remove from denylist to allow.", - "LoadingPlaceholder": "Use `Placeholder` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", - "MarkedLines": "Jetpack-specific component — remove from denylist to allow.", + "IconTooltip": "Use `Tooltip` in combination with `Icon` from `@wordpress/ui` instead.", + "IndeterminateProgressBar": "Use `Spinner` or `ProgressBar` from `@wordpress/components` instead.", "NavigatorModal": "Use `Dialog` from `@wordpress/ui` and `Navigator` from `@wordpress/components` instead.", "Notice": "Use `Notice` from `@wordpress/ui` instead.", - "NumberControl": "Use `__experimentalNumberControl` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", - "NumberSlider": "Use `RangeControl` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", + "NumberControl": "Use `__experimentalNumberControl` from `@wordpress/components` instead.", + "NumberSlider": "Use `RangeControl` from `@wordpress/components` instead.", "Popover": "Use `Popover` from `@wordpress/ui` instead.", - "PricingTable": "Jetpack-specific pricing component — remove from denylist to allow.", - "PricingTableColumn": "Jetpack-specific pricing component — remove from denylist to allow.", - "PricingTableHeader": "Jetpack-specific pricing component — remove from denylist to allow.", - "PricingTableItem": "Jetpack-specific pricing component — remove from denylist to allow.", - "ProductOffer": "Jetpack-specific pricing component — remove from denylist to allow.", - "ProductPrice": "Jetpack-specific pricing component — remove from denylist to allow.", - "ProgressBar": "Use `ProgressBar` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", - "QRCode": "Jetpack-specific component — remove from denylist to allow.", - "RadioControl": "Use `RadioControl` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", - "RecordMeterBar": "Jetpack-specific visualization — remove from denylist to allow.", - "Spinner": "Use `Spinner` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", + "ProgressBar": "Use `ProgressBar` or `Spinner` from `@wordpress/components` instead.", + "RadioControl": "Use `RadioControl` from `@wordpress/components` instead.", + "Spinner": "Use `Spinner` from `@wordpress/components` instead.", "SplitButton": "Use `Button` from `@wordpress/ui` (and `Dropdown` from `@wordpress/components` if needed) instead.", - "StatCard": "Jetpack-specific component — remove from denylist to allow.", "Status": "Use `Badge` from `@wordpress/ui` instead.", - "TermsOfService": "Jetpack-specific component — remove from denylist to allow.", - "Testimonials": "Jetpack-specific component — remove from denylist to allow.", "Text": "Use `Text` from `@wordpress/ui` instead.", - "ThemeProvider": "Use `@wordpress/theme` instead.", "Title": "Use `Text` from `@wordpress/ui` instead.", - "ToggleControl": "Use `ToggleControl` from `@wordpress/components` (no `@wordpress/ui` equivalent yet).", - "useBreakpointMatch": "Jetpack layout hook — remove from denylist to allow.", - "useGlobalNotices": "Use `@wordpress/notices` instead.", - "ZendeskChat": "Jetpack-specific integration — remove from denylist to allow." + "ToggleControl": "Use `ToggleControl` from `@wordpress/components` instead.", + "useBreakpointMatch": "Use `@wordpress/viewport` instead.", + "useGlobalNotices": "Use `@wordpress/notices` instead." }, "subpaths": { "button": "Use `Button` from `@wordpress/ui` instead.", - "global-notices": "Use `@wordpress/notices` instead.", - "gravatar": "Use `@gravatar-com/hovercards` or `@wordpress/components` when appropriate.", - "jetpack-footer": "Jetpack branding component — remove from denylist to allow.", - "pricing-table": "Jetpack-specific pricing component — remove from denylist to allow.", - "product-price": "Jetpack-specific pricing component — remove from denylist to allow." + "global-notices": "Use `@wordpress/notices` instead." } } From 4e565894fca6f89d47eea95f164f2382d4196102 Mon Sep 17 00:00:00 2001 From: Mikael Korpela Date: Fri, 5 Jun 2026 15:12:02 +0300 Subject: [PATCH 3/3] Cleanup base.mjs --- tools/js-tools/eslintrc/base.mjs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tools/js-tools/eslintrc/base.mjs b/tools/js-tools/eslintrc/base.mjs index 81c38f3af60a..6467d1b3d8f2 100644 --- a/tools/js-tools/eslintrc/base.mjs +++ b/tools/js-tools/eslintrc/base.mjs @@ -205,12 +205,6 @@ export function makeBaseConfig( configurl, opts = {} ) { wordpressEslintPlugin.configs.custom, wordpressEslintPlugin.configs.esnext, wordpressEslintPlugin.configs.i18n, - { - rules: { - '@wordpress/use-recommended-components': 'error', - }, - }, - { plugins: { 'you-dont-need-lodash-underscore': fixupPluginRules( @@ -476,7 +470,7 @@ export function makeBaseConfig( configurl, opts = {} ) { }, }, - // Various Node-oriented files should allow 'node' globals. + // Various config files should allow 'node' globals. { files: [ '.pnpmfile.cjs',