From 3e474a0fb9ac86b0bf42da66a661f418e0499e82 Mon Sep 17 00:00:00 2001 From: Josh Wooding <12938082+joshwooding@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:20:32 +0000 Subject: [PATCH] Improve deprecated token tooling (#4619) --- .changeset/cool-spoons-judge.md | 5 + .changeset/violet-dancers-hope.md | 12 ++ .stylelintrc.json | 35 +++++ package.json | 1 + packages/theme/css/deprecated/fade.css | 1 - packages/theme/css/deprecated/palette.css | 14 -- packages/theme/css/foundations/fade.css | 2 + .../correct-theme-token-usage/index.js | 64 +------- .../no-deprecated-token-usage/index.js | 142 ++++++++++++++++++ 9 files changed, 200 insertions(+), 76 deletions(-) create mode 100644 .changeset/cool-spoons-judge.md create mode 100644 .changeset/violet-dancers-hope.md create mode 100644 tooling/stylelint/no-deprecated-token-usage/index.js diff --git a/.changeset/cool-spoons-judge.md b/.changeset/cool-spoons-judge.md new file mode 100644 index 00000000000..baea8688a6c --- /dev/null +++ b/.changeset/cool-spoons-judge.md @@ -0,0 +1,5 @@ +--- +"@salt-ds/theme": patch +--- + +Added missing fade token: `--salt-color-gray-500-fade-background`. diff --git a/.changeset/violet-dancers-hope.md b/.changeset/violet-dancers-hope.md new file mode 100644 index 00000000000..79bd5e19579 --- /dev/null +++ b/.changeset/violet-dancers-hope.md @@ -0,0 +1,12 @@ +--- +"@salt-ds/theme": patch +--- + +Removed incorrectly deprecated tokens: + +- `--salt-color-orange-600-fade-background` +- `--salt-palette-negative-foreground-disabled` +- `--salt-palette-positive-foreground-disabled` +- `--salt-palette-warning-border-disabled` +- `--salt-palette-accent-background-disabled` +- `--salt-palette-accent-border-disabled` diff --git a/.stylelintrc.json b/.stylelintrc.json index a79efd17bbb..3aa71112400 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,6 +1,7 @@ { "plugins": [ "./tooling/stylelint/correct-theme-token-usage/index.js", + "./tooling/stylelint/no-deprecated-token-usage/index.js", "./tooling/stylelint/custom-property-attributes-kebab-case/index.js", "./tooling/stylelint/custom-property-starts-with-component-name/index.js" ], @@ -8,6 +9,9 @@ "salt/correct-theme-token-usage": { "logLevel": "default" }, + "salt/no-deprecated-token-usage": { + "logLevel": "default" + }, "salt/custom-property-attributes-kebab-case": { "logLevel": "default" }, @@ -19,9 +23,40 @@ { "files": ["**/ag-grid-theme/**/*.css"], "rules": { + "salt/custom-property-attributes-kebab-case": null, + "salt/custom-property-starts-with-component-name": null, + "salt/no-deprecated-token-usage": [ + false, + { + "severity": "warning" + } + ] + } + }, + { + "files": ["**/theme/css/**/*.css"], + "rules": { + "salt/correct-theme-token-usage": null, "salt/custom-property-attributes-kebab-case": null, "salt/custom-property-starts-with-component-name": null } + }, + { + "files": ["**/theme/css/deprecated/**/*.css"], + "rules": { + "salt/no-deprecated-token-usage": null + } + }, + { + "files": ["**/lab/**/*.css"], + "rules": { + "salt/no-deprecated-token-usage": [ + false, + { + "severity": "warning" + } + ] + } } ] } diff --git a/package.json b/package.json index f07b7576099..d9f46fb1227 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "lint:style:icon": "yarn stylelint -f verbose \"packages/icons/src/**/*.css\"", "lint:style:lab": "yarn stylelint -f verbose \"packages/lab/src/**/*.css\"", "lint:style:ag-theme": "yarn stylelint -f verbose \"packages/ag-grid-theme/css/**/*.css\"", + "lint:style:theme": "yarn stylelint -f verbose \"packages/theme/css/**/*.css\"", "storybook": "storybook dev -p 6006", "build-storybook": "yarn build:ag-grid-theme && yarn bundle:css && storybook build --stats-json", "typecheck": "tsc --noEmit", diff --git a/packages/theme/css/deprecated/fade.css b/packages/theme/css/deprecated/fade.css index e5f96edea12..9877d62a1a1 100644 --- a/packages/theme/css/deprecated/fade.css +++ b/packages/theme/css/deprecated/fade.css @@ -2,7 +2,6 @@ --salt-color-orange-500-fade-foreground: rgba(234, 115, 25, var(--salt-palette-opacity-foreground)); --salt-color-orange-700-fade-foreground: rgba(214, 85, 19, var(--salt-palette-opacity-foreground)); --salt-color-orange-400-fade-background: rgba(238, 133, 43, var(--salt-palette-opacity-background)); - --salt-color-orange-600-fade-background: rgba(224, 101, 25, var(--salt-palette-opacity-background)); --salt-color-blue-300-fade-fill: rgba(51, 141, 205, var(--salt-palette-opacity-fill)); --salt-color-blue-500-fade-fill: rgba(38, 112, 169, var(--salt-palette-opacity-fill)); } diff --git a/packages/theme/css/deprecated/palette.css b/packages/theme/css/deprecated/palette.css index f0a3c386140..854e8c8b23c 100644 --- a/packages/theme/css/deprecated/palette.css +++ b/packages/theme/css/deprecated/palette.css @@ -51,16 +51,11 @@ --salt-palette-info-foreground-disabled: var(--salt-color-blue-500-fade-foreground); --salt-palette-info-foreground: var(--salt-color-blue-500); - --salt-palette-negative-foreground-disabled: var(--salt-color-red-700-fade-foreground); - - --salt-palette-positive-foreground-disabled: var(--salt-color-green-700-fade-foreground); - --salt-palette-success-border-disabled: var(--salt-color-green-500-fade-border); --salt-palette-success-foreground-disabled: var(--salt-color-green-500-fade-foreground); --salt-palette-success-foreground: var(--salt-color-green-500); --salt-palette-warning-foreground-disabled: var(--salt-color-orange-700-fade-foreground); - --salt-palette-warning-border-disabled: var(--salt-color-orange-700-fade-border); --salt-palette-warning-foreground: var(--salt-color-orange-700); /* Navigate */ @@ -83,9 +78,7 @@ --salt-palette-track-background-disabled: var(--salt-color-gray-60-fade-background); /* Accent */ - --salt-palette-accent-background-disabled: var(--salt-color-blue-500-fade-background); --salt-palette-accent-foreground-disabled: var(--salt-color-white-fade-foreground); - --salt-palette-accent-border-disabled: var(--salt-color-blue-500-fade-border); } .salt-theme[data-mode="dark"] { @@ -123,16 +116,11 @@ --salt-palette-info-foreground-disabled: var(--salt-color-blue-500-fade-foreground); --salt-palette-info-foreground: var(--salt-color-blue-500); - --salt-palette-negative-foreground-disabled: var(--salt-color-red-300-fade-foreground); - - --salt-palette-positive-foreground-disabled: var(--salt-color-green-300-fade-foreground); - --salt-palette-success-border-disabled: var(--salt-color-green-400-fade-border); --salt-palette-success-foreground-disabled: var(--salt-color-green-400-fade-foreground); --salt-palette-success-foreground: var(--salt-color-green-400); --salt-palette-warning-foreground-disabled: var(--salt-color-orange-500-fade-foreground); - --salt-palette-warning-border-disabled: var(--salt-color-orange-500-fade-border); --salt-palette-warning-foreground: var(--salt-color-orange-500); /* Navigate */ @@ -155,7 +143,5 @@ --salt-palette-track-background-disabled: var(--salt-color-gray-300-fade-background); /* Accent */ - --salt-palette-accent-background-disabled: var(--salt-color-blue-500-fade-background); --salt-palette-accent-foreground-disabled: var(--salt-color-white-fade-foreground); - --salt-palette-accent-border-disabled: var(--salt-color-blue-500-fade-border); } diff --git a/packages/theme/css/foundations/fade.css b/packages/theme/css/foundations/fade.css index d4d0d036c0d..0c6ef7228b6 100644 --- a/packages/theme/css/foundations/fade.css +++ b/packages/theme/css/foundations/fade.css @@ -50,12 +50,14 @@ --salt-color-gray-70-fade-background: rgba(180, 183, 190, var(--salt-palette-opacity-disabled)); --salt-color-gray-200-fade-background: rgba(97, 101, 110, var(--salt-palette-opacity-disabled)); --salt-color-gray-300-fade-background: rgba(76, 80, 91, var(--salt-palette-opacity-disabled)); + --salt-color-gray-500-fade-background: rgba(59, 63, 70, var(--salt-palette-opacity-disabled)); --salt-color-gray-600-fade-background: rgba(47, 49, 54, var(--salt-palette-opacity-disabled)); --salt-color-gray-800-fade-background: rgba(36, 37, 38, var(--salt-palette-opacity-disabled)); --salt-color-white-fade-background: rgba(255, 255, 255, var(--salt-palette-opacity-disabled)); --salt-color-green-500-fade-background: rgba(36, 135, 75, var(--salt-palette-opacity-disabled)); --salt-color-green-600-fade-background: rgba(24, 114, 61, var(--salt-palette-opacity-disabled)); --salt-color-red-600-fade-background: rgba(196, 32, 16, var(--salt-palette-opacity-disabled)); + --salt-color-orange-600-fade-background: rgba(224, 101, 25, var(--salt-palette-opacity-disabled)); --salt-color-white-fade-background-readonly: rgba(255, 255, 255, var(--salt-palette-opacity-background-readonly)); --salt-color-gray-20-fade-background-readonly: rgba(234, 237, 239, var(--salt-palette-opacity-background-readonly)); diff --git a/tooling/stylelint/correct-theme-token-usage/index.js b/tooling/stylelint/correct-theme-token-usage/index.js index ceb367f3052..b90c4a8031c 100644 --- a/tooling/stylelint/correct-theme-token-usage/index.js +++ b/tooling/stylelint/correct-theme-token-usage/index.js @@ -1,8 +1,5 @@ const valueParser = require("postcss-value-parser"); const stylelint = require("stylelint"); -const glob = require("fast-glob"); -const csstree = require("css-tree"); -const fs = require("node:fs"); const { report, ruleMessages } = stylelint.utils; @@ -35,23 +32,6 @@ const declarationValueIndex = function declarationValueIndex(decl) { }, 0); }; -const deprecatedTokensSet = new Set( - glob - // Matches all files in src folders that start with a capital letter which should be all components. - .sync("./packages/theme/css/deprecated/*.css") - .flatMap((file) => { - const ast = csstree.parse(fs.readFileSync(file, { encoding: "utf-8" })); - return csstree - .findAll( - ast, - (node, item, list) => - node.type === "Declaration" && node.property.startsWith("--salt"), - ) - .map((decl) => decl.property); - }) - .filter(Boolean), -); - // ---- Start of plugin ---- const ruleName = "salt/correct-theme-token-usage"; @@ -59,8 +39,6 @@ const ruleName = "salt/correct-theme-token-usage"; const messages = ruleMessages(ruleName, { noExpectedFoundationPalette: (propertyChecked) => `No foundation or palette variables (${propertyChecked}) should be used in component`, // Can encode option in error message if needed - noDeprecated: (propertyChecked) => - `No deprecated token (${propertyChecked}) should be used in component`, }); const meta = { @@ -129,12 +107,6 @@ function isAllowedKeys(property, verboseLog) { return checkResult; } -function isDeprecatedToken(property, verboseLog) { - const checkResult = deprecatedTokensSet.has(property); - verboseLog && console.log("Checking", property, "is deprecated", checkResult); - return checkResult; -} - module.exports = stylelint.createPlugin( ruleName, (primaryOption, secondaryOptionObject, context) => { @@ -160,16 +132,7 @@ module.exports = stylelint.createPlugin( if (!firstNode) return; if (!isAllowedKeys(firstNode.value, verboseLog)) { - complainNoExpecteFoundationOrPalette( - declarationValueIndex(decl) + firstNode.sourceIndex, - firstNode.value.length, - decl, - firstNode.value, - ); - } - - if (isDeprecatedToken(firstNode.value, verboseLog)) { - complainDeprecatedTokenUsage( + complainNoExpectedFoundationOrPalette( declarationValueIndex(decl) + firstNode.sourceIndex, firstNode.value.length, decl, @@ -183,17 +146,13 @@ module.exports = stylelint.createPlugin( verboseLog && console.log({ prop }); if (!isAllowedKeys(prop, verboseLog)) { - complainNoExpecteFoundationOrPalette(0, prop.length, decl, prop); - } - - if (isDeprecatedToken(prop, verboseLog)) { - complainDeprecatedTokenUsage(0, prop.length, decl, prop); + complainNoExpectedFoundationOrPalette(0, prop.length, decl, prop); } return; }); - function complainNoExpecteFoundationOrPalette( + function complainNoExpectedFoundationOrPalette( index, length, decl, @@ -208,23 +167,6 @@ module.exports = stylelint.createPlugin( endIndex: index + length, }); } - - function complainDeprecatedTokenUsage( - index, - length, - decl, - propertyChecked, - ) { - report({ - result, - ruleName, - message: messages.noDeprecated(propertyChecked), - node: decl, - index, - endIndex: index + length, - severity: "warning", // Keep deprecated token in warning for now - }); - } }; }, ); diff --git a/tooling/stylelint/no-deprecated-token-usage/index.js b/tooling/stylelint/no-deprecated-token-usage/index.js new file mode 100644 index 00000000000..eda8ac9268a --- /dev/null +++ b/tooling/stylelint/no-deprecated-token-usage/index.js @@ -0,0 +1,142 @@ +const valueParser = require("postcss-value-parser"); +const stylelint = require("stylelint"); +const glob = require("fast-glob"); +const csstree = require("css-tree"); +const fs = require("node:fs"); + +const { report, ruleMessages } = stylelint.utils; + +// A few stylelint utils are not exported +// copied from https://github.com/stylelint/stylelint/tree/main/lib/utils + +const isValueFunction = function isValueFunction(node) { + return node.type === "function"; +}; + +const declarationValueIndex = function declarationValueIndex(decl) { + const raws = decl.raws; + + return [ + // @ts-expect-error -- TS2571: Object is of type 'unknown'. + raws.prop?.prefix, + // @ts-expect-error -- TS2571: Object is of type 'unknown'. + raws.prop?.raw || decl.prop, + // @ts-expect-error -- TS2571: Object is of type 'unknown'. + raws.prop?.suffix, + raws.between || ":", + // @ts-expect-error -- TS2339: Property 'prefix' does not exist on type '{ value: string; raw: string; }'. + raws.value?.prefix, + ].reduce((count, str) => { + if (str) { + return count + str.length; + } + + return count; + }, 0); +}; + +const deprecatedTokensSet = new Set( + glob + // Matches all files in src folders that start with a capital letter which should be all components. + .sync("./packages/theme/css/deprecated/*.css") + .flatMap((file) => { + const ast = csstree.parse(fs.readFileSync(file, { encoding: "utf-8" })); + return csstree + .findAll( + ast, + (node, item, list) => + node.type === "Declaration" && node.property.startsWith("--salt"), + ) + .map((decl) => decl.property); + }) + .filter(Boolean), +); + +// ---- Start of plugin ---- + +const ruleName = "salt/no-deprecated-token-usage"; + +const messages = ruleMessages(ruleName, { + noDeprecated: (propertyChecked) => + `No deprecated tokens should be used. (${propertyChecked})`, +}); + +const meta = { + // Point to style documentation + url: "https://saltdesignsystem-storybook.pages.dev/?path=/story/theme-characteristics-about-characteristics--docs", +}; + +function isDeprecatedToken(property, verboseLog) { + const checkResult = deprecatedTokensSet.has(property); + verboseLog && console.log("Checking", property, "is deprecated", checkResult); + return checkResult; +} + +module.exports = stylelint.createPlugin( + ruleName, + (primaryOption, secondaryOptionObject, context) => { + return (root, result) => { + const verboseLog = primaryOption.logLevel === "verbose"; + + root.walkDecls((decl) => { + const { prop, value } = decl; + + const parsedValue = valueParser(value); + + parsedValue.walk((node) => { + if (!isValueFunction(node)) return; + + if (node.value.toLowerCase() !== "var") return; + + const { nodes } = node; + + const firstNode = nodes[0]; + + verboseLog && console.log({ nodes }); + + if (!firstNode) return; + + if (isDeprecatedToken(firstNode.value, verboseLog)) { + complainDeprecatedTokenUsage( + declarationValueIndex(decl) + firstNode.sourceIndex, + firstNode.value.length, + decl, + firstNode.value, + ); + } + + return; + }); + + verboseLog && console.log({ prop }); + + if (isDeprecatedToken(prop, verboseLog)) { + complainDeprecatedTokenUsage(0, prop.length, decl, prop); + } + + return; + }); + + function complainDeprecatedTokenUsage( + index, + length, + decl, + propertyChecked, + ) { + report({ + result, + ruleName, + message: messages.noDeprecated(propertyChecked), + node: decl, + index, + endIndex: index + length, + severity: secondaryOptionObject?.severity ?? "error", + }); + } + }; + }, +); + +module.exports.ruleName = ruleName; +module.exports.messages = messages; +module.exports.meta = meta;