-
Notifications
You must be signed in to change notification settings - Fork 19
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
feat(Masthead): Update structure #730
Changes from 8 commits
4b080a5
8aa3242
4c45148
0062d93
a95c722
9abb29c
b93f9e0
83cfbc4
b892ff8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { ImportSpecifier, JSXOpeningElement } from "estree-jsx"; | ||
|
||
/** Resolves the imported name of a node, even if that node has an aliased local name */ | ||
export function getImportedName( | ||
namedImports: ImportSpecifier[], | ||
node: JSXOpeningElement | ||
) { | ||
if (node.name.type !== "JSXIdentifier") { | ||
return; | ||
} | ||
|
||
const nodeName = node.name.name; | ||
|
||
const nodeImport = namedImports.find((imp) => imp.local.name === nodeName); | ||
|
||
return nodeImport?.imported.name; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { ImportSpecifier } from "estree-jsx"; | ||
|
||
/** Resolves the local name of an import */ | ||
export function getLocalComponentName( | ||
namedImports: ImportSpecifier[], | ||
importedName: string | ||
) { | ||
const componentImport = namedImports.find( | ||
(name) => name.imported.name === importedName | ||
); | ||
|
||
const isAlias = | ||
componentImport?.imported.name !== componentImport?.local.name; | ||
|
||
if (componentImport && isAlias) { | ||
return componentImport.local.name; | ||
} | ||
|
||
return importedName; | ||
} |
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% | ||
``` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
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.`, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit not a blocker: wdyt about updating the message that gets output to only mention the applicable element? Instead of saying both "toggle and brand...", on a MastheadToggle, only mention "toggle...". |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit not a blocker: for these comments for the pre/post renamed file, maybe instead "Stage [one|two] of a file that [has been|has NOT been] fixed by masthead-name-changes rule" |
||
{ | ||
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", | ||
}, | ||
], | ||
}, | ||
// with aliases | ||
{ | ||
code: `import { Masthead as MH, MastheadBrand as MB, MastheadMain as MM, MastheadToggle as MT } from '@patternfly/react-core'; <MH><MT>Foo</MT><MM><MB>Bar</MB></MM></MH>`, | ||
output: `import { Masthead as MH, MastheadBrand as MB, MastheadMain as MM, MastheadToggle as MT } from '@patternfly/react-core'; <MH><MM><MT>Foo</MT><MB>Bar</MB></MM></MH>`, | ||
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", | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import { Masthead as MH, MastheadBrand as MB, MastheadMain as MM, MastheadToggle as MT } from '@patternfly/react-core'; <MH><MM><MT>Foo</MT><MB>Bar</MB></MM></MH>`, | ||
output: `import { Masthead as MH, MastheadBrand as MB, MastheadMain as MM, MastheadToggle as MT } from '@patternfly/react-core'; <MH><MM><MT>Foo</MT><MB data-codemods><MB>Bar</MB></MB></MM></MH>`, | ||
errors: [ | ||
{ | ||
message: `The structure of Masthead has been updated, MastheadToggle and MastheadBrand should now be wrapped in MastheadMain.`, | ||
type: "JSXOpeningElement", | ||
}, | ||
], | ||
}, | ||
// with dist imports | ||
{ | ||
code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle } from '@patternfly/react-core/dist/esm/components/Masthead'; <Masthead><MastheadToggle>Foo</MastheadToggle><MastheadMain><MastheadLogo>Bar</MastheadLogo></MastheadMain></Masthead>`, | ||
output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/esm/components/Masthead'; <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", | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/esm/components/Masthead'; <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/dist/esm/components/Masthead'; <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", | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle } from '@patternfly/react-core/dist/js/components/Masthead'; <Masthead><MastheadToggle>Foo</MastheadToggle><MastheadMain><MastheadLogo>Bar</MastheadLogo></MastheadMain></Masthead>`, | ||
output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/js/components/Masthead'; <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", | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/js/components/Masthead'; <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/dist/js/components/Masthead'; <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", | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle } from '@patternfly/react-core/dist/dynamic/components/Masthead'; <Masthead><MastheadToggle>Foo</MastheadToggle><MastheadMain><MastheadLogo>Bar</MastheadLogo></MastheadMain></Masthead>`, | ||
output: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/dynamic/components/Masthead'; <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", | ||
}, | ||
], | ||
}, | ||
{ | ||
code: `import { Masthead, MastheadLogo, MastheadMain, MastheadToggle, MastheadBrand } from '@patternfly/react-core/dist/dynamic/components/Masthead'; <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/dist/dynamic/components/Masthead'; <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", | ||
}, | ||
], | ||
}, | ||
], | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import { Rule } from "eslint"; | ||
import { ImportSpecifier } from "estree-jsx"; | ||
import { JSXOpeningElementWithParent } from "../../helpers"; | ||
import { | ||
getAllImportsFromPackage, | ||
getChildElementByName, | ||
getImportedName, | ||
getLocalComponentName, | ||
hasCodeModDataTag, | ||
} from "../../helpers"; | ||
// https://github.com/patternfly/patternfly-react/pull/10809 | ||
|
||
function moveNodeIntoMastheadMain( | ||
context: Rule.RuleContext, | ||
fixer: Rule.RuleFixer, | ||
node: JSXOpeningElementWithParent, | ||
namedImports: ImportSpecifier[] | ||
) { | ||
if (!node.parent || !node.parent.parent) { | ||
return []; | ||
} | ||
|
||
const localMastheadMain = getLocalComponentName(namedImports, "MastheadMain"); | ||
const mastheadMain = getChildElementByName( | ||
node.parent.parent, | ||
localMastheadMain | ||
); | ||
|
||
if (!mastheadMain) { | ||
return []; | ||
} | ||
|
||
const fixes = [fixer.remove(node.parent)]; | ||
|
||
const nodeString = context.getSourceCode().getText(node.parent); | ||
|
||
fixes.push(fixer.insertTextAfter(mastheadMain.openingElement, nodeString)); | ||
|
||
return fixes; | ||
} | ||
|
||
function wrapNodeInMastheadBrand( | ||
fixer: Rule.RuleFixer, | ||
node: JSXOpeningElementWithParent, | ||
namedImports: ImportSpecifier[] | ||
) { | ||
if (!node.parent) { | ||
return []; | ||
} | ||
|
||
const fixes = []; | ||
|
||
const closingNode = node.parent?.closingElement | ||
? node.parent.closingElement | ||
: node; | ||
|
||
const importCount = namedImports.length - 1; | ||
const lastImport = namedImports[importCount]; | ||
|
||
const localMastheadBrand = getLocalComponentName( | ||
namedImports, | ||
"MastheadBrand" | ||
); | ||
|
||
fixes.push( | ||
fixer.insertTextBefore(node, `<${localMastheadBrand} data-codemods>`) | ||
); | ||
fixes.push(fixer.insertTextAfter(closingNode, `</${localMastheadBrand}>`)); | ||
|
||
if (!namedImports.some((imp) => imp.imported.name === "MastheadBrand")) { | ||
fixes.push(fixer.insertTextAfter(lastImport, ", MastheadBrand")); | ||
} | ||
|
||
return fixes; | ||
} | ||
|
||
module.exports = { | ||
meta: { fixable: "code" }, | ||
create: function (context: Rule.RuleContext) { | ||
const targetComponents = [ | ||
"MastheadBrand", | ||
"MastheadToggle", | ||
"MastheadLogo", | ||
"Masthead", | ||
"MastheadMain", | ||
]; | ||
const componentImports = getAllImportsFromPackage( | ||
context, | ||
"@patternfly/react-core", | ||
targetComponents | ||
); | ||
const _namedImports = componentImports.filter( | ||
(imp) => imp.type === "ImportSpecifier" | ||
); | ||
// TS isn't properly resolving that namedImports is just ImportSpecifiers, hence this seemingly unneeded assertion | ||
const namedImports = _namedImports as ImportSpecifier[]; | ||
|
||
const message = | ||
"The structure of Masthead has been updated, MastheadToggle and MastheadBrand should now be wrapped in MastheadMain."; | ||
|
||
return !namedImports.length | ||
? {} | ||
: { | ||
JSXOpeningElement(node: JSXOpeningElementWithParent) { | ||
const nodeImportedName = getImportedName(namedImports, node); | ||
|
||
if (node.name.type !== "JSXIdentifier" || !nodeImportedName) { | ||
return; | ||
} | ||
const parentOpeningElement = node.parent?.parent?.openingElement; | ||
|
||
if (!parentOpeningElement) { | ||
return; | ||
} | ||
|
||
const parentImportedName = getImportedName( | ||
namedImports, | ||
parentOpeningElement | ||
); | ||
|
||
if ( | ||
nodeImportedName === "MastheadToggle" && | ||
parentImportedName !== "MastheadMain" | ||
) { | ||
context.report({ | ||
node, | ||
message, | ||
fix: (fixer) => | ||
moveNodeIntoMastheadMain(context, fixer, node, namedImports), | ||
}); | ||
return; | ||
} | ||
|
||
const isPreRenameMastheadBrand = | ||
nodeImportedName === "MastheadBrand" && | ||
parentImportedName === "MastheadMain" && | ||
!hasCodeModDataTag(node); | ||
|
||
const isPostRenameMastheadBrand = | ||
nodeImportedName === "MastheadLogo" && | ||
parentImportedName !== "MastheadBrand"; | ||
|
||
if (isPreRenameMastheadBrand || isPostRenameMastheadBrand) { | ||
context.report({ | ||
node, | ||
message, | ||
fix: (fixer) => | ||
wrapNodeInMastheadBrand(fixer, node, namedImports), | ||
}); | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The valid tests should probably be updated. One would be the
code
from one of the invalid tests (without the import statement), and the other could be theoutput
from one of the invalid tests.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops