tor-browser

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

commit ef292b5ecbe5cec8aadeb61688b490871a34b912
parent 549157b501218c26a3cee2699fd137bc41a16619
Author: Jon Oliver <jooliver@mozilla.com>
Date:   Wed,  8 Oct 2025 21:50:27 +0000

Bug 1988859 - add font-weight stylelint rule r=frontend-codestyle-reviewers,jules

- Add use-font-weight-tokens stylelint rule
- Add tests for use-font-weight-tokens rule
- Add docs for use-font-weight-tokens rule
- Update stylelint rollouts with exclusions for use-font-weight-tokens rule

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

Diffstat:
M.stylelintrc.js | 2++
Adocs/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-font-weight-tokens.rst | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mstylelint-rollouts.config.js | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtools/lint/stylelint/stylelint-plugin-mozilla/rules/index.mjs | 2++
Atools/lint/stylelint/stylelint-plugin-mozilla/rules/use-font-weight-tokens.mjs | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atools/lint/stylelint/stylelint-plugin-mozilla/tests/use-font-weight-tokens.tests.mjs | 183+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 584 insertions(+), 0 deletions(-)

diff --git a/.stylelintrc.js b/.stylelintrc.js @@ -273,6 +273,7 @@ module.exports = { "stylelint-plugin-mozilla/use-border-radius-tokens": true, "stylelint-plugin-mozilla/use-border-color-tokens": true, "stylelint-plugin-mozilla/use-font-size-tokens": true, + "stylelint-plugin-mozilla/use-font-weight-tokens": true, }, overrides: [ @@ -416,6 +417,7 @@ module.exports = { "stylelint-plugin-mozilla/use-border-radius-tokens": false, "stylelint-plugin-mozilla/use-border-color-tokens": false, "stylelint-plugin-mozilla/use-font-size-tokens": false, + "stylelint-plugin-mozilla/use-font-weight-tokens": false, }, }, { diff --git a/docs/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-font-weight-tokens.rst b/docs/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-font-weight-tokens.rst @@ -0,0 +1,166 @@ +======================== +use-font-weight-tokens +======================== + +This rule checks that CSS declarations use font-weight design token variables +instead of hardcoded values. This ensures consistent font-weight usage across +the application and makes it easier to maintain design system consistency. + +This rule is autofixable and can automatically replace some font-weight values +with appropriate design tokens where possible. + +Examples of incorrect code for this rule: +----------------------------------------- + +.. code-block:: css + + .normal-text { + font-weight: normal; + } + +.. code-block:: css + + .bold-text { + font-weight: bold; + } + +.. code-block:: css + + .bolder-text { + font-weight: bolder; + } + +.. code-block:: css + + .lighter-text { + font-weight: lighter; + } + +.. code-block:: css + + .custom-text { + font-weight: 400; + } + +.. code-block:: css + + .heading { + font-weight: 600; + } + +.. code-block:: css + + .bold-text { + font-weight: 700; + } + +.. code-block:: css + + .light-text { + font-weight: 300; + } + +.. code-block:: css + + .heavy-text { + font-weight: 900; + } + +Examples of correct token usage for this rule: +---------------------------------------------- + +.. code-block:: css + + .normal-text { + font-weight: var(--font-weight); + } + +.. code-block:: css + + .bold-text { + font-weight: var(--font-weight-bold); + } + +.. code-block:: css + + .button-text { + font-weight: var(--button-font-weight); + } + +.. code-block:: css + + .heading-text { + font-weight: var(--heading-font-weight); + } + +.. code-block:: css + + /* Local CSS variables that reference valid font-weight tokens are allowed */ + :root { + --custom-font-weight: var(--font-weight-bold); + } + + .custom-text { + font-weight: var(--custom-font-weight); + } + +.. code-block:: css + + .custom-text { + font-weight: var(--custom-font-weight, var(--font-weight-bold)); + } + +The rule also allows these non-token values: + +.. code-block:: css + + .inherited-text { + font-weight: inherit; + } + +.. code-block:: css + + .initial-text { + font-weight: initial; + } + +.. code-block:: css + + .unset-text { + font-weight: unset; + } + +Autofix functionality +--------------------- + +This rule can automatically fix some violations by replacing values with +appropriate design tokens: + +- ``normal`` → ``var(--font-weight)`` +- ``600`` → ``var(--font-weight-bold)`` + +Examples of autofixable violations: + +.. code-block:: css + + /* Before */ + .normal-text { + font-weight: normal; + } + + /* After autofix */ + .normal-text { + font-weight: var(--font-weight); + } + +.. code-block:: css + + /* Before */ + .bold-text { + font-weight: 600; + } + + /* After autofix */ + .bold-text { + font-weight: var(--font-weight-bold); + } diff --git a/stylelint-rollouts.config.js b/stylelint-rollouts.config.js @@ -462,4 +462,136 @@ module.exports = [ "tools/tryselect/selectors/chooser/static/style.css", ], }, + { + // stylelint fixes for this rule will be addressed in Bug 1992736 + name: "rollout-use-font-weight-tokens", + rules: { + "stylelint-plugin-mozilla/use-font-weight-tokens": null, + }, + files: [ + "browser/base/content/aboutDialog.css", + "browser/base/content/safeMode.css", + "browser/branding/aurora/stubinstaller/profile_cleanup_page.css", + "browser/branding/nightly/stubinstaller/profile_cleanup_page.css", + "browser/branding/official/stubinstaller/installing_page.css", + "browser/branding/official/stubinstaller/profile_cleanup_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/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-list-lit-item.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/password-rules-tooltip.css", + "browser/components/backup/content/password-validation-inputs.css", + "browser/components/backup/content/restore-from-backup.css", + "browser/components/backup/content/turn-on-scheduled-backups.css", + "browser/components/firefoxview/card-container.css", + "browser/components/firefoxview/fxview-tab-row.css", + "browser/components/firefoxview/history.css", + "browser/components/firefoxview/view-syncedtabs.css", + "browser/components/genai/content/smart-assist.css", + "browser/components/protections/content/protections.css", + "browser/components/search/content/contentSearchUI.css", + "browser/components/tabunloader/content/aboutUnloads.css", + "browser/components/textrecognition/textrecognition.css", + "browser/extensions/webcompat/about-compat/aboutCompat.css", + "browser/themes/linux/browser.css", + "browser/themes/shared/UITour.css", + "browser/themes/shared/aboutTabCrashed.css", + "browser/themes/shared/addon-notification.css", + "browser/themes/shared/addons/extension-controlled.css", + "browser/themes/shared/autocomplete.css", + "browser/themes/shared/blockedSite.css", + "browser/themes/shared/browser-shared.css", + "browser/themes/shared/controlcenter/panel.css", + "browser/themes/shared/customizableui/customizeMode.css", + "browser/themes/shared/customizableui/panelUI-shared.css", + "browser/themes/shared/identity-credential-notification.css", + "browser/themes/shared/migration/migration-wizard.css", + "browser/themes/shared/pageInfo.css", + "browser/themes/shared/places/editBookmarkPanel.css", + "browser/themes/shared/preferences/fxaPairDevice.css", + "browser/themes/shared/preferences/preferences.css", + "browser/themes/shared/preferences/privacy.css", + "browser/themes/shared/preferences/siteDataSettings.css", + "browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.css", + "browser/themes/shared/search/searchbar.css", + "browser/themes/shared/setDesktopBackground.css", + "browser/themes/shared/tabbrowser/ctrlTab.css", + "browser/themes/shared/tabbrowser/fullscreen-and-pointerlock.css", + "browser/themes/shared/tabbrowser/tabs.css", + "browser/themes/shared/translations/panel.css", + "browser/themes/shared/urlbar-dynamic-results.css", + "browser/themes/shared/urlbar-searchbar.css", + "browser/themes/windows/browser.css", + "dom/crypto/test/test_WebCrypto.css", + "dom/events/test/pointerevents/wpt/pointerevent_styles.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/xmlbase/xmlbase.css", + "layout/mathml/mathml.css", + "layout/style/res/forms.css", + "layout/style/res/html.css", + "layout/style/res/ua.css", + "layout/style/res/viewsource.css", + "security/manager/pki/resources/content/clientauthask.css", + "security/manager/pki/resources/content/exceptionDialog.css", + "testing/mozbase/mozlog/mozlog/formatters/html/style.css", + "toolkit/components/aboutconfig/content/aboutconfig.css", + "toolkit/components/aboutinference/content/aboutInference.css", + "toolkit/components/aboutmemory/content/aboutMemory.css", + "toolkit/components/aboutprocesses/content/aboutProcesses.css", + "toolkit/components/aboutwebauthn/content/aboutWebauthn.css", + "toolkit/components/aboutwindowsmessages/content/aboutWindowsMessages.css", + "toolkit/components/certviewer/content/components/info-group.css", + "toolkit/components/certviewer/content/components/info-item.css", + "toolkit/components/normandy/content/about-studies/about-studies.css", + "toolkit/content/aboutLogging/aboutLogging.css", + "toolkit/content/aboutTelemetry.css", + "toolkit/content/aboutUrlClassifier.css", + "toolkit/content/aboutwebrtc/aboutWebrtc.css", + "toolkit/content/resetProfile.css", + "toolkit/content/widgets/moz-box-common.css", + "toolkit/content/widgets/moz-message-bar/moz-message-bar.css", + "toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css", + "toolkit/mozapps/extensions/content/aboutaddons.css", + "toolkit/mozapps/extensions/content/shortcuts.css", + "toolkit/mozapps/handling/content/handler.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/wizard.css", + "toolkit/themes/osx/mozapps/handling/handling.css", + "toolkit/themes/osx/mozapps/update/updates.css", + "toolkit/themes/shared/aboutHttpsOnlyError.css", + "toolkit/themes/shared/aboutNetError.css", + "toolkit/themes/shared/aboutReader.css", + "toolkit/themes/shared/aboutSupport.css", + "toolkit/themes/shared/alert.css", + "toolkit/themes/shared/appPicker.css", + "toolkit/themes/shared/commonDialog.css", + "toolkit/themes/shared/datetimeinputpickers.css", + "toolkit/themes/shared/dirListing/dirListing.css", + "toolkit/themes/shared/downloads/unknownContentType.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/toolbarbutton.css", + "toolkit/themes/windows/global/global.css", + "toolkit/themes/windows/global/wizard.css", + "toolkit/themes/windows/mozapps/handling/handling.css", + "toolkit/themes/windows/mozapps/update/updates.css", + ], + }, ]; diff --git a/tools/lint/stylelint/stylelint-plugin-mozilla/rules/index.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/rules/index.mjs @@ -8,10 +8,12 @@ import noBaseDesignTokens from "./no-base-design-tokens.mjs"; 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"; export default { "no-base-design-tokens": noBaseDesignTokens, "use-border-radius-tokens": useBorderRadiusTokens, "use-border-color-tokens": useBorderColorTokens, "use-font-size-tokens": useFontSizeTokens, + "use-font-weight-tokens": useFontWeightTokens, }; diff --git a/tools/lint/stylelint/stylelint-plugin-mozilla/rules/use-font-weight-tokens.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/rules/use-font-weight-tokens.mjs @@ -0,0 +1,99 @@ +/* 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 { + createTokenNamesArray, + createAllowList, + createRawValuesObject, + getLocalCustomProperties, + isValidTokenUsage, + namespace, +} from "../helpers.mjs"; + +const { + utils: { report, ruleMessages, validateOptions }, +} = stylelint; + +const ruleName = namespace("use-font-weight-tokens"); + +const messages = ruleMessages(ruleName, { + rejected: value => `${value} should use a font-weight design token.`, +}); + +const meta = { + url: "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-font-weight-tokens.html", + fixable: true, +}; + +const PROPERTY_NAME = "font-weight"; + +const tokenCSS = createTokenNamesArray([PROPERTY_NAME]); + +const ALLOW_LIST = createAllowList(); + +const tokenFixMap = createRawValuesObject([PROPERTY_NAME]); + +const ruleFunction = primaryOption => { + return (root, result) => { + const validOptions = validateOptions(result, ruleName, { + actual: primaryOption, + possible: [true], + }); + + if (!validOptions) { + return; + } + + const cssCustomProperties = getLocalCustomProperties(root); + + root.walkDecls(declarations => { + // ignore properties other than font-weight + if (declarations.prop !== PROPERTY_NAME) { + return; + } + + if ( + isValidTokenUsage( + 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 = tokenFixMap[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-font-weight-tokens.tests.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/tests/use-font-weight-tokens.tests.mjs @@ -0,0 +1,183 @@ +/** + * 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 useFontWeightTokens from "../rules/use-font-weight-tokens.mjs"; + +let plugin = stylelint.createPlugin( + useFontWeightTokens.ruleName, + useFontWeightTokens +); +let { + ruleName, + rule: { messages }, +} = plugin; + +testRule({ + plugins: [plugin], + ruleName, + config: [true, { tokenType: "brand" }], + fix: false, + accept: [ + // allowed token values + { + code: ".a { font-weight: var(--font-weight); }", + description: "Using font-weight token is valid.", + }, + { + code: ".a { font-weight: var(--font-weight-bold); }", + description: "Using font-weight-bold token is valid.", + }, + { + code: ".a { font-weight: var(--button-font-weight); }", + description: "Using button-font-weight token is valid.", + }, + { + code: ".a { font-weight: var(--heading-font-weight); }", + description: "Using heading-font-weight token is valid.", + }, + // allowed CSS values + { + code: ".a { font-weight: inherit; }", + description: "Using inherit is valid.", + }, + { + code: ".a { font-weight: initial; }", + description: "Using initial is valid.", + }, + { + code: ".a { font-weight: unset; }", + description: "Using unset is valid.", + }, + { + code: ".a { font-weight:var(--my-local, var(--font-weight-bold)); }", + description: + "Using a custom property with fallback to design token is valid.", + }, + { + code: ` + :root { --custom-token: var(--font-weight-bold); } + .a { font-weight: var(--custom-token); } + `, + description: + "Using a custom property with fallback to a design token is valid.", + }, + ], + + reject: [ + { + code: ".a { font-weight: normal; }", + message: messages.rejected("normal"), + description: "Using normal keyword should use a design token.", + }, + { + code: ".a { font-weight: bold; }", + message: messages.rejected("bold"), + description: "Using bold keyword should use a design token.", + }, + { + code: ".a { font-weight: bolder; }", + message: messages.rejected("bolder"), + description: "Using bolder keyword should use a design token.", + }, + { + code: ".a { font-weight: lighter; }", + message: messages.rejected("lighter"), + description: "Using lighter keyword should use a design token.", + }, + { + code: ".a { font-weight: 100; }", + message: messages.rejected("100"), + description: "Using a numeric value should use a design token.", + }, + { + code: ".a { font-weight: 200; }", + message: messages.rejected("200"), + description: "Using a numeric value should use a design token.", + }, + { + code: ".a { font-weight: 300; }", + message: messages.rejected("300"), + description: "Using a numeric value should use a design token.", + }, + { + code: ".a { font-weight: 400; }", + message: messages.rejected("400"), + description: "Using a numeric value should use a design token.", + }, + { + code: ".a { font-weight: 500; }", + message: messages.rejected("500"), + description: "Using a numeric value should use a design token.", + }, + { + code: ".a { font-weight: 600; }", + message: messages.rejected("600"), + description: "Using a numeric value should use a design token.", + }, + { + code: ".a { font-weight: 700; }", + message: messages.rejected("700"), + description: "Using a numeric value should use a design token.", + }, + { + code: ".a { font-weight: 800; }", + message: messages.rejected("800"), + description: "Using a numeric value should use a design token.", + }, + { + code: ".a { font-weight: 900; }", + message: messages.rejected("900"), + description: "Using a numeric value should use a design token.", + }, + { + code: ".a { font-weight: calc(var(--my-local) + 100); }", + message: messages.rejected("calc(var(--my-local) + 100)"), + description: + "Using a calc() with custom variables should use a design token.", + }, + { + code: ".a { font-weight: var(--random-token, 600); }", + message: messages.rejected("var(--random-token, 600)"), + description: "Using a custom property should use a design token.", + }, + { + code: ` + :root { --custom-token: 600; } + .a { font-weight: 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.", + }, + ], +}); + +// autofix tests +testRule({ + plugins: [plugin], + ruleName, + config: [true, { tokenType: "brand" }], + fix: true, + reject: [ + { + code: ".a { font-weight: normal; }", + fixed: ".a { font-weight: var(--font-weight); }", + message: messages.rejected("normal"), + description: "Normal keyword should be fixed to use design token.", + }, + { + code: ".a { font-weight: 600; }", + fixed: ".a { font-weight: var(--font-weight-bold); }", + message: messages.rejected("600"), + description: "Numeric value should be fixed to use design token.", + }, + ], +});