diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/text-replace-with-content.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/text-replace-with-content.test.ts index 33294b28b..d921301fa 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/text-replace-with-content.test.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/text-replace-with-content.test.ts @@ -1,17 +1,21 @@ -const ruleTester = require('../../ruletester'); -import * as rule from './text-replace-with-content'; +const ruleTester = require("../../ruletester"); +import * as rule from "./text-replace-with-content"; const errorMessage = `We have replaced Text, TextContent, TextList and TextListItem with one Content component. Running this fix will change all of those components names to Content and add a \`component\` prop where necessary.`; const importDeclarationError = { message: errorMessage, - type: 'ImportDeclaration', + type: "ImportDeclaration", }; const jsxElementError = { message: errorMessage, - type: 'JSXElement', + type: "JSXElement", +}; +const identifierError = { + message: errorMessage, + type: "Identifier", }; -ruleTester.run('text-replace-with-content', rule, { +ruleTester.run("text-replace-with-content", rule, { valid: [ { code: ``, @@ -97,6 +101,16 @@ ruleTester.run('text-replace-with-content', rule, { output: `import { Content } from '@patternfly/react-core'; `, errors: [importDeclarationError, jsxElementError], }, + { + code: `import { TextVariants } from '@patternfly/react-core'; const foo = TextVariants.h1`, + output: `import { ContentVariants } from '@patternfly/react-core'; const foo = ContentVariants.h1`, + errors: [importDeclarationError, identifierError], + }, + { + code: `import { TextProps } from '@patternfly/react-core'; interface Foo extends TextProps {}`, + output: `import { ContentProps } from '@patternfly/react-core'; interface Foo extends ContentProps {}`, + errors: [importDeclarationError, identifierError], + }, // with alias { code: `import { Text as PFText } from '@patternfly/react-core'; Abc`, diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/text-replace-with-content.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/text-replace-with-content.ts index 64137fa89..30dfca8e1 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/text-replace-with-content.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/text-replace-with-content.ts @@ -1,47 +1,66 @@ -import { Rule } from 'eslint'; +import { Rule } from "eslint"; import { ImportDeclaration, ImportSpecifier, JSXElement, JSXIdentifier, -} from 'estree-jsx'; -import { getAttribute, getFromPackage, pfPackageMatches } from '../../helpers'; +} from "estree-jsx"; +import { + getAttribute, + getFromPackage, + IdentifierWithParent, + pfPackageMatches, +} from "../../helpers"; // https://github.com/patternfly/patternfly-react/pull/10643 module.exports = { - meta: { fixable: 'code' }, + meta: { fixable: "code" }, create: function (context: Rule.RuleContext) { - const { imports } = getFromPackage(context, '@patternfly/react-core'); + const { imports } = getFromPackage(context, "@patternfly/react-core"); + + const textComponents = ["Text", "TextContent", "TextList", "TextListItem"]; - const textComponents = ['Text', 'TextContent', 'TextList', 'TextListItem']; + const nonComponentTextIdentifiers = ["TextProps", "TextVariants"]; - const componentImports = imports.filter((specifier) => - textComponents.includes(specifier.imported.name) + const allTextIdentifiers = [ + ...textComponents, + ...nonComponentTextIdentifiers, + ]; + + const textImports = imports.filter((specifier) => + allTextIdentifiers.includes(specifier.imported.name) ); const errorMessage = - 'We have replaced Text, TextContent, TextList and TextListItem with one Content component. Running this fix will change all of those components names to Content and add a `component` prop where necessary.'; + "We have replaced Text, TextContent, TextList and TextListItem with one Content component. Running this fix will change all of those components names to Content and add a `component` prop where necessary."; - return !componentImports.length + return !textImports.length ? {} : { ImportDeclaration(node: ImportDeclaration) { - if (pfPackageMatches('@patternfly/react-core', node.source.value)) { + if (pfPackageMatches("@patternfly/react-core", node.source.value)) { const specifierToReplace = node.specifiers.find( (specifier) => - specifier.type === 'ImportSpecifier' && - textComponents.includes(specifier.imported.name) + specifier.type === "ImportSpecifier" && + allTextIdentifiers.includes(specifier.imported.name) ) as ImportSpecifier; if (!specifierToReplace) { return; } + const specifierName = specifierToReplace.imported.name; + const newText = nonComponentTextIdentifiers.includes( + specifierName + ) + ? specifierName.replace("Text", "Content") + : "Content"; + context.report({ node, message: errorMessage, fix(fixer) { - return fixer.replaceText(specifierToReplace, 'Content'); + return fixer.replaceText(specifierToReplace, newText); }, }); } @@ -50,8 +69,8 @@ module.exports = { const openingElement = node.openingElement; const closingElement = node.closingElement; - if (openingElement.name.type === 'JSXIdentifier') { - const componentImport = componentImports.find( + if (openingElement.name.type === "JSXIdentifier") { + const componentImport = textImports.find( (imp) => imp.local.name === (openingElement.name as JSXIdentifier).name ); @@ -61,12 +80,12 @@ module.exports = { } const componentName = componentImport.imported.name as - | 'Text' - | 'TextContent' - | 'TextList' - | 'TextListItem'; + | "Text" + | "TextContent" + | "TextList" + | "TextListItem"; - const componentAttribute = getAttribute(node, 'component'); + const componentAttribute = getAttribute(node, "component"); context.report({ node, @@ -74,11 +93,11 @@ module.exports = { fix(fixer) { const fixes = []; - if (!componentAttribute && componentName !== 'TextContent') { + if (!componentAttribute && componentName !== "TextContent") { const componentMap = { - Text: 'p', - TextList: 'ul', - TextListItem: 'li', + Text: "p", + TextList: "ul", + TextListItem: "li", }; fixes.push( @@ -89,31 +108,31 @@ module.exports = { ); } - if (componentName === 'TextContent') { - const isVisitedAttribute = getAttribute(node, 'isVisited'); + if (componentName === "TextContent") { + const isVisitedAttribute = getAttribute(node, "isVisited"); if (isVisitedAttribute) { fixes.push( fixer.replaceText( isVisitedAttribute.name, - 'isVisitedLink' + "isVisitedLink" ) ); } } - if (componentName === 'TextList') { - const isPlainAttribute = getAttribute(node, 'isPlain'); + if (componentName === "TextList") { + const isPlainAttribute = getAttribute(node, "isPlain"); if (isPlainAttribute) { fixes.push( - fixer.replaceText(isPlainAttribute.name, 'isPlainList') + fixer.replaceText(isPlainAttribute.name, "isPlainList") ); } } - fixes.push(fixer.replaceText(openingElement.name, 'Content')); + fixes.push(fixer.replaceText(openingElement.name, "Content")); if (closingElement) { fixes.push( - fixer.replaceText(closingElement.name, 'Content') + fixer.replaceText(closingElement.name, "Content") ); } @@ -122,6 +141,37 @@ module.exports = { }); } }, + Identifier(node: IdentifierWithParent) { + const textImport = textImports.find( + (imp) => imp.local.name === node.name + ); + + if (!textImport) { + return; + } + + const isComponent = textComponents.includes(node.name); + + // the JSXElement fixer will handle the text components + if (isComponent) { + return; + } + + // imports are handled by the ImportDeclaration fixer + if (node.parent && node.parent.type === "ImportSpecifier") { + return; + } + + const newText = node.name.replace("Text", "Content"); + + context.report({ + node, + message: errorMessage, + fix(fixer) { + return [fixer.replaceText(node, newText)]; + }, + }); + }, }; }, }; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/textReplaceWithContentInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/textReplaceWithContentInput.tsx index 4a71d68be..e1d5be78a 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/textReplaceWithContentInput.tsx +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/textReplaceWithContentInput.tsx @@ -3,23 +3,30 @@ import { TextContent, TextList, TextListItem, + TextProps, + TextVariants, } from "@patternfly/react-core"; -export const TextReplaceWithContentInput = () => ( - <> - Abc - Abc - Abc - Abc - Abc - Abc - Abc - Abc - Abc - - A - B - C - - -); +export const TextReplaceWithContentInput = () => { + interface Foo extends TextProps {} + + return ( + <> + Abc + Abc + Abc + Abc + Abc + Abc + Abc + Abc + Abc + Abc + + A + B + C + + + ); +}; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/textReplaceWithContentOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/textReplaceWithContentOutput.tsx index aa099bb56..745919aa0 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/textReplaceWithContentOutput.tsx +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/textReplaceWithContent/textReplaceWithContentOutput.tsx @@ -3,23 +3,30 @@ import { Content, Content, Content, + ContentProps, + ContentVariants, } from "@patternfly/react-core"; -export const TextReplaceWithContentInput = () => ( - <> - Abc - Abc - Abc - Abc - Abc - Abc - Abc - Abc - Abc - - A - B - C - - -); +export const TextReplaceWithContentInput = () => { + interface Foo extends ContentProps {} + + return ( + <> + Abc + Abc + Abc + Abc + Abc + Abc + Abc + Abc + Abc + Abc + + A + B + C + + + ); +};