tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
M.stylelintrc.js | 3+++
Adocs/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-space-tokens.rst | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mstylelint-rollouts.config.js | 338+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtools/lint/stylelint/stylelint-plugin-mozilla/rules/index.mjs | 2++
Atools/lint/stylelint/stylelint-plugin-mozilla/rules/use-space-tokens.mjs | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/lint/stylelint/stylelint-plugin-mozilla/tests/use-space-tokens.tests.mjs | 1050+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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.", + }, + ], +});