diff --git a/.eslintrc.js b/.eslintrc.js index ff8bc06054..d533b005ad 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,7 +21,16 @@ module.exports = require('@sumup/foundry/eslint')( rules: { 'import/no-unresolved': 'off', 'notice/notice': 'off', - '@typescript-eslint/no-unused-vars': 'off' + '@typescript-eslint/no-unused-vars': 'off', + 'prettier/prettier': 'off' + } + }, + { + files: ['src/cli/migrate/*.ts'], + rules: { + // jscodeshift expect no return value for files + // that should not be transformed. + 'consistent-return': 'off' } } ] diff --git a/src/cli/migrate/__testfixtures__/as-prop.input.js b/src/cli/migrate/__testfixtures__/as-prop.input.js new file mode 100644 index 0000000000..8d532aa3cd --- /dev/null +++ b/src/cli/migrate/__testfixtures__/as-prop.input.js @@ -0,0 +1,35 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { Heading, SubHeading, Text, Input } from '@sumup/circuit-ui'; + +const BaseHeading = () => ; + +const RedHeading = styled(Heading)` + color: red; +`; + +const StyledHeading = () => ; + +const BaseSubHeading = () => ; + +const RedSubHeading = styled(SubHeading)` + color: red; +`; + +const StyledSubHeading = () => ; + +const BaseText = () => ; + +const RedText = styled(Text)` + color: red; +`; + +const StyledText = () => ; + +const BaseInput = () => ; + +const RedInput = styled(Input)` + color: red; +`; + +const StyledInput = () => ; diff --git a/src/cli/migrate/__testfixtures__/as-prop.output.js b/src/cli/migrate/__testfixtures__/as-prop.output.js new file mode 100644 index 0000000000..8124f8934d --- /dev/null +++ b/src/cli/migrate/__testfixtures__/as-prop.output.js @@ -0,0 +1,35 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { Heading, SubHeading, Text, Input } from '@sumup/circuit-ui'; + +const BaseHeading = () => ; + +const RedHeading = styled(Heading)` + color: red; +`; + +const StyledHeading = () => ; + +const BaseSubHeading = () => ; + +const RedSubHeading = styled(SubHeading)` + color: red; +`; + +const StyledSubHeading = () => ; + +const BaseText = () => ; + +const RedText = styled(Text)` + color: red; +`; + +const StyledText = () => ; + +const BaseInput = () => ; + +const RedInput = styled(Input)` + color: red; +`; + +const StyledInput = () => ; diff --git a/src/cli/migrate/__testfixtures__/exit-animations.input.js b/src/cli/migrate/__testfixtures__/exit-animations.input.js new file mode 100644 index 0000000000..921d4a1082 --- /dev/null +++ b/src/cli/migrate/__testfixtures__/exit-animations.input.js @@ -0,0 +1,23 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { LoadingButton } from '@sumup/circuit-ui'; + +const BaseLoadingButton = () => ( + +); + +const RedLoadingButton = styled(LoadingButton)` + color: red; +`; + +const StyledLoadingButton = () => ( + +); diff --git a/src/cli/migrate/__testfixtures__/exit-animations.output.js b/src/cli/migrate/__testfixtures__/exit-animations.output.js new file mode 100644 index 0000000000..12985a4bb5 --- /dev/null +++ b/src/cli/migrate/__testfixtures__/exit-animations.output.js @@ -0,0 +1,15 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { LoadingButton } from '@sumup/circuit-ui'; + +const BaseLoadingButton = () => ( + +); + +const RedLoadingButton = styled(LoadingButton)` + color: red; +`; + +const StyledLoadingButton = () => ( + +); diff --git a/src/cli/migrate/__testfixtures__/input-deepref-prop.input.js b/src/cli/migrate/__testfixtures__/input-deepref-prop.input.js new file mode 100644 index 0000000000..c2a01d6272 --- /dev/null +++ b/src/cli/migrate/__testfixtures__/input-deepref-prop.input.js @@ -0,0 +1,17 @@ +import React, { useRef } from 'react'; +import styled from '@emotion/styled'; +import { Input } from '@sumup/circuit-ui'; + +const Form = () => { + const ref = useRef(null); + return ; +}; + +const RedInput = styled(Input)` + color: red; +`; + +const RedForm = () => { + const ref = useRef(null); + return ; +}; diff --git a/src/cli/migrate/__testfixtures__/input-deepref-prop.output.js b/src/cli/migrate/__testfixtures__/input-deepref-prop.output.js new file mode 100644 index 0000000000..4dda473655 --- /dev/null +++ b/src/cli/migrate/__testfixtures__/input-deepref-prop.output.js @@ -0,0 +1,17 @@ +import React, { useRef } from 'react'; +import styled from '@emotion/styled'; +import { Input } from '@sumup/circuit-ui'; + +const Form = () => { + const ref = useRef(null); + return ; +}; + +const RedInput = styled(Input)` + color: red; +`; + +const RedForm = () => { + const ref = useRef(null); + return ; +}; diff --git a/src/cli/migrate/__testfixtures__/list-variant-enum.input.js b/src/cli/migrate/__testfixtures__/list-variant-enum.input.js new file mode 100644 index 0000000000..541de7a268 --- /dev/null +++ b/src/cli/migrate/__testfixtures__/list-variant-enum.input.js @@ -0,0 +1,28 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { List, Text } from '@sumup/circuit-ui'; + +const Ordered = () => primary; + +const Unordered = () => Secondary; + +const RedList = styled(List)` + color: red; +`; + +const BlueList = styled(List)` + color: blue; +`; + +const BlueText = styled(Text)` + color: blue; +`; + +const Styled = () => ( + <> + Ordered red + Unordered blue + Text + Text blue + +); diff --git a/src/cli/migrate/__testfixtures__/list-variant-enum.output.js b/src/cli/migrate/__testfixtures__/list-variant-enum.output.js new file mode 100644 index 0000000000..1b0fc7eb9e --- /dev/null +++ b/src/cli/migrate/__testfixtures__/list-variant-enum.output.js @@ -0,0 +1,28 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { List, Text } from '@sumup/circuit-ui'; + +const Ordered = () => primary; + +const Unordered = () => Secondary; + +const RedList = styled(List)` + color: red; +`; + +const BlueList = styled(List)` + color: blue; +`; + +const BlueText = styled(Text)` + color: blue; +`; + +const Styled = () => ( + <> + Ordered red + Unordered blue + Text + Text blue + +); diff --git a/src/cli/migrate/__testfixtures__/onchange-prop.input.js b/src/cli/migrate/__testfixtures__/onchange-prop.input.js new file mode 100644 index 0000000000..91bf30434f --- /dev/null +++ b/src/cli/migrate/__testfixtures__/onchange-prop.input.js @@ -0,0 +1,21 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { RadioButton, Switch } from '@sumup/circuit-ui'; + +const BaseRadioButton = () => ; + +const RedRadioButton = styled(RadioButton)` + color: red; +`; + +const StyledRadioButton = () => ( + +); + +const BaseSwitch = () => ; + +const RedSwitch = styled(Switch)` + color: red; +`; + +const StyledSwitch = () => ; diff --git a/src/cli/migrate/__testfixtures__/onchange-prop.output.js b/src/cli/migrate/__testfixtures__/onchange-prop.output.js new file mode 100644 index 0000000000..f6cfe94f7d --- /dev/null +++ b/src/cli/migrate/__testfixtures__/onchange-prop.output.js @@ -0,0 +1,21 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { RadioButton, Switch } from '@sumup/circuit-ui'; + +const BaseRadioButton = () => ; + +const RedRadioButton = styled(RadioButton)` + color: red; +`; + +const StyledRadioButton = () => ( + +); + +const BaseSwitch = () => ; + +const RedSwitch = styled(Switch)` + color: red; +`; + +const StyledSwitch = () => ; diff --git a/src/cli/migrate/__testfixtures__/selector-props.input.js b/src/cli/migrate/__testfixtures__/selector-props.input.js new file mode 100644 index 0000000000..fa481d42a8 --- /dev/null +++ b/src/cli/migrate/__testfixtures__/selector-props.input.js @@ -0,0 +1,11 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { Selector } from '@sumup/circuit-ui'; + +const BaseSelector = () => ; + +const RedSelector = styled(Selector)` + color: red; +`; + +const StyledSelector = () => ; diff --git a/src/cli/migrate/__testfixtures__/selector-props.output.js b/src/cli/migrate/__testfixtures__/selector-props.output.js new file mode 100644 index 0000000000..806512a858 --- /dev/null +++ b/src/cli/migrate/__testfixtures__/selector-props.output.js @@ -0,0 +1,11 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { Selector } from '@sumup/circuit-ui'; + +const BaseSelector = () => ; + +const RedSelector = styled(Selector)` + color: red; +`; + +const StyledSelector = () => ; diff --git a/src/cli/migrate/__tests__/transforms.spec.js b/src/cli/migrate/__tests__/transforms.spec.js index 4b7a5660c3..708fdbf6aa 100644 --- a/src/cli/migrate/__tests__/transforms.spec.js +++ b/src/cli/migrate/__tests__/transforms.spec.js @@ -18,3 +18,9 @@ import { defineTest } from 'jscodeshift/dist/testUtils'; jest.autoMockOff(); defineTest(__dirname, 'button-variant-enum'); +defineTest(__dirname, 'list-variant-enum'); +defineTest(__dirname, 'onchange-prop'); +defineTest(__dirname, 'as-prop'); +defineTest(__dirname, 'selector-props'); +defineTest(__dirname, 'exit-animations'); +defineTest(__dirname, 'input-deepref-prop'); diff --git a/src/cli/migrate/as-prop.ts b/src/cli/migrate/as-prop.ts new file mode 100644 index 0000000000..4c06833d37 --- /dev/null +++ b/src/cli/migrate/as-prop.ts @@ -0,0 +1,47 @@ +/** + * Copyright 2020, SumUp Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Transform, JSCodeshift, Collection } from 'jscodeshift'; + +import { renameJSXAttribute, findLocalNames } from './utils'; + +function transformFactory( + j: JSCodeshift, + root: Collection, + componentName: string +): void { + const components = findLocalNames(j, root, componentName); + + if (!components) { + return; + } + + components.forEach(component => { + renameJSXAttribute(j, root, component, 'element', 'as'); + }); +} + +const transform: Transform = (file, api) => { + const j = api.jscodeshift; + const root = j(file.source); + + ['Heading', 'SubHeading', 'Text', 'Input'].forEach(componentName => { + transformFactory(j, root, componentName); + }); + + return root.toSource(); +}; + +export default transform; diff --git a/src/cli/migrate/button-variant-enum.ts b/src/cli/migrate/button-variant-enum.ts index 3af5d790d9..495c9fdd8c 100644 --- a/src/cli/migrate/button-variant-enum.ts +++ b/src/cli/migrate/button-variant-enum.ts @@ -15,27 +15,19 @@ import { Transform, JSCodeshift, Collection } from 'jscodeshift'; -import { findImportsByPath, findStyledComponentNames } from './utils'; +import { findLocalNames } from './utils'; function transformFactory( j: JSCodeshift, root: Collection, buttonName: string ): void { - const imports = findImportsByPath(j, root, '@sumup/circuit-ui'); + const components = findLocalNames(j, root, buttonName); - const buttonImport = imports.find(i => i.name === buttonName); - - if (!buttonImport) { + if (!components) { return; } - const localName = buttonImport.local; - - const styledButtons = findStyledComponentNames(j, root, localName); - - const components = [localName, ...styledButtons]; - components.forEach(component => { // Change variants from boolean to enum prop ['primary', 'secondary'].forEach(variant => { diff --git a/src/cli/migrate/exit-animations.ts b/src/cli/migrate/exit-animations.ts new file mode 100644 index 0000000000..4760ab1a2c --- /dev/null +++ b/src/cli/migrate/exit-animations.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2020, SumUp Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Transform } from 'jscodeshift'; + +import { findLocalNames } from './utils'; + +const transform: Transform = (file, api) => { + const j = api.jscodeshift; + const root = j(file.source); + + const components = findLocalNames(j, root, 'LoadingButton'); + + if (!components) { + return; + } + + components.forEach(component => { + ['exitAnimation', 'exitAnimationDuration', 'onAnimationComplete'].forEach( + prop => { + root + .findJSXElements(component) + .find(j.JSXAttribute, { + name: { + type: 'JSXIdentifier', + name: prop + } + }) + .remove(); + } + ); + }); + + return root.toSource(); +}; + +export default transform; diff --git a/src/cli/migrate/input-deepref-prop.ts b/src/cli/migrate/input-deepref-prop.ts new file mode 100644 index 0000000000..037ac5d01a --- /dev/null +++ b/src/cli/migrate/input-deepref-prop.ts @@ -0,0 +1,37 @@ +/** + * Copyright 2020, SumUp Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Transform } from 'jscodeshift'; + +import { renameJSXAttribute, findLocalNames } from './utils'; + +const transform: Transform = (file, api) => { + const j = api.jscodeshift; + const root = j(file.source); + + const components = findLocalNames(j, root, 'Input'); + + if (!components) { + return; + } + + components.forEach(component => { + renameJSXAttribute(j, root, component, 'deepRef', 'ref'); + }); + + return root.toSource(); +}; + +export default transform; diff --git a/src/cli/migrate/list-variant-enum.ts b/src/cli/migrate/list-variant-enum.ts new file mode 100644 index 0000000000..d47589a267 --- /dev/null +++ b/src/cli/migrate/list-variant-enum.ts @@ -0,0 +1,50 @@ +/** + * Copyright 2020, SumUp Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Transform } from 'jscodeshift'; + +import { findLocalNames } from './utils'; + +const transform: Transform = (file, api) => { + const j = api.jscodeshift; + const root = j(file.source); + + const components = findLocalNames(j, root, 'List'); + + if (!components) { + return null; + } + + components.forEach(component => { + // Change variants from boolean to enum prop + ['ordered', 'unordered'].forEach(variant => { + root + .findJSXElements(component) + .find(j.JSXAttribute, { + name: { + type: 'JSXIdentifier', + name: variant + } + }) + .replaceWith(() => + j.jsxAttribute(j.jsxIdentifier('variant'), j.stringLiteral(variant)) + ); + }); + }); + + return root.toSource(); +}; + +export default transform; diff --git a/src/cli/migrate/onchange-prop.ts b/src/cli/migrate/onchange-prop.ts new file mode 100644 index 0000000000..672dd65ca1 --- /dev/null +++ b/src/cli/migrate/onchange-prop.ts @@ -0,0 +1,46 @@ +/** + * Copyright 2020, SumUp Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Transform, JSCodeshift, Collection } from 'jscodeshift'; + +import { renameJSXAttribute, findLocalNames } from './utils'; + +function transformFactory( + j: JSCodeshift, + root: Collection, + componentName: string +): void { + const components = findLocalNames(j, root, componentName); + + if (!components) { + return; + } + + components.forEach(component => { + renameJSXAttribute(j, root, component, 'onToggle', 'onChange'); + }); +} + +const transform: Transform = (file, api) => { + const j = api.jscodeshift; + const root = j(file.source); + + transformFactory(j, root, 'RadioButton'); + transformFactory(j, root, 'Switch'); + + return root.toSource(); +}; + +export default transform; diff --git a/src/cli/migrate/selector-props.ts b/src/cli/migrate/selector-props.ts new file mode 100644 index 0000000000..5de0a80638 --- /dev/null +++ b/src/cli/migrate/selector-props.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2020, SumUp Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Transform } from 'jscodeshift'; + +import { renameJSXAttribute, findLocalNames } from './utils'; + +const transform: Transform = (file, api) => { + const j = api.jscodeshift; + const root = j(file.source); + + const components = findLocalNames(j, root, 'Selector'); + + if (!components) { + return; + } + + components.forEach(component => { + renameJSXAttribute(j, root, component, 'onClick', 'onChange'); + renameJSXAttribute(j, root, component, 'selected', 'checked'); + }); + + return root.toSource(); +}; + +export default transform; diff --git a/src/cli/migrate/utils/index.ts b/src/cli/migrate/utils/index.ts index b9715eb37d..c2e912b861 100644 --- a/src/cli/migrate/utils/index.ts +++ b/src/cli/migrate/utils/index.ts @@ -92,3 +92,43 @@ export function findStyledComponentNames( return styledComponents; } + +export function findLocalNames( + j: JSCodeshift, + root: Collection, + componentName: string +): string[] | null { + const imports = findImportsByPath(j, root, '@sumup/circuit-ui'); + + const buttonImport = imports.find(i => i.name === componentName); + + if (!buttonImport) { + return null; + } + + const localName = buttonImport.local; + + const styledButtons = findStyledComponentNames(j, root, localName); + + return [localName, ...styledButtons]; +} + +export function renameJSXAttribute( + j: JSCodeshift, + root: Collection, + componentName: string, + fromName: string, + toName: string +): void { + root + .findJSXElements(componentName) + .find(j.JSXAttribute, { + name: { + type: 'JSXIdentifier', + name: fromName + } + }) + .replaceWith(nodePath => + j.jsxAttribute(j.jsxIdentifier(toName), nodePath.node.value) + ); +}