commit 36d5a96930b123fccda7b0d4f2ef43773b6a6e22
parent 25553ff0fe4665a51149d8be27881a7920c2bbba
Author: dustin-jw <dwhisman@mozilla.com>
Date: Tue, 14 Oct 2025 19:28:42 +0000
Bug 1988862 - Add a Stylelint rule to enforce using space tokens r=frontend-codestyle-reviewers,hjones
- Add tests for margin shorthand
- Add tests for padding shorthand
- Add tests for inset shorthand
- Add tests for gap shorthand
- Add tests for margin/padding block/inline shorthand
- Add tests for margin/padding longhand properties
- Remove tokens-table values from auto-fixable list
- Add tests for inset block/inline, plus top, left, right, and bottom
- Add tests for column-gap and row-gap
- Add tests for keywords
- Add files to be ignored for rollout
- Add tests for variable fallbacks
Differential Revision: https://phabricator.services.mozilla.com/D267857
Diffstat:
6 files changed, 1653 insertions(+), 0 deletions(-)
diff --git a/.stylelintrc.js b/.stylelintrc.js
@@ -274,6 +274,7 @@ module.exports = {
"stylelint-plugin-mozilla/use-border-color-tokens": true,
"stylelint-plugin-mozilla/use-font-size-tokens": true,
"stylelint-plugin-mozilla/use-font-weight-tokens": true,
+ "stylelint-plugin-mozilla/use-space-tokens": true,
},
overrides: [
@@ -418,6 +419,7 @@ module.exports = {
"stylelint-plugin-mozilla/use-border-color-tokens": false,
"stylelint-plugin-mozilla/use-font-size-tokens": false,
"stylelint-plugin-mozilla/use-font-weight-tokens": false,
+ "stylelint-plugin-mozilla/use-space-tokens": false,
},
},
{
@@ -429,6 +431,7 @@ module.exports = {
rules: {
"stylelint-plugin-mozilla/use-border-radius-tokens": true,
"stylelint-plugin-mozilla/use-border-color-tokens": false,
+ "stylelint-plugin-mozilla/use-space-tokens": false,
},
},
],
diff --git a/docs/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-space-tokens.rst b/docs/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-space-tokens.rst
@@ -0,0 +1,100 @@
+================
+use-space-tokens
+================
+
+This rule checks that CSS declarations use space design token variables
+instead of hardcoded values. This ensures consistent spacing (e.g. margins,
+padding, gaps, etc.) across the application and makes it easier to maintain
+design system consistency.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: css
+
+ .custom-button {
+ padding: 0.5rem;
+ }
+
+.. code-block:: css
+
+ .card {
+ margin-inline: 8px;
+ }
+
+.. code-block:: css
+
+ .overlay {
+ inset: 1rem;
+ }
+
+.. code-block:: css
+
+ .grid {
+ gap: 4px 12px;
+ }
+
+Examples of correct token usage for this rule:
+----------------------------------------------
+
+.. code-block:: css
+
+ .custom-button {
+ padding-block: var(--space-small);
+ }
+
+.. code-block:: css
+
+ .custom-button {
+ padding-inline: var(--space-medium);
+ }
+
+.. code-block:: css
+
+ .custom-button {
+ column-gap: var(--space-xxsmall);
+ }
+
+.. code-block:: css
+
+ .custom-button {
+ margin-block-start: var(--space-large);
+ }
+
+.. code-block:: css
+
+ /* Local CSS variables that reference valid space tokens are allowed */
+ :root {
+ --custom-space: var(--space-xsmall);
+ }
+
+ .custom-button {
+ padding: var(--custom-space);
+ }
+
+.. code-block:: css
+
+ .custom-button {
+ margin-inline-end: var(--custom-space, --space-xlarge);
+ }
+
+
+The rule also allows these values to be non-token values:
+
+.. code-block:: css
+
+ .inherited-inset {
+ inset: inherit;
+ }
+
+.. code-block:: css
+
+ .unset-padding {
+ padding: unset;
+ }
+
+.. code-block:: css
+
+ .initial-row-gap {
+ row-gap: initial;
+ }
diff --git a/stylelint-rollouts.config.js b/stylelint-rollouts.config.js
@@ -594,4 +594,342 @@ module.exports = [
"toolkit/themes/windows/mozapps/update/updates.css",
],
},
+ {
+ // stylelint fixes for this rule will be addressed in Bug 1993108
+ name: "rollout-use-space-tokens",
+ rules: {
+ "stylelint-plugin-mozilla/use-space-tokens": null,
+ },
+ files: [
+ "browser/base/content/aboutDialog.css",
+ "browser/base/content/pageinfo/pageInfo.css",
+ "browser/base/content/sanitizeDialog.css",
+ "browser/branding/aurora/content/aboutDialog.css",
+ "browser/branding/aurora/stubinstaller/installing_page.css",
+ "browser/branding/aurora/stubinstaller/profile_cleanup_page.css",
+ "browser/branding/nightly/content/aboutDialog.css",
+ "browser/branding/nightly/stubinstaller/installing_page.css",
+ "browser/branding/nightly/stubinstaller/profile_cleanup_page.css",
+ "browser/branding/official/content/aboutDialog.css",
+ "browser/branding/official/stubinstaller/installing_page.css",
+ "browser/branding/official/stubinstaller/profile_cleanup_page.css",
+ "browser/branding/unofficial/content/aboutDialog.css",
+ "browser/branding/unofficial/stubinstaller/installing_page.css",
+ "browser/branding/unofficial/stubinstaller/profile_cleanup_page.css",
+ "browser/components/aboutlogins/content/aboutLogins.css",
+ "browser/components/aboutlogins/content/aboutLoginsImportReport.css",
+ "browser/components/aboutlogins/content/components/confirmation-dialog.css",
+ "browser/components/aboutlogins/content/components/fxaccounts-button.css",
+ "browser/components/aboutlogins/content/components/generic-dialog.css",
+ "browser/components/aboutlogins/content/components/import-error-dialog.css",
+ "browser/components/aboutlogins/content/components/import-summary-dialog.css",
+ "browser/components/aboutlogins/content/components/login-alert.css",
+ "browser/components/aboutlogins/content/components/login-command-button.css",
+ "browser/components/aboutlogins/content/components/login-filter.css",
+ "browser/components/aboutlogins/content/components/login-intro.css",
+ "browser/components/aboutlogins/content/components/login-item.css",
+ "browser/components/aboutlogins/content/components/login-list-lit-item.css",
+ "browser/components/aboutlogins/content/components/login-list.css",
+ "browser/components/aboutlogins/content/components/login-message-popup.css",
+ "browser/components/aboutlogins/content/components/login-timeline.css",
+ "browser/components/aboutlogins/content/components/menu-button.css",
+ "browser/components/aboutlogins/content/components/remove-logins-dialog.css",
+ "browser/components/aboutwelcome/content-src/aboutwelcome.scss",
+ "browser/components/asrouter/content-src/components/ASRouterAdmin/ASRouterAdmin.scss",
+ "browser/components/asrouter/content-src/styles/_feature-callout.scss",
+ "browser/components/backup/content/backup-settings.css",
+ "browser/components/backup/content/password-rules-tooltip.css",
+ "browser/components/backup/content/password-validation-inputs.css",
+ "browser/components/backup/content/turn-on-scheduled-backups.css",
+ "browser/components/contextualidentity/content/usercontext.css",
+ "browser/components/downloads/content/downloads.css",
+ "browser/components/enterprisepolicies/content/aboutPolicies.css",
+ "browser/components/firefoxview/card-container.css",
+ "browser/components/firefoxview/firefoxview.css",
+ "browser/components/firefoxview/fxview-empty-state.css",
+ "browser/components/firefoxview/fxview-tab-row.css",
+ "browser/components/firefoxview/history.css",
+ "browser/components/firefoxview/opentabs-tab-row.css",
+ "browser/components/firefoxview/view-opentabs.css",
+ "browser/components/firefoxview/view-syncedtabs.css",
+ "browser/components/genai/chat.css",
+ "browser/components/genai/content/link-preview-card.css",
+ "browser/components/ipprotection/content/ipprotection-content.css",
+ "browser/components/ipprotection/content/ipprotection-header.css",
+ "browser/components/places/metadataViewer/interactionsViewer.css",
+ "browser/components/preferences/dialogs/clearSiteData.css",
+ "browser/components/preferences/dialogs/sitePermissions.css",
+ "browser/components/preferences/widgets/security-privacy/security-privacy-card/security-privacy-card.css",
+ "browser/components/profiles/content/edit-profile-card.css",
+ "browser/components/profiles/content/profile-avatar-selector.css",
+ "browser/components/profiles/content/profile-selector.css",
+ "browser/components/profiles/content/profiles-pages.css",
+ "browser/components/protections/content/protections.css",
+ "browser/components/screenshots/overlay/overlay.css",
+ "browser/components/screenshots/screenshots-buttons.css",
+ "browser/components/screenshots/screenshots-preview.css",
+ "browser/components/search/content/addEngine.css",
+ "browser/components/search/content/contentSearchUI.css",
+ "browser/components/search/test/browser/telemetry/serp.css",
+ "browser/components/security/unexpectedScriptLoad.css",
+ "browser/components/sidebar/sidebar-main.css",
+ "browser/components/sidebar/sidebar-panel-header.css",
+ "browser/components/sidebar/sidebar-tab-row.css",
+ "browser/components/sidebar/sidebar.css",
+ "browser/components/tabunloader/content/aboutUnloads.css",
+ "browser/components/textrecognition/textrecognition.css",
+ "browser/components/urlbar/tests/browser/dynamicResult0.css",
+ "browser/components/urlbar/tests/browser/dynamicResult1.css",
+ "browser/components/webrtc/content/webrtc-preview/webrtc-preview.css",
+ "browser/extensions/formautofill/content/formautofill.css",
+ "browser/extensions/formautofill/content/manageDialog.css",
+ "browser/extensions/formautofill/skin/shared/editAddress.css",
+ "browser/extensions/formautofill/skin/shared/editDialog-shared.css",
+ "browser/extensions/newtab/content-src/components/Base/_Base.scss",
+ "browser/extensions/newtab/content-src/components/Card/_Card.scss",
+ "browser/extensions/newtab/content-src/components/CollapsibleSection/_CollapsibleSection.scss",
+ "browser/extensions/newtab/content-src/components/ConfirmDialog/_ConfirmDialog.scss",
+ "browser/extensions/newtab/content-src/components/ContextMenu/_ContextMenu.scss",
+ "browser/extensions/newtab/content-src/components/CustomizeMenu/_CustomizeMenu.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/AdBanner/_AdBanner.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/AdBannerContextMenu/AdBannerContextMenu.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/DSEmptyState/_DSEmptyState.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/DSImage/_DSImage.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/DSThumbsUpDownButtons/_DSThumbsUpDownButtons.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_DownloadMobilePromoHighlight.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_FeatureHighlight.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_FollowSectionButtonHighlight.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/FeatureHighlight/_WallpaperFeatureHighlight.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/InterestPicker/_InterestPicker.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/ListFeed/_ListFeed.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/SectionContextMenu/_SectionContextMenu.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/TopSites/_TopSites.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/TopicSelection/_TopicSelection.scss",
+ "browser/extensions/newtab/content-src/components/ModalOverlay/_ModalOverlay.scss",
+ "browser/extensions/newtab/content-src/components/Notifications/_Notifications.scss",
+ "browser/extensions/newtab/content-src/components/Search/_Search.scss",
+ "browser/extensions/newtab/content-src/components/TopSites/_TopSites.scss",
+ "browser/extensions/newtab/content-src/components/WallpaperCategories/_WallpaperCategories.scss",
+ "browser/extensions/newtab/content-src/components/Weather/_Weather.scss",
+ "browser/extensions/newtab/content-src/components/Widgets/Lists/_Lists.scss",
+ "browser/extensions/newtab/content-src/components/Widgets/_Widgets.scss",
+ "browser/extensions/newtab/content-src/styles/_variables.scss",
+ "browser/extensions/webcompat/about-compat/aboutCompat.css",
+ "browser/themes/linux/browser.css",
+ "browser/themes/linux/customizableui/panelUI.css",
+ "browser/themes/linux/sanitizeDialog.css",
+ "browser/themes/osx/browser.css",
+ "browser/themes/osx/customizableui/panelUI.css",
+ "browser/themes/osx/places/organizer.css",
+ "browser/themes/osx/sanitizeDialog.css",
+ "browser/themes/shared/UITour.css",
+ "browser/themes/shared/aboutRestartRequired.css",
+ "browser/themes/shared/aboutSessionRestore.css",
+ "browser/themes/shared/aboutTabCrashed.css",
+ "browser/themes/shared/aboutWelcomeBack.css",
+ "browser/themes/shared/addon-notification.css",
+ "browser/themes/shared/addons/unified-extensions.css",
+ "browser/themes/shared/autocomplete.css",
+ "browser/themes/shared/blockedSite.css",
+ "browser/themes/shared/browser-shared.css",
+ "browser/themes/shared/contextmenu.css",
+ "browser/themes/shared/controlcenter/panel.css",
+ "browser/themes/shared/customizableui/customizeMode.css",
+ "browser/themes/shared/customizableui/panelUI-shared.css",
+ "browser/themes/shared/downloads/allDownloadsView.inc.css",
+ "browser/themes/shared/downloads/contentAreaDownloadsView.css",
+ "browser/themes/shared/downloads/download-blockedStates.css",
+ "browser/themes/shared/downloads/downloads.inc.css",
+ "browser/themes/shared/downloads/indicator.css",
+ "browser/themes/shared/downloads/progressmeter.css",
+ "browser/themes/shared/formautofill-notification.css",
+ "browser/themes/shared/identity-block/identity-block.css",
+ "browser/themes/shared/identity-credential-notification.css",
+ "browser/themes/shared/migration/migration-dialog-window.css",
+ "browser/themes/shared/migration/migration-wizard.css",
+ "browser/themes/shared/notification-icons.css",
+ "browser/themes/shared/pageInfo.css",
+ "browser/themes/shared/places/editBookmark.css",
+ "browser/themes/shared/places/editBookmarkPanel.css",
+ "browser/themes/shared/places/organizer-shared.css",
+ "browser/themes/shared/places/sidebar.css",
+ "browser/themes/shared/places/tree-icons.css",
+ "browser/themes/shared/preferences/applications.css",
+ "browser/themes/shared/preferences/containers-dialog.css",
+ "browser/themes/shared/preferences/containers.css",
+ "browser/themes/shared/preferences/dialog.css",
+ "browser/themes/shared/preferences/fxaPairDevice.css",
+ "browser/themes/shared/preferences/preferences.css",
+ "browser/themes/shared/preferences/privacy.css",
+ "browser/themes/shared/preferences/search.css",
+ "browser/themes/shared/preferences/siteDataSettings.css",
+ "browser/themes/shared/preferences/translations.css",
+ "browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css",
+ "browser/themes/shared/sanitizeDialog_v2.css",
+ "browser/themes/shared/search/searchbar.css",
+ "browser/themes/shared/setDesktopBackground.css",
+ "browser/themes/shared/sidebar.css",
+ "browser/themes/shared/syncedtabs/sidebar.css",
+ "browser/themes/shared/tab-list-tree.css",
+ "browser/themes/shared/tabbrowser/content-area.css",
+ "browser/themes/shared/tabbrowser/ctrlTab.css",
+ "browser/themes/shared/tabbrowser/fullscreen-and-pointerlock.css",
+ "browser/themes/shared/tabbrowser/tabs.css",
+ "browser/themes/shared/toolbarbutton-icons.css",
+ "browser/themes/shared/toolbarbuttons.css",
+ "browser/themes/shared/translations/panel.css",
+ "browser/themes/shared/urlbar-dynamic-results.css",
+ "browser/themes/shared/urlbar-searchbar.css",
+ "browser/themes/shared/urlbarView.css",
+ "browser/themes/shared/webRTC-indicator.css",
+ "browser/themes/windows/browser.css",
+ "browser/themes/windows/customizableui/panelUI.css",
+ "browser/themes/windows/places/organizer.css",
+ "browser/themes/windows/sanitizeDialog.css",
+ "browser/tools/mozscreenshots/mozscreenshots/extension/lib/mozscreenshots-style.css",
+ "dom/crypto/test/test_WebCrypto.css",
+ "dom/events/test/pointerevents/wpt/pointerevent_styles.css",
+ "dom/tests/mochitest/webcomponents/inert_style.css",
+ "dom/xml/resources/XMLPrettyPrint.css",
+ "dom/xml/test/old/books/classic.css",
+ "dom/xml/test/old/books/common.css",
+ "dom/xml/test/old/books/list.css",
+ "dom/xml/test/old/xlink/link.css",
+ "dom/xml/test/old/xmlbase/xmlbase.css",
+ "gfx/layers/apz/test/mochitest/helper_subframe_style.css",
+ "layout/generic/test/frame_selection_underline.css",
+ "layout/inspector/tests/chrome/test_bug727834.css",
+ "layout/mathml/mathml.css",
+ "layout/style/res/accessiblecaret.css",
+ "layout/style/res/forms.css",
+ "layout/style/res/html.css",
+ "layout/style/res/quirk.css",
+ "layout/style/res/ua.css",
+ "layout/style/res/viewsource.css",
+ "security/manager/pki/resources/content/clientauthask.css",
+ "security/manager/pki/resources/content/deletecert.css",
+ "security/manager/pki/resources/content/exceptionDialog.css",
+ "testing/mochitest/static/harness.css",
+ "testing/mozbase/mozlog/mozlog/formatters/html/style.css",
+ "testing/talos/talos/tests/scroll/reader.css",
+ "toolkit/components/aboutcheckerboard/content/aboutCheckerboard.css",
+ "toolkit/components/aboutconfig/content/aboutconfig.css",
+ "toolkit/components/aboutinference/content/aboutInference.css",
+ "toolkit/components/aboutinference/content/model-files-view.css",
+ "toolkit/components/aboutmemory/content/aboutMemory.css",
+ "toolkit/components/aboutprocesses/content/aboutProcesses.css",
+ "toolkit/components/aboutthirdparty/content/aboutThirdParty.css",
+ "toolkit/components/aboutwebauthn/content/aboutWebauthn.css",
+ "toolkit/components/aboutwindowsmessages/content/aboutWindowsMessages.css",
+ "toolkit/components/certviewer/content/components/about-certificate-section.css",
+ "toolkit/components/certviewer/content/components/certificate-section.css",
+ "toolkit/components/certviewer/content/components/error-section.css",
+ "toolkit/components/certviewer/content/components/info-group.css",
+ "toolkit/components/certviewer/content/components/info-item.css",
+ "toolkit/components/certviewer/content/components/list-item.css",
+ "toolkit/components/normandy/content/about-studies/about-studies.css",
+ "toolkit/components/normandy/skin/shared/Heartbeat.css",
+ "toolkit/components/printing/content/print.css",
+ "toolkit/components/printing/content/printPagination.css",
+ "toolkit/components/printing/content/printPreview.css",
+ "toolkit/components/printing/content/simplifyMode.css",
+ "toolkit/components/printing/content/toggle-group.css",
+ "toolkit/components/prompts/content/commonDialog.css",
+ "toolkit/components/prompts/content/selectDialog.css",
+ "toolkit/components/satchel/megalist/content/components/login-line/login-line.css",
+ "toolkit/components/translations/content/about-translations.css",
+ "toolkit/content/aboutGlean.css",
+ "toolkit/content/aboutLogging/aboutLogging.css",
+ "toolkit/content/aboutMozilla.css",
+ "toolkit/content/aboutTelemetry.css",
+ "toolkit/content/aboutUrlClassifier.css",
+ "toolkit/content/aboutwebrtc/aboutWebrtc.css",
+ "toolkit/content/buildconfig.css",
+ "toolkit/content/resetProfile.css",
+ "toolkit/content/widgets/infobar.css",
+ "toolkit/content/widgets/moz-box-item/moz-box-item.css",
+ "toolkit/content/widgets/moz-button/moz-button.css",
+ "toolkit/content/widgets/moz-card/moz-card.css",
+ "toolkit/content/widgets/moz-five-star/moz-five-star.css",
+ "toolkit/content/widgets/moz-input-common.css",
+ "toolkit/content/widgets/moz-input-text/moz-input-text.css",
+ "toolkit/content/widgets/moz-message-bar/moz-message-bar.css",
+ "toolkit/content/widgets/moz-page-nav/moz-page-nav.css",
+ "toolkit/content/widgets/moz-reorderable-list/moz-reorderable-list.css",
+ "toolkit/content/widgets/moz-select/moz-select.css",
+ "toolkit/content/widgets/moz-toggle/moz-toggle.css",
+ "toolkit/content/widgets/moz-visual-picker/moz-visual-picker-item.css",
+ "toolkit/content/widgets/panel-list/panel-item.css",
+ "toolkit/content/widgets/panel-list/panel-list.css",
+ "toolkit/content/xul.css",
+ "toolkit/crashreporter/content/crashes.css",
+ "toolkit/mozapps/extensions/content/aboutaddons.css",
+ "toolkit/mozapps/extensions/content/shortcuts.css",
+ "toolkit/mozapps/handling/content/handler.css",
+ "toolkit/themes/linux/global/autocomplete.css",
+ "toolkit/themes/linux/global/global.css",
+ "toolkit/themes/linux/global/richlistbox.css",
+ "toolkit/themes/linux/mozapps/update/updates.css",
+ "toolkit/themes/mobile/global/aboutMemory.css",
+ "toolkit/themes/mobile/global/aboutNetworking.css",
+ "toolkit/themes/mobile/global/aboutSupport.css",
+ "toolkit/themes/osx/global/autocomplete.css",
+ "toolkit/themes/osx/global/button.css",
+ "toolkit/themes/osx/global/dialog.css",
+ "toolkit/themes/osx/global/global.css",
+ "toolkit/themes/osx/global/in-content/common.css",
+ "toolkit/themes/osx/global/richlistbox.css",
+ "toolkit/themes/osx/global/wizard.css",
+ "toolkit/themes/osx/mozapps/handling/handling.css",
+ "toolkit/themes/osx/mozapps/update/updates.css",
+ "toolkit/themes/shared/aboutCache.css",
+ "toolkit/themes/shared/aboutHttpsOnlyError.css",
+ "toolkit/themes/shared/aboutLicense.css",
+ "toolkit/themes/shared/aboutNetError.css",
+ "toolkit/themes/shared/aboutNetworking.css",
+ "toolkit/themes/shared/aboutProfiles.css",
+ "toolkit/themes/shared/aboutReader.css",
+ "toolkit/themes/shared/aboutServiceWorkers.css",
+ "toolkit/themes/shared/aboutSupport.css",
+ "toolkit/themes/shared/alert.css",
+ "toolkit/themes/shared/appPicker.css",
+ "toolkit/themes/shared/arrowscrollbox.css",
+ "toolkit/themes/shared/checkbox.css",
+ "toolkit/themes/shared/close-icon.css",
+ "toolkit/themes/shared/commonDialog.css",
+ "toolkit/themes/shared/datetimeinputpickers.css",
+ "toolkit/themes/shared/design-system/tokens-table.css",
+ "toolkit/themes/shared/dirListing/dirListing.css",
+ "toolkit/themes/shared/downloads/unknownContentType.css",
+ "toolkit/themes/shared/error-pages.css",
+ "toolkit/themes/shared/findbar.css",
+ "toolkit/themes/shared/global-shared.css",
+ "toolkit/themes/shared/in-content/common-shared.css",
+ "toolkit/themes/shared/in-content/info-pages.css",
+ "toolkit/themes/shared/menu.css",
+ "toolkit/themes/shared/menulist.css",
+ "toolkit/themes/shared/offlineSupportPages.css",
+ "toolkit/themes/shared/pictureinpicture/player.css",
+ "toolkit/themes/shared/pictureinpicture/texttracks.css",
+ "toolkit/themes/shared/popup.css",
+ "toolkit/themes/shared/popupnotification.css",
+ "toolkit/themes/shared/profileSelection.css",
+ "toolkit/themes/shared/radio.css",
+ "toolkit/themes/shared/tabbox.css",
+ "toolkit/themes/shared/toolbar.css",
+ "toolkit/themes/shared/toolbarbutton.css",
+ "toolkit/themes/shared/tree/tree.css",
+ "toolkit/themes/windows/global/autocomplete.css",
+ "toolkit/themes/windows/global/button.css",
+ "toolkit/themes/windows/global/dialog.css",
+ "toolkit/themes/windows/global/global.css",
+ "toolkit/themes/windows/global/printPageSetup.css",
+ "toolkit/themes/windows/global/richlistbox.css",
+ "toolkit/themes/windows/global/wizard.css",
+ "toolkit/themes/windows/mozapps/handling/handling.css",
+ "toolkit/themes/windows/mozapps/update/updates.css",
+ "tools/tryselect/selectors/chooser/static/style.css",
+ ],
+ },
];
diff --git a/tools/lint/stylelint/stylelint-plugin-mozilla/rules/index.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/rules/index.mjs
@@ -9,6 +9,7 @@ import useBorderRadiusTokens from "./use-border-radius-tokens.mjs";
import useBorderColorTokens from "./use-border-color-tokens.mjs";
import useFontSizeTokens from "./use-font-size-tokens.mjs";
import useFontWeightTokens from "./use-font-weight-tokens.mjs";
+import useSpaceTokens from "./use-space-tokens.mjs";
export default {
"no-base-design-tokens": noBaseDesignTokens,
@@ -16,4 +17,5 @@ export default {
"use-border-color-tokens": useBorderColorTokens,
"use-font-size-tokens": useFontSizeTokens,
"use-font-weight-tokens": useFontWeightTokens,
+ "use-space-tokens": useSpaceTokens,
};
diff --git a/tools/lint/stylelint/stylelint-plugin-mozilla/rules/use-space-tokens.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/rules/use-space-tokens.mjs
@@ -0,0 +1,160 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+import stylelint from "stylelint";
+import valueParser from "postcss-value-parser";
+import {
+ namespace,
+ createTokenNamesArray,
+ isValidTokenUsage,
+ getLocalCustomProperties,
+ usesRawFallbackValues,
+ usesRawShorthandValues,
+ createAllowList,
+} from "../helpers.mjs";
+
+const {
+ utils: { report, ruleMessages, validateOptions },
+} = stylelint;
+
+const ruleName = namespace("use-space-tokens");
+
+const messages = ruleMessages(ruleName, {
+ rejected: value =>
+ `${value} should be using a space design token. This may be fixable by running the same command again with --fix.`,
+});
+
+const meta = {
+ url: "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-space-tokens.html",
+ fixable: true,
+};
+
+const INCLUDE_CATEGORIES = ["space"];
+
+const tokenCSS = createTokenNamesArray(INCLUDE_CATEGORIES);
+
+// Allowed values in CSS
+const ALLOW_LIST = createAllowList(["0", "auto"]);
+
+const CSS_PROPERTIES = [
+ "margin",
+ "margin-block",
+ "margin-block-end",
+ "margin-block-start",
+ "margin-inline",
+ "margin-inline-end",
+ "margin-inline-start",
+ "margin-top",
+ "margin-right",
+ "margin-bottom",
+ "margin-left",
+ "padding",
+ "padding-block",
+ "padding-block-end",
+ "padding-block-start",
+ "padding-inline",
+ "padding-inline-end",
+ "padding-inline-start",
+ "padding-top",
+ "padding-right",
+ "padding-bottom",
+ "padding-left",
+ "gap",
+ "column-gap",
+ "row-gap",
+ "inset",
+ "inset-block",
+ "inset-block-end",
+ "inset-block-start",
+ "inset-inline",
+ "inset-inline-end",
+ "inset-inline-start",
+ "top",
+ "right",
+ "bottom",
+ "left",
+];
+
+// the token tree has values that don't make sense to auto-fix, like changing 0 to var(--button-padding-icon),
+// so we'll ignore those and stick to auto-fixable values that are likely to be used
+const RAW_VALUE_TO_TOKEN_VALUE = {
+ "2px": "var(--space-xxsmall)",
+ "4px": "var(--space-xsmall)",
+ "8px": "var(--space-small)",
+ "12px": "var(--space-medium)",
+ "16px": "var(--space-large)",
+ "24px": "var(--space-xlarge)",
+ "32px": "var(--space-xxlarge)",
+};
+
+const ruleFunction = primaryOption => {
+ return (root, result) => {
+ const validOptions = validateOptions(result, ruleName, {
+ actual: primaryOption,
+ possible: [true],
+ });
+
+ if (!validOptions) {
+ return;
+ }
+
+ // Walk declarations once to generate a lookup table of variables.
+ const cssCustomProperties = getLocalCustomProperties(root);
+
+ // Walk declarations again to detect non-token values.
+ root.walkDecls(declarations => {
+ // If the property is not in our list to check, skip it.
+ if (!CSS_PROPERTIES.includes(declarations.prop)) {
+ return;
+ }
+
+ // Otherwise, see if we are using the tokens correctly
+ if (
+ isValidTokenUsage(
+ declarations.value,
+ tokenCSS,
+ cssCustomProperties,
+ ALLOW_LIST
+ ) &&
+ !usesRawFallbackValues(declarations.value, RAW_VALUE_TO_TOKEN_VALUE) &&
+ !usesRawShorthandValues(
+ declarations.value,
+ tokenCSS,
+ cssCustomProperties,
+ ALLOW_LIST
+ )
+ ) {
+ return;
+ }
+
+ report({
+ message: messages.rejected(declarations.value),
+ node: declarations,
+ result,
+ ruleName,
+ fix: () => {
+ const val = valueParser(declarations.value);
+ let hasFixes = false;
+ val.walk(node => {
+ if (node.type == "word") {
+ const token = RAW_VALUE_TO_TOKEN_VALUE[node.value.trim()];
+ if (token) {
+ hasFixes = true;
+ node.value = token;
+ }
+ }
+ });
+ if (hasFixes) {
+ declarations.value = val.toString();
+ }
+ },
+ });
+ });
+ };
+};
+
+ruleFunction.ruleName = ruleName;
+ruleFunction.messages = messages;
+ruleFunction.meta = meta;
+
+export default ruleFunction;
diff --git a/tools/lint/stylelint/stylelint-plugin-mozilla/tests/use-space-tokens.tests.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/tests/use-space-tokens.tests.mjs
@@ -0,0 +1,1050 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/PL/2.0/.
+ */
+
+// Bug 1948378: remove this exception when the eslint import plugin fully
+// supports exports in package.json files
+// eslint-disable-next-line import/no-unresolved
+import { testRule } from "stylelint-test-rule-node";
+import stylelint from "stylelint";
+import useSpaceTokens from "../rules/use-space-tokens.mjs";
+
+let plugin = stylelint.createPlugin(useSpaceTokens.ruleName, useSpaceTokens);
+let {
+ ruleName,
+ rule: { messages },
+} = plugin;
+
+testRule({
+ plugins: [plugin],
+ ruleName,
+ config: true,
+ fix: false,
+ accept: [
+ {
+ code: ".a { margin: var(--space-small); }",
+ description: "Using space token for margin is valid.",
+ },
+ {
+ code: ".a { margin: var(--space-small) var(--space-large); }",
+ description:
+ "Using space token for margin with two shorthand values is valid.",
+ },
+ {
+ code: ".a { margin: var(--space-small) var(--space-large) var(--space-medium); }",
+ description:
+ "Using space token for margin with three shorthand values is valid.",
+ },
+ {
+ code: ".a { margin: var(--space-small) var(--space-large) var(--space-medium) var(--space-xlarge); }",
+ description:
+ "Using space token for margin with four shorthand values is valid.",
+ },
+ {
+ code: ".a { margin-block: var(--space-small); }",
+ description: "Using space token for margin-block is valid.",
+ },
+ {
+ code: ".a { margin-block: var(--space-small) var(--space-large); }",
+ description:
+ "Using space token for margin-block with two shorthand values is valid.",
+ },
+ {
+ code: ".a { margin-inline: var(--space-small); }",
+ description: "Using space token for margin-inline is valid.",
+ },
+ {
+ code: ".a { margin-inline: var(--space-small) var(--space-large); }",
+ description:
+ "Using space token for margin-inline with two shorthand values is valid.",
+ },
+ {
+ code: ".a { margin-block-end: var(--space-xxsmall); }",
+ description: "Using space token for margin-block-end is valid.",
+ },
+ {
+ code: ".a { margin-block-start: var(--space-xsmall); }",
+ description: "Using space token for margin-block-start is valid.",
+ },
+ {
+ code: ".a { margin-inline-end: var(--space-small); }",
+ description: "Using space token for margin-inline-end is valid.",
+ },
+ {
+ code: ".a { margin-inline-start: var(--space-medium); }",
+ description: "Using space token for margin-inline-start is valid.",
+ },
+ {
+ code: ".a { margin-top: var(--space-large); }",
+ description: "Using space token for margin-top is valid.",
+ },
+ {
+ code: ".a { margin-right: var(--space-xlarge); }",
+ description: "Using space token for margin-right is valid.",
+ },
+ {
+ code: ".a { margin-bottom: var(--space-xxlarge); }",
+ description: "Using space token for margin-bottom is valid.",
+ },
+ {
+ code: ".a { margin-left: var(--space-small); }",
+ description: "Using space token for margin-left is valid.",
+ },
+ {
+ code: ".a { padding: var(--space-small); }",
+ description: "Using space token for padding is valid.",
+ },
+ {
+ code: ".a { padding: var(--space-small) var(--space-large); }",
+ description:
+ "Using space token for padding with two shorthand values is valid.",
+ },
+ {
+ code: ".a { padding: var(--space-small) var(--space-large) var(--space-medium); }",
+ description:
+ "Using space token for padding with three shorthand values is valid.",
+ },
+ {
+ code: ".a { padding: var(--space-small) var(--space-large) var(--space-medium) var(--space-xlarge); }",
+ description:
+ "Using space token for padding with four shorthand values is valid.",
+ },
+ {
+ code: ".a { padding-block: var(--space-small); }",
+ description: "Using space token for padding-block is valid.",
+ },
+ {
+ code: ".a { padding-block: var(--space-small) var(--space-large); }",
+ description:
+ "Using space token for padding-block with two shorthand values is valid.",
+ },
+ {
+ code: ".a { padding-inline: var(--space-small); }",
+ description: "Using space token for padding-inline is valid.",
+ },
+ {
+ code: ".a { padding-inline: var(--space-small) var(--space-large); }",
+ description:
+ "Using space token for padding-inline with two shorthand values is valid.",
+ },
+ {
+ code: ".a { padding-block-end: var(--space-xxsmall); }",
+ description: "Using space token for padding-block-end is valid.",
+ },
+ {
+ code: ".a { padding-block-start: var(--space-xsmall); }",
+ description: "Using space token for padding-block-start is valid.",
+ },
+ {
+ code: ".a { padding-inline-end: var(--space-small); }",
+ description: "Using space token for padding-inline-end is valid.",
+ },
+ {
+ code: ".a { padding-inline-start: var(--space-medium); }",
+ description: "Using space token for padding-inline-start is valid.",
+ },
+ {
+ code: ".a { padding-top: var(--space-large); }",
+ description: "Using space token for padding-top is valid.",
+ },
+ {
+ code: ".a { padding-right: var(--space-xlarge); }",
+ description: "Using space token for padding-right is valid.",
+ },
+ {
+ code: ".a { padding-bottom: var(--space-xxlarge); }",
+ description: "Using space token for padding-bottom is valid.",
+ },
+ {
+ code: ".a { padding-left: var(--space-small); }",
+ description: "Using space token for padding-left is valid.",
+ },
+ {
+ code: ".a { inset: var(--space-small); }",
+ description: "Using space token for inset is valid.",
+ },
+ {
+ code: ".a { inset: var(--space-small) var(--space-large); }",
+ description:
+ "Using space token for inset with two shorthand values is valid.",
+ },
+ {
+ code: ".a { inset: var(--space-small) var(--space-large) var(--space-medium); }",
+ description:
+ "Using space token for inset with three shorthand values is valid.",
+ },
+ {
+ code: ".a { inset: var(--space-small) var(--space-large) var(--space-medium) var(--space-xlarge); }",
+ description:
+ "Using space token for inset with four shorthand values is valid.",
+ },
+ {
+ code: ".a { inset-block: var(--space-small); }",
+ description: "Using space token for inset-block is valid.",
+ },
+ {
+ code: ".a { inset-block: var(--space-small) var(--space-large); }",
+ description:
+ "Using space token for inset-block with two shorthand values is valid.",
+ },
+ {
+ code: ".a { inset-inline: var(--space-small); }",
+ description: "Using space token for inset-inline is valid.",
+ },
+ {
+ code: ".a { inset-inline: var(--space-small) var(--space-large); }",
+ description:
+ "Using space token for inset-inline with two shorthand values is valid.",
+ },
+ {
+ code: ".a { inset-block-end: var(--space-xxsmall); }",
+ description: "Using space token for inset-block-end is valid.",
+ },
+ {
+ code: ".a { inset-block-start: var(--space-xsmall); }",
+ description: "Using space token for inset-block-start is valid.",
+ },
+ {
+ code: ".a { inset-inline-end: var(--space-small); }",
+ description: "Using space token for inset-inline-end is valid.",
+ },
+ {
+ code: ".a { inset-inline-start: var(--space-medium); }",
+ description: "Using space token for inset-inline-start is valid.",
+ },
+ {
+ code: ".a { top: var(--space-small); }",
+ description: "Using space token for top is valid.",
+ },
+ {
+ code: ".a { right: var(--space-xxlarge); }",
+ description: "Using space token for right is valid.",
+ },
+ {
+ code: ".a { bottom: var(--space-xlarge); }",
+ description: "Using space token for bottom is valid.",
+ },
+ {
+ code: ".a { left: var(--space-medium); }",
+ description: "Using space token for left is valid.",
+ },
+ {
+ code: ".a { gap: var(--space-small); }",
+ description: "Using space token for gap is valid.",
+ },
+ {
+ code: ".a { gap: var(--space-small) var(--space-large); }",
+ description:
+ "Using space token for gap with two shorthand values is valid.",
+ },
+ {
+ code: ".a { column-gap: var(--space-medium); }",
+ description: "Using space token for column-gap is valid.",
+ },
+ {
+ code: ".a { row-gap: var(--space-xlarge); }",
+ description: "Using space token for row-gap is valid.",
+ },
+ {
+ code: ".a { margin-inline: auto; }",
+ description: "Using auto for spacing is valid.",
+ },
+ {
+ code: ".a { padding: 0; }",
+ description: "Using 0 for spacing is valid.",
+ },
+ {
+ code: ".a { inset: initial; }",
+ description: "Using a keyword for spacing is valid.",
+ },
+ {
+ code: ".a { gap: inherit; }",
+ description: "Using a keyword for spacing is valid.",
+ },
+ {
+ code: ".a { margin-block-start: revert; }",
+ description: "Using a keyword for spacing is valid.",
+ },
+ {
+ code: ".a { left: revert-layer; }",
+ description: "Using a keyword for spacing is valid.",
+ },
+ {
+ code: ".a { column-gap: unset; }",
+ description: "Using a keyword for spacing is valid.",
+ },
+ {
+ code: `
+ :root { --local-padding: var(--space-small); }
+ .a { padding: var(--local-padding); }
+ `,
+ description:
+ "Using a locally declared variable that resolves to a space token is valid.",
+ },
+ {
+ code: ".a { padding: var(--random-padding, var(--space-small)); }",
+ description:
+ "Using a variable that falls back to a space token is valid.",
+ },
+ ],
+ reject: [
+ {
+ code: ".a { margin: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { margin: 0.5rem 1rem; }",
+ message: messages.rejected("0.5rem 1rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin: 0.5em var(--space-large); }",
+ message: messages.rejected("0.5em var(--space-large)"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin: 0.5rem 1rem 0.25rem; }",
+ message: messages.rejected("0.5rem 1rem 0.25rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin: var(--space-small) 1rem 0.25rem; }",
+ message: messages.rejected("var(--space-small) 1rem 0.25rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin: var(--space-small) 1rem var(--space-xsmall); }",
+ message: messages.rejected("var(--space-small) 1rem var(--space-xsmall)"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin: 0.5em 1em 0.25em 0.5em; }",
+ message: messages.rejected("0.5em 1em 0.25em 0.5em"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin: var(--space-small) 1em 0.25em 0.5em; }",
+ message: messages.rejected("var(--space-small) 1em 0.25em 0.5em"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin: var(--space-small) 1em 0.25em var(--space-small); }",
+ message: messages.rejected(
+ "var(--space-small) 1em 0.25em var(--space-small)"
+ ),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin: var(--space-small) var(--space-medium) 0.25em var(--space-small); }",
+ message: messages.rejected(
+ "var(--space-small) var(--space-medium) 0.25em var(--space-small)"
+ ),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin-block: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { margin-block: 0.5rem 1rem; }",
+ message: messages.rejected("0.5rem 1rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin-block: 0.5em var(--space-large); }",
+ message: messages.rejected("0.5em var(--space-large)"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin-inline: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { margin-inline: 0.5rem 1rem; }",
+ message: messages.rejected("0.5rem 1rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin-inline: var(--space-large) 0.5em; }",
+ message: messages.rejected("var(--space-large) 0.5em"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { margin-block-end: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { margin-block-start: 1rem; }",
+ message: messages.rejected("1rem"),
+ description: "Space value in rem should use a design token.",
+ },
+ {
+ code: ".a { margin-inline-end: 5%; }",
+ message: messages.rejected("5%"),
+ description: "Space value in percent should use a design token.",
+ },
+ {
+ code: ".a { margin-inline-start: 0.5em; }",
+ message: messages.rejected("0.5em"),
+ description: "Space value in em should use a design token.",
+ },
+ {
+ code: ".a { margin-top: 1lh; }",
+ message: messages.rejected("1lh"),
+ description: "Space value in lh should use a design token.",
+ },
+ {
+ code: ".a { margin-right: 1cqi; }",
+ message: messages.rejected("1cqi"),
+ description: "Space value in cqi should use a design token.",
+ },
+ {
+ code: ".a { margin-bottom: 1ex; }",
+ message: messages.rejected("1ex"),
+ description: "Space value in ex should use a design token.",
+ },
+ {
+ code: ".a { margin-left: 1ch; }",
+ message: messages.rejected("1ch"),
+ description: "Space value in ch should use a design token.",
+ },
+ {
+ code: ".a { padding: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { padding: 0.5rem 1rem; }",
+ message: messages.rejected("0.5rem 1rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding: 0.5em var(--space-large); }",
+ message: messages.rejected("0.5em var(--space-large)"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding: 0.5rem 1rem 0.25rem; }",
+ message: messages.rejected("0.5rem 1rem 0.25rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding: var(--space-small) 1rem 0.25rem; }",
+ message: messages.rejected("var(--space-small) 1rem 0.25rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding: var(--space-small) 1rem var(--space-xsmall); }",
+ message: messages.rejected("var(--space-small) 1rem var(--space-xsmall)"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding: 0.5em 1em 0.25em 0.5em; }",
+ message: messages.rejected("0.5em 1em 0.25em 0.5em"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding: var(--space-small) 1em 0.25em 0.5em; }",
+ message: messages.rejected("var(--space-small) 1em 0.25em 0.5em"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding: var(--space-small) 1em 0.25em var(--space-small); }",
+ message: messages.rejected(
+ "var(--space-small) 1em 0.25em var(--space-small)"
+ ),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding: var(--space-small) var(--space-medium) 0.25em var(--space-small); }",
+ message: messages.rejected(
+ "var(--space-small) var(--space-medium) 0.25em var(--space-small)"
+ ),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding-block: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { padding-block: 0.5rem 1rem; }",
+ message: messages.rejected("0.5rem 1rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding-block: 0.5em var(--space-large); }",
+ message: messages.rejected("0.5em var(--space-large)"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding-inline: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { padding-inline: 0.5rem 1rem; }",
+ message: messages.rejected("0.5rem 1rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding-inline: var(--space-large) 0.5em; }",
+ message: messages.rejected("var(--space-large) 0.5em"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { padding-block-end: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { padding-block-start: 1rem; }",
+ message: messages.rejected("1rem"),
+ description: "Space value in rem should use a design token.",
+ },
+ {
+ code: ".a { padding-inline-end: 5%; }",
+ message: messages.rejected("5%"),
+ description: "Space value in percent should use a design token.",
+ },
+ {
+ code: ".a { padding-inline-start: 0.5em; }",
+ message: messages.rejected("0.5em"),
+ description: "Space value in em should use a design token.",
+ },
+ {
+ code: ".a { padding-top: 1lh; }",
+ message: messages.rejected("1lh"),
+ description: "Space value in lh should use a design token.",
+ },
+ {
+ code: ".a { padding-right: 1cqi; }",
+ message: messages.rejected("1cqi"),
+ description: "Space value in cqi should use a design token.",
+ },
+ {
+ code: ".a { padding-bottom: 1ex; }",
+ message: messages.rejected("1ex"),
+ description: "Space value in ex should use a design token.",
+ },
+ {
+ code: ".a { padding-left: 1ch; }",
+ message: messages.rejected("1ch"),
+ description: "Space value in ch should use a design token.",
+ },
+ {
+ code: ".a { inset: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { inset: 0.5rem 1rem; }",
+ message: messages.rejected("0.5rem 1rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset: 0.5em var(--space-large); }",
+ message: messages.rejected("0.5em var(--space-large)"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset: 0.5rem 1rem 0.25rem; }",
+ message: messages.rejected("0.5rem 1rem 0.25rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset: var(--space-small) 1rem 0.25rem; }",
+ message: messages.rejected("var(--space-small) 1rem 0.25rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset: var(--space-small) 1rem var(--space-xsmall); }",
+ message: messages.rejected("var(--space-small) 1rem var(--space-xsmall)"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset: 0.5em 1em 0.25em 0.5em; }",
+ message: messages.rejected("0.5em 1em 0.25em 0.5em"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset: var(--space-small) 1em 0.25em 0.5em; }",
+ message: messages.rejected("var(--space-small) 1em 0.25em 0.5em"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset: var(--space-small) 1em 0.25em var(--space-small); }",
+ message: messages.rejected(
+ "var(--space-small) 1em 0.25em var(--space-small)"
+ ),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset: var(--space-small) var(--space-medium) 0.25em var(--space-small); }",
+ message: messages.rejected(
+ "var(--space-small) var(--space-medium) 0.25em var(--space-small)"
+ ),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset-block: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { inset-block: 0.5rem 1rem; }",
+ message: messages.rejected("0.5rem 1rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset-block: 0.5em var(--space-large); }",
+ message: messages.rejected("0.5em var(--space-large)"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset-inline: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { inset-inline: 0.5rem 1rem; }",
+ message: messages.rejected("0.5rem 1rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset-inline: var(--space-large) 0.5em; }",
+ message: messages.rejected("var(--space-large) 0.5em"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { inset-block-end: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { inset-block-start: 1rem; }",
+ message: messages.rejected("1rem"),
+ description: "Space value in rem should use a design token.",
+ },
+ {
+ code: ".a { inset-inline-end: 5%; }",
+ message: messages.rejected("5%"),
+ description: "Space value in percent should use a design token.",
+ },
+ {
+ code: ".a { inset-inline-start: 0.5em; }",
+ message: messages.rejected("0.5em"),
+ description: "Space value in em should use a design token.",
+ },
+ {
+ code: ".a { top: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { right: 0.5rem; }",
+ message: messages.rejected("0.5rem"),
+ description: "Space value in rem should use a design token.",
+ },
+ {
+ code: ".a { bottom: 1ch; }",
+ message: messages.rejected("1ch"),
+ description: "Space value in ch should use a design token.",
+ },
+ {
+ code: ".a { left: 1lh; }",
+ message: messages.rejected("1lh"),
+ description: "Space value in lh should use a design token.",
+ },
+ {
+ code: ".a { gap: 5px; }",
+ message: messages.rejected("5px"),
+ description: "Space value in px should use a design token.",
+ },
+ {
+ code: ".a { gap: 0.5rem 1rem; }",
+ message: messages.rejected("0.5rem 1rem"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { gap: var(--space-large) 0.5em; }",
+ message: messages.rejected("var(--space-large) 0.5em"),
+ description: "Space values in shorthand should use a design token.",
+ },
+ {
+ code: ".a { column-gap: ch; }",
+ message: messages.rejected("ch"),
+ description: "Space value in ch should use a design token.",
+ },
+ {
+ code: ".a { row-gap: 0.5ex; }",
+ message: messages.rejected("0.5ex"),
+ description: "Space value in ex should use a design token.",
+ },
+ {
+ code: `
+ :root { --local-padding: 5px; }
+ .a { padding: var(--local-padding); }
+ `,
+ message: messages.rejected("var(--local-padding)"),
+ description:
+ "Using a locally declared variable that does not resolve to a space token is invalid.",
+ },
+ {
+ code: ".a { padding: var(--random-padding, 5px); }",
+ message: messages.rejected("var(--random-padding, 5px)"),
+ description:
+ "Using a variable that does not fall back to a space token is invalid.",
+ },
+ ],
+});
+
+// autofix tests
+testRule({
+ plugins: [plugin],
+ ruleName,
+ config: true,
+ fix: true,
+ reject: [
+ {
+ code: ".a { margin: 2px; }",
+ fixed: ".a { margin: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin: 8px 16px; }",
+ fixed: ".a { margin: var(--space-small) var(--space-large); }",
+ message: messages.rejected("8px 16px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin: 8px 16px 4px; }",
+ fixed:
+ ".a { margin: var(--space-small) var(--space-large) var(--space-xsmall); }",
+ message: messages.rejected("8px 16px 4px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin: 8px 16px 4px 12px; }",
+ fixed:
+ ".a { margin: var(--space-small) var(--space-large) var(--space-xsmall) var(--space-medium); }",
+ message: messages.rejected("8px 16px 4px 12px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-block: 2px; }",
+ fixed: ".a { margin-block: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-block: 8px var(--space-large); }",
+ fixed: ".a { margin-block: var(--space-small) var(--space-large); }",
+ message: messages.rejected("8px var(--space-large)"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-inline: 2px; }",
+ fixed: ".a { margin-inline: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-inline: var(--space-small) 16px; }",
+ fixed: ".a { margin-inline: var(--space-small) var(--space-large); }",
+ message: messages.rejected("var(--space-small) 16px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-block-end: 2px; }",
+ fixed: ".a { margin-block-end: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-block-start: 4px; }",
+ fixed: ".a { margin-block-start: var(--space-xsmall); }",
+ message: messages.rejected("4px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-inline-end: 8px; }",
+ fixed: ".a { margin-inline-end: var(--space-small); }",
+ message: messages.rejected("8px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-inline-start: 12px; }",
+ fixed: ".a { margin-inline-start: var(--space-medium); }",
+ message: messages.rejected("12px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-top: 16px; }",
+ fixed: ".a { margin-top: var(--space-large); }",
+ message: messages.rejected("16px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-right: 24px; }",
+ fixed: ".a { margin-right: var(--space-xlarge); }",
+ message: messages.rejected("24px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-bottom: 32px; }",
+ fixed: ".a { margin-bottom: var(--space-xxlarge); }",
+ message: messages.rejected("32px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-left: 2px; }",
+ fixed: ".a { margin-left: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { padding: 2px; }",
+ fixed: ".a { padding: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { padding: 8px 16px; }",
+ fixed: ".a { padding: var(--space-small) var(--space-large); }",
+ message: messages.rejected("8px 16px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { padding: 8px 16px 4px; }",
+ fixed:
+ ".a { padding: var(--space-small) var(--space-large) var(--space-xsmall); }",
+ message: messages.rejected("8px 16px 4px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { padding: 8px 16px 4px 12px; }",
+ fixed:
+ ".a { padding: var(--space-small) var(--space-large) var(--space-xsmall) var(--space-medium); }",
+ message: messages.rejected("8px 16px 4px 12px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { padding-block: 2px; }",
+ fixed: ".a { padding-block: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { padding-block: 8px var(--space-large); }",
+ fixed: ".a { padding-block: var(--space-small) var(--space-large); }",
+ message: messages.rejected("8px var(--space-large)"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { padding-inline: 2px; }",
+ fixed: ".a { padding-inline: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { padding-inline: var(--space-small) 16px; }",
+ fixed: ".a { padding-inline: var(--space-small) var(--space-large); }",
+ message: messages.rejected("var(--space-small) 16px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-block-end: 2px; }",
+ fixed: ".a { margin-block-end: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-block-start: 4px; }",
+ fixed: ".a { margin-block-start: var(--space-xsmall); }",
+ message: messages.rejected("4px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-inline-end: 8px; }",
+ fixed: ".a { margin-inline-end: var(--space-small); }",
+ message: messages.rejected("8px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-inline-start: 12px; }",
+ fixed: ".a { margin-inline-start: var(--space-medium); }",
+ message: messages.rejected("12px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-top: 16px; }",
+ fixed: ".a { margin-top: var(--space-large); }",
+ message: messages.rejected("16px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-right: 24px; }",
+ fixed: ".a { margin-right: var(--space-xlarge); }",
+ message: messages.rejected("24px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-bottom: 32px; }",
+ fixed: ".a { margin-bottom: var(--space-xxlarge); }",
+ message: messages.rejected("32px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { margin-left: 2px; }",
+ fixed: ".a { margin-left: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { inset: 2px; }",
+ fixed: ".a { inset: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { inset: 8px 16px; }",
+ fixed: ".a { inset: var(--space-small) var(--space-large); }",
+ message: messages.rejected("8px 16px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { inset: 8px 16px 4px; }",
+ fixed:
+ ".a { inset: var(--space-small) var(--space-large) var(--space-xsmall); }",
+ message: messages.rejected("8px 16px 4px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { inset: 8px 16px 4px 12px; }",
+ fixed:
+ ".a { inset: var(--space-small) var(--space-large) var(--space-xsmall) var(--space-medium); }",
+ message: messages.rejected("8px 16px 4px 12px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { inset-block: 2px; }",
+ fixed: ".a { inset-block: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { inset-block: 8px var(--space-large); }",
+ fixed: ".a { inset-block: var(--space-small) var(--space-large); }",
+ message: messages.rejected("8px var(--space-large)"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { inset-inline: 2px; }",
+ fixed: ".a { inset-inline: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { inset-inline: var(--space-small) 16px; }",
+ fixed: ".a { inset-inline: var(--space-small) var(--space-large); }",
+ message: messages.rejected("var(--space-small) 16px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { inset-block-end: 2px; }",
+ fixed: ".a { inset-block-end: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { inset-block-start: 4px; }",
+ fixed: ".a { inset-block-start: var(--space-xsmall); }",
+ message: messages.rejected("4px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { inset-inline-end: 8px; }",
+ fixed: ".a { inset-inline-end: var(--space-small); }",
+ message: messages.rejected("8px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { inset-inline-start: 12px; }",
+ fixed: ".a { inset-inline-start: var(--space-medium); }",
+ message: messages.rejected("12px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { top: 2px; }",
+ fixed: ".a { top: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { right: 4px; }",
+ fixed: ".a { right: var(--space-xsmall); }",
+ message: messages.rejected("4px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { bottom: 8px; }",
+ fixed: ".a { bottom: var(--space-small); }",
+ message: messages.rejected("8px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { left: 32px; }",
+ fixed: ".a { left: var(--space-xxlarge); }",
+ message: messages.rejected("32px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { gap: 2px; }",
+ fixed: ".a { gap: var(--space-xxsmall); }",
+ message: messages.rejected("2px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { gap: 24px 32px; }",
+ fixed: ".a { gap: var(--space-xlarge) var(--space-xxlarge); }",
+ message: messages.rejected("24px 32px"),
+ description:
+ "Space values in shorthand should be fixed to use a design token.",
+ },
+ {
+ code: ".a { column-gap: 24px; }",
+ fixed: ".a { column-gap: var(--space-xlarge); }",
+ message: messages.rejected("24px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ {
+ code: ".a { row-gap: 16px; }",
+ fixed: ".a { row-gap: var(--space-large); }",
+ message: messages.rejected("16px"),
+ description: "Space value in px should be fixed to use a design token.",
+ },
+ ],
+});