From 22f19fcc61ac50d0a4367d4122af1788cd46c9af Mon Sep 17 00:00:00 2001 From: Afsal K Date: Thu, 5 Dec 2024 01:16:41 +0530 Subject: [PATCH] feat: implement codemod ibm-products-update-http-errors (#17960) * feat(ibm-update-http-errors): implement ibm-update-http-errors codemod * feat(upgrade): include ibm-products-update-http-errors in migrate cli * test(ibm-products-update-http-errors): include sample in fixtures folder --------- Co-authored-by: Nikhil Tomar <63502271+2nikhiltom@users.noreply.github.com> Co-authored-by: Riddhi Bansal <41935566+riddhybansal@users.noreply.github.com> Co-authored-by: Guilherme Datilio Ribeiro --- packages/upgrade/src/upgrades.js | 31 +++ .../ibm-products-update-http-errors.input.js | 82 ++++++++ .../ibm-products-update-http-errors.output.js | 40 ++++ .../ibm-products-update-http-errors-test.js | 12 ++ .../ibm-products-update-http-errors.js | 192 ++++++++++++++++++ 5 files changed, 357 insertions(+) create mode 100644 packages/upgrade/transforms/__testfixtures__/ibm-products-update-http-errors.input.js create mode 100644 packages/upgrade/transforms/__testfixtures__/ibm-products-update-http-errors.output.js create mode 100644 packages/upgrade/transforms/__tests__/ibm-products-update-http-errors-test.js create mode 100644 packages/upgrade/transforms/ibm-products-update-http-errors.js diff --git a/packages/upgrade/src/upgrades.js b/packages/upgrade/src/upgrades.js index 8a9bd4c05f14..018120b5bd89 100644 --- a/packages/upgrade/src/upgrades.js +++ b/packages/upgrade/src/upgrades.js @@ -396,6 +396,37 @@ export const upgrades = [ }); }, }, + { + name: 'ibm-products-update-http-errors', + description: + 'Rewrites HttpError403, HttpError404, HttpErrorOther to FullPageError', + migrate: async (options) => { + const transform = path.join( + TRANSFORM_DIR, + 'ibm-products-update-http-errors.js' + ); + const paths = + Array.isArray(options.paths) && options.paths.length > 0 + ? options.paths + : await glob(['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'], { + cwd: options.workspaceDir, + ignore: [ + '**/es/**', + '**/lib/**', + '**/umd/**', + '**/node_modules/**', + '**/storybook-static/**', + ], + }); + + await run({ + dry: !options.write, + transform, + paths, + verbose: options.verbose, + }); + }, + }, { name: 'ibm-products-update-userprofileimage', description: 'Rewrites UserProfileImage to UserAvatar', diff --git a/packages/upgrade/transforms/__testfixtures__/ibm-products-update-http-errors.input.js b/packages/upgrade/transforms/__testfixtures__/ibm-products-update-http-errors.input.js new file mode 100644 index 000000000000..eb58558f45f9 --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/ibm-products-update-http-errors.input.js @@ -0,0 +1,82 @@ +import React from 'react'; +import { + HTTPError403, + HTTPError404, + HTTPErrorOther, +} from '@carbon/ibm-products'; +import './_example.scss'; + +export const Example = () => ( + <> + + + + + +); diff --git a/packages/upgrade/transforms/__testfixtures__/ibm-products-update-http-errors.output.js b/packages/upgrade/transforms/__testfixtures__/ibm-products-update-http-errors.output.js new file mode 100644 index 000000000000..940131571225 --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/ibm-products-update-http-errors.output.js @@ -0,0 +1,40 @@ +import { Link } from "@carbon/react"; +import React from 'react'; +import { FullPageError } from '@carbon/ibm-products'; +import './_example.scss'; + +export const Example = () => ( + <> + Carbon Design System
Carbon for IBM Products component library
Carbon for IBM Products component library
Carbon for IBM Products component library
Carbon for IBM Products component library} + title="Forbidden" + kind="403" /> + Carbon Design System
Carbon for IBM Products component library} + title="Page not found" + kind="404" /> + Carbon Design System
Carbon for IBM Products component library} + title="Bad gateway" + kind="custom" /> + + +); diff --git a/packages/upgrade/transforms/__tests__/ibm-products-update-http-errors-test.js b/packages/upgrade/transforms/__tests__/ibm-products-update-http-errors-test.js new file mode 100644 index 000000000000..c5b5c5c44e10 --- /dev/null +++ b/packages/upgrade/transforms/__tests__/ibm-products-update-http-errors-test.js @@ -0,0 +1,12 @@ +/** + * Copyright IBM Corp. 2021, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const { defineTest } = require('jscodeshift/dist/testUtils'); + +defineTest(__dirname, 'ibm-products-update-http-errors'); diff --git a/packages/upgrade/transforms/ibm-products-update-http-errors.js b/packages/upgrade/transforms/ibm-products-update-http-errors.js new file mode 100644 index 000000000000..a40f795d0fbc --- /dev/null +++ b/packages/upgrade/transforms/ibm-products-update-http-errors.js @@ -0,0 +1,192 @@ +/** + * Copyright IBM Corp. 2021, 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + * + * Rewrites HttpError403, HttpError404, HttpErrorOther to FullPageError + * + * Transforms: + * + * , , + * + * Into: + * + * + */ + +'use strict'; + +const transform = (file, api) => { + const j = api.jscodeshift; + const root = j(file.source); + let dirtyFlag = false; + + const componentKindMap = { + HTTPError403: '403', + HTTPError404: '404', + HTTPErrorOther: 'custom', + }; + const NEW_COMPONENT = 'FullPageError'; + const LINK_COMPONENT = 'Link'; + + root.find(j.JSXElement).forEach((element) => { + const openingElement = element.node.openingElement; + const componentName = openingElement.name.name; + + if (componentKindMap[componentName]) { + dirtyFlag = true; + // Opening tag + openingElement.name.name = NEW_COMPONENT; + + if (element.node.closingElement) { + // Closing tag + element.node.closingElement.name.name = NEW_COMPONENT; + } + + // Attach new 'kind' attribute + const kindAttribute = j.jsxAttribute( + j.jsxIdentifier('kind'), + j.literal(componentKindMap[componentName]) + ); + openingElement.attributes.push(kindAttribute); + + // Change errorCodeLabel attribute to label + const errorCodeLabelProp = openingElement.attributes.find( + (attr) => attr.name.name === 'errorCodeLabel' + ); + if (errorCodeLabelProp) { + errorCodeLabelProp.name.name = 'label'; + } + + // Convert 'links' prop to 'children' with tags + const linksProp = openingElement.attributes.find( + (attr) => attr.name.name === 'links' + ); + if (linksProp) { + linksProp.name.name = 'children'; + + // Convert link value elements + const linkValues = linksProp.value.expression.elements; + let linksLen = linkValues.length; + const linkElements = []; + + linkValues.forEach((link) => { + const linkTag = j.jsxElement( + j.jsxOpeningElement(j.jsxIdentifier(LINK_COMPONENT), [ + j.jsxAttribute( + j.jsxIdentifier('href'), + j.jsxExpressionContainer( + j.literal(link.properties[0].value.value) + ), + j.jsxClosingElement(j.jsxIdentifier(LINK_COMPONENT)) + ), + ]), + j.jsxClosingElement(j.jsxIdentifier(LINK_COMPONENT)), + [j.jsxText(link.properties[1].value.value)] + ); + linkElements.push(linkTag); + linksLen--; + if (linksLen) { + const brTag = j.jsxElement( + j.jsxOpeningElement(j.jsxIdentifier('br'), [], true) + ); + + linkElements.push(brTag); + } + }); + + const linksFragment = j.jsxFragment( + j.jsxOpeningFragment(), + j.jsxClosingFragment(), + linkElements + ); + + linksProp.value = j.jsxExpressionContainer(linksFragment); + } + } + }); + + // Transform import statements if necessary + if (dirtyFlag) { + const importDeclarations = root.find(j.ImportDeclaration); + const CARBON_PATH = '@carbon/react'; + const linkComponentSpecifier = j.importSpecifier( + j.identifier(LINK_COMPONENT), + j.identifier(LINK_COMPONENT) + ); + + // Check for '@carbon/react' import statement exists + const existCarbonImport = importDeclarations.some( + (importDeclaration) => importDeclaration.node.source.value === CARBON_PATH + ); + + // Update @carbon/imb-products import statement + importDeclarations.forEach((statement) => { + const specifiers = statement.node.specifiers; + const source = statement.node.source; + const PRODUCTS_PATH = '@carbon/ibm-products'; + + if (source.value === PRODUCTS_PATH) { + // Check new component name already imported + const isNewIdExists = specifiers.some( + (spec) => spec.imported.name === NEW_COMPONENT + ); + + // If the new component not already imported, import it + if (!isNewIdExists) { + const newSpecifier = j.importSpecifier( + j.identifier(NEW_COMPONENT), + j.identifier(NEW_COMPONENT) + ); + // Including the new specifier into @carbon/ibm-products import statement + specifiers.push(newSpecifier); + } + + Object.keys(componentKindMap).forEach((key) => { + // Check the old component names exists + const identifierIndex = specifiers.findIndex( + (spec) => spec.imported.name === key + ); + + // Remove all old components' import + if (identifierIndex !== -1) { + specifiers.splice(identifierIndex, 1); + } + }); + } + + // if the @carbon/react import statement already exists update the import + if (existCarbonImport && source.value === CARBON_PATH) { + // Check Link component already imported + const linkIdIndex = specifiers.findIndex( + (spec) => spec.imported.name === LINK_COMPONENT + ); + + // if Link component does not in the imported components list + if (linkIdIndex === -1) { + // Include the Link component in imported components list + specifiers.push(linkComponentSpecifier); + } + } + }); + + // If @carbon/react import statement not exists + if (!existCarbonImport) { + const carbonImportDeclaration = j.importDeclaration( + [linkComponentSpecifier], + j.literal(CARBON_PATH) + ); + + root + .find(j.Program) + .forEach((program) => + program.node.body.unshift(carbonImportDeclaration) + ); + } + } + + return root.toSource(); +}; + +module.exports = transform;