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 9ae2c1c9c..e891fe394 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts @@ -7,6 +7,8 @@ import { JSXFragment, JSXOpeningElement, MemberExpression, + Property, + SpreadElement, } from "estree-jsx"; export function getAttribute( @@ -35,22 +37,47 @@ export function getAnyAttribute( return foundAttribute; } +/** + * Attribute value and its type + */ +type Attribute = + | { type: "string"; value: string } + | { + type: "Literal"; + value: string | number | bigint | boolean | RegExp | null | undefined; + } + | { type: "MemberExpression"; value: MemberExpression } + | { type: "ObjectExpression"; value: (Property | SpreadElement)[] } + | { type: "undefined"; value: undefined }; + +const UNDEFINED: Attribute = { type: "undefined", value: undefined }; + +/** + * Helper to get the raw value from a JSXAttribute value. If the JSXAttribute value is an Identifier, it tries to get the value of that variable. If the JSXAttribute value is a JSXExpressionContainer: {"value"}, it returns the inner content. + * MemberExpressions and ObjectExpressions are not parsed further. + * @param context Rule context + * @param node JSXAttribute value + * @returns Attribute in this form: { type: [specific type of the returned value], value: [actual value]}. Literal types are further processed, if their value is of type "string", type: "string" is returned, otherwise type: "Literal" + */ export function getAttributeValue( context: Rule.RuleContext, node?: JSXAttribute["value"] -) { +): Attribute { if (!node) { - return; + return UNDEFINED; } const valueType = node.type; if (valueType === "Literal") { - return node.value; + if (typeof node.value === "string") { + return { type: "string", value: node.value }; + } + return { type: "Literal", value: node.value }; } if (valueType !== "JSXExpressionContainer") { - return; + return UNDEFINED; } if (node.expression.type === "Identifier") { @@ -58,14 +85,18 @@ export function getAttributeValue( return getVariableValue(node.expression.name, variableScope, context); } if (node.expression.type === "MemberExpression") { - return getMemberExpression(node.expression); + return { type: "MemberExpression", value: node.expression }; } if (node.expression.type === "Literal") { - return node.expression.value; + if (typeof node.expression.value === "string") { + return { type: "string", value: node.expression.value }; + } + return { type: "Literal", value: node.expression.value }; } if (node.expression.type === "ObjectExpression") { - return node.expression.properties; + return { type: "ObjectExpression", value: node.expression.properties }; } + return UNDEFINED; } export function getExpression(node?: JSXAttribute["value"]) { @@ -78,15 +109,6 @@ export function getExpression(node?: JSXAttribute["value"]) { } } -function getMemberExpression(node: MemberExpression) { - if (!node) { - return; - } - const { object, property } = node; - - return { object, property }; -} - export function getVariableDeclaration( name: string, scope: Scope.Scope | null @@ -103,22 +125,39 @@ export function getVariableDeclaration( return undefined; } +export function getVariableInit( + variableDeclaration: Scope.Variable | undefined +) { + if (!variableDeclaration || !variableDeclaration.defs.length) { + return; + } + + const variableDefinition = variableDeclaration.defs[0]; + + if (variableDefinition.type !== "Variable") { + return; + } + + return variableDefinition.node.init; +} + +/** + * Helper to get the raw value of a variable, given by its name. Returns an Attribute object, similarly to getAttributeValue helper. + * @param name Variable name + * @param scope Scope where to look for the variable declaration + * @param context Rule context + * @returns Attribute in this form: { type: [specific type of the returned value], value: [actual value]}. Literal types are further processed, if their value is of type "string", type: "string" is returned, otherwise type: "Literal" + */ export function getVariableValue( name: string, scope: Scope.Scope | null, context: Rule.RuleContext -) { +): Attribute { const variableDeclaration = getVariableDeclaration(name, scope); - if (!variableDeclaration) { - return; - } - - const variableInit = variableDeclaration.defs.length - ? variableDeclaration.defs[0].node.init - : undefined; + const variableInit = getVariableInit(variableDeclaration); if (!variableInit) { - return; + return UNDEFINED; } if (variableInit.type === "Identifier") { return getVariableValue( @@ -128,12 +167,16 @@ export function getVariableValue( ); } if (variableInit.type === "Literal") { - return variableInit.value; + if (typeof variableInit.value === "string") { + return { type: "string", value: variableInit.value }; + } + return { type: "Literal", value: variableInit.value }; } if (variableInit.type === "MemberExpression") { - return getMemberExpression(variableInit); + return { type: "MemberExpression", value: variableInit }; } if (variableInit.type === "ObjectExpression") { - return variableInit.properties; + return { type: "ObjectExpression", value: variableInit.properties }; } + return UNDEFINED; } diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getEnumPropertyName.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getEnumPropertyName.ts new file mode 100644 index 000000000..0e9392956 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getEnumPropertyName.ts @@ -0,0 +1,26 @@ +import { Rule } from "eslint"; +import { MemberExpression } from "estree-jsx"; +import { getVariableValue } from "."; + +/** Used to get a property name on an enum (MemberExpression). */ +export function getEnumPropertyName( + context: Rule.RuleContext, + enumNode: MemberExpression +) { + if (enumNode.property.type === "Identifier") { + // E.g. const key = "key"; someEnum[key] + if (enumNode.computed) { + const scope = context.getSourceCode().getScope(enumNode); + const propertyName = enumNode.property.name; + + return getVariableValue(propertyName, scope, context).value?.toString(); + } + // E.g. someEnum.key + return enumNode.property.name; + } + + // E.g. someEnum["key"] + if (enumNode.property.type === "Literal") { + return enumNode.property.value?.toString(); + } +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeForAttributeFixer.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeForAttributeFixer.ts index 2c72749d4..0113d1436 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeForAttributeFixer.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getNodeForAttributeFixer.ts @@ -1,6 +1,6 @@ import { Rule } from "eslint"; import { JSXAttribute } from "estree-jsx"; -import { getVariableDeclaration } from "./JSXAttributes"; +import { getVariableDeclaration, getVariableInit } from "./JSXAttributes"; /** Used to find the node where a prop value is initially assigned, to then be passed * as a fixer function's nodeOrToken argument. Useful for when a prop may have an inline value, e.g. ``, or @@ -41,6 +41,6 @@ function getJSXExpressionContainerValue( scope ); - return variableDeclaration && variableDeclaration.defs[0].node.init; + return getVariableInit(variableDeclaration); } } diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getObjectProperty.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getObjectProperty.ts index 8446eaf23..c8f913f64 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/getObjectProperty.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/getObjectProperty.ts @@ -1,6 +1,6 @@ import { Rule } from "eslint"; -import { Property, Identifier } from "estree-jsx"; -import { getVariableDeclaration } from "./JSXAttributes"; +import { Property } from "estree-jsx"; +import { propertyNameMatches } from "./propertyNameMatches"; /** Can be used to run logic on the specified property of an ObjectExpression or * only if the specified property exists. @@ -14,26 +14,9 @@ export function getObjectProperty( return; } - const matchedProperty = properties.find((property) => { - const isIdentifier = property.key.type === "Identifier"; - const { computed } = property; - - // E.g. const key = "key"; {[key]: value} - if (isIdentifier && computed) { - const scope = context.getSourceCode().getScope(property); - const propertyName = (property.key as Identifier).name; - const propertyVariable = getVariableDeclaration(propertyName, scope); - return propertyVariable?.defs[0].node.init.value === name; - } - // E.g. {key: value} - if (isIdentifier && !computed) { - return (property.key as Identifier).name === name; - } - // E.g. {"key": value} or {["key"]: value} - if (property.key.type === "Literal") { - return property.key.value === name; - } - }); + const matchedProperty = properties.find((property) => + propertyNameMatches(context, property.key, property.computed, name) + ); return matchedProperty; } diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts index 4770ebeec..1bdd9b081 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts @@ -8,6 +8,7 @@ export * from "./getChildJSXElementByName"; export * from "./getCodeModDataTag"; export * from "./getComponentImportName"; export * from "./getEndRange"; +export * from "./getEnumPropertyName"; export * from "./getFromPackage"; export * from "./getImportedName"; export * from "./getImportPath"; @@ -22,12 +23,14 @@ export * from "./helpers"; export * from "./importAndExport"; export * from "./includesImport"; export * from "./interfaces"; +export * from "./isEnumValue"; export * from "./isReactIcon"; export * from "./JSXAttributes"; export * from "./makeJSXElementSelfClosing"; export * from "./nodeMatches/checkMatchingImportDeclaration"; export * from "./nodeMatches/checkMatchingJSXOpeningElement"; export * from "./pfPackageMatches"; +export * from "./propertyNameMatches"; export * from "./removeElement"; export * from "./removeEmptyLineAfter"; export * from "./removePropertiesFromObjectExpression"; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/isEnumValue.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/isEnumValue.ts new file mode 100644 index 000000000..f5cc0fad8 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/isEnumValue.ts @@ -0,0 +1,33 @@ +import { MemberExpression } from "estree-jsx"; +import { propertyNameMatches } from "./propertyNameMatches"; +import { Rule } from "eslint"; + +/** Checks whether an enum node (MemberExpression), e.g. ButtonVariant["primary"] + * has a given enumName ("ButtonVariant") and a given propertyName ("primary"), or one of given property names. */ +export function isEnumValue( + context: Rule.RuleContext, + enumExpression: MemberExpression, + enumName: string, + propertyName: string | string[] +) { + if ( + enumExpression?.object?.type === "Identifier" && + enumExpression?.object?.name === enumName + ) { + const nameMatches = (name: string) => + propertyNameMatches( + context, + enumExpression.property, + enumExpression.computed, + name + ); + + if (Array.isArray(propertyName)) { + return propertyName.some((name) => nameMatches(name)); + } + + return nameMatches(propertyName); + } + + return false; +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/propertyNameMatches.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/propertyNameMatches.ts new file mode 100644 index 000000000..521a565fd --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/propertyNameMatches.ts @@ -0,0 +1,36 @@ +import { Rule } from "eslint"; +import { Expression, PrivateIdentifier } from "estree-jsx"; +import { getVariableValue } from "./JSXAttributes"; + +/** Check whether a property name is of a given value. + * Property can either be of an ObjectExpression - {propName: "value"} or MemberExpression - someObject.propName */ +export function propertyNameMatches( + context: Rule.RuleContext, + key: Expression | PrivateIdentifier, + computed: boolean, + name: string +) { + if (key.type === "Identifier") { + // E.g. const key = "key"; {[key]: value}; someObject[key] + if (computed) { + const scope = context.getSourceCode().getScope(key); + const propertyName = key.name; + const propertyVariableValue = getVariableValue( + propertyName, + scope, + context + ).value; + + return propertyVariableValue === name; + } + // E.g. {key: value}; someObject.key + return key.name === name; + } + + // E.g. {"key": value} or {["key"]: value}; someObject["key"] + if (key.type === "Literal") { + return key.value === name; + } + + return false; +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/bannerReplaceVariantProp/banner-replace-variantProp.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/bannerReplaceVariantProp/banner-replace-variantProp.ts index 9a67ec9e8..4289f6e28 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/bannerReplaceVariantProp/banner-replace-variantProp.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/bannerReplaceVariantProp/banner-replace-variantProp.ts @@ -27,7 +27,7 @@ module.exports = { const attributeValue = getAttributeValue( context, attribute.value - ); + ).value; const isValueDefault = attributeValue === "default"; const fixMessage = isValueDefault ? "remove the variant property" diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/buttonMoveIconsIconProp/button-moveIcons-icon-prop.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/buttonMoveIconsIconProp/button-moveIcons-icon-prop.ts index bb9c9d4ba..1e3cb877b 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/buttonMoveIconsIconProp/button-moveIcons-icon-prop.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/buttonMoveIconsIconProp/button-moveIcons-icon-prop.ts @@ -10,6 +10,7 @@ import { getChildJSXElementByName, isReactIcon, makeJSXElementSelfClosing, + isEnumValue, } from "../../helpers"; // https://github.com/patternfly/patternfly-react/pull/10663 @@ -37,23 +38,39 @@ module.exports = { node.openingElement.name.type === "JSXIdentifier" && buttonImport.local.name === node.openingElement.name.name ) { - const variantProp = getAttribute(node.openingElement, "variant"); const iconProp = getAttribute(node.openingElement, "icon"); if (iconProp) { return; } - const variantValue = getAttributeValue( - context, - variantProp?.value - ); - const isEnumValuePlain = - buttonVariantEnumImport && - variantValue?.object?.name === - buttonVariantEnumImport.local.name && - variantValue?.property.name === "plain"; + const isPlainVariant = () => { + const variantProp = getAttribute( + node.openingElement, + "variant" + ); + const { value: variantValue, type: variantValueType } = + getAttributeValue(context, variantProp?.value); + + if (variantValue === "plain") { + return true; + } + + if (variantValueType === "MemberExpression") { + return ( + !!buttonVariantEnumImport && + isEnumValue( + context, + variantValue, + buttonVariantEnumImport.local.name, + "plain" + ) + ); + } + + return false; + }; - const isPlain = variantValue === "plain" || isEnumValuePlain; + const isPlain = isPlainVariant(); let plainButtonChildrenString: string | undefined; let nodeWithChildren: JSXElement | JSXFragment = node; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/cardUpdatedClickableMarkup/card-updated-clickable-markup.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/cardUpdatedClickableMarkup/card-updated-clickable-markup.ts index 5940c269f..bdf49e919 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/cardUpdatedClickableMarkup/card-updated-clickable-markup.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/cardUpdatedClickableMarkup/card-updated-clickable-markup.ts @@ -1,7 +1,6 @@ import { Rule } from "eslint"; -import { JSXElement, Property, Literal } from "estree-jsx"; +import { JSXElement, ObjectExpression, Property } from "estree-jsx"; import { - getAllImportsFromPackage, getFromPackage, checkMatchingJSXOpeningElement, getAttribute, @@ -51,22 +50,27 @@ module.exports = { if (!cardHeaderChild || !selectableActionsProp) { return; } - const selectableActionsValue = getAttributeValue( - context, - selectableActionsProp.value - ); - if (!selectableActionsValue) { + const { + value: selectableActionsValue, + type: selectableActionsValueType, + } = getAttributeValue(context, selectableActionsProp.value); + // selectableActions prop on CardHeader accepts an object + if (selectableActionsValueType !== "ObjectExpression") { return; } + const selectableActionsProperties = selectableActionsValue.filter( + (val) => val.type === "Property" + ) as Property[]; + const nameProperty = getObjectProperty( context, - selectableActionsValue, + selectableActionsProperties, "name" ); const idProperty = getObjectProperty( context, - selectableActionsValue, + selectableActionsProperties, "selectableActionId" ); @@ -92,11 +96,11 @@ module.exports = { return []; } const propertiesToKeep = removePropertiesFromObjectExpression( - selectableActionsValue, + selectableActionsProperties, validPropertiesToRemove ); const replacementProperties = propertiesToKeep - .map((property: Property) => + .map((property) => context.getSourceCode().getText(property) ) .join(", "); @@ -105,6 +109,11 @@ module.exports = { context, selectableActionsProp ); + + if (!nodeToUpdate) { + return []; + } + return fixer.replaceText( nodeToUpdate, propertiesToKeep.length diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/colorPropsReplacedColors/colorProps-replaced-colors.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/colorPropsReplacedColors/colorProps-replaced-colors.ts index faa9e14f0..971cc9b29 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/colorPropsReplacedColors/colorProps-replaced-colors.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/colorPropsReplacedColors/colorProps-replaced-colors.ts @@ -2,7 +2,7 @@ import { Rule } from "eslint"; import { JSXOpeningElement } from "estree-jsx"; import { getFromPackage, getAttribute, getAttributeValue } from "../../helpers"; -const replacementMap: { [key: string]: string } = { +const replacementMap = { cyan: "teal", gold: "yellow", }; @@ -32,8 +32,11 @@ module.exports = { return; } - const colorValue = getAttributeValue(context, colorProp.value); - if (Object.keys(replacementMap).includes(colorValue)) { + const colorValue = getAttributeValue( + context, + colorProp.value + ).value; + if (colorValue === "cyan" || colorValue === "gold") { const newColorValue = replacementMap[colorValue]; const message = `The \`color\` prop on ${node.name.name} has been updated to replace "${colorValue}" with "${newColorValue}".`; context.report({ diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/componentGroupsUnavailableContentBodyTextPropsUpdate/component-groups-unavailableContent-bodyText-props-update.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/componentGroupsUnavailableContentBodyTextPropsUpdate/component-groups-unavailableContent-bodyText-props-update.ts index 46d00c077..693aed809 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/componentGroupsUnavailableContentBodyTextPropsUpdate/component-groups-unavailableContent-bodyText-props-update.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/componentGroupsUnavailableContentBodyTextPropsUpdate/component-groups-unavailableContent-bodyText-props-update.ts @@ -28,11 +28,22 @@ module.exports = { node, "statusPageLinkText" ); - const statusPageLinkTextString: string = - getAttributeValue(context, statusPageLinkTextProp?.value) ?? - "status page"; - if (statusPageLinkTextProp && statusPageLinkTextString.length) { + const { + type: statusPageLinkTextPropType, + value: statusPageLinkTextPropValue, + } = getAttributeValue(context, statusPageLinkTextProp?.value); + + const statusPageLinkTextString = + statusPageLinkTextPropType === "string" + ? statusPageLinkTextPropValue + : "status page"; + + if ( + statusPageLinkTextProp && + statusPageLinkTextPropType === "string" && + statusPageLinkTextString.length + ) { const firstChar = statusPageLinkTextString.charAt(0); if (firstChar !== firstChar.toUpperCase()) { @@ -66,10 +77,10 @@ module.exports = { } const preStatusLinkTextString = - getAttributeValue(context, preStatusLinkTextProp?.value) ?? + getAttributeValue(context, preStatusLinkTextProp?.value).value ?? "Try refreshing the page. If the problem persists, contact your organization administrator or visit our"; const postStatusLinkTextString = - getAttributeValue(context, postStatusLinkTextProp?.value) ?? + getAttributeValue(context, postStatusLinkTextProp?.value).value ?? "for known outages."; const bodyTextAttribute = `bodyText="${preStatusLinkTextString} ${statusPageLinkTextString} ${postStatusLinkTextString}"`; 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 index 7cca10765..9b830c274 100644 --- 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 @@ -1,6 +1,12 @@ import { Rule } from "eslint"; import { JSXOpeningElement } from "estree-jsx"; -import { getFromPackage, getAttribute, getAttributeValue } from "../../helpers"; +import { + getFromPackage, + getAttribute, + getAttributeValue, + isEnumValue, + getEnumPropertyName, +} from "../../helpers"; // https://github.com/patternfly/patternfly-react/pull/10211 module.exports = { @@ -38,18 +44,31 @@ module.exports = { context, colorVariantProp.value ); - const drawerColorVariantLocalName = - drawerColorVariantEnumImport && - drawerColorVariantEnumImport.local.name; + + const colorVariantValueEnum = + colorVariantValue?.type === "MemberExpression" + ? colorVariantValue.value + : null; + const hasPatternFlyEnum = - colorVariantValue && - colorVariantValue.object && - colorVariantValue.object.name === drawerColorVariantLocalName; + drawerColorVariantEnumImport && + colorVariantValueEnum && + colorVariantValueEnum.object && + context.getSourceCode().getText(colorVariantValueEnum.object) === + drawerColorVariantEnumImport.local.name; + + const isNoBackgroundEnum = + hasPatternFlyEnum && + isEnumValue( + context, + colorVariantValueEnum, + drawerColorVariantEnumImport.local.name, + "noBackground" + ); + const hasNoBackgroundValue = - colorVariantValue && colorVariantValue.property - ? hasPatternFlyEnum && - colorVariantValue.property.name === "noBackground" - : colorVariantValue === "no-background"; + colorVariantValue?.value === "no-background" || + isNoBackgroundEnum; if (!hasPatternFlyEnum && !hasNoBackgroundValue) { return; @@ -68,15 +87,19 @@ module.exports = { } if (!hasNoBackgroundValue && hasPatternFlyEnum) { - const enumPropertyName = colorVariantValue.property.name; - fixes.push( - fixer.replaceText( - colorVariantProp, - validDrawerContentValues.includes(enumPropertyName) - ? `colorVariant="${colorVariantValue.property.name}"` - : "" - ) + const enumPropertyName = getEnumPropertyName( + context, + colorVariantValueEnum ); + enumPropertyName && + fixes.push( + fixer.replaceText( + colorVariantProp, + validDrawerContentValues.includes(enumPropertyName) + ? `colorVariant="${enumPropertyName}"` + : "" + ) + ); } return fixes; }, diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/kebabToggleReplaceWithMenuToggle/kebabToggle-replace-with-menuToggle.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/kebabToggleReplaceWithMenuToggle/kebabToggle-replace-with-menuToggle.ts index c73ef38a5..1e4027d3f 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/kebabToggleReplaceWithMenuToggle/kebabToggle-replace-with-menuToggle.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/kebabToggleReplaceWithMenuToggle/kebabToggle-replace-with-menuToggle.ts @@ -102,7 +102,8 @@ module.exports = { const iconProp = getAttribute(node, "icon"); const variantProp = getAttribute(node, "variant"); const variantValue = - variantProp && getAttributeValue(context, variantProp.value); + variantProp && + getAttributeValue(context, variantProp.value).value; context.report({ node, diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.ts index d3b588abc..1ecbbb2b0 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.ts @@ -1,6 +1,11 @@ import { Rule } from "eslint"; import { JSXOpeningElement } from "estree-jsx"; -import { getFromPackage, getAttribute, getAttributeValue } from "../../helpers"; +import { + getFromPackage, + getAttribute, + getAttributeValue, + isEnumValue, +} from "../../helpers"; // https://github.com/patternfly/patternfly-react/pull/10650 module.exports = { @@ -28,12 +33,20 @@ module.exports = { return; } - const typeValue = getAttributeValue(context, typeProp.value); + const { type: typePropType, value: typePropValue } = + getAttributeValue(context, typeProp.value); + const isEnumValueNav = pageSectionTypeEnum && - typeValue.object?.name === pageSectionTypeEnum.local.name && - typeValue.property.name === "nav"; - if (typeValue !== "nav" && !isEnumValueNav) { + typePropType === "MemberExpression" && + isEnumValue( + context, + typePropValue, + pageSectionTypeEnum.local.name, + "nav" + ); + + if (typePropValue !== "nav" && !isEnumValueNav) { return; } diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionUpdateVariantValues/pageSection-update-variant-values.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionUpdateVariantValues/pageSection-update-variant-values.ts index 108d0b26c..a7d08b0cf 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionUpdateVariantValues/pageSection-update-variant-values.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionUpdateVariantValues/pageSection-update-variant-values.ts @@ -1,4 +1,10 @@ -import { getFromPackage, getAttribute, getAttributeValue } from "../../helpers"; +import { + getFromPackage, + getAttribute, + getAttributeValue, + isEnumValue, + attributeValueIsString, +} from "../../helpers"; import { Rule } from "eslint"; import { JSXOpeningElement } from "estree-jsx"; @@ -30,25 +36,39 @@ module.exports = { if (!variantProp || !variantProp.value) { return; } - const variantValue = getAttributeValue( - context, - variantProp.value - ); - const pageSectionVariantLocalName = - pageSectionVariantImport && pageSectionVariantImport.local.name; + const { type: variantPropType, value: variantPropValue } = + getAttributeValue(context, variantProp.value); + const variantValueEnum = + variantPropType === "MemberExpression" + ? variantPropValue + : null; + const hasPatternFlyEnum = - variantValue?.object && - variantValue.object.name === pageSectionVariantLocalName; - const variantValueIsLiteral = - variantProp.value.type === "Literal" || - (variantProp.value.type === "JSXExpressionContainer" && - variantProp.value.expression.type === "Literal"); - if (!variantValueIsLiteral && !hasPatternFlyEnum) { + pageSectionVariantImport && + variantValueEnum?.object && + context.getSourceCode().getText(variantValueEnum.object) === + pageSectionVariantImport.local.name; + + if ( + !attributeValueIsString(variantProp.value) && + !hasPatternFlyEnum + ) { return; } - const hasValidValue = variantValue?.property - ? validValues.includes(variantValue.property.name) - : validValues.includes(variantValue); + + const isValidEnumValue = + hasPatternFlyEnum && + isEnumValue( + context, + variantValueEnum, + pageSectionVariantImport.local.name, + validValues + ); + + const hasValidValue = + isValidEnumValue || + (variantPropType === "string" && + validValues.includes(variantPropValue)); if (!hasValidValue) { context.report({ diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarGroupUpdatedIconButtonGroupVariant/toolbarGroup-updated-variant.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarGroupUpdatedIconButtonGroupVariant/toolbarGroup-updated-variant.ts index 5129fac90..4eb0bbdbe 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarGroupUpdatedIconButtonGroupVariant/toolbarGroup-updated-variant.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarGroupUpdatedIconButtonGroupVariant/toolbarGroup-updated-variant.ts @@ -1,6 +1,12 @@ import { Rule } from "eslint"; import { JSXOpeningElement } from "estree-jsx"; -import { getFromPackage, getAttribute, getAttributeValue } from "../../helpers"; +import { + getFromPackage, + getAttribute, + getAttributeValue, + getEnumPropertyName, + isEnumValue, +} from "../../helpers"; // https://github.com/patternfly/patternfly-react/pull/10674 module.exports = { @@ -20,6 +26,7 @@ module.exports = { "icon-button-group": "action-group-plain", }; const oldVariantNames = Object.keys(renames); + type OldVariantType = "button-group" | "icon-button-group"; return !componentImports.length ? {} @@ -37,25 +44,44 @@ module.exports = { return; } - const variantValue = getAttributeValue(context, variant.value); + const { value: variantValue, type: variantType } = + getAttributeValue(context, variant.value); + const variantValueEnum = + variantType === "MemberExpression" ? variantValue : null; + const isEnumToRename = variantEnumImport && - variantValue.object?.name === variantEnumImport.local.name && - oldVariantNames.includes(variantValue.property.value); + variantValueEnum && + isEnumValue( + context, + variantValueEnum, + variantEnumImport.local.name, + oldVariantNames + ); - if (!oldVariantNames.includes(variantValue) && !isEnumToRename) { + if ( + !( + variantType === "string" && + oldVariantNames.includes(variantValue) + ) && + !isEnumToRename + ) { return; } - const variantToRename: "button-group" | "icon-button-group" = - variantValue.property?.value ?? variantValue; + const variantToRename = isEnumToRename + ? (getEnumPropertyName( + context, + variantValueEnum + ) as OldVariantType) + : (variantValue as OldVariantType); context.report({ node, message: `The \`${variantToRename}\` variant of ${applicableComponent.imported.name} has been renamed to \`${renames[variantToRename]}\`.`, fix(fixer) { return fixer.replaceText( - isEnumToRename ? variantValue.property : variant, + isEnumToRename ? variantValueEnum.property : variant, isEnumToRename ? `"${renames[variantToRename]}"` : `variant="${renames[variantToRename]}"` diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarItemVariantPropUpdates/toolbarItem-variant-prop-updates.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarItemVariantPropUpdates/toolbarItem-variant-prop-updates.ts index ccd82f0c8..05538e20d 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarItemVariantPropUpdates/toolbarItem-variant-prop-updates.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarItemVariantPropUpdates/toolbarItem-variant-prop-updates.ts @@ -10,6 +10,8 @@ import { getAttribute, getAttributeValue, attributeValueIsString, + getEnumPropertyName, + isEnumValue, } from "../../helpers"; // https://github.com/patternfly/patternfly-react/pull/10649 @@ -70,20 +72,30 @@ module.exports = { return; } - const variantValue = getAttributeValue(context, variant.value); + const { type: variantType, value: variantValue } = + getAttributeValue(context, variant.value); - const variantValueIsLiteral = attributeValueIsString( - variant.value - ); + const variantValueEnum = + variantType === "MemberExpression" ? variantValue : null; + + const isEnumToRemove = + enumImport && + variantValueEnum && + isEnumValue( + context, + variantValueEnum, + enumImport.local.name, + variantsToRemove + ); if ( - (variantValueIsLiteral && + (variantType === "string" && variantsToRemove.includes(variantValue)) || - (nodeIsEnum(variantValue) && - variantsToRemove.includes(variantValue.property.value)) + isEnumToRemove ) { - const variantToRemove = - variantValue.property?.value ?? variantValue; + const variantToRemove = isEnumToRemove + ? getEnumPropertyName(context, variantValueEnum) + : variantValue; context.report({ node, @@ -94,7 +106,7 @@ module.exports = { }); } - if (variantValueIsLiteral && variantValue === "chip-group") { + if (variantType === "string" && variantValue === "chip-group") { context.report({ node, message: diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarReplacedSpacerSpaceItems/toolbar-replaced-spacer-spaceItems.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarReplacedSpacerSpaceItems/toolbar-replaced-spacer-spaceItems.ts index f31838469..5a1a162aa 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarReplacedSpacerSpaceItems/toolbar-replaced-spacer-spaceItems.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarReplacedSpacerSpaceItems/toolbar-replaced-spacer-spaceItems.ts @@ -1,6 +1,5 @@ import { Rule } from "eslint"; import { JSXOpeningElement } from "estree-jsx"; -import { Property } from "estree-jsx"; import { getFromPackage, getAttribute, getAttributeValue } from "../../helpers"; // https://github.com/patternfly/patternfly-react/pull/10418 @@ -34,8 +33,23 @@ module.exports = { const spaceItemsPropMessage = `${ spacerProp ? " Additionally, the" : "The" } spaceItems property has been removed from ${node.name.name}.`; - const spacerVal = - spacerProp && getAttributeValue(context, spacerProp.value); + + const getSpacerValue = () => { + if (!spacerProp) { + return; + } + + const { type, value } = getAttributeValue( + context, + spacerProp.value + ); + + if (type !== "ObjectExpression") { + return; + } + + return value; + }; context.report({ node, @@ -45,18 +59,23 @@ module.exports = { fix(fixer) { const fixes = []; - if (spacerProp) { - spacerVal && - spacerVal.forEach((val: Property) => { - const newValue = - val.value?.type === "Literal" && - (val.value.value as string).replace("spacer", "gap"); + const spacerVal = getSpacerValue(); + + if (spacerProp && spacerVal) { + spacerVal.forEach((val) => { + if (val.type !== "Property") { + return; + } + + const newValue = + val.value?.type === "Literal" && + (val.value.value as string).replace("spacer", "gap"); - newValue && - fixes.push( - fixer.replaceText(val.value, `"${newValue}"`) - ); - }); + newValue && + fixes.push( + fixer.replaceText(val.value, `"${newValue}"`) + ); + }); fixes.push(fixer.replaceText(spacerProp.name, "gap")); } diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarUpdateAlignValues/toolbar-update-align-values.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarUpdateAlignValues/toolbar-update-align-values.ts index 03b531821..608da1857 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarUpdateAlignValues/toolbar-update-align-values.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/toolbarUpdateAlignValues/toolbar-update-align-values.ts @@ -47,15 +47,25 @@ module.exports = { return; } - const attributeValue = getAttributeValue( - context, - attribute.value - ); + const { type: attrValueType, value: attrValue } = + getAttributeValue(context, attribute.value); + + // align prop on Toolbar[Component] accepts an object + if (attrValueType !== "ObjectExpression") { + return; + } + + const attributeValueProperties = attrValue.filter( + (prop) => prop.type === "Property" + ) as Property[]; if ( - attributeValue.every( - (property: Property) => - property.value.type === "Literal" && - !oldPropValues.includes(property.value.value as string) + attributeValueProperties.every( + (property) => + (property.value.type === "Literal" && + !oldPropValues.includes( + property.value.value as string + )) || + property.value.type !== "Literal" ) ) { return; @@ -67,18 +77,18 @@ module.exports = { fix(fixer) { const fixes = []; - for (const property of attributeValue) { - if (oldPropValues.includes(property.value.value)) { - const propertyKeyValue = - property.key.type === "Literal" - ? `"${property.key.value}"` - : property.key.name; + for (const property of attributeValueProperties) { + if (property.value.type !== "Literal") { + continue; + } + + const propertyValueString = property.value.value as string; // value is expected to be "alignLeft" or "alignRight" + + if (oldPropValues.includes(propertyValueString)) { fixes.push( fixer.replaceText( - property, - `${propertyKeyValue}: "${ - newPropValueMap[property.value.value] - }"` + property.value, + `"${newPropValueMap[propertyValueString]}"` ) ); } diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/wizardNavItemWarnUpdateMarkup/wizardNavItem-warn-update-markup.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/wizardNavItemWarnUpdateMarkup/wizardNavItem-warn-update-markup.ts index cb18af483..8d8c6e37e 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/wizardNavItemWarnUpdateMarkup/wizardNavItem-warn-update-markup.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/wizardNavItemWarnUpdateMarkup/wizardNavItem-warn-update-markup.ts @@ -26,7 +26,8 @@ module.exports = { 'Additionally, when the nav item has a status of "error" the icon will be rendered before the item content, and the WizardToggle will also now render an error icon.'; const statusProp = getAttribute(node, "status"); const statusValue = - statusProp && getAttributeValue(context, statusProp.value); + statusProp && + getAttributeValue(context, statusProp.value).value; context.report({ node, diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/wizardStepUpdatedBodyTyping/wizardStep-updated-body-typing.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/wizardStepUpdatedBodyTyping/wizardStep-updated-body-typing.ts index b66f3ed70..d89550abe 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/wizardStepUpdatedBodyTyping/wizardStep-updated-body-typing.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/wizardStepUpdatedBodyTyping/wizardStep-updated-body-typing.ts @@ -24,7 +24,10 @@ module.exports = { if (!bodyProp) { return; } - const bodyValue = getAttributeValue(context, bodyProp.value); + const bodyValue = getAttributeValue( + context, + bodyProp.value + ).value; if (bodyValue === null) { context.report({