Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tokens): provide autofix for all global tokens #763

Merged
merged 8 commits into from
Sep 24, 2024
Merged
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];
}

wise-king-sullyman marked this conversation as resolved.
Show resolved Hide resolved
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") {
wise-king-sullyman marked this conversation as resolved.
Show resolved Hide resolved
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
Loading