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

refactor(EmptyState): extract helpers, fix(EmptyState): getElementChildText fix #602

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { JSXElement, JSXAttribute } from "estree-jsx";

export function getAttribute(node: JSXElement, attributeName: string) {
const attributes = node.openingElement.attributes.filter(
(attr) => attr.type === "JSXAttribute"
) as JSXAttribute[];
return attributes.find((attr) => attr.name.name === attributeName);
adamviktora marked this conversation as resolved.
Show resolved Hide resolved
}

export function getExpression(node?: JSXAttribute["value"]) {
if (!node) {
return;
}

if (node.type === "JSXExpressionContainer") {
return node.expression;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { JSXElement } from "estree-jsx";

export function getChildElementByName(node: JSXElement, name: string) {
return node.children?.find(
(child) =>
child.type === "JSXElement" &&
child.openingElement.name.type === "JSXIdentifier" &&
child.openingElement.name.name === name
) as JSXElement | undefined;
}

export function nodeIsComponentNamed(node: JSXElement, componentName: string) {
if (node.openingElement.name.type === "JSXIdentifier") {
return node.openingElement.name.name === componentName;
}

return false;
}
74 changes: 74 additions & 0 deletions packages/eslint-plugin-pf-codemods/src/rules/helpers/getText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Rule } from "eslint";

import {
JSXAttribute,
JSXElement,
JSXExpressionContainer,
JSXFragment,
Node,
} from "estree-jsx";

export function getAttributeText(
context: Rule.RuleContext,
attribute?: JSXAttribute
) {
if (!attribute) {
return "";
}

return context.getSourceCode().getText(attribute);
}

export function getAttributeValueText(
context: Rule.RuleContext,
attribute?: JSXAttribute
) {
if (!attribute || !attribute.value) {
return "";
}

return context.getSourceCode().getText(attribute.value);
}

export function getNodesText(context: Rule.RuleContext, nodes: Node[]) {
return nodes.map((node) => context.getSourceCode().getText(node)).join("");
}

export function getChildrenAsAttributeValueText(
context: Rule.RuleContext,
children: JSXElement["children"]
) {
if (!children.length) {
return `""`;
}

// is a single text-only child
if (children.length === 1 && children[0].type === "JSXText") {
const childText = children[0].value.trim();

if (childText.includes(`"`)) {
return `{<>${childText}</>}`;
}

return `"${childText}"`;
}

const nonEmptyChildrenNodes = children.filter(
(child) => !(child.type === "JSXText" && child.value.trim() === "")
);

if (nonEmptyChildrenNodes.length === 1) {
const singleChild = nonEmptyChildrenNodes[0];
const singleChildText = context
.getSourceCode()
.getText(
singleChild as JSXExpressionContainer | JSXElement | JSXFragment
);

return singleChild.type === "JSXExpressionContainer"
? singleChildText
: `{${singleChildText}}`;
}

return `{<>${getNodesText(context, children as Node[])}</>}`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ImportSpecifier } from "estree-jsx";

export function includesImport(
importSpecifiers: ImportSpecifier[],
targetImport: string
) {
return importSpecifiers.some(
(specifier) => specifier.imported.name === targetImport
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export * from "./findAncestor";
export * from "./helpers";
export * from "./pfPackageMatches";
export * from "./getFromPackage";
export * from "./getText";
export * from "./includesImport";
export * from "./JSXAttributes";
export * from "./JSXElements";
export * from "./pfPackageMatches";
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ ruleTester.run("emptyStateHeader-move-into-emptyState", rule, {
],
},
{
// without any EmptyStateHeader props but with children
// without any EmptyStateHeader props but with children as text
code: `import {
EmptyState,
EmptyStateHeader,
Expand Down Expand Up @@ -148,6 +148,112 @@ ruleTester.run("emptyStateHeader-move-into-emptyState", rule, {
},
],
},
{
// without any EmptyStateHeader props but with children as single JSXElement
code: `import {
EmptyState,
EmptyStateHeader,
} from "@patternfly/react-core";

export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
<EmptyState>
<EmptyStateHeader>
<h1>Foo bar</h1>
</EmptyStateHeader>
</EmptyState>
);
`,
output: `import {
EmptyState,
EmptyStateHeader,
} from "@patternfly/react-core";

export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
<EmptyState titleText={<h1>Foo bar</h1>}>
</EmptyState>
);
`,
errors: [
{
message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState.`,
type: "JSXElement",
},
],
},
{
// without any EmptyStateHeader props but with children as single JSXExpressionContainer
code: `import {
EmptyState,
EmptyStateHeader,
} from "@patternfly/react-core";

const title = "Some title";

export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
<EmptyState>
<EmptyStateHeader>
{title}
</EmptyStateHeader>
</EmptyState>
);
`,
output: `import {
EmptyState,
EmptyStateHeader,
} from "@patternfly/react-core";

const title = "Some title";

export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
<EmptyState titleText={title}>
</EmptyState>
);
`,
errors: [
{
message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState.`,
type: "JSXElement",
},
],
},
{
// without any EmptyStateHeader props but with children consisting of multiple elements
code: `import {
EmptyState,
EmptyStateHeader,
} from "@patternfly/react-core";

const title = "Some title";

export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
<EmptyState>
<EmptyStateHeader>
{title} followed by some text
</EmptyStateHeader>
</EmptyState>
);
`,
output: `import {
EmptyState,
EmptyStateHeader,
} from "@patternfly/react-core";

const title = "Some title";

export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
<EmptyState titleText={<>
{title} followed by some text
</>}>
</EmptyState>
);
`,
errors: [
{
message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState.`,
type: "JSXElement",
},
],
},
wise-king-sullyman marked this conversation as resolved.
Show resolved Hide resolved
{
// without an EmptyStateHeader or titleText
code: `import { EmptyState } from "@patternfly/react-core";
Expand Down
Loading
Loading