diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts index 99d85b62e..bf81fd33c 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts @@ -1,7 +1,17 @@ -import { JSXElement, JSXAttribute } from "estree-jsx"; +import { Rule, Scope } from "eslint"; +import { + JSXElement, + JSXAttribute, + JSXOpeningElement, + MemberExpression, +} from "estree-jsx"; -export function getAttribute(node: JSXElement, attributeName: string) { - return node.openingElement.attributes.find( +export function getAttribute( + node: JSXElement | JSXOpeningElement, + attributeName: string +) { + const nodeProperty = node.type === "JSXElement" ? node.openingElement : node; + return nodeProperty.attributes.find( (attr) => attr.type === "JSXAttribute" && attr.name.name === attributeName ) as JSXAttribute | undefined; } @@ -15,3 +25,77 @@ export function getExpression(node?: JSXAttribute["value"]) { return node.expression; } } + +function getMemberExpression(node: MemberExpression) { + if (!node) { + return; + } + const { object, property } = node; + + return { object, property }; +} + +export function getVariableDeclaration( + name: string, + scope: Scope.Scope | null +) { + while (scope !== null) { + const variable = scope.variables.find((v) => v.name === name); + + if (variable) { + return variable; + } + + scope = scope.upper; + } + return undefined; +} + +export function getVariableValue(name: string, scope: Scope.Scope | null) { + const variableDeclaration = getVariableDeclaration(name, scope); + + if (!variableDeclaration) { + return; + } + + const variableInit = variableDeclaration.defs.length + ? variableDeclaration.defs[0].node.init + : undefined; + + if (!variableInit) { + return; + } + if (variableInit.type === "Literal") { + return variableInit.value; + } + if (variableInit.type === "MemberExpression") { + return getMemberExpression(variableInit); + } +} + +export function getAttributeValue( + context: Rule.RuleContext, + node?: JSXAttribute["value"] +) { + if (!node) { + return; + } + + const valueType = node.type; + + if (valueType === "Literal") { + return node.value; + } + + const isExpressionContainer = valueType === "JSXExpressionContainer"; + if (isExpressionContainer && node.expression.type === "Identifier") { + const variableScope = context.getSourceCode().getScope(node); + return getVariableValue(node.expression.name, variableScope); + } + if (isExpressionContainer && node.expression.type === "MemberExpression") { + return getMemberExpression(node.expression); + } + if (isExpressionContainer && node.expression.type === "Literal") { + return node.expression.value; + } +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/helpers.js b/packages/eslint-plugin-pf-codemods/src/rules/helpers/helpers.js index a9c3bfb74..e6962c976 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/helpers.js +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/helpers.js @@ -1,6 +1,7 @@ import { getFromPackage } from './getFromPackage'; import { pfPackageMatches } from './pfPackageMatches'; import { findAncestor } from './findAncestor'; +import { getVariableDeclaration } from './JSXAttributes'; const evk = require('eslint-visitor-keys'); @@ -492,7 +493,7 @@ export function addCallbackParam( if (propProperties.type === 'ArrowFunctionExpression') { propProperties.params = attribute.value?.expression?.params; } else if (propProperties.type === 'Identifier') { - const matchingVariable = findVariableDeclaration( + const matchingVariable = getVariableDeclaration( propProperties.name, context.getSourceCode().getScope(node) ); @@ -763,16 +764,3 @@ export function getAllJSXElements(context) { return jsxElements; } - -export function findVariableDeclaration(name, scope) { - while (scope !== null) { - const variable = scope.variables.find((v) => v.name === name); - - if (variable) { - return variable; - } - - scope = scope.upper; - } - return undefined; -} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContent-replace-noBackground-colorVariant.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContent-replace-noBackground-colorVariant.md new file mode 100644 index 000000000..f187e8aa0 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContent-replace-noBackground-colorVariant.md @@ -0,0 +1,19 @@ +### drawerContent-replace-noBackground-colorVariant [(#10211)](https://github.com/patternfly/patternfly-react/pull/10211) + +The "no-background" value of the `colorVariant` prop on DrawerContent has been removed, and a new "primary" value has been added. + +Additionally, a new DrawerContentColorVariant enum has been added and should be used instead of the DrawerColorVariant enum. The fix when the DrawerColorVariant enum is being used will replace the `colorVariant` prop value with a string. + +#### Examples + +In: + +```jsx +%inputExample% +``` + +Out: + +```jsx +%outputExample% +``` diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContent-replace-noBackground-colorVariant.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContent-replace-noBackground-colorVariant.test.ts new file mode 100644 index 000000000..d69cd8776 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContent-replace-noBackground-colorVariant.test.ts @@ -0,0 +1,97 @@ +const ruleTester = require("../../ruletester"); +import * as rule from "./drawerContent-replace-noBackground-colorVariant"; + +ruleTester.run("drawerContent-replace-noBackground-colorVariant", rule, { + valid: [ + { + code: ``, + }, + { + code: ``, + }, + { + code: `import { DrawerContent } from '@patternfly/react-core'; `, + }, + { + code: `import { DrawerContent, DrawerContentColorVariant } from '@patternfly/react-core'; `, + }, + { + code: `import { DrawerContent } from '@patternfly/react-core'; `, + }, + { + code: `import { DrawerContent } from '@patternfly/react-core'; `, + }, + ], + invalid: [ + { + code: `import { DrawerContent } from '@patternfly/react-core'; `, + output: `import { DrawerContent } from '@patternfly/react-core'; `, + errors: [ + { + message: `The "no-background" value of the \`colorVariant\` prop on DrawerContent has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { DrawerContent } from '@patternfly/react-core'; `, + output: `import { DrawerContent } from '@patternfly/react-core'; `, + errors: [ + { + message: `The "no-background" value of the \`colorVariant\` prop on DrawerContent has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { DrawerContent } from '@patternfly/react-core'; const color = "no-background"; `, + output: `import { DrawerContent } from '@patternfly/react-core'; const color = "no-background"; `, + errors: [ + { + message: `The "no-background" value of the \`colorVariant\` prop on DrawerContent has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { DrawerContent, DrawerColorVariant } from '@patternfly/react-core'; `, + output: `import { DrawerContent, DrawerColorVariant } from '@patternfly/react-core'; `, + errors: [ + { + message: `The DrawerContentColorVariant enum should be used instead of the DrawerColorVariant enum when passed to the DrawerContent component. This fix will replace the colorVariant prop value with a string.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { DrawerContent, DrawerColorVariant } from '@patternfly/react-core'; `, + output: `import { DrawerContent, DrawerColorVariant } from '@patternfly/react-core'; `, + errors: [ + { + message: `The "no-background" value of the \`colorVariant\` prop on DrawerContent has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { DrawerContent, DrawerColorVariant } from '@patternfly/react-core'; const color = DrawerColorVariant.default; `, + output: `import { DrawerContent, DrawerColorVariant } from '@patternfly/react-core'; const color = DrawerColorVariant.default; `, + errors: [ + { + message: `The DrawerContentColorVariant enum should be used instead of the DrawerColorVariant enum when passed to the DrawerContent component. This fix will replace the colorVariant prop value with a string.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { DrawerContent, DrawerColorVariant } from '@patternfly/react-core'; const color = DrawerColorVariant.noBackground; `, + output: `import { DrawerContent, DrawerColorVariant } from '@patternfly/react-core'; const color = DrawerColorVariant.noBackground; `, + errors: [ + { + message: `The "no-background" value of the \`colorVariant\` prop on DrawerContent has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContent-replace-noBackground-colorVariant.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContent-replace-noBackground-colorVariant.ts new file mode 100644 index 000000000..7cca10765 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContent-replace-noBackground-colorVariant.ts @@ -0,0 +1,87 @@ +import { Rule } from "eslint"; +import { JSXOpeningElement } from "estree-jsx"; +import { getFromPackage, getAttribute, getAttributeValue } from "../../helpers"; + +// https://github.com/patternfly/patternfly-react/pull/10211 +module.exports = { + meta: { fixable: "code" }, + create: function (context: Rule.RuleContext) { + const { imports } = getFromPackage(context, "@patternfly/react-core"); + + const drawerContentImport = imports.find( + (specifier) => specifier.imported.name === "DrawerContent" + ); + const drawerColorVariantEnumImport = imports.find( + (specifier) => specifier.imported.name === "DrawerColorVariant" + ); + const validDrawerContentValues = ["default", "primary", "secondary"]; + + return !drawerContentImport + ? {} + : { + JSXOpeningElement(node: JSXOpeningElement) { + if ( + !( + node.name.type === "JSXIdentifier" && + drawerContentImport.local.name === node.name.name + ) + ) { + return; + } + const colorVariantProp = getAttribute(node, "colorVariant"); + + if (!colorVariantProp) { + return; + } + + const colorVariantValue = getAttributeValue( + context, + colorVariantProp.value + ); + const drawerColorVariantLocalName = + drawerColorVariantEnumImport && + drawerColorVariantEnumImport.local.name; + const hasPatternFlyEnum = + colorVariantValue && + colorVariantValue.object && + colorVariantValue.object.name === drawerColorVariantLocalName; + const hasNoBackgroundValue = + colorVariantValue && colorVariantValue.property + ? hasPatternFlyEnum && + colorVariantValue.property.name === "noBackground" + : colorVariantValue === "no-background"; + + if (!hasPatternFlyEnum && !hasNoBackgroundValue) { + return; + } + + const message = hasNoBackgroundValue + ? 'The "no-background" value of the `colorVariant` prop on DrawerContent has been removed.' + : "The DrawerContentColorVariant enum should be used instead of the DrawerColorVariant enum when passed to the DrawerContent component. This fix will replace the colorVariant prop value with a string."; + context.report({ + node, + message, + fix(fixer) { + const fixes = []; + if (hasNoBackgroundValue) { + fixes.push(fixer.replaceText(colorVariantProp, "")); + } + + if (!hasNoBackgroundValue && hasPatternFlyEnum) { + const enumPropertyName = colorVariantValue.property.name; + fixes.push( + fixer.replaceText( + colorVariantProp, + validDrawerContentValues.includes(enumPropertyName) + ? `colorVariant="${colorVariantValue.property.name}"` + : "" + ) + ); + } + return fixes; + }, + }); + }, + }; + }, +}; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContentReplaceNoBackgroundColorVariantInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContentReplaceNoBackgroundColorVariantInput.tsx new file mode 100644 index 000000000..6a0ec1fa2 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContentReplaceNoBackgroundColorVariantInput.tsx @@ -0,0 +1,15 @@ +import { DrawerContent, DrawerColorVariant } from "@patternfly/react-core"; + +export const DrawerContentReplaceNoBackgroundColorVariantInput = () => { + const stringColor = "no-background"; + const enumColor = DrawerColorVariant.default; + + return ( + <> + + + + + + ); +}; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContentReplaceNoBackgroundColorVariantOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContentReplaceNoBackgroundColorVariantOutput.tsx new file mode 100644 index 000000000..0e30848c3 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/drawerContentReplaceNoBackgroundColorVariant/drawerContentReplaceNoBackgroundColorVariantOutput.tsx @@ -0,0 +1,15 @@ +import { DrawerContent, DrawerColorVariant } from "@patternfly/react-core"; + +export const DrawerContentReplaceNoBackgroundColorVariantInput = () => { + const stringColor = "no-background"; + const enumColor = DrawerColorVariant.default; + + return ( + <> + + + + + + ); +};