mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.52k stars 32.19k forks source link

v5 Box sx prop codemod doesn't work when MUI imported with an alias #26441

Closed rsslldnphy closed 2 years ago

rsslldnphy commented 3 years ago

The codemod to convert Box style props to the sx prop syntax doesn't work if material UI is imported into a module alias, i.e. import * as UI from "@material-ui/core";

Current Behavior 😯

Running the codemod on files like this:

import * as UI from "@material-ui/core";
const Foo = () => <UI.Box px={2} />;

leaves them unmodified:

Processing 1 files...
Spawning 1 workers...
Sending 1 files to free worker...
All done.
Results:
0 errors
1 unmodified
0 skipped
0 ok

It works as expected on files like this:

import { Box } from "@material-ui/core";
const Foo = () => <Box px={2} />;

Expected Behavior 🤔

The codemod should work for files using the module alias.

Steps to Reproduce 🕹

Steps:

  1. Create a file, foo.tsx containing the following:
    
    import React from 'react';
    import * as UI from '@material-ui/core';

const Foo = () =>

2. Run `npx jscodeshift --extensions tsx --parser tsx -t node_modules/@material-ui/codemod/node/v5.0.0/box-sx-prop.js foo.tsx`

## Context 🔦

<!--
  What are you trying to accomplish? How has this issue affected you?
  Providing context helps us come up with a solution that is most useful in the real world.
-->

We do `import * as UI from "@material-ui/core";` both to reduce the number of imports needed in a given file as well as to make it explicit at the point of using a component where that component comes from. (Analyzing the resulting bundle shows that treeshaking still works correctly so this doesn't have a negative impact on our bundle size.)

We'd like to upgrade to v5 and are hoping to use the codemod to help us do so.

## Your Environment 🌎

<!--
  Run `npx @material-ui/envinfo` and post the results.
  If you encounter issues with TypeScript please include the used tsconfig.
-->
<details>
  <summary>`npx @material-ui/envinfo`</summary>

System: OS: macOS 11.2 Binaries: Node: 15.12.0 - /usr/local/bin/node Yarn: 1.22.10 - ~/Code/gg/goodgym-web/node_modules/.bin/yarn npm: 7.6.3 - /usr/local/bin/npm Browsers: Chrome: 90.0.4430.212 Edge: Not Found Firefox: 88.0.1 Safari: 14.0.3 npmPackages: @emotion/styled: 10.0.27 @material-ui/codemod: ^5.0.0-alpha.33 => 5.0.0-alpha.33 @material-ui/core: ^5.0.0-alpha.34 => 5.0.0-alpha.34 @material-ui/icons: ^5.0.0-alpha.34 => 5.0.0-alpha.34 @material-ui/lab: ^5.0.0-alpha.34 => 5.0.0-alpha.34 @material-ui/private-theming: 5.0.0-alpha.33 @material-ui/styled-engine: 5.0.0-alpha.34 @material-ui/styles: 5.0.0-alpha.33 @material-ui/system: 5.0.0-alpha.34 @material-ui/types: 6.0.0 @material-ui/unstyled: 5.0.0-alpha.34 @material-ui/utils: 5.0.0-alpha.33 @types/react: ^16.9.53 => 16.14.5 react: ^17.0.1 => 17.0.2 react-dom: ^17.0.1 => 17.0.2 typescript: ^4.0.3 => 4.2.4


</details>
rsslldnphy commented 3 years ago

I've never used jscodeshift but have managed to hack something together which meets my needs - with the module alias material ui is imported as hardcoded to UI:

diff --git a/packages/material-ui-codemod/src/util/propsToObject.js b/packages/material-ui-codemod/src/util/propsToObject.js
index 9309a5edff..641c4d5131 100644
--- a/packages/material-ui-codemod/src/util/propsToObject.js
+++ b/packages/material-ui-codemod/src/util/propsToObject.js
@@ -11,8 +11,19 @@ export default function propsToObject({ j, root, componentName, propName, props
     return value;
   }

-  return root.findJSXElements(componentName).forEach((path) => {
+  const isJSXIdentifier = (node, name) => j.JSXIdentifier.check(node) && node.name === name;
+  return root.findJSXElements().forEach((path) => {
+    const { openingElement } = path.value;
+    const isUnscopedElement = isJSXIdentifier(openingElement.name, componentName);
+    const isScopedElement =
+      j.JSXMemberExpression.check(openingElement.name) &&
+      isJSXIdentifier(openingElement.name.object, 'UI') &&
+      isJSXIdentifier(openingElement.name.property, componentName);
+
+    if (!isUnscopedElement && !isScopedElement) return;
+
     let propValue = [];
+
     const attributes = path.node.openingElement.attributes;
     attributes.forEach((node, index) => {
       // Only transform whitelisted props

Posting it here in case it's useful for anyone else facing the same problem (you can just change the hardcoded 'UI' to whatever alias you use) or in case it's helpful for someone who knows jscodeshift and can make this code work for any alias the material-ui module is imported as.