-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
578475a
commit 4b080a5
Showing
6 changed files
with
406 additions
and
0 deletions.
There are no files selected for viewing
96 changes: 96 additions & 0 deletions
96
packages/eslint-plugin-pf-codemods/src/rules/helpers/stringifyJSXElement.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { Rule } from "eslint"; | ||
import { | ||
JSXAttribute, | ||
JSXIdentifier, | ||
JSXMemberExpression, | ||
JSXNamespacedName, | ||
JSXElement, | ||
Expression, | ||
} from "estree-jsx"; | ||
import { getAttributeValue } from "./JSXAttributes"; | ||
|
||
// new interfaces, just local for now to avoid conflicts with the masthead rename RP | ||
interface JSXElementWithParent extends JSXElement { | ||
parent?: JSXElement; | ||
} | ||
|
||
// Similar story here, will use the getName helper from my other PR once it goes in | ||
function getName( | ||
nodeName: JSXIdentifier | JSXMemberExpression | JSXNamespacedName | ||
) { | ||
switch (nodeName.type) { | ||
case "JSXIdentifier": | ||
return nodeName.name; | ||
case "JSXMemberExpression": | ||
return getName(nodeName.object); | ||
case "JSXNamespacedName": | ||
return nodeName.namespace.name; | ||
} | ||
} | ||
|
||
// same story here, will remove this from this file once the other PR is in and I can merge it into this branch | ||
function getAttributeName(attr: JSXAttribute) { | ||
switch (attr.name.type) { | ||
case "JSXIdentifier": | ||
return attr.name.name; | ||
case "JSXNamespacedName": | ||
return attr.name.name.name; | ||
} | ||
} | ||
|
||
/** Gets a string representation of an element including */ | ||
export function stringifyJSXElement( | ||
context: Rule.RuleContext, | ||
node: JSXElementWithParent | ||
) { | ||
const { openingElement, children, closingElement } = node; | ||
|
||
let str = "<"; | ||
|
||
str += getName(openingElement.name); | ||
|
||
if (openingElement.attributes.length) { | ||
const nonSpreadAttributes = openingElement.attributes.filter( | ||
(attr) => attr.type === "JSXAttribute" | ||
); | ||
nonSpreadAttributes.forEach((attr) => { | ||
const attrName = getAttributeName(attr as JSXAttribute); | ||
|
||
const attrValue = getAttributeValue( | ||
context, | ||
(attr as JSXAttribute).value | ||
); | ||
|
||
const attrValueWrapper = | ||
typeof attrValue === "string" ? `"${attrValue}"` : `{${attrValue}}`; | ||
|
||
str += ` ${attrName}=${attrValueWrapper}`; | ||
}); | ||
} | ||
|
||
if (openingElement.selfClosing) { | ||
str += "/"; | ||
} | ||
|
||
str += ">"; | ||
|
||
children.forEach((child) => { | ||
switch (child.type) { | ||
case "JSXElement": | ||
str += stringifyJSXElement(context, child as JSXElementWithParent); | ||
break; | ||
case "JSXText": | ||
str += child.raw; | ||
break; | ||
case "JSXExpressionContainer": | ||
case "JSXSpreadChild": | ||
case "JSXFragment": | ||
} | ||
}); | ||
|
||
if (closingElement) { | ||
str += `</${getName(closingElement.name)}>`; | ||
} | ||
|
||
return str; | ||
} |
18 changes: 18 additions & 0 deletions
18
...pf-codemods/src/rules/v6/mastheadStructureChanges/masthead-structure-changes.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
### masthead-structure-changes [(#10809)](https://github.com/patternfly/patternfly-react/pull/10809) | ||
|
||
The structure of Masthead has been updated, MastheadToggle and MastheadBrand should now be wrapped in MastheadMain. | ||
|
||
#### Examples | ||
|
||
In: | ||
|
||
```jsx | ||
%inputExample% | ||
``` | ||
|
||
Out: | ||
|
||
```jsx | ||
%outputExample% | ||
``` | ||
|
67 changes: 67 additions & 0 deletions
67
...ugin-pf-codemods/src/rules/v6/mastheadStructureChanges/masthead-structure-changes.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
const ruleTester = require("../../ruletester"); | ||
import * as rule from "./masthead-structure-changes"; | ||
|
||
ruleTester.run("masthead-structure-changes", rule, { | ||
valid: [ | ||
{ | ||
code: `<Masthead />`, | ||
}, | ||
{ | ||
code: `import { Masthead } from '@patternfly/react-core'; <Masthead someOtherProp />`, | ||
}, | ||
], | ||
invalid: [ | ||
// stage one of a pre-renamed file | ||
{ | ||
code: `import { Masthead, MastheadBrand, MastheadMain, MastheadToggle } from '@patternfly/react-core'; <Masthead><MastheadToggle>Foo</MastheadToggle><MastheadMain><MastheadBrand>Bar</MastheadBrand></MastheadMain></Masthead>`, | ||
output: `import { Masthead, MastheadBrand, MastheadMain, MastheadToggle } from '@patternfly/react-core'; <Masthead><MastheadMain><MastheadToggle>Foo</MastheadToggle><MastheadBrand>Bar</MastheadBrand></MastheadMain></Masthead>`, | ||
errors: [ | ||
{ | ||
message: `The structure of Masthead has been updated, MastheadToggle and MastheadBrand should now be wrapped in MastheadMain.`, | ||
type: "JSXOpeningElement", | ||
}, | ||
{ | ||
message: `The structure of Masthead has been updated, MastheadToggle and MastheadBrand should now be wrapped in MastheadMain.`, | ||
type: "JSXOpeningElement", | ||
}, | ||
], | ||
}, | ||
// stage two of a pre-renamed file | ||
{ | ||
code: `import { Masthead, MastheadBrand, MastheadMain, MastheadToggle } from '@patternfly/react-core'; <Masthead><MastheadMain><MastheadToggle>Foo</MastheadToggle><MastheadBrand>Bar</MastheadBrand></MastheadMain></Masthead>`, | ||
output: `import { Masthead, MastheadBrand, MastheadMain, MastheadToggle } from '@patternfly/react-core'; <Masthead><MastheadMain><MastheadToggle>Foo</MastheadToggle><MastheadBrand data-codemods><MastheadBrand>Bar</MastheadBrand></MastheadBrand></MastheadMain></Masthead>`, | ||
errors: [ | ||
{ | ||
message: `The structure of Masthead has been updated, MastheadToggle and MastheadBrand should now be wrapped in MastheadMain.`, | ||
type: "JSXOpeningElement", | ||
}, | ||
], | ||
}, | ||
// stage one of a post-renamed file | ||
{ | ||
code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle } from '@patternfly/react-core'; <Masthead><MastheadToggle>Foo</MastheadToggle><MastheadMain><MastheadLogo>Bar</MastheadLogo></MastheadMain></Masthead>`, | ||
output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core'; <Masthead><MastheadToggle>Foo</MastheadToggle><MastheadMain><MastheadBrand data-codemods><MastheadLogo>Bar</MastheadLogo></MastheadBrand></MastheadMain></Masthead>`, | ||
errors: [ | ||
{ | ||
message: `The structure of Masthead has been updated, MastheadToggle and MastheadBrand should now be wrapped in MastheadMain.`, | ||
type: "JSXOpeningElement", | ||
}, | ||
{ | ||
message: `The structure of Masthead has been updated, MastheadToggle and MastheadBrand should now be wrapped in MastheadMain.`, | ||
type: "JSXOpeningElement", | ||
}, | ||
], | ||
}, | ||
// stage two of a post-renamed file | ||
{ | ||
code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core'; <Masthead><MastheadToggle>Foo</MastheadToggle><MastheadMain><MastheadBrand data-codemods><MastheadLogo>Bar</MastheadLogo></MastheadBrand></MastheadMain></Masthead>`, | ||
output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core'; <Masthead><MastheadMain><MastheadToggle>Foo</MastheadToggle><MastheadBrand data-codemods><MastheadLogo>Bar</MastheadLogo></MastheadBrand></MastheadMain></Masthead>`, | ||
errors: [ | ||
{ | ||
message: `The structure of Masthead has been updated, MastheadToggle and MastheadBrand should now be wrapped in MastheadMain.`, | ||
type: "JSXOpeningElement", | ||
}, | ||
], | ||
}, | ||
], | ||
}); |
171 changes: 171 additions & 0 deletions
171
...nt-plugin-pf-codemods/src/rules/v6/mastheadStructureChanges/masthead-structure-changes.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import { Rule } from "eslint"; | ||
import { | ||
ImportDefaultSpecifier, | ||
ImportSpecifier, | ||
JSXAttribute, | ||
JSXElement, | ||
JSXOpeningElement, | ||
} from "estree-jsx"; | ||
import { getAllImportsFromPackage, getChildElementByName } from "../../helpers"; | ||
import { stringifyJSXElement } from "../../helpers/stringifyJSXElement"; | ||
// https://github.com/patternfly/patternfly-react/pull/10809 | ||
|
||
// new interfaces, just local for now to avoid conflicts with the masthead rename RP | ||
interface JSXElementWithParent extends JSXElement { | ||
parent?: JSXElement; | ||
} | ||
|
||
interface JSXOpeningElementWithParent extends JSXOpeningElement { | ||
parent?: JSXElementWithParent; | ||
} | ||
|
||
// same story here, will remove this from this file once the other PR is in and I can merge it into this branch | ||
function getAttributeName(attr: JSXAttribute) { | ||
switch (attr.name.type) { | ||
case "JSXIdentifier": | ||
return attr.name.name; | ||
case "JSXNamespacedName": | ||
return attr.name.name.name; | ||
} | ||
} | ||
|
||
// same story here, will remove this from this file once the other PR is in and I can merge it into this branch | ||
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"); | ||
} | ||
|
||
function moveNodeIntoMastheadMain( | ||
context: Rule.RuleContext, | ||
fixer: Rule.RuleFixer, | ||
node: JSXOpeningElementWithParent | ||
) { | ||
if (!node.parent || !node.parent.parent) { | ||
return []; | ||
} | ||
|
||
const mastheadMain = getChildElementByName( | ||
node.parent.parent, | ||
"MastheadMain" | ||
); | ||
|
||
if (!mastheadMain) { | ||
return []; | ||
} | ||
|
||
const fixes = [fixer.remove(node.parent)]; | ||
|
||
const nodeString = stringifyJSXElement(context, node.parent); | ||
|
||
fixes.push(fixer.insertTextAfter(mastheadMain.openingElement, nodeString)); | ||
|
||
return fixes; | ||
} | ||
|
||
function wrapNodeInMastheadBrand( | ||
fixer: Rule.RuleFixer, | ||
node: JSXOpeningElementWithParent, | ||
componentImports: (ImportSpecifier | ImportDefaultSpecifier)[] | ||
) { | ||
if (!node.parent) { | ||
return []; | ||
} | ||
|
||
const fixes = []; | ||
|
||
const closingNode = node.parent?.closingElement | ||
? node.parent.closingElement | ||
: node; | ||
|
||
fixes.push(fixer.insertTextBefore(node, "<MastheadBrand data-codemods>")); | ||
fixes.push(fixer.insertTextAfter(closingNode, "</MastheadBrand>")); | ||
|
||
const importCount = componentImports.length - 1; | ||
const lastImport = componentImports[importCount]; | ||
|
||
const namedImports = componentImports.filter( | ||
(imp) => imp.type === "ImportSpecifier" | ||
); | ||
const importNames = namedImports.map( | ||
(imp) => (imp as ImportSpecifier).imported.name | ||
); | ||
|
||
if (!importNames.includes("MastheadBrand")) { | ||
fixes.push(fixer.insertTextAfter(lastImport, ", MastheadBrand")); | ||
} | ||
|
||
return fixes; | ||
} | ||
|
||
module.exports = { | ||
meta: { fixable: "code" }, | ||
create: function (context: Rule.RuleContext) { | ||
const componentImports = getAllImportsFromPackage( | ||
context, | ||
"@patternfly/react-core", | ||
["MastheadBrand", "MastheadToggle", "MastheadLogo"] | ||
); | ||
|
||
const message = | ||
"The structure of Masthead has been updated, MastheadToggle and MastheadBrand should now be wrapped in MastheadMain."; | ||
|
||
return !componentImports.length | ||
? {} | ||
: { | ||
JSXOpeningElement(node: JSXOpeningElementWithParent) { | ||
if ( | ||
node.name.type !== "JSXIdentifier" || | ||
!componentImports | ||
.map((imp) => imp.local.name) | ||
.includes(node.name.name) | ||
) { | ||
return; | ||
} | ||
const parentOpeningElement = node.parent?.parent?.openingElement; | ||
|
||
if (!parentOpeningElement) { | ||
return; | ||
} | ||
|
||
const nodeName = node.name.name; | ||
const parentName = | ||
parentOpeningElement.name.type === "JSXIdentifier" && | ||
parentOpeningElement.name.name; | ||
|
||
if ( | ||
nodeName === "MastheadToggle" && | ||
parentName !== "MastheadMain" | ||
) { | ||
context.report({ | ||
node, | ||
message, | ||
fix: (fixer) => moveNodeIntoMastheadMain(context, fixer, node), | ||
}); | ||
return; | ||
} | ||
|
||
const isPreRenameMastheadBrand = | ||
nodeName === "MastheadBrand" && | ||
parentName === "MastheadMain" && | ||
!hasCodeModDataTag(node); | ||
|
||
const isPostRenameMastheadBrand = | ||
nodeName === "MastheadLogo" && parentName !== "MastheadBrand"; | ||
|
||
if (isPreRenameMastheadBrand || isPostRenameMastheadBrand) { | ||
context.report({ | ||
node, | ||
message, | ||
fix: (fixer) => | ||
wrapNodeInMastheadBrand(fixer, node, componentImports), | ||
}); | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |
25 changes: 25 additions & 0 deletions
25
...lugin-pf-codemods/src/rules/v6/mastheadStructureChanges/mastheadStructureChangesInput.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { | ||
Masthead, | ||
MastheadBrand, | ||
MastheadMain, | ||
MastheadToggle, | ||
MastheadLogo | ||
} from "@patternfly/react-core"; | ||
|
||
export const MastheadStructureChangesInputPreNameChange = () => ( | ||
<Masthead> | ||
<MastheadToggle>Foo</MastheadToggle> | ||
<MastheadMain> | ||
<MastheadBrand>Bar</MastheadBrand> | ||
</MastheadMain> | ||
</Masthead> | ||
); | ||
|
||
export const MastheadStructureChangesInputPostNameChange = () => ( | ||
<Masthead> | ||
<MastheadToggle>Foo</MastheadToggle> | ||
<MastheadMain> | ||
<MastheadLogo>Bar</MastheadLogo> | ||
</MastheadMain> | ||
</Masthead> | ||
); |
Oops, something went wrong.