tor-browser

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

commit 9ac0c56f0e8c4e169be9d68d0ce2d1352aab7f9f
parent c09107d2916a4c4c5dc425e5b3f454f1686ad837
Author: Jon Oliver <jooliver@mozilla.com>
Date:   Fri, 17 Oct 2025 16:56:49 +0000

Bug 1988864 - add box-shadow stylelint rule r=frontend-codestyle-reviewers,hjones

- Add use-box-shadow-tokens stylelint rule
- Add tests for use-box-shadow-tokens rule
- Add docs for use-box-shadow-tokens rule
- Update stylelint rollouts with exclusions for use-box-shadow-tokens rule

Differential Revision: https://phabricator.services.mozilla.com/D268200

Diffstat:
M.stylelintrc.js | 2++
Adocs/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-box-shadow-tokens.rst | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mstylelint-rollouts.config.js | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtools/lint/stylelint/stylelint-plugin-mozilla/rules/index.mjs | 2++
Atools/lint/stylelint/stylelint-plugin-mozilla/rules/use-box-shadow-tokens.mjs | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/lint/stylelint/stylelint-plugin-mozilla/tests/use-box-shadow-tokens.tests.mjs | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 406 insertions(+), 0 deletions(-)

diff --git a/.stylelintrc.js b/.stylelintrc.js @@ -277,6 +277,7 @@ module.exports = { "stylelint-plugin-mozilla/use-font-weight-tokens": true, "stylelint-plugin-mozilla/use-space-tokens": true, "stylelint-plugin-mozilla/use-text-color-tokens": true, + "stylelint-plugin-mozilla/use-box-shadow-tokens": true, }, overrides: [ @@ -424,6 +425,7 @@ module.exports = { "stylelint-plugin-mozilla/use-font-weight-tokens": false, "stylelint-plugin-mozilla/use-space-tokens": false, "stylelint-plugin-mozilla/use-text-color-tokens": false, + "stylelint-plugin-mozilla/use-box-shadow-tokens": false, }, }, { diff --git a/docs/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-box-shadow-tokens.rst b/docs/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-box-shadow-tokens.rst @@ -0,0 +1,112 @@ +======================== +use-box-shadow-tokens +======================== + +This rule checks that CSS declarations use box-shadow design token variables +instead of hardcoded values. This ensures consistent box-shadow usage across +the application and makes it easier to maintain design system consistency. + +Examples of incorrect code for this rule: +----------------------------------------- + +.. code-block:: css + + .button { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + +.. code-block:: css + + .element { + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); + } + +Examples of correct token usage for this rule: +---------------------------------------------- + +.. code-block:: css + + .card { + box-shadow: var(--box-shadow-card); + } + +.. code-block:: css + + .card-hover { + box-shadow: var(--box-shadow-card-hover); + } + +.. code-block:: css + + .level-1-shadow { + box-shadow: var(--box-shadow-level-1); + } + +.. code-block:: css + + .level-2-shadow { + box-shadow: var(--box-shadow-level-2); + } + +.. code-block:: css + + .level-3-shadow { + box-shadow: var(--box-shadow-level-3); + } + +.. code-block:: css + + .level-4-shadow { + box-shadow: var(--box-shadow-level-4); + } + +.. code-block:: css + + .popup { + box-shadow: var(--box-shadow-popup); + } + +.. code-block:: css + + .tab { + box-shadow: var(--box-shadow-tab); + } + + +The rule also allows these non-token values: + +.. code-block:: css + + .inherited-shadow { + box-shadow: inherit; + } + +.. code-block:: css + + .initial-shadow { + box-shadow: initial; + } + +.. code-block:: css + + .revert-shadow { + box-shadow: revert; + } + +.. code-block:: css + + .revert-layer-shadow { + box-shadow: revert-layer; + } + +.. code-block:: css + + .unset-shadow { + box-shadow: unset; + } + +.. code-block:: css + + .no-shadow { + box-shadow: none; + } diff --git a/stylelint-rollouts.config.js b/stylelint-rollouts.config.js @@ -1393,4 +1393,79 @@ module.exports = [ "tools/tryselect/selectors/chooser/static/style.css", ], }, + { + // stylelint fixes for this rule will be addressed in Bug 1993565 + name: "rollout-use-box-shadow-tokens", + rules: { + "stylelint-plugin-mozilla/use-box-shadow-tokens": null, + }, + files: [ + "browser/components/aboutlogins/content/components/confirmation-dialog.css", + "browser/components/aboutlogins/content/components/generic-dialog.css", + "browser/components/aboutlogins/content/components/login-alert.css", + "browser/components/aboutlogins/content/components/login-message-popup.css", + "browser/components/aboutlogins/content/components/remove-logins-dialog.css", + "browser/components/aboutwelcome/content-src/aboutwelcome.scss", + "browser/components/asrouter/content-src/styles/_feature-callout.scss", + "browser/components/firefoxview/card-container.css", + "browser/components/firefoxview/history.css", + "browser/components/genai/content/model-optin.css", + "browser/components/profiles/content/profile-avatar-selector.css", + "browser/components/protections/content/protections.css", + "browser/components/screenshots/overlay/overlay.css", + "browser/components/search/content/contentSearchUI.css", + "browser/components/urlbar/tests/browser/dynamicResult0.css", + "browser/components/urlbar/tests/browser/dynamicResult1.css", + "browser/extensions/newtab/content-src/components/Card/_Card.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/DiscoveryStreamAdmin/DiscoveryStreamAdmin.scss", + "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/_CardGrid.scss", + "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss", + "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/DSDismiss/_DSDismiss.scss", + "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/DSTextPromo/_DSTextPromo.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/_WallpaperFeatureHighlight.scss", + "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/TopicSelection/_TopicSelection.scss", + "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/TopicsWidget/_TopicsWidget.scss", + "browser/extensions/newtab/content-src/components/ErrorBoundary/_ErrorBoundary.scss", + "browser/extensions/newtab/content-src/components/ModalOverlay/_ModalOverlay.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/styles/_mixins.scss", + "browser/extensions/newtab/content-src/styles/_variables.scss", + "browser/extensions/newtab/content-src/styles/activity-stream.scss", + "browser/themes/shared/UITour.css", + "browser/themes/shared/addons/unified-extensions.css", + "browser/themes/shared/autocomplete.css", + "browser/themes/shared/browser-shared.css", + "browser/themes/shared/customizableui/customizeMode.css", + "browser/themes/shared/customizableui/panelUI-shared.css", + "browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css", + "browser/themes/shared/sidebar.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/urlbar-searchbar.css", + "browser/themes/windows/places/organizer.css", + "gfx/layers/layerviewer/tree.css", + "layout/style/res/ua.css", + "toolkit/components/aboutinference/content/aboutInference.css", + "toolkit/content/aboutTelemetry.css", + "toolkit/content/widgets/infobar.css", + "toolkit/mozapps/extensions/content/shortcuts.css", + "toolkit/themes/shared/aboutReader.css", + "toolkit/themes/shared/alert.css", + "toolkit/themes/shared/findbar.css", + "toolkit/themes/shared/in-content/common-shared.css", + "toolkit/themes/shared/pictureinpicture/player.css", + "toolkit/themes/shared/popup.css", + "toolkit/themes/shared/toolbarbutton.css", + "toolkit/themes/shared/tree/tree.css", + ], + }, ]; diff --git a/tools/lint/stylelint/stylelint-plugin-mozilla/rules/index.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/rules/index.mjs @@ -12,6 +12,7 @@ import useFontWeightTokens from "./use-font-weight-tokens.mjs"; import useSpaceTokens from "./use-space-tokens.mjs"; import useBackgroundColorTokens from "./use-background-color-tokens.mjs"; import useTextColorTokens from "./use-text-color-tokens.mjs"; +import useBoxShadowTokens from "./use-box-shadow-tokens.mjs"; export default { "no-base-design-tokens": noBaseDesignTokens, @@ -22,4 +23,5 @@ export default { "use-space-tokens": useSpaceTokens, "use-background-color-tokens": useBackgroundColorTokens, "use-text-color-tokens": useTextColorTokens, + "use-box-shadow-tokens": useBoxShadowTokens, }; diff --git a/tools/lint/stylelint/stylelint-plugin-mozilla/rules/use-box-shadow-tokens.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/rules/use-box-shadow-tokens.mjs @@ -0,0 +1,74 @@ +/* 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 { + createTokenNamesArray, + createAllowList, + getLocalCustomProperties, + isValidTokenUsage, + namespace, +} from "../helpers.mjs"; + +const { + utils: { report, ruleMessages, validateOptions }, +} = stylelint; + +const ruleName = namespace("use-box-shadow-tokens"); + +const messages = ruleMessages(ruleName, { + rejected: value => `${value} should use a box-shadow design token.`, +}); + +const meta = { + url: "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-box-shadow-tokens.html", + fixable: false, +}; + +const PROPERTY_NAME = "box-shadow"; + +const tokenCSS = createTokenNamesArray([PROPERTY_NAME]); + +const ALLOW_LIST = createAllowList(["none"]); + +const ruleFunction = primaryOption => { + return (root, result) => { + const validOptions = validateOptions(result, ruleName, { + actual: primaryOption, + possible: [true], + }); + + if (!validOptions) { + return; + } + + const cssCustomProperties = getLocalCustomProperties(root); + + root.walkDecls(PROPERTY_NAME, declarations => { + if ( + isValidTokenUsage( + declarations.value, + tokenCSS, + cssCustomProperties, + ALLOW_LIST + ) + ) { + return; + } + + report({ + message: messages.rejected(declarations.value), + node: declarations, + result, + ruleName, + }); + }); + }; +}; + +ruleFunction.ruleName = ruleName; +ruleFunction.messages = messages; +ruleFunction.meta = meta; + +export default ruleFunction; diff --git a/tools/lint/stylelint/stylelint-plugin-mozilla/tests/use-box-shadow-tokens.tests.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/tests/use-box-shadow-tokens.tests.mjs @@ -0,0 +1,141 @@ +/** + * 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/. + */ + +// 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 useBoxShadowTokens from "../rules/use-box-shadow-tokens.mjs"; + +let plugin = stylelint.createPlugin( + useBoxShadowTokens.ruleName, + useBoxShadowTokens +); +let { + ruleName, + rule: { messages }, +} = plugin; + +testRule({ + plugins: [plugin], + ruleName, + config: [true, { tokenType: "brand" }], + fix: false, + accept: [ + // allowed token values + { + code: ".a { box-shadow: var(--box-shadow-card); }", + description: "Using box-shadow-card token is valid.", + }, + { + code: ".a { box-shadow: var(--box-shadow-card-hover); }", + description: "Using box-shadow-card-hover token is valid.", + }, + { + code: ".a { box-shadow: var(--box-shadow-level-1); }", + description: "Using box-shadow-level-1 token is valid.", + }, + { + code: ".a { box-shadow: var(--box-shadow-level-2); }", + description: "Using box-shadow-level-2 token is valid.", + }, + { + code: ".a { box-shadow: var(--box-shadow-level-3); }", + description: "Using box-shadow-level-3 token is valid.", + }, + { + code: ".a { box-shadow: var(--box-shadow-level-4); }", + description: "Using box-shadow-level-4 token is valid.", + }, + { + code: ".a { box-shadow: var(--box-shadow-popup); }", + description: "Using box-shadow-popup token is valid.", + }, + { + code: ".a { box-shadow: var(--box-shadow-tab); }", + description: "Using box-shadow-tab token is valid.", + }, + // allowed CSS values + { + code: ".a { box-shadow: inherit; }", + description: "Using inherit is valid.", + }, + { + code: ".a { box-shadow: initial; }", + description: "Using initial is valid.", + }, + { + code: ".a { box-shadow: revert; }", + description: "Using revert is valid.", + }, + { + code: ".a { box-shadow: revert-layer; }", + description: "Using revert-layer is valid.", + }, + { + code: ".a { box-shadow: unset; }", + description: "Using unset is valid.", + }, + { + code: ".a { box-shadow: none; }", + description: "Using none keyword is valid.", + }, + // fallbacks and custom properties + { + code: ".a { box-shadow:var(--my-local, var(--box-shadow-level-1)); }", + description: + "Using a custom property with fallback to design token is valid.", + }, + { + code: ` + :root { --custom-token: var(--box-shadow-card); } + .a { box-shadow: var(--custom-token); } + `, + description: + "Using a custom property with fallback to a design token is valid.", + }, + ], + + reject: [ + { + code: ".a { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); }", + message: messages.rejected("0 1px 3px rgba(0, 0, 0, 0.12)"), + description: "Using hardcoded box-shadow should use a design token.", + }, + { + code: ".a { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); }", + message: messages.rejected( + "0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)" + ), + description: "Using multiple box-shadows should use a design token.", + }, + { + code: ".a { box-shadow: calc(var(--my-local) + 1px) 2px 4px rgba(0, 0, 0, 0.1); }", + message: messages.rejected( + "calc(var(--my-local) + 1px) 2px 4px rgba(0, 0, 0, 0.1)" + ), + description: + "Using a calc() with custom variables should use a design token.", + }, + { + code: ".a { box-shadow: var(--random-token, 0 2px 4px rgba(0, 0, 0, 0.1)); }", + message: messages.rejected( + "var(--random-token, 0 2px 4px rgba(0, 0, 0, 0.1))" + ), + description: "Using a custom property should use a design token.", + }, + { + code: ` + :root { --custom-token: 0 2px 4px rgba(0, 0, 0, 0.1); } + .a { box-shadow: var(--custom-token); } + `, + message: messages.rejected("var(--custom-token)"), + description: + "Using a custom property that does not resolve to a design token should use a design token.", + }, + ], +});