Skip to content

Commit

Permalink
feat(PPDSC-2163): codemods for enums (#331)
Browse files Browse the repository at this point in the history
* feat(PPDSC-2163): codemods for enums

* fix(PPDSC-2163): address reviews

* fix(PPDSC-2163): ignore eslintcache

* fix(PPDSC-2163): remove then ignore

* fix(PPDSC-2163): remove eslint change

* fix(PPDSC-2163): add more comps

* fix(PPDSC-2163): add iconButton and allow renaming

* fix(PPDSC-2163): lint

* fix(PPDSC-2163): address reviews
  • Loading branch information
Xin00163 authored Aug 16, 2022
1 parent 96ca376 commit 1430e4e
Show file tree
Hide file tree
Showing 9 changed files with 309 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ junit.xml
/src/icons/filled/material
/src/icons/outlined/material
cypress/config/a11y-components.json
lib/codemod/.eslintcache
9 changes: 9 additions & 0 deletions lib/codemod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ The list includes these transformers
- [`emotion-icons`](#emotion-icons)
- [`remove-redundant-marker-ul`](#remove-redundant-marker-ul)
- [`update-list-item-marker-ul-value`](#update-list-item-marker-ul-value)
- [`enum-to-union`](#enum-to-union)


#### `emotion-icons`
Expand Down Expand Up @@ -61,6 +62,14 @@ Unordered List has now a default marker, the script passes `listItemMarker` with
- <UnorderedList></UnorderedList>
+ <UnorderedList listItemMarker={null}></UnorderedList>
```
#### `enum-to-union`

Some of NewsKit components support enum as the prop type, the script remove the imports of enum and replace enum type with union type.

```diff
- <Button size={ButtonSize.Small}>Button</Button>
+ <Button size="small">Button</Button>
```



Expand Down
2 changes: 2 additions & 0 deletions lib/codemod/src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const TRANSFORMS = {
'Unordered List has now a default marker, removes the prop passing the same icon now set as default.',
'update-list-item-marker-ul-value':
'Unordered List has now a default marker, the script passes "listItemMarker" with a value of "null" to UnorderedList components originally not passing any marker.',
'enum-to-union':
'Replace enum type with union type and remove the imports of enums',
};

function expandFilePathsIfNeeded(filesBeforeExpansion) {
Expand Down
88 changes: 88 additions & 0 deletions lib/codemod/src/transforms/__tests__/enums-to-union/actual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
Button,
IconButton,
ButtonSize,
MenuItemAlign,
MenuItemSize,
Menu,
MenuItem,
Slider,
LabelPosition,
Stack,
Flow,
StackDistribution,
StackChild,
AlignSelfValues,
TagSize,
Tabs,
Tab,
TabAlign,
TabsIndicatorPosition,
TabSize,
TabsDistribution,
FormInput,
FormInputTextField,
TextFieldSize,
TextInputSize,
} from 'newskit';

const Component = () => (
<>
<Button size={ButtonSize.Small}>Button</Button>
<IconButton size={ButtonSize.Small}><IconFilledLight overrides={{size: 'iconSize010'}} /></IconButton>
<Menu vertical align={MenuItemAlign.Start} size={MenuItemSize.Small}>
<MenuItem href={href}>Menu item knickknackatory 1</MenuItem>
<MenuItem href={href}>Menu item knickknackatory 2</MenuItem>
<MenuItem href={href}>Menu item knickknackatory 3</MenuItem>
</Menu>
<Slider
values={[50]}
max={100}
min={0}
minLabel="0%"
maxLabel="100%"
thumbLabel
labelPosition={LabelPosition.After}
/>
<Stack
list
flow={Flow.VerticalCenter}
stackDistribution={StackDistribution.Start}
spaceInline="space030"
ariaLabel="Tag list"
>
<Tag>child 1</Tag>
<Tag>child 2</Tag>
</Stack>
<StackChild alignSelf={AlignSelfValues.Stretch}>
<Divider />
</StackChild>
<Tag size={TagSize.Medium}>Tag</Tag>
<Tabs
size={TabSize.Medium}
align={TabAlign.Start}
vertical
indicatorPosition={TabsIndicatorPosition.Start}
distribution={TabsDistribution.Equal}
>
<Tab label="Tab">
Tab
</Tab>
</Tabs>
<Label size={TextFieldSize.Small}>Label</Label>
<Form onSubmit={onSubmit}>
<FormInput state="valid" name="email-valid">
<FormInputLabel size={TextFieldSize.Small}>E-mail</FormInputLabel>
<FormInputTextField size={TextFieldSize.Small} />
</FormInput>
<TextInput
size={TextInputSize.Large}
label="Email"
name="email"
assistiveText="Your email"
/>
</Form>
<Avatar size={AvatarSize.Small} align={AlignContent.Start}>New image</Avatar>
<ArticleHeader align={HeaderAlign.Start}>Header</ArticleHeader>
</>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const path = require('path');
const jscodeshift = require('jscodeshift');
const transform = require('../../enum-to-union');
const {readFile} = require('../utils');

function read(fileName) {
return readFile(path.join(__dirname, fileName));
}

describe('@newskit/codemod:', () => {
test('enum-to-union', () => {
const actual = transform(
{
source: read('./actual.js'),
},
{jscodeshift},
{},
);

const expected = read('./expected.js');
expect(actual).toBe(expected);
});
});
74 changes: 74 additions & 0 deletions lib/codemod/src/transforms/__tests__/enums-to-union/expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
Button,
IconButton,
Menu,
MenuItem,
Slider,
Stack,
StackChild,
Tabs,
Tab,
FormInput,
FormInputTextField,
} from 'newskit';

const Component = () => (
<>
<Button size="small">Button</Button>
<IconButton size="small"><IconFilledLight overrides={{size: 'iconSize010'}} /></IconButton>
<Menu vertical align="start" size="small">
<MenuItem href={href}>Menu item knickknackatory 1</MenuItem>
<MenuItem href={href}>Menu item knickknackatory 2</MenuItem>
<MenuItem href={href}>Menu item knickknackatory 3</MenuItem>
</Menu>
<Slider
values={[50]}
max={100}
min={0}
minLabel="0%"
maxLabel="100%"
thumbLabel
labelPosition="after"
/>
<Stack
list
flow="vertical-center"
stackDistribution="flex-start"
spaceInline="space030"
ariaLabel="Tag list"
>
<Tag>child 1</Tag>
<Tag>child 2</Tag>
</Stack>
<StackChild alignSelf="stretch">
<Divider />
</StackChild>
<Tag size="medium">Tag</Tag>
<Tabs
size="medium"
align="start"
vertical
indicatorPosition="start"
distribution="equal"
>
<Tab label="Tab">
Tab
</Tab>
</Tabs>
<Label size="small">Label</Label>
<Form onSubmit={onSubmit}>
<FormInput state="valid" name="email-valid">
<FormInputLabel size="small">E-mail</FormInputLabel>
<FormInputTextField size="small" />
</FormInput>
<TextInput
size="large"
label="Email"
name="email"
assistiveText="Your email"
/>
</Form>
<Avatar size={AvatarSize.Small} align={AlignContent.Start}>New image</Avatar>
<ArticleHeader align={HeaderAlign.Start}>Header</ArticleHeader>
</>
);
89 changes: 89 additions & 0 deletions lib/codemod/src/transforms/enum-to-union.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const {toKebabCase} = require('../utils/to-kebab-case');

const enumNames = [
'ButtonSize',
'FlagSize',
'MenuItemAlign',
'MenuItemSize',
'LabelPosition',
'Flow',
'StackDistribution',
'AlignSelfValues',
'TabAlign',
'TabSize',
'TabsDistribution',
'TabsIndicatorPosition',
'TagSize',
'TextFieldSize',
'TextInputSize',
];

const enumPropList = [
'size',
'flow',
'stackDistribution',
'align',
'alignSelf',
'labelPosition',
'indicatorPosition',
'distribution',
];

const getNewskitImports = (j, root) =>
root.find(j.ImportDeclaration, {source: {value: 'newskit'}}).nodes();

const removeEnumImports = (j, root) => {
let newskitImports = getNewskitImports(j, root);

j(newskitImports)
.find(j.ImportSpecifier)
.filter(s => {
const name = s.value.imported.name;
if (enumNames.includes(name)) {
j(s).remove();
}
});
};

const transformEnums = (j, root) => {
enumPropList.forEach(propName => {
root
.find(j.JSXAttribute, {
name: {
type: 'JSXIdentifier',
name: propName,
},
value: {
type: 'JSXExpressionContainer',
},
})
.find(j.JSXExpressionContainer)
.filter(path => enumNames.includes(path.value.expression.object.name))
.replaceWith(path => {
const propValue = path.value.expression.property.name;
const newPropValue = `${toKebabCase(propValue)}`;
const newPath =
propName === 'stackDistribution' &&
['start', 'end'].includes(newPropValue)
? j.literal(`flex-${toKebabCase(propValue)}`)
: j.literal(`${toKebabCase(propValue)}`);
return newPath;
});
});
};

module.exports = function transformer(file, api, options) {
const j = api.jscodeshift;
const root = j(file.source);

const printOptions = options.printOptions || {
quote: 'double',
objectCurlySpacing: false,
};

removeEnumImports(j, root);

transformEnums(j, root);

return root.toSource(printOptions);
};
8 changes: 8 additions & 0 deletions lib/codemod/src/utils/__tests__/to-kebab-case.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const {toKebabCase} = require('../to-kebab-case');

describe('to-kebab-case', () => {
test('toKebabCase', () => {
expect(toKebabCase('HelloWorld')).toBe('hello-world');
expect(toKebabCase('Hello World')).toBe('hello-world');
});
});
15 changes: 15 additions & 0 deletions lib/codemod/src/utils/to-kebab-case.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
function toKebabCase(str) {
if (str) {
return str
.match(/[A-Z]{2,}(?=[A-Z][a-z0-9]*|\b)|[A-Z]?[a-z0-9]*|[A-Z]|[0-9]+/g)
.filter(Boolean)
.map(x => x.toLowerCase())
.join('-');
}
/* istanbul ignore next */
return '';
}

module.exports = {
toKebabCase,
};

0 comments on commit 1430e4e

Please sign in to comment.