Skip to content

Commit

Permalink
feat(tokens): provide autofix for global non-color tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
adamviktora committed Sep 10, 2024
1 parent f950da7 commit 973e700
Show file tree
Hide file tree
Showing 11 changed files with 706 additions and 51 deletions.
7 changes: 4 additions & 3 deletions packages/eslint-plugin-pf-codemods/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './configs';
export * from './ruleCuration';
export * from './ruleCustomization'
export * from "./configs";
export * from "./ruleCuration";
export * from "./ruleCustomization";
export * from "./tokenLists";
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export const warningRules = [
"tabs-update-markup",
"tabs-warn-children-type-changed",
"Th-Td-warn-update-markup",
"tokens-warn",
"toolbarLabelGroupContent-updated-markup",
"tooltip-warn-triggerRef-may-be-required",
"treeView-warn-selectable-styling-modifier-removed",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,11 @@ ruleTester.run("tokens-warn", rule, {
},
{
code: `<div style={{ borderWidth: "var(--pf-v5-global--BorderWidth--lg)" }}></div>`,
output: `<div style={{ borderWidth: "var(--pf-v5-global--BorderWidth--lg)" }}></div>`,
output: `<div style={{ borderWidth: "var(--pf-t--global--border--width--extra-strong)" }}></div>`,
errors: [
{
message: getWarnMessage("var(--pf-v5-global--BorderWidth--lg)"),
message:
"--pf-v5-global--BorderWidth--lg is an old CSS token and has been replaced with --pf-t--global--border--width--extra-strong. If you want to use a different token, check our new documentation https://staging-v6.patternfly.org/tokens/all-patternfly-tokens.",
type: "Literal",
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,82 +1,204 @@
import { Rule } from "eslint";
import {
IdentifierWithParent,
getDefaultDeclarationString,
getDefaultImportsFromPackage,
getFromPackage,
} from "../../helpers";
import { ImportDeclaration, ImportSpecifier, Literal } from "estree-jsx";
import { oldTokens } from "./tokenLists/oldTokens";
import { oldCssVarNamesV5 } from "./tokenLists/oldCssVarNamesV5";
import {
Identifier,
ImportDeclaration,
ImportSpecifier,
Literal,
} from "estree-jsx";
import {
oldTokens,
oldCssVarNamesV5,
globalNonColorTokensMap,
oldGlobalNonColorTokens,
oldGlobalNonColorCssVarNames,
globalNonColorCssVarNamesMap,
} from "../../../tokenLists";

module.exports = {
meta: {},
meta: { fixable: "code" },
create: function (context: Rule.RuleContext) {
const tokensPackage = "@patternfly/react-tokens";

const { imports: tokenSpecifiers } = getFromPackage(context, tokensPackage);

const defaultTokensWithDeclaration = getDefaultImportsFromPackage(
const defaultTokenImports = getDefaultImportsFromPackage(
context,
tokensPackage
)
.map((specifier) => ({
specifier,
path: getDefaultDeclarationString(specifier),
declaration: specifier.parent,
}))
.filter(({ path }) => path !== undefined)
.map(({ path, declaration }) => ({
.map(({ specifier, path, declaration }) => ({
specifier,
token: (path as string).split("/").pop() as string,
declaration,
}));

const getMessage = (tokenName: string) =>
const getWarnMessage = (tokenName: string) =>
`${tokenName} is an old CSS token. About half of our tokens have been replaced with newer ones. To find a suitable replacement token, check our new documentation https://staging-v6.patternfly.org/tokens/all-patternfly-tokens.`;

const getFixMessage = (oldToken: string, newToken: string) =>
`${oldToken} is an old CSS token and has been replaced with ${newToken}. If you want to use a different token, check our new documentation https://staging-v6.patternfly.org/tokens/all-patternfly-tokens.`;

const shouldReplaceToken = (token: string) =>
oldGlobalNonColorTokens.includes(token) &&
globalNonColorTokensMap[token as keyof typeof globalNonColorTokensMap] !==
"SKIP";

const replaceToken = (
node: ImportDeclaration | ImportSpecifier | Identifier,
oldToken: string
) => {
const newToken =
globalNonColorTokensMap[
oldToken as keyof typeof globalNonColorTokensMap
];

context.report({
node,
message: getFixMessage(oldToken, newToken),
fix(fixer) {
if (node.type === "ImportDeclaration") {
const newDeclaration = node.source.value
?.toString()
.replace(oldToken, newToken) as string;

return [
fixer.replaceText(node.specifiers[0], newToken),
fixer.replaceText(node.source, `"${newDeclaration}"`),
];
}

if (node.type === "ImportSpecifier") {
return fixer.replaceText(node.imported, newToken);
}

return fixer.replaceText(node, newToken);
},
});
};

const replaceTokenOrWarn = (
node: ImportSpecifier | ImportDeclaration,
token: string
) => {
if (shouldReplaceToken(token)) {
replaceToken(node, token);
} else if (oldTokens.includes(token)) {
context.report({
node,
message: getWarnMessage(token),
});
}
};

return {
ImportSpecifier(node: ImportSpecifier) {
if (tokenSpecifiers.includes(node)) {
const tokenName = node.imported.name;
if (oldTokens.includes(tokenName)) {
context.report({
node,
message: getMessage(tokenName),
});
}
const token = node.imported.name;
replaceTokenOrWarn(node, token);
}
},
ImportDeclaration(node: ImportDeclaration) {
const tokenWithDeclaration = defaultTokensWithDeclaration.find(
const tokenWithDeclaration = defaultTokenImports.find(
({ declaration }) => node.source.value === declaration?.source.value
);

if (!tokenWithDeclaration) {
return;
}

replaceTokenOrWarn(node, tokenWithDeclaration.token);
},
Identifier(node: Identifier) {
const parentType = (node as IdentifierWithParent).parent?.type;
// handle ImportSpecifier and ImportDeclaration separately
if (
tokenWithDeclaration &&
oldTokens.includes(tokenWithDeclaration.token)
parentType === "ImportSpecifier" ||
parentType === "ImportDefaultSpecifier"
) {
context.report({
node,
message: getMessage(tokenWithDeclaration.token),
});
return;
}

const tokenInfo = defaultTokenImports.find(
({ specifier }) => node.name === specifier.local.name
);

if (tokenInfo && shouldReplaceToken(tokenInfo.token)) {
replaceToken(node, tokenInfo.token);
}

const unaliasedTokenSpecifier = tokenSpecifiers.find(
(specifier) =>
specifier.local.name === specifier.imported.name &&
node.name === specifier.local.name
);

if (unaliasedTokenSpecifier && shouldReplaceToken(node.name)) {
replaceToken(node, node.name);
}
},
Literal(node: Literal) {
if (
typeof node.value === "string" &&
[...oldCssVarNames, ...oldCssVars].includes(node.value)
) {
if (typeof node.value !== "string") {
return;
}

let varName = node.value;
const varRegex = /var\(([^)]+)\)/;
const match = node.value.match(varRegex);

if (match) {
varName = match[1];
}

const shouldReplaceVar =
oldGlobalNonColorCssVarNames.includes(varName) &&
globalNonColorCssVarNamesMap[
varName as keyof typeof globalNonColorCssVarNamesMap
] !== "SKIP";

if (shouldReplaceVar) {
const newVarName =
globalNonColorCssVarNamesMap[
varName as keyof typeof globalNonColorCssVarNamesMap
];

if (newVarName !== "SKIP") {
context.report({
node,
message: getFixMessage(varName, newVarName),
fix(fixer) {
return fixer.replaceText(
node,
node.value?.toString().startsWith("var")
? `"var(${newVarName})"`
: `"${newVarName}"`
);
},
});
}
} else if (oldCssVarNames.includes(varName)) {
context.report({
node,
message: getMessage(node.value),
message: getWarnMessage(node.value),
});
}
},
};
},
};

// consumers may run class-name-updater before codemods, so we have to check also old tokens with v6 prefix
// consumers may have run the old class-name-updater before codemods, so we should check also old tokens with v6 prefix
const oldCssVarNamesV6 = oldCssVarNamesV5.map((cssVarName) =>
cssVarName.replace("v5", "v6")
);
const oldCssVarNames = [...oldCssVarNamesV5, ...oldCssVarNamesV6];
const oldCssVars = oldCssVarNames.map((cssVarName) => `var(${cssVarName})`);
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
// replacements (fixable with --fix)
import global_BorderWidth_lg from "@patternfly/react-tokens/dist/esm/global_BorderWidth_lg";
import { global_FontWeight_normal } from "@patternfly/react-tokens";

global_BorderWidth_lg;
global_FontWeight_normal;

document.documentElement.style.setProperty("--pf-v5-global--ZIndex--lg", "3");
<div
style={{
borderWidth: "var(--pf-v5-global--BorderWidth--lg)",
boxShadow: "var(--pf-v5-global--BoxShadow--sm)",
marginTop: "var(--pf-v5-global--spacer--3xl)",
}}
></div>;

// warnings (not fixable)
import global_warning_color_100 from "@patternfly/react-tokens/dist/esm/global_warning_color_100";
import { c_alert__FontSize } from "@patternfly/react-tokens";

global_warning_color_100;
c_alert__FontSize;

<>
<div
style={{
"--pf-v5-global--success-color--200": "#abc",
}}
></div>
<div style={{ borderWidth: "var(--pf-v5-global--BorderWidth--lg)" }}></div>
</>;
<div
style={{
color: "var(--pf-v5-global--success-color--200)",
width: "var(--pf-v5-global--arrow--width)",
}}
></div>;
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
// replacements (fixable with --fix)
import global_border_width_extra_strong from "@patternfly/react-tokens/dist/esm/global_border_width_extra_strong";
import { global_font_weight_body_default } from "@patternfly/react-tokens";

global_border_width_extra_strong;
global_font_weight_body_default;

document.documentElement.style.setProperty("--pf-t--global--z-index--lg", "3");
<div
style={{
borderWidth: "var(--pf-t--global--border--width--extra-strong)",
boxShadow: "var(--pf-t--global--box-shadow--sm)",
marginTop: "var(--pf-t--global--spacer--3xl)",
}}
></div>;

// warnings (not fixable)
import global_warning_color_100 from "@patternfly/react-tokens/dist/esm/global_warning_color_100";
import { c_alert__FontSize } from "@patternfly/react-tokens";

global_warning_color_100;
c_alert__FontSize;

<>
<div
style={{
"--pf-v5-global--success-color--200": "#abc",
}}
></div>
<div style={{ borderWidth: "var(--pf-v5-global--BorderWidth--lg)" }}></div>
</>;
<div
style={{
color: "var(--pf-v5-global--success-color--200)",
width: "var(--pf-v5-global--arrow--width)",
}}
></div>;
4 changes: 4 additions & 0 deletions packages/eslint-plugin-pf-codemods/src/tokenLists/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./oldCssVarNamesV5";
export * from "./oldGlobalCssVarNames";
export * from "./oldGlobalTokens";
export * from "./oldTokens";
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// names of css variables of the oldTokens list
// names of css variables for each token of the oldTokens list
export const oldCssVarNamesV5 = [
"--pf-v5-c-about-modal-box__brand--PaddingBottom",
"--pf-v5-c-about-modal-box__brand--PaddingLeft",
Expand Down
Loading

0 comments on commit 973e700

Please sign in to comment.