Skip to content

Commit

Permalink
fix(Text): Replace TextVariants and TextProps with Content (#707)
Browse files Browse the repository at this point in the history
  • Loading branch information
wise-king-sullyman authored Jul 23, 2024
1 parent 00f9444 commit 5e77a4c
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -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: `<Text />`,
Expand Down Expand Up @@ -97,6 +101,16 @@ ruleTester.run('text-replace-with-content', rule, {
output: `import { Content } from '@patternfly/react-core'; <Content component="ul" isPlainList></Content>`,
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'; <PFText>Abc</PFText>`,
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
},
});
}
Expand All @@ -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
);
Expand All @@ -61,24 +80,24 @@ 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,
message: errorMessage,
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(
Expand All @@ -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")
);
}

Expand All @@ -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)];
},
});
},
};
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@ import {
TextContent,
TextList,
TextListItem,
TextProps,
TextVariants,
} from "@patternfly/react-core";

export const TextReplaceWithContentInput = () => (
<>
<Text component="h3">Abc</Text>
<Text>Abc</Text>
<TextContent>Abc</TextContent>
<TextContent isVisited>Abc</TextContent>
<TextList>Abc</TextList>
<TextList isPlain>Abc</TextList>
<TextList component="ol">Abc</TextList>
<TextListItem>Abc</TextListItem>
<TextListItem component="dt">Abc</TextListItem>
<TextList>
<TextListItem>A</TextListItem>
<TextListItem>B</TextListItem>
<TextListItem>C</TextListItem>
</TextList>
</>
);
export const TextReplaceWithContentInput = () => {
interface Foo extends TextProps {}

return (
<>
<Text component="h3">Abc</Text>
<Text>Abc</Text>
<TextContent>Abc</TextContent>
<TextContent isVisited>Abc</TextContent>
<TextList>Abc</TextList>
<TextList isPlain>Abc</TextList>
<TextList component="ol">Abc</TextList>
<TextListItem>Abc</TextListItem>
<TextListItem component="dt">Abc</TextListItem>
<TextListItem component={TextVariants.dt}>Abc</TextListItem>
<TextList>
<TextListItem>A</TextListItem>
<TextListItem>B</TextListItem>
<TextListItem>C</TextListItem>
</TextList>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@ import {
Content,
Content,
Content,
ContentProps,
ContentVariants,
} from "@patternfly/react-core";

export const TextReplaceWithContentInput = () => (
<>
<Content component="h3">Abc</Content>
<Content component="p">Abc</Content>
<Content>Abc</Content>
<Content isVisitedLink>Abc</Content>
<Content component="ul">Abc</Content>
<Content component="ul" isPlainList>Abc</Content>
<Content component="ol">Abc</Content>
<Content component="li">Abc</Content>
<Content component="dt">Abc</Content>
<Content component="ul">
<Content component="li">A</Content>
<Content component="li">B</Content>
<Content component="li">C</Content>
</Content>
</>
);
export const TextReplaceWithContentInput = () => {
interface Foo extends ContentProps {}

return (
<>
<Content component="h3">Abc</Content>
<Content component="p">Abc</Content>
<Content>Abc</Content>
<Content isVisitedLink>Abc</Content>
<Content component="ul">Abc</Content>
<Content component="ul" isPlainList>Abc</Content>
<Content component="ol">Abc</Content>
<Content component="li">Abc</Content>
<Content component="dt">Abc</Content>
<Content component={ContentVariants.dt}>Abc</Content>
<Content component="ul">
<Content component="li">A</Content>
<Content component="li">B</Content>
<Content component="li">C</Content>
</Content>
</>
);
};

0 comments on commit 5e77a4c

Please sign in to comment.