commit b0c5a2223b93f7d631914423d166290ddb77a3ff
parent d754e748bbe1327276fcd42a09d630067303907f
Author: dustin-jw <dwhisman@mozilla.com>
Date: Thu, 16 Oct 2025 16:19:03 +0000
Bug 1988863 - Add a Stylelint rule to enforce using text-color tokens r=frontend-codestyle-reviewers,hjones
Differential Revision: https://phabricator.services.mozilla.com/D268022
Diffstat:
8 files changed, 551 insertions(+), 9 deletions(-)
diff --git a/.stylelintrc.js b/.stylelintrc.js
@@ -270,12 +270,13 @@ module.exports = {
},
],
"stylelint-plugin-mozilla/no-base-design-tokens": true,
- "stylelint-plugin-mozilla/use-border-radius-tokens": true,
+ "stylelint-plugin-mozilla/use-background-color-tokens": true,
"stylelint-plugin-mozilla/use-border-color-tokens": true,
+ "stylelint-plugin-mozilla/use-border-radius-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,
- "stylelint-plugin-mozilla/use-background-color-tokens": true,
+ "stylelint-plugin-mozilla/use-text-color-tokens": true,
},
overrides: [
@@ -416,12 +417,13 @@ module.exports = {
"browser/components/backup/content/archive.css",
],
rules: {
- "stylelint-plugin-mozilla/use-border-radius-tokens": false,
+ "stylelint-plugin-mozilla/use-background-color-tokens": false,
"stylelint-plugin-mozilla/use-border-color-tokens": false,
+ "stylelint-plugin-mozilla/use-border-radius-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,
- "stylelint-plugin-mozilla/use-background-color-tokens": false,
+ "stylelint-plugin-mozilla/use-text-color-tokens": false,
},
},
{
@@ -431,10 +433,11 @@ module.exports = {
"devtools/client/aboutdebugging/src/**",
],
rules: {
- "stylelint-plugin-mozilla/use-border-radius-tokens": true,
+ "stylelint-plugin-mozilla/use-background-color-tokens": false,
"stylelint-plugin-mozilla/use-border-color-tokens": false,
+ "stylelint-plugin-mozilla/use-border-radius-tokens": true,
"stylelint-plugin-mozilla/use-space-tokens": false,
- "stylelint-plugin-mozilla/use-background-color-tokens": false,
+ "stylelint-plugin-mozilla/use-text-color-tokens": false,
},
},
],
diff --git a/docs/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-text-color-tokens.rst b/docs/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-text-color-tokens.rst
@@ -0,0 +1,105 @@
+=====================
+use-text-color-tokens
+=====================
+
+This rule checks that CSS declarations use text-color design token variables
+instead of hard-coded values. This ensures consistent text-color across
+the application and makes it easier to maintain design system adoption.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: css
+
+ .card {
+ color: #191919;
+ }
+
+.. code-block:: css
+
+ .custom-button {
+ color: rgba(42 42 42 / 0.15);
+ }
+
+.. code-block:: css
+
+ button:hover {
+ color: rgba(0 0 0 / 0.25);
+ }
+
+.. code-block:: css
+
+ :root {
+ --my-token: blue;
+ }
+
+ .my-button {
+ color: var(--my-token, oklch(55% 0.21 15));
+ }
+
+Examples of correct token usage for this rule:
+----------------------------------------------
+
+.. code-block:: css
+
+ .card {
+ color: var(--text-color);
+ }
+
+.. code-block:: css
+
+ .custom-button {
+ color: var(--text-color);
+ }
+
+.. code-block:: css
+
+ button:hover {
+ color: --text-color;
+ }
+
+.. code-block:: css
+
+ /* You may set a fallback for a token. */
+
+ .my-button {
+ color: var(--text-color, oklch(55% 0.21 15));
+ }
+
+.. code-block:: css
+
+ /* Local CSS variables that reference valid text-color tokens are allowed */
+
+ :root {
+ --my-token: var(--text-color);
+ }
+
+ .my-button {
+ color: var(--my-token, oklch(55% 0.21 15));
+ }
+
+The rule also allows these values non-token values:
+
+.. code-block:: css
+
+ .inherited-text-color{
+ color: inherit;
+ }
+
+.. code-block:: css
+
+ .unset-text-color {
+ color: unset;
+ }
+
+.. code-block:: css
+
+ .initial-text-color {
+ color: initial;
+ }
+
+.. code-block:: css
+
+ .current-text-color {
+ color: currentColor;
+ }
diff --git a/stylelint-rollouts.config.js b/stylelint-rollouts.config.js
@@ -1154,4 +1154,243 @@ module.exports = [
"tools/tryselect/selectors/chooser/static/style.css",
],
},
+ {
+ // stylelint fixes for this rule will be addressed in Bug 1994016
+ name: "rollout-use-text-color-tokens",
+ rules: {
+ "stylelint-plugin-mozilla/use-text-color-tokens": null,
+ },
+ files: [
+ "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/aboutLoginsImportReport.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-item.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/contextualidentity/content/usercontext.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/opentabs-tab-row.css",
+ "browser/components/firefoxview/view-syncedtabs.css",
+ "browser/components/genai/content/link-preview-card.css",
+ "browser/components/ipprotection/content/ipprotection-content.css",
+ "browser/components/preferences/widgets/nav-notice/nav-notice.css",
+ "browser/components/profiles/content/edit-profile-card.css",
+ "browser/components/profiles/content/profile-avatar-selector.css",
+ "browser/components/profiles/content/profiles-theme-card.css",
+ "browser/components/protections/content/protections.css",
+ "browser/components/screenshots/overlay/overlay.css",
+ "browser/components/screenshots/screenshots-buttons.css",
+ "browser/components/search/content/contentSearchUI.css",
+ "browser/components/search/test/browser/telemetry/serp.css",
+ "browser/components/sidebar/sidebar-customize.css",
+ "browser/components/sidebar/sidebar-pins-promo.css",
+ "browser/components/sidebar/sidebar.css",
+ "browser/components/urlbar/tests/browser/dynamicResult0.css",
+ "browser/components/urlbar/tests/browser/dynamicResult1.css",
+ "browser/extensions/formautofill/skin/shared/editDialog-shared.css",
+ "browser/extensions/newtab/content-src/components/A11yLinkButton/_A11yLinkButton.scss",
+ "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/CustomizeMenu/_CustomizeMenu.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamBase/_DiscoveryStreamBase.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/AdBanner/_AdBanner.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/CardGrid/_CardGrid.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/CardSections/_CardSections.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/DSCard/_DSCard.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/DSContextFooter/_DSContextFooter.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/DSEmptyState/_DSEmptyState.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/DSMessage/_DSMessage.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/InterestPicker/_InterestPicker.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/Navigation/_Navigation.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/PersonalizedCard/_PersonalizedCard.scss",
+ "browser/extensions/newtab/content-src/components/DiscoveryStreamComponents/SectionTitle/_SectionTitle.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/DiscoveryStreamComponents/TrendingSearches/_TrendingSearches.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/Sections/_Sections.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/_icons.scss",
+ "browser/extensions/newtab/content-src/styles/activity-stream.scss",
+ "browser/extensions/webcompat/about-compat/aboutCompat.css",
+ "browser/themes/linux/browser.css",
+ "browser/themes/linux/places/organizer.css",
+ "browser/themes/osx/browser.css",
+ "browser/themes/osx/places/organizer.css",
+ "browser/themes/shared/UITour.css",
+ "browser/themes/shared/aboutSessionRestore.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/controlcenter/panel.css",
+ "browser/themes/shared/customizableui/customizeMode.css",
+ "browser/themes/shared/customizableui/panelUI-shared.css",
+ "browser/themes/shared/downloads/downloads.inc.css",
+ "browser/themes/shared/formautofill-notification.css",
+ "browser/themes/shared/identity-block/identity-block.css",
+ "browser/themes/shared/migration/migration-wizard.css",
+ "browser/themes/shared/pageInfo.css",
+ "browser/themes/shared/places/sidebar.css",
+ "browser/themes/shared/places/tree-icons.css",
+ "browser/themes/shared/preferences/fxaPairDevice.css",
+ "browser/themes/shared/preferences/preferences.css",
+ "browser/themes/shared/privatebrowsing/aboutPrivateBrowsing.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/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/toolbarbuttons.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/places/organizer.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/security/test/sri/style1.css",
+ "dom/security/test/sri/style3.css",
+ "dom/security/test/sri/style4.css",
+ "dom/security/test/sri/style5.css",
+ "dom/security/test/sri/style6.css",
+ "dom/security/test/sri/style_301.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",
+ "layout/inspector/tests/bug1202095-2.css",
+ "layout/style/TopLevelImageDocument.css",
+ "layout/style/res/forms.css",
+ "layout/style/res/html.css",
+ "layout/style/res/ua.css",
+ "layout/style/res/viewsource.css",
+ "layout/style/test/chrome/bug535806-css.css",
+ "layout/style/test/chrome/import_useless1.css",
+ "layout/style/test/chrome/import_useless2.css",
+ "layout/style/test/file_bug1443344.css",
+ "layout/style/test/mapped.css",
+ "layout/style/test/post-redirect-1.css",
+ "layout/style/test/post-redirect-2.css",
+ "layout/style/test/post-redirect-3.css",
+ "netwerk/test/browser/res.css",
+ "testing/mochitest/static/harness.css",
+ "testing/mochitest/tests/SimpleTest/test.css",
+ "testing/mozbase/mozlog/mozlog/formatters/html/style.css",
+ "testing/talos/talos/tests/scroll/reader.css",
+ "testing/web-platform/mozilla/tests/fetch/fetchpriority/support/resources/dummy.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/certviewer/content/components/certificate-section.css",
+ "toolkit/components/certviewer/content/components/info-item.css",
+ "toolkit/components/certviewer/content/components/list-item.css",
+ "toolkit/components/extensions/test/mochitest/file_style_bad.css",
+ "toolkit/components/extensions/test/mochitest/file_style_good.css",
+ "toolkit/components/extensions/test/mochitest/file_style_redirect.css",
+ "toolkit/components/extensions/test/xpcshell/data/file_style_bad.css",
+ "toolkit/components/extensions/test/xpcshell/data/file_style_good.css",
+ "toolkit/components/extensions/test/xpcshell/data/file_style_redirect.css",
+ "toolkit/components/extensions/test/xpcshell/data/file_stylesheet_cache.css",
+ "toolkit/components/normandy/content/about-studies/about-studies.css",
+ "toolkit/components/printing/content/printPagination.css",
+ "toolkit/components/printing/content/printPreview.css",
+ "toolkit/components/satchel/megalist/content/components/login-form/login-form.css",
+ "toolkit/components/satchel/megalist/content/components/password-card/password-card.css",
+ "toolkit/components/satchel/megalist/content/megalist.css",
+ "toolkit/content/aboutLogging/aboutLogging.css",
+ "toolkit/content/aboutMozilla.css",
+ "toolkit/content/aboutwebrtc/aboutWebrtc.css",
+ "toolkit/content/widgets/infobar.css",
+ "toolkit/content/widgets/moz-breadcrumb-group/moz-breadcrumb.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-button.css",
+ "toolkit/content/widgets/moz-promo/moz-promo.css",
+ "toolkit/content/xul.css",
+ "toolkit/crashreporter/content/crashes.css",
+ "toolkit/mozapps/extensions/content/aboutaddons.css",
+ "toolkit/mozapps/extensions/content/shortcuts.css",
+ "toolkit/themes/linux/global/autocomplete.css",
+ "toolkit/themes/linux/global/richlistbox.css",
+ "toolkit/themes/linux/mozapps/update/updates.css",
+ "toolkit/themes/mobile/global/aboutMemory.css",
+ "toolkit/themes/mobile/global/aboutSupport.css",
+ "toolkit/themes/osx/global/autocomplete.css",
+ "toolkit/themes/osx/global/button.css",
+ "toolkit/themes/osx/global/richlistbox.css",
+ "toolkit/themes/osx/mozapps/handling/handling.css",
+ "toolkit/themes/osx/mozapps/update/updates.css",
+ "toolkit/themes/shared/aboutReader.css",
+ "toolkit/themes/shared/aboutSupport.css",
+ "toolkit/themes/shared/alert.css",
+ "toolkit/themes/shared/checkbox.css",
+ "toolkit/themes/shared/datetimeinputpickers.css",
+ "toolkit/themes/shared/design-system/tokens-table.css",
+ "toolkit/themes/shared/dirListing/dirListing.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/narrate.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/radio.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/global.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
@@ -11,6 +11,7 @@ import useFontSizeTokens from "./use-font-size-tokens.mjs";
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";
export default {
"no-base-design-tokens": noBaseDesignTokens,
@@ -20,4 +21,5 @@ export default {
"use-font-weight-tokens": useFontWeightTokens,
"use-space-tokens": useSpaceTokens,
"use-background-color-tokens": useBackgroundColorTokens,
+ "use-text-color-tokens": useTextColorTokens,
};
diff --git a/tools/lint/stylelint/stylelint-plugin-mozilla/rules/no-base-design-tokens.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/rules/no-base-design-tokens.mjs
@@ -2,8 +2,6 @@
* 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/. */
-/* eslint-env node */
-
import stylelint from "stylelint";
import { namespace } from "../helpers.mjs";
diff --git a/tools/lint/stylelint/stylelint-plugin-mozilla/rules/use-border-color-tokens.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/rules/use-border-color-tokens.mjs
@@ -1,7 +1,6 @@
/* 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/. */
-/* eslint-env node */
import stylelint from "stylelint";
import {
diff --git a/tools/lint/stylelint/stylelint-plugin-mozilla/rules/use-text-color-tokens.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/rules/use-text-color-tokens.mjs
@@ -0,0 +1,88 @@
+/* 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 {
+ namespace,
+ createTokenNamesArray,
+ isValidTokenUsage,
+ usesRawColors,
+ createAllowList,
+ getLocalCustomProperties,
+} from "../helpers.mjs";
+
+const {
+ utils: { report, ruleMessages, validateOptions },
+} = stylelint;
+
+// Name our rule, set the error message, and link to meta
+const ruleName = namespace("use-text-color-tokens");
+
+const messages = ruleMessages(ruleName, {
+ rejected: value => `${value} should use a text-color design token.`,
+});
+
+const meta = {
+ url: "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/stylelint-plugin-mozilla/rules/use-text-color-tokens.html",
+ fixable: false,
+};
+
+const INCLUDE_CATEGORIES = ["text-color"];
+
+const tokenCSS = createTokenNamesArray(INCLUDE_CATEGORIES);
+
+// Allowed text-color values in CSS
+const ALLOW_LIST = createAllowList(["currentColor"]);
+
+const CSS_PROPERTIES = ["color"];
+
+const ruleFunction = primaryOption => {
+ return (root, result) => {
+ const validOptions = validateOptions(result, ruleName, {
+ actual: primaryOption,
+ possible: [true],
+ });
+
+ if (!validOptions) {
+ return;
+ }
+
+ // The first time through gathers our custom properties
+ const cssCustomProperties = getLocalCustomProperties(root);
+
+ // And then we validate our properties
+ 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
+ ) &&
+ !usesRawColors(declarations.value)
+ ) {
+ 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-text-color-tokens.tests.mjs b/tools/lint/stylelint/stylelint-plugin-mozilla/tests/use-text-color-tokens.tests.mjs
@@ -0,0 +1,108 @@
+/**
+ * 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 useTextColorTokens from "../rules/use-text-color-tokens.mjs";
+
+let plugin = stylelint.createPlugin(
+ useTextColorTokens.ruleName,
+ useTextColorTokens
+);
+let {
+ ruleName,
+ rule: { messages },
+} = plugin;
+
+testRule({
+ plugins: [plugin],
+ ruleName,
+ config: true,
+ fix: false,
+ accept: [
+ {
+ code: ".a { color: var(--text-color); }",
+ description: "Using text color token for color is valid.",
+ },
+ {
+ code: ".a { color: var(--text-color, #000); }",
+ description:
+ "Using text color token with fallback value for color is valid.",
+ },
+ {
+ code: `
+ :root { --local-color: var(--text-color); }
+ .a { color: var(--local-color); }
+ `,
+ description:
+ "Using locally defined variable that falls back to text color token for color is valid.",
+ },
+ {
+ code: ".a { color: inherit; }",
+ description: "Using keyword for color is valid.",
+ },
+ {
+ code: ".a { color: initial; }",
+ description: "Using keyword for color is valid.",
+ },
+ {
+ code: ".a { color: revert; }",
+ description: "Using keyword for color is valid.",
+ },
+ {
+ code: ".a { color: revert-layer; }",
+ description: "Using keyword for color is valid.",
+ },
+ {
+ code: ".a { color: unset; }",
+ description: "Using keyword for color is valid.",
+ },
+ {
+ code: ".a { color: currentColor; }",
+ description: "Using currentColor for color is valid.",
+ },
+ ],
+ reject: [
+ {
+ code: ".a { color: #000; }",
+ message: messages.rejected("#000"),
+ description: "#000 should use a text-color design token.",
+ },
+ {
+ code: ".a { color: rgba(42 42 42 / 0.15); }",
+ message: messages.rejected("rgba(42 42 42 / 0.15)"),
+ description:
+ "rgba(42 42 42 / 0.15) should use a text-color design token.",
+ },
+ {
+ code: ".a { color: oklch(69% 0.19 15); }",
+ message: messages.rejected("oklch(69% 0.19 15)"),
+ description: "oklch(69% 0.19 15) should use a text-color design token.",
+ },
+ {
+ code: ".a { color: AccentColorText; }",
+ message: messages.rejected("AccentColorText"),
+ description: "AccentColorText should use a text-color design token.",
+ },
+ {
+ code: ".a { color: var(--random-color, #000); }",
+ message: messages.rejected("var(--random-color, #000)"),
+ description:
+ "var(--random-color, #000) should use a text-color design token.",
+ },
+ {
+ code: `
+ :root { --custom-token: #666; }
+ .a { color: var(--custom-token); }
+ `,
+ message: messages.rejected("var(--custom-token)"),
+ description: "var(--custom-token) should use a text-color design token.",
+ },
+ ],
+});