@mui/x-codemod 6.0.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # @mui/x-codemod
2
+
3
+ > Codemod scripts for MUI X
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@mui/x-codemod.svg?style=flat-square)](https://www.npmjs.com/package/@mui/x-codemod)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@mui/x-codemod.svg?style=flat-square)](https://www.npmjs.com/package/@mui/x-codemod)
7
+
8
+ This repository contains a collection of codemod scripts based for use with
9
+ [jscodeshift](https://github.com/facebook/jscodeshift) that help update MUI X APIs.
10
+
11
+ ## Setup & run
12
+
13
+ <!-- #default-branch-switch -->
14
+
15
+ ```bash
16
+ npx @mui/x-codemod <codemod> <paths...>
17
+
18
+ Applies a `@mui/x-codemod` to the specified paths
19
+
20
+ Positionals:
21
+ codemod The name of the codemod [string]
22
+ paths Paths forwarded to `jscodeshift` [string]
23
+
24
+ Options:
25
+ --version Show version number [boolean]
26
+ --help Show help [boolean]
27
+ --parser which parser for jscodeshift to use.
28
+ [string] [default: 'tsx']
29
+ --jscodeshift Pass options directly to jscodeshift [array]
30
+
31
+ Examples:
32
+ npx @mui/x-codemod v6.0.0/preset-safe src
33
+ npx @mui/x-codemod v6.0.0/component-rename-prop src --
34
+ --component=DataGrid --from=prop --to=newProp
35
+ ```
36
+
37
+ ### `jscodeshift` options
38
+
39
+ To pass more options directly to jscodeshift, use `--jscodeshift=...`. For example:
40
+
41
+ ```sh
42
+ // single option
43
+ npx @mui/x-codemod --jscodeshift=--run-in-band
44
+ // multiple options
45
+ npx @mui/x-codemod --jscodeshift=--cpus=1 --jscodeshift=--print --jscodeshift=--dry --jscodeshift=--verbose=2
46
+ ```
47
+
48
+ See all available options [here](https://github.com/facebook/jscodeshift#usage-cli).
49
+
50
+ ### `Recast` Options
51
+
52
+ Options to [recast](https://github.com/benjamn/recast)'s printer can be provided
53
+ through jscodeshift's `printOptions` command line argument
54
+
55
+ ```sh
56
+ npx @mui/x-codemod <transform> <path> --jscodeshift="--printOptions='{\"quote\":\"double\"}'"
57
+ ```
58
+
59
+ ## Included scripts
60
+
61
+ ### v6.0.0
62
+
63
+ #### 🚀 `preset-safe`
64
+
65
+ A combination of all important transformers for migrating v5 to v6. ⚠️ This codemod should be run only once.
66
+
67
+ ```sh
68
+ npx @mui/x-codemod v6.0.0/preset-safe <path|folder>
69
+ ```
70
+
71
+ The list includes these transformers
72
+
73
+ - [`localization-provider-rename-locale`](#localization-provider-rename-locale)
74
+ - [`text-props-to-localeText`](#text-props-to-localeText)
75
+
76
+ #### `localization-provider-rename-locale`
77
+
78
+ Renames `locale` into `adapterLocale` (or `LocalizationProvider`)
79
+
80
+ ```diff
81
+ <LocalizationProvider
82
+ dateAdapter={AdapterDayjs}
83
+ - locale="fr"
84
+ + adapterLocale="fr"
85
+ >
86
+ {children}
87
+ </LocalizationProvider
88
+
89
+ ```
90
+
91
+ ```sh
92
+ npx @mui/x-codemod v6.0.0/localization-provider-rename-locale <path>
93
+ ```
94
+
95
+ #### `text-props-to-localeText`
96
+
97
+ Replace props used for localization such as `cancelText` to their corresponding `localeText` key.
98
+
99
+ ```diff
100
+ <DatePicker
101
+ - cancelText="Cancelar"
102
+ + localeText={{
103
+ + cancelButtonLabel: "Cancelar"
104
+ + }}
105
+ />
106
+ ```
107
+
108
+ ```sh
109
+ npx @mui/x-codemod v6.0.0/text-props-to-localeText <path>
110
+ ```
111
+
112
+ If you were always using the same text value in all your components, consider moving those translation from the component to the `LocalizationProvider` by hand.
113
+
114
+ ```diff
115
+ <LocalizationProvider
116
+ dateAdapter={AdapterDayjs}
117
+ + localeText={{ cancelButtonLabel: "Cancelar" }}
118
+ >
119
+ <DatePicker
120
+ - localeText={{ cancelButtonLabel: "Cancelar" }}
121
+ />
122
+ <DateTimePicker
123
+ - localeText={{ cancelButtonLabel: "Cancelar" }}
124
+ />
125
+ </LocalizationProvider>
126
+ ```
127
+
128
+ You can find more details about this breaking change in [the migration guide](https://next.mui.com/x/migration/migration-pickers-v5/#rename-the-locale-prop-on-localizationprovider).
package/codemod.js ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
5
+ var _child_process = _interopRequireDefault(require("child_process"));
6
+ var _fs = require("fs");
7
+ var _path = _interopRequireDefault(require("path"));
8
+ var _yargs = _interopRequireDefault(require("yargs"));
9
+ const jscodeshiftPackage = require('jscodeshift/package.json');
10
+ const jscodeshiftDirectory = _path.default.dirname(require.resolve('jscodeshift'));
11
+ const jscodeshiftExecutable = _path.default.join(jscodeshiftDirectory, jscodeshiftPackage.bin.jscodeshift);
12
+ async function runTransform(transform, files, flags, codemodFlags) {
13
+ const transformerSrcPath = _path.default.resolve(__dirname, './src', transform);
14
+ const transformerBuildPath = _path.default.resolve(__dirname, transform);
15
+ let transformerPath;
16
+ try {
17
+ await _fs.promises.stat(transformerSrcPath);
18
+ transformerPath = transformerSrcPath;
19
+ } catch (srcPathError) {
20
+ try {
21
+ await _fs.promises.stat(transformerBuildPath);
22
+ transformerPath = transformerBuildPath;
23
+ } catch (buildPathError) {
24
+ if (buildPathError.code === 'ENOENT') {
25
+ throw new Error(`Transform '${transform}' not found. Check out ${_path.default.resolve(__dirname, './README.md for a list of available codemods.')}`);
26
+ }
27
+ throw buildPathError;
28
+ }
29
+ }
30
+ const args = [
31
+ // can't directly spawn `jscodeshiftExecutable` due to https://github.com/facebook/jscodeshift/issues/424
32
+ jscodeshiftExecutable, '--transform', transformerPath, ...codemodFlags, '--extensions', 'js,ts,jsx,tsx', '--parser', flags.parser || 'tsx', '--ignore-pattern', '**/node_modules/**', ...flags.jscodeshift];
33
+ args.push(...files);
34
+
35
+ // eslint-disable-next-line no-console -- debug information
36
+ console.log(`Executing command: jscodeshift ${args.join(' ')}`);
37
+ const jscodeshiftProcess = _child_process.default.spawnSync('node', args, {
38
+ stdio: 'inherit'
39
+ });
40
+ if (jscodeshiftProcess.error) {
41
+ throw jscodeshiftProcess.error;
42
+ }
43
+ }
44
+ function run(argv) {
45
+ const {
46
+ codemod,
47
+ paths,
48
+ _: other,
49
+ jscodeshift,
50
+ parser
51
+ } = argv;
52
+ return runTransform(codemod, paths.map(filePath => _path.default.resolve(filePath)), {
53
+ jscodeshift,
54
+ parser
55
+ }, other || []);
56
+ }
57
+ _yargs.default.command({
58
+ command: '$0 <codemod> <paths...>',
59
+ describe: 'Applies a `@mui/x-codemod` to the specified paths',
60
+ // @ts-expect-error
61
+ builder: command => {
62
+ return command.positional('codemod', {
63
+ description: 'The name of the codemod',
64
+ type: 'string'
65
+ }).positional('paths', {
66
+ array: true,
67
+ description: 'Paths forwarded to `jscodeshift`',
68
+ type: 'string'
69
+ }).option('parser', {
70
+ description: 'which parser for jscodeshift to use',
71
+ default: 'tsx',
72
+ type: 'string'
73
+ }).option('jscodeshift', {
74
+ description: '(Advanced) Pass options directly to jscodeshift',
75
+ default: [],
76
+ type: 'array'
77
+ });
78
+ },
79
+ handler: run
80
+ }).scriptName('npx @mui/x-codemod').example('$0 v6.0.0/localization-provider-rename-locale src', 'Run "localization-provider-rename-locale" codemod on "src" path').example('$0 v6.0.0/component-rename-prop src -- --component=DataGrid --from=prop --to=newProp', 'Run "component-rename-prop" codemod in "src" path on "DataGrid" component with custom "from" and "to" arguments').help().parse();
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@mui/x-codemod",
3
+ "version": "6.0.0-alpha.11",
4
+ "bin": "./codemod.js",
5
+ "private": false,
6
+ "author": "MUI Team",
7
+ "description": "Codemod scripts for MUI X.",
8
+ "keywords": [
9
+ "react",
10
+ "react-component",
11
+ "mui",
12
+ "codemod",
13
+ "jscodeshift"
14
+ ],
15
+ "scripts": {
16
+ "test": "cd ../../ && cross-env NODE_ENV=test mocha 'packages/x-codemod/**/*.test.ts'",
17
+ "typescript": "tsc -p tsconfig.json",
18
+ "prebuild": "rimraf build",
19
+ "copy-files": "cpy README.md build && cpy package.json build",
20
+ "build": "node ../../scripts/build.mjs node --ignore 'src/types.ts' && yarn copy-files"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/mui/mui-x.git",
25
+ "directory": "packages/x-codemod"
26
+ },
27
+ "license": "MIT",
28
+ "homepage": "https://github.com/mui/mui-x/tree/next/packages/x-codemod",
29
+ "funding": {
30
+ "type": "opencollective",
31
+ "url": "https://opencollective.com/mui"
32
+ },
33
+ "dependencies": {
34
+ "@babel/core": "^7.20.2",
35
+ "@babel/runtime": "^7.20.1",
36
+ "@babel/traverse": "^7.20.1",
37
+ "jscodeshift": "0.13.1",
38
+ "jscodeshift-add-imports": "^1.0.10",
39
+ "yargs": "^17.6.2"
40
+ },
41
+ "devDependencies": {
42
+ "@types/jscodeshift": "^0.11.5"
43
+ },
44
+ "sideEffects": false,
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "engines": {
49
+ "node": ">=14.0.0"
50
+ }
51
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = propsToObject;
7
+ function propsToObject({
8
+ j,
9
+ root,
10
+ componentName,
11
+ aliasName,
12
+ propName,
13
+ props
14
+ }) {
15
+ function buildObject(node, value) {
16
+ const shorthand = node.value.expression && node.value.expression.name === node.name.name;
17
+ const property = j.objectProperty(j.identifier(node.name.name), node.value.expression ? node.value.expression : node.value);
18
+ property.shorthand = shorthand;
19
+ value.push(property);
20
+ return value;
21
+ }
22
+ const result = aliasName ? root.find(j.JSXElement, {
23
+ openingElement: {
24
+ name: {
25
+ property: {
26
+ name: componentName
27
+ }
28
+ }
29
+ }
30
+ }) : root.findJSXElements(componentName);
31
+ return result.forEach(path => {
32
+ // @ts-expect-error
33
+ if (!aliasName || aliasName && path.node.openingElement.name.object.name === aliasName) {
34
+ let propValue = [];
35
+ const attributes = path.node.openingElement.attributes;
36
+ attributes?.forEach((node, index) => {
37
+ // Only transform whitelisted props
38
+ if (node.type === 'JSXAttribute' && props.includes(node.name.name)) {
39
+ propValue = buildObject(node, propValue);
40
+ delete attributes[index];
41
+ }
42
+ });
43
+ if (propValue.length > 0) {
44
+ const propNameAttr = attributes?.find(attr => attr.type === 'JSXAttribute' && attr.name.name === propName);
45
+ if (propNameAttr && propNameAttr.type === 'JSXAttribute') {
46
+ // @ts-expect-error
47
+ (propNameAttr.value.expression?.properties || []).push(...j.objectExpression(propValue).properties);
48
+ } else {
49
+ attributes?.push(j.jsxAttribute(j.jsxIdentifier(propName), j.jsxExpressionContainer(j.objectExpression(propValue))));
50
+ }
51
+ }
52
+ }
53
+ });
54
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = readFile;
8
+ var _fs = _interopRequireDefault(require("fs"));
9
+ var _os = require("os");
10
+ function readFile(filePath) {
11
+ const fileContents = _fs.default.readFileSync(filePath, 'utf8').toString();
12
+ if (_os.EOL !== '\n') {
13
+ return fileContents.replace(/\n/g, _os.EOL);
14
+ }
15
+ return fileContents;
16
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = renameClassKey;
7
+ function renameClassKey({
8
+ root,
9
+ componentName,
10
+ classes,
11
+ printOptions
12
+ }) {
13
+ const source = root.findJSXElements(componentName).forEach(path => {
14
+ path.node.openingElement.attributes?.forEach(node => {
15
+ if (node.type === 'JSXAttribute' && node.name.name === 'classes') {
16
+ // @ts-expect-error
17
+ node.value?.expression?.properties?.forEach(subNode => {
18
+ if (Object.keys(classes).includes(subNode.key.name)) {
19
+ subNode.key.name = classes[subNode.key.name];
20
+ }
21
+ });
22
+ }
23
+ });
24
+ }).toSource(printOptions);
25
+ return Object.entries(classes).reduce((result, [currentKey, newKey]) => {
26
+ const regex = new RegExp(`.Mui${componentName}-${currentKey}`, 'gm');
27
+ return result.replace(regex, `.Mui${componentName}-${newKey}`);
28
+ }, source);
29
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = renameProps;
7
+ function renameProps({
8
+ root,
9
+ componentName,
10
+ props
11
+ }) {
12
+ return root.findJSXElements(componentName).forEach(path => {
13
+ path.node.openingElement.attributes?.forEach(node => {
14
+ if (node.type === 'JSXAttribute' && Object.keys(props).includes(node.name.name)) {
15
+ node.name.name = props[node.name.name];
16
+ }
17
+ });
18
+ });
19
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = transformer;
8
+ var _renameProps = _interopRequireDefault(require("../../util/renameProps"));
9
+ function transformer(file, api, options) {
10
+ const j = api.jscodeshift;
11
+ const root = j(file.source);
12
+ const printOptions = options.printOptions;
13
+ return (0, _renameProps.default)({
14
+ root,
15
+ componentName: options.component,
16
+ props: {
17
+ [options.from]: options.to
18
+ }
19
+ }).toSource(printOptions);
20
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = transformer;
8
+ var _renameProps = _interopRequireDefault(require("../../util/renameProps"));
9
+ function transformer(file, api, options) {
10
+ const j = api.jscodeshift;
11
+ const root = j(file.source);
12
+ const printOptions = options.printOptions;
13
+ return (0, _renameProps.default)({
14
+ root,
15
+ componentName: 'LocalizationProvider',
16
+ props: {
17
+ locale: 'adapterLocale'
18
+ }
19
+ }).toSource(printOptions);
20
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = transformer;
8
+ var _localizationProviderRenameLocale = _interopRequireDefault(require("../localization-provider-rename-locale"));
9
+ var _textPropsToLocaleText = _interopRequireDefault(require("../text-props-to-localeText"));
10
+ function transformer(file, api, options) {
11
+ file.source = (0, _localizationProviderRenameLocale.default)(file, api, options);
12
+
13
+ // Should be run before the renaming of components such as `ClockPicker` to `TimeClock`.
14
+ file.source = (0, _textPropsToLocaleText.default)(file, api, options);
15
+ return file.source;
16
+ }
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = transformer;
8
+ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
9
+ const defaultPropsToKey = {
10
+ cancelText: ['cancelButtonLabel'],
11
+ okText: ['okButtonLabel'],
12
+ todayText: ['todayButtonLabel'],
13
+ clearText: ['clearButtonLabel'],
14
+ endText: ['end'],
15
+ getClockLabelText: ['clockLabelText'],
16
+ getHoursClockNumberText: ['hoursClockNumberText'],
17
+ getMinutesClockNumberText: ['minutesClockNumberText'],
18
+ getSecondsClockNumberText: ['secondsClockNumberText'],
19
+ getViewSwitchingButtonText: ['calendarViewSwitchingButtonAriaLabel'],
20
+ startText: ['start']
21
+ };
22
+ const isMonthSwitchComponent = {
23
+ DatePicker: true,
24
+ StaticDatePicker: true,
25
+ MobileDatePicker: true,
26
+ DesktopDatePicker: true,
27
+ DateRangePicker: true,
28
+ StaticDateRangePicker: true,
29
+ MobileDateRangePicker: true,
30
+ DesktopDateRangePicker: true,
31
+ CalendarPicker: true,
32
+ // Special cases of DateTimePickers present in both
33
+ DateTimePicker: true,
34
+ StaticDateTimePicker: true,
35
+ MobileDateTimePicker: true,
36
+ DesktopDateTimePicker: true
37
+ };
38
+ const isViewSwitchComponent = {
39
+ TimePicker: true,
40
+ StaticTimePicker: true,
41
+ MobileTimePicker: true,
42
+ DesktopTimePicker: true,
43
+ DateTimePicker: true,
44
+ ClockPicker: true,
45
+ // Special cases of DateTimePickers present in both
46
+ StaticDateTimePicker: true,
47
+ MobileDateTimePicker: true,
48
+ DesktopDateTimePicker: true
49
+ };
50
+ const needsWrapper = {
51
+ ClockPicker: true,
52
+ CalendarPicker: true
53
+ };
54
+ const impactedComponents = ['DateRangePicker', 'CalendarPicker', 'ClockPicker', 'DatePicker', 'DateRangePicker', 'DateRangePickerDay', 'DateTimePicker', 'DesktopDatePicker', 'DesktopDateRangePicker', 'DesktopDateTimePicker', 'DesktopTimePicker', 'MobileDatePicker', 'MobileDateRangePicker', 'MobileDateTimePicker', 'MobileTimePicker', 'StaticDatePicker', 'StaticDateRangePicker', 'StaticDateTimePicker', 'StaticTimePicker', 'TimePicker'];
55
+
56
+ /**
57
+ * @param {import('jscodeshift').FileInfo} file
58
+ * @param {import('jscodeshift').API} api
59
+ */
60
+ function transformer(file, api, options) {
61
+ const j = api.jscodeshift;
62
+ const printOptions = options.printOptions;
63
+ const root = j(file.source);
64
+ impactedComponents.forEach(componentName => {
65
+ const propsToKey = (0, _extends2.default)({}, defaultPropsToKey, {
66
+ leftArrowButtonText: [...(isViewSwitchComponent[componentName] ? ['openPreviousView'] : []), ...(isMonthSwitchComponent[componentName] ? ['previousMonth'] : [])],
67
+ rightArrowButtonText: [...(isViewSwitchComponent[componentName] ? ['openNextView'] : []), ...(isMonthSwitchComponent[componentName] ? ['nextMonth'] : [])]
68
+ });
69
+ root.findJSXElements(componentName).forEach(path => {
70
+ const newLocaleText = [];
71
+ const attributes = path.node.openingElement.attributes;
72
+ attributes.forEach((node, index) => {
73
+ if (node.type === 'JSXAttribute' && propsToKey[node.name.name] !== undefined) {
74
+ const newNames = propsToKey[node.name.name];
75
+ newNames.forEach(newName => {
76
+ const property = j.objectProperty(j.identifier(newName), node.value.expression ? node.value.expression : j.literal(node.value.value));
77
+ property.shorthand = node.value.expression && node.value.expression.name === newName;
78
+ newLocaleText.push(property);
79
+ });
80
+ delete attributes[index];
81
+ }
82
+ });
83
+ if (newLocaleText.length > 0) {
84
+ if (needsWrapper[componentName]) {
85
+ // From : https://www.codeshiftcommunity.com/docs/react/#wrapping-components
86
+
87
+ // Create a new JSXElement called "LocalizationProvider" and use the original component as children
88
+ const wrappedComponent = j.jsxElement(j.jsxOpeningElement(j.jsxIdentifier('LocalizationProvider'), [
89
+ // Add the new localeText prop
90
+ j.jsxAttribute(j.jsxIdentifier('localeText'), j.jsxExpressionContainer(j.objectExpression(newLocaleText)))]), j.jsxClosingElement(j.jsxIdentifier('LocalizationProvider')), [path.value] // Pass in the original component as children
91
+ );
92
+
93
+ j(path).replaceWith(wrappedComponent);
94
+ } else {
95
+ attributes.push(j.jsxAttribute(j.jsxIdentifier('localeText'), j.jsxExpressionContainer(j.objectExpression(newLocaleText))));
96
+ }
97
+ }
98
+ });
99
+ });
100
+ return root.toSource(printOptions);
101
+ }