Skip to content

Commit

Permalink
feat(Masthead): Update subcomponent names
Browse files Browse the repository at this point in the history
  • Loading branch information
wise-king-sullyman committed Jul 31, 2024
1 parent 578475a commit 7950b85
Show file tree
Hide file tree
Showing 14 changed files with 362 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { JSXAttribute } from "estree-jsx";

/** Gets the name value of a JSX attribute */
export function getAttributeName(attr: JSXAttribute) {
switch (attr.name.type) {
case "JSXIdentifier":
return attr.name.name;
case "JSXNamespacedName":
return attr.name.name.name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ImportSpecifier } from "estree-jsx";
import { ImportDefaultSpecifierWithParent } from "./interfaces";
import { getDeclarationString } from "./getDeclarationString";

/** Gets the name of an import based on the specifier and an array of names that should be looked for in default import paths */
export function getComponentImportName(
importSpecifier: ImportSpecifier | ImportDefaultSpecifierWithParent,
potentialNames: string[]
) {
if (importSpecifier.type === "ImportSpecifier") {
return importSpecifier.imported.name;
}

return potentialNames.find((name) =>
getDeclarationString(importSpecifier)?.includes(name)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ImportDefaultSpecifierWithParent } from "./interfaces";

/** Gets the import path string for a default import */
export function getDeclarationString(
defaultImportSpecifier: ImportDefaultSpecifierWithParent
) {
return defaultImportSpecifier?.parent?.source.value?.toString();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ExportSpecifier,
} from "estree-jsx";
import { pfPackageMatches } from "./pfPackageMatches";
import { ImportDefaultSpecifierWithParent } from "./interfaces";

type Declarations = ImportDeclaration | ExportNamedDeclaration;
type Specifiers = ImportSpecifier | ExportSpecifier;
Expand Down Expand Up @@ -126,5 +127,5 @@ export function getAllImportsFromPackage(
componentNames.includes(imp.imported.name)
);

return [filteredImports, defaultImports].flat();
return [filteredImports, defaultImports as ImportDefaultSpecifierWithParent[]].flat();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { JSXOpeningElement, JSXMemberExpression } from "estree-jsx";

/** Gets the name of an opening element or member expression */
export function getNodeName(node: JSXOpeningElement | JSXMemberExpression) {
if (node.type === "JSXMemberExpression") {
switch (node.object.type) {
case "JSXMemberExpression":
return getNodeName(node.object);
case "JSXIdentifier":
return node.object.name;
}
}

switch (node.name.type) {
case "JSXMemberExpression":
return getNodeName(node.name);
case "JSXIdentifier":
case "JSXNamespacedName":
return typeof node.name.name === "string"
? node.name.name
: node.name.name.name;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { JSXAttribute, JSXOpeningElement } from "estree-jsx";
import { getAttributeName } from "./getAttributeName";

/** Returns true if the passed opening element has a data-codemods attribute */
export function hasCodeModDataTag(openingElement: JSXOpeningElement) {
const nonSpreadAttributes = openingElement.attributes.filter(
(attr) => attr.type === "JSXAttribute"
);
const attributeNames = nonSpreadAttributes.map((attr) =>
getAttributeName(attr as JSXAttribute)
);
return attributeNames.includes("data-codemods");
}
5 changes: 5 additions & 0 deletions packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
export * from "./contextReports";
export * from "./findAncestor";
export * from "./fixers";
export * from "./getAttributeName";
export * from "./getComponentImportName";
export * from "./getDeclarationString";
export * from "./getEndRange";
export * from "./getFromPackage";
export * from "./getNodeName";
export * from "./getText";
export * from "./hasCodemodDataTag";
export * from "./helpers";
export * from "./importAndExport";
export * from "./includesImport";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
Identifier,
ImportDefaultSpecifier,
ImportDeclaration,
JSXOpeningElement,
JSXElement,
} from "estree-jsx";

export interface IdentifierWithParent extends Identifier {
Expand All @@ -13,3 +15,7 @@ export interface ImportDefaultSpecifierWithParent
extends ImportDefaultSpecifier {
parent?: ImportDeclaration;
}

export interface JSXOpeningElementWithParent extends JSXOpeningElement {
parent?: JSXElement;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Rule } from "eslint";
import { ImportSpecifier } from "estree-jsx";
import { getAllImportsFromPackage } from "./getFromPackage";
import {
ImportDefaultSpecifierWithParent,
JSXOpeningElementWithParent,
} from "./interfaces";
import {
getDeclarationString,
getComponentImportName,
getNodeName,
hasCodeModDataTag,
} from "./index";

interface ComponentRenames {
[currentName: string]: string;
}

function formatDefaultMessage(oldName: string, newName: string) {
return `${oldName} has been renamed to ${newName}.`;
}

function getFixes(
fixer: Rule.RuleFixer,
nodeImport: ImportSpecifier | ImportDefaultSpecifierWithParent,
node: JSXOpeningElementWithParent,
oldName: string,
newName: string
) {
const fixes = [];

const isNamedImport = nodeImport.type === "ImportSpecifier";
if (isNamedImport) {
fixes.push(fixer.replaceText(nodeImport.imported, newName));
} else {
const importDeclaration = nodeImport.parent;
const newImportDeclaration = importDeclaration?.source.raw?.replace(
oldName,
newName
);
if (importDeclaration && newImportDeclaration) {
fixes.push(
fixer.replaceText(importDeclaration.source, newImportDeclaration)
);
}
}

const shouldRenameNode =
isNamedImport && nodeImport.imported.name === nodeImport.local.name;

if (shouldRenameNode) {
fixes.push(fixer.replaceText(node.name, newName));
fixes.push(fixer.insertTextAfter(node.name, " data-codemods"));
}

const closingElement = node?.parent?.closingElement;
if (shouldRenameNode && closingElement) {
fixes.push(fixer.replaceText(closingElement.name, newName));
}

return fixes;
}

export function renameComponent(
renames: ComponentRenames,
packageName = "@patternfly/react-core"
) {
return function (context: Rule.RuleContext) {
const oldNames = Object.keys(renames);
const imports = getAllImportsFromPackage(context, packageName, oldNames);

if (imports.length === 0) {
return {};
}

return {
JSXOpeningElement(node: JSXOpeningElementWithParent) {
if (hasCodeModDataTag(node)) {
return;
}

const nodeName = getNodeName(node);
const nodeImport = imports.find((imp) => {
if (imp.type === "ImportSpecifier") {
return [imp.imported.name, imp.local.name].includes(nodeName);
}

return oldNames.some((name) =>
getDeclarationString(imp)?.includes(name)
);
});

if (!nodeImport) {
return;
}

const oldName = getComponentImportName(nodeImport, oldNames);

if (!oldName) {
return;
}

const newName = renames[oldName];

context.report({
node,
message: formatDefaultMessage(oldName, newName),
fix: (fixer) => getFixes(fixer, nodeImport, node, oldName, newName),
});
},
};
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
### masthead-name-changes [(#10809)](https://github.com/patternfly/patternfly-react/pull/10809)

The old `MastheadBrand` component has been renamed to `MastheadLogo`, and the old `MastheadMain` has been renamed to `MastheadBrand`.

#### Examples

In:

```jsx
%inputExample%
```

Out:

```jsx
%outputExample%
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const ruleTester = require("../../ruletester");
import * as rule from "./masthead-name-changes";

ruleTester.run("masthead-name-changes", rule, {
valid: [
{
code: `<MastheadBrand />`,
},
{
code: `<MastheadMain />`,
},
{
code: `import { MastheadBrand } from '@patternfly/react-core'; <MastheadBrand data-codemods />`,
},
{
code: `import { MastheadMain } from '@patternfly/react-core'; <MastheadMain data-codemods />`,
},
],
invalid: [
{
code: `import { MastheadBrand } from '@patternfly/react-core'; <MastheadBrand />`,
output: `import { MastheadLogo } from '@patternfly/react-core'; <MastheadLogo data-codemods />`,
errors: [
{
message: `MastheadBrand has been renamed to MastheadLogo.`,
type: "JSXOpeningElement",
},
],
},
{
code: `import { MastheadMain } from '@patternfly/react-core'; <MastheadMain />`,
output: `import { MastheadBrand } from '@patternfly/react-core'; <MastheadBrand data-codemods />`,
errors: [
{
message: `MastheadMain has been renamed to MastheadBrand.`,
type: "JSXOpeningElement",
},
],
},
// with other props
{
code: `import { MastheadBrand } from '@patternfly/react-core'; <MastheadBrand className="foo" />`,
output: `import { MastheadLogo } from '@patternfly/react-core'; <MastheadLogo data-codemods className="foo" />`,
errors: [
{
message: `MastheadBrand has been renamed to MastheadLogo.`,
type: "JSXOpeningElement",
},
],
},
// because of how the unit tests run I have to handle having both MastheadBrand and MastheadMain together in stages
{
code: `import { MastheadBrand, MastheadMain } from '@patternfly/react-core'; <MastheadMain><MastheadBrand>Logo</MastheadBrand></MastheadMain>`,
output: `import { MastheadLogo, MastheadMain } from '@patternfly/react-core'; <MastheadMain><MastheadLogo data-codemods>Logo</MastheadLogo></MastheadMain>`,
errors: [
{
message: `MastheadMain has been renamed to MastheadBrand.`,
type: "JSXOpeningElement",
},
{
message: `MastheadBrand has been renamed to MastheadLogo.`,
type: "JSXOpeningElement",
},
],
},
{
code: `import { MastheadLogo, MastheadMain } from '@patternfly/react-core'; <MastheadMain><MastheadLogo data-codemods>Logo</MastheadLogo></MastheadMain>`,
output: `import { MastheadLogo, MastheadBrand } from '@patternfly/react-core'; <MastheadBrand data-codemods><MastheadLogo data-codemods>Logo</MastheadLogo></MastheadBrand>`,
errors: [
{
message: `MastheadMain has been renamed to MastheadBrand.`,
type: "JSXOpeningElement",
},
],
},
// with alias
{
code: `import { MastheadBrand as MB } from '@patternfly/react-core'; <MB />`,
output: `import { MastheadLogo as MB } from '@patternfly/react-core'; <MB />`,
errors: [
{
message: `MastheadBrand has been renamed to MastheadLogo.`,
type: "JSXOpeningElement",
},
],
},
// dist imports
{
code: `import { MastheadBrand } from '@patternfly/react-core/dist/esm/components/Masthead/MastheadBrand'; <MastheadBrand />`,
output: `import { MastheadLogo } from '@patternfly/react-core/dist/esm/components/Masthead/MastheadBrand'; <MastheadLogo data-codemods />`,
errors: [
{
message: `MastheadBrand has been renamed to MastheadLogo.`,
type: "JSXOpeningElement",
},
],
},
{
code: `import { MastheadBrand } from '@patternfly/react-core/dist/js/components/Masthead/MastheadBrand'; <MastheadBrand />`,
output: `import { MastheadLogo } from '@patternfly/react-core/dist/js/components/Masthead/MastheadBrand'; <MastheadLogo data-codemods />`,
errors: [
{
message: `MastheadBrand has been renamed to MastheadLogo.`,
type: "JSXOpeningElement",
},
],
},
{
code: `import { MastheadBrand } from '@patternfly/react-core/dist/dynamic/components/Masthead/MastheadBrand'; <MastheadBrand />`,
output: `import { MastheadLogo } from '@patternfly/react-core/dist/dynamic/components/Masthead/MastheadBrand'; <MastheadLogo data-codemods />`,
errors: [
{
message: `MastheadBrand has been renamed to MastheadLogo.`,
type: "JSXOpeningElement",
},
],
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { renameComponent } from "../../helpers/renameComponent";

// https://github.com/patternfly/patternfly-react/pull/10809

const renames = {
MastheadBrand: "MastheadLogo",
MastheadMain: "MastheadBrand",
};

module.exports = {
meta: { fixable: "code" },
create: renameComponent(renames),
};
Loading

0 comments on commit 7950b85

Please sign in to comment.