-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add refactor-breakpointmods rule (#72)
* setup * added Page rule * added show rule for ToolbarToggleGroup * build out all rules * finished rules, added tests * PR feedback
- Loading branch information
Showing
4 changed files
with
238 additions
and
1 deletion.
There are no files selected for viewing
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
129 changes: 129 additions & 0 deletions
129
packages/eslint-plugin-pf-codemods/lib/rules/refactor-breakpointmods.js
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,129 @@ | ||
const { getPackageImports } = require('../helpers'); | ||
const componentsToUpdate = [ | ||
'Flex', | ||
'FlexItem', | ||
'PageSection', | ||
'ToolbarItem', | ||
'ToolbarToggleGroup' | ||
]; | ||
const camelCase = str => str.replace(/-([a-z])/g, groups => groups[1].toUpperCase()); | ||
const breakpointPropNames = { | ||
visibility: ['hidden', 'visible'], | ||
widths: ['width_25', 'width_33', 'width_50', 'width_66', 'width_75', 'width_100'], | ||
padding: ['padding', 'noPadding'], | ||
inset: ['insetNone', 'insetSm', 'insetMd', 'insetLg', 'insetXl', 'inset2xl'], | ||
spacer: ['spacerNone', 'spacerXs', 'spacerSm', 'spacerMd', 'spacerLg', 'spacerXl', 'spacer2xl', 'spacer3xl', 'spacer4xl'], | ||
spaceItems: ['spaceItemsNone', 'spaceItemsXs', 'spaceItemsSm', 'spaceItemsMd', 'spaceItemsLg', 'spaceItemsXl', 'spaceItems2xl', 'spaceItems3xl', 'spaceItems4xl'], | ||
show: ['show'], | ||
grow: ['grow'], | ||
shrink: ['shrink'], | ||
flex: ['flexDefault', 'flexNone', 'flex_1', 'flex_2', 'flex_3', 'flex_4'], | ||
direction: ['column', 'columnReverse', 'row', 'rowReverse'], | ||
alignItems: ['alignItemsFlexStart', 'alignItemsFlexEnd', 'alignItemsCenter', 'alignItemsStretch', 'alignItemsBaseline'], | ||
alignContent: ['alignContentFlexStart', 'alignContentFlexEnd', 'alignContentCenter', 'alignContentStretch', 'alignContentSpaceBetween', 'alignContentSpaceAround'], | ||
alignSelf: ['alignSelfFlexStart', 'alignSelfFlexEnd', 'alignSelfCenter', 'alignSelfStretch', 'alignSelfBaseline'], | ||
align: ['alignLeft', 'alignRight'], | ||
justifyContent: ['justifyContentFlexStart', 'justifyContentFlexEnd', 'justifyContentCenter', 'justifyContentSpaceBetween', 'justifyContentSpaceAround', 'justifyContentSpaceEvenly'], | ||
display: ['inlineFlex'], | ||
fullWidth: ['fullWidth'], | ||
flexWrap: ['wrap', 'wrapReverse', 'nowrap'] | ||
}; | ||
const errorMessages = { | ||
Flex: 'Removed breakpointMods prop from Flex in favor of spacer, spaceItems, grow, shrink, flex, direction, alignItems, alignContent, alignSelf, align, justifyContent, display, fullWidth and flexWrap', | ||
FlexItem: 'Removed breakpointMods prop from FlexItem in favor of spacer, grow, shrink, flex, alignSelf, align, and fullWidth', | ||
ToolbarItem: 'Removed breakpointMods prop from ToolbarItem in favor of visiblity, alignment, and spacer', | ||
ToolbarToggleGroup: 'Removed breakpointMods prop from ToolbarToggleGroup in favor of visiblity, alignment, spacer, and spaceItems' | ||
}; | ||
|
||
// https://github.com/patternfly/patternfly-react/pull/4310 | ||
module.exports = { | ||
create: function(context) { | ||
|
||
const imports = getPackageImports(context, '@patternfly/react-core') | ||
.filter(specifier => componentsToUpdate.includes(specifier.imported.name)); | ||
|
||
return imports.length == 0 ? {} : { | ||
JSXOpeningElement(node) { | ||
const nodeName = node.name.name; | ||
const nodeImport = imports.find(imp => imp.local.name === nodeName); | ||
if (nodeImport) { | ||
const importName = nodeImport.imported.name; | ||
// PageSection | ||
if (importName === 'PageSection') { | ||
const attribute = node.attributes.find(node => node.name && node.name.name === 'hasNoPadding'); | ||
if (attribute) { | ||
context.report({ | ||
node, | ||
message: `hasNoPadding prop on PageSection component removed in favor of padding={{ default: 'noPadding' }}`, | ||
fix(fixer) { | ||
return fixer.replaceText(attribute, `padding={{ default: 'noPadding' }}`); | ||
} | ||
}); | ||
} | ||
} else { | ||
// ToolbarToggleGroup | ||
if (importName === 'ToolbarToggleGroup') { | ||
const attribute = node.attributes.find(attr => attr.name && attr.name.name === 'breakpoint'); | ||
if (attribute) { | ||
const breakpointVal = attribute.value.value; | ||
const newAttr = `show={{ ${breakpointVal}: 'show' }}` | ||
context.report({ | ||
node, | ||
message: `breakpoint prop on ToolbarToggleGroup removed in favor of show={{ ${breakpointVal}: 'show' }}`, | ||
fix(fixer) { | ||
return fixer.replaceText(attribute, newAttr); | ||
} | ||
}); | ||
} | ||
} | ||
// breakpointMods prop | ||
const attribute = node.attributes.find(attr => attr.name && attr.name.name === 'breakpointMods'); | ||
if ( | ||
attribute && | ||
attribute.value.type === 'JSXExpressionContainer' && | ||
attribute.value.expression && | ||
attribute.value.expression.type === 'ArrayExpression' | ||
) { | ||
const breakpointModsArr = attribute.value.expression.elements; | ||
const newModifier = {}; | ||
breakpointModsArr.forEach(breakpointModObj => { | ||
if (breakpointModObj.type === 'ObjectExpression') { | ||
const modifierNode = breakpointModObj.properties.find(prop => prop.key.name === 'modifier'); | ||
const modVal = modifierNode.value; | ||
const modifierValue = camelCase( | ||
modVal.type === 'Literal' && modVal.value || | ||
modVal.type === 'MemberExpression' && modVal.property.type === 'Literal' && modVal.property.value || | ||
modVal.type === 'MemberExpression' && modVal.property.type === 'Identifier' && modVal.property.name | ||
); | ||
if (modifierValue) { | ||
const modifierName = Object.keys(breakpointPropNames).find(propName => breakpointPropNames[propName].includes(modifierValue)); | ||
const breakpointNode = breakpointModObj.properties.find(prop => prop.key.name === 'breakpoint'); | ||
if (!newModifier[modifierName]) { | ||
newModifier[modifierName] = {}; | ||
} | ||
if (breakpointNode) { | ||
const breakpointName = breakpointNode.value.value; | ||
newModifier[modifierName][breakpointName] = modifierValue; | ||
} else { | ||
newModifier[modifierName].default = modifierValue; | ||
} | ||
} | ||
} | ||
}); | ||
const newProps = Object.entries(newModifier).reduce((acc, [key, val]) => { | ||
return acc += `${key}={${JSON.stringify(val)}} `; | ||
}, ``); | ||
context.report({ | ||
node, | ||
message: errorMessages[nodeName], | ||
fix(fixer) { | ||
return fixer.replaceText(attribute, newProps); | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
}; |
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
103 changes: 103 additions & 0 deletions
103
packages/eslint-plugin-pf-codemods/test/rules/refactor-breakpointmods.js
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,103 @@ | ||
const ruleTester = require('./ruletester'); | ||
const rule = require('../../lib/rules/refactor-breakpointmods'); | ||
|
||
ruleTester.run("refactor-breakpointmods", rule, { | ||
valid: [ | ||
{ | ||
code: `import { Flex, FlexItem } from '@patternfly/react-core'; | ||
<Flex alignSelf={{default: "alignSelfStretch"}}> | ||
<FlexItem alignSelf={{default: "alignSelfStretch"}}></FlexItem> | ||
</Flex>` | ||
}, | ||
{ | ||
code: `import { PageSection } from '@patternfly/react-core'; | ||
<PageSection padding={{ default: 'noPadding' }}></PageSection>` | ||
}, | ||
{ | ||
code: `import { ToolbarItem } from '@patternfly/react-core'; | ||
<ToolbarItem visibility={{ | ||
default: 'visible', | ||
lg: 'hidden' | ||
}}></ToolbarItem>` | ||
}, | ||
{ | ||
code: `import { ToolbarToggleGroup } from '@patternfly/react-core'; | ||
<ToolbarToggleGroup spacer={{default: "spacerNone"}} show={{xl: 'show'}}></ToolbarToggleGroup>` | ||
} | ||
], | ||
invalid: [ | ||
{ | ||
code: `import { Flex, FlexItem, FlexModifiers } from '@patternfly/react-core'; | ||
<Flex breakpointMods={[ | ||
{modifier: FlexModifiers.column}, | ||
{modifier: FlexModifiers["align-self-stretch"]}, | ||
{modifier: 'spacer-none'}, | ||
{modifier: 'row', breakpoint: 'sm'} | ||
]}> | ||
<FlexItem breakpointMods={[ | ||
{breakpoint: 'xl', modifier: FlexModifiers.column}, | ||
{modifier: FlexModifiers["align-self-stretch"], breakpoint: 'md'}, | ||
{modifier: FlexModifiers.row} | ||
]}></FlexItem> | ||
</Flex>`, | ||
output: `import { Flex, FlexItem, FlexModifiers } from '@patternfly/react-core'; | ||
<Flex direction={{"default":"column","sm":"row"}} alignSelf={{"default":"alignSelfStretch"}} spacer={{"default":"spacerNone"}} > | ||
<FlexItem direction={{"xl":"column","default":"row"}} alignSelf={{"md":"alignSelfStretch"}} ></FlexItem> | ||
</Flex>`, | ||
errors: [ | ||
{ | ||
message: `Removed breakpointMods prop from Flex in favor of spacer, spaceItems, grow, shrink, flex, direction, alignItems, alignContent, alignSelf, align, justifyContent, display, fullWidth and flexWrap`, | ||
type: "JSXOpeningElement", | ||
}, | ||
{ | ||
message: `Removed breakpointMods prop from FlexItem in favor of spacer, grow, shrink, flex, alignSelf, align, and fullWidth`, | ||
type: "JSXOpeningElement", | ||
}, | ||
] | ||
}, | ||
{ | ||
code: `import { PageSection as MyPageSection } from '@patternfly/react-core'; | ||
<MyPageSection hasNoPadding></MyPageSection>`, | ||
output: `import { PageSection as MyPageSection } from '@patternfly/react-core'; | ||
<MyPageSection padding={{ default: 'noPadding' }}></MyPageSection>`, | ||
errors: [{ | ||
message: `hasNoPadding prop on PageSection component removed in favor of padding={{ default: 'noPadding' }}`, | ||
type: "JSXOpeningElement", | ||
}] | ||
}, | ||
{ | ||
code: `import { ToolbarItem } from '@patternfly/react-core'; | ||
<ToolbarItem breakpointMods={[ | ||
{modifier: 'spacer-none'}, | ||
{breakpoint: 'lg', modifier: 'spacer-lg'}, | ||
{modifier:"align-right", breakpoint: 'sm'} | ||
]}></ToolbarItem>`, | ||
output: `import { ToolbarItem } from '@patternfly/react-core'; | ||
<ToolbarItem spacer={{"default":"spacerNone","lg":"spacerLg"}} align={{"sm":"alignRight"}} ></ToolbarItem>`, | ||
errors: [{ | ||
message: `Removed breakpointMods prop from ToolbarItem in favor of visiblity, alignment, and spacer`, | ||
type: "JSXOpeningElement", | ||
}] | ||
}, | ||
{ | ||
code: `import { ToolbarToggleGroup } from '@patternfly/react-core'; | ||
<ToolbarToggleGroup breakpoint="xl" breakpointMods={[ | ||
{ modifier: 'visible' }, | ||
{ modifier: 'hidden', breakpoint: 'lg' } | ||
]}></ToolbarToggleGroup>`, | ||
output: `import { ToolbarToggleGroup } from '@patternfly/react-core'; | ||
<ToolbarToggleGroup show={{ xl: 'show' }} visibility={{"default":"visible","lg":"hidden"}} ></ToolbarToggleGroup>`, | ||
errors: [ | ||
{ | ||
message: `breakpoint prop on ToolbarToggleGroup removed in favor of show={{ xl: 'show' }}`, | ||
type: "JSXOpeningElement", | ||
}, | ||
{ | ||
message: `Removed breakpointMods prop from ToolbarToggleGroup in favor of visiblity, alignment, spacer, and spaceItems`, | ||
type: "JSXOpeningElement", | ||
} | ||
] | ||
}, | ||
] | ||
}); | ||
|