@availity/mui-codemod 0.1.0-alpha.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
+
5
+ ## 0.1.0-alpha.0 (2025-02-24)
6
+
7
+
8
+ ### Features
9
+
10
+ * **mui-codemod:** add MUI v6 codemods for element users ([3be1a1b](https://github.com/Availity/element/commit/3be1a1ba2dab07968d26361bf4965b96fae682b5))
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @availity/mui-codemod
2
+
3
+ > Availity MUI v1 Codemods to be used when migrating from @availity/element v0 to v1.
4
+
5
+ [![Version](https://img.shields.io/npm/v/@availity/mui-codemod.svg?style=for-the-badge)](https://www.npmjs.com/package/@availity/mui-codemod)
6
+ [![NPM Downloads](https://img.shields.io/npm/dt/@availity/mui-codemod.svg?style=for-the-badge)](https://www.npmjs.com/package/@availity/mui-codemod)
7
+ [![Dependency Status](https://img.shields.io/librariesio/release/npm/@availity/mui-codemod?style=for-the-badge)](https://github.com/Availity/element/blob/main/packages/mui-codemod/package.json)
8
+
9
+ ## Documentation
10
+
11
+ This package extends the MUI v6 Codemods: [MUI Codemod Docs](https://mui.com/material-ui/migration/upgrade-to-v6/)
12
+
13
+ Availity standards for design and usage can be found in the [Availity Design Guide](https://zeroheight.com/2e36e50c7)
14
+
15
+ ## Usage
16
+
17
+ ### Migrate Grid props
18
+
19
+ `npx @availity/mui-codemod@latest v1.0.0/grid-v2-props <path/to/folder>`
20
+
21
+ ### Migrate sx props
22
+
23
+ `npx @availity/mui-codemod@latest v1.0.0/sx-props <path/to/folder>`
24
+
25
+ ### Migrate system props
26
+
27
+ `npx @availity/mui-codemod@latest v1.0.0/system-props <path/to/folder>`
package/jest.config.js ADDED
@@ -0,0 +1,7 @@
1
+ const global = require('../../jest.config.global');
2
+
3
+ module.exports = {
4
+ ...global,
5
+ displayName: 'codemod',
6
+ coverageDirectory: '../../coverage/codemod',
7
+ };
package/jsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["."],
4
+ "exclude": ["dist", "build", "node_modules"]
5
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@availity/mui-codemod",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "Availity MUI v1 Codemods - part of the @availity/element design system",
5
+ "keywords": [
6
+ "react",
7
+ "typescript",
8
+ "availity",
9
+ "mui"
10
+ ],
11
+ "homepage": "https://availity.github.io/element/?path=/docs/components-codemod-introduction--docs",
12
+ "bugs": {
13
+ "url": "https://github.com/Availity/element/issues"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/Availity/element.git",
18
+ "directory": "packages/codemod"
19
+ },
20
+ "license": "MIT",
21
+ "author": "Availity Developers <AVOSS@availity.com>",
22
+ "bin": "./src/codemod.js",
23
+ "engines": {
24
+ "node": ">=18.0.0"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup src/codemod.js --format esm,cjs --dts",
28
+ "dev": "tsup src/codemod.js --format esm,cjs --watch --dts",
29
+ "clean": "rm -rf dist",
30
+ "clean:nm": "rm -rf node_modules",
31
+ "publish": "yarn npm publish --tolerate-republish --access public",
32
+ "publish:canary": "yarn npm publish --access public --tag canary"
33
+ },
34
+ "devDependencies": {
35
+ "tsup": "^8.3.6",
36
+ "typescript": "^5.4.5"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "dependencies": {
42
+ "@types/jscodeshift": "^0.12.0",
43
+ "jscodeshift": "^17.1.2",
44
+ "postcss-cli": "^11.0.0",
45
+ "yargs": "^17.7.2"
46
+ }
47
+ }
package/project.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "mui-codemod",
3
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/codemod/src",
5
+ "projectType": "library",
6
+ "tags": [],
7
+ "targets": {
8
+ "test": {
9
+ "executor": "@nx/jest:jest",
10
+ "outputs": ["{workspaceRoot}/coverage/codemod"],
11
+ "options": {
12
+ "jestConfig": "packages/codemod/jest.config.js"
13
+ }
14
+ },
15
+ "version": {
16
+ "executor": "@jscutlery/semver:version",
17
+ "options": {
18
+ "preset": "conventional",
19
+ "commitMessageFormat": "chore({projectName}): release version ${version} [skip ci]",
20
+ "tagPrefix": "@availity/{projectName}@",
21
+ "trackDeps": true,
22
+ "skipCommitTypes": ["docs"]
23
+ }
24
+ }
25
+ }
26
+ }
package/src/codemod.js ADDED
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+
3
+ const childProcess = require('child_process');
4
+ const { promises: fs } = require('fs');
5
+ const path = require('path');
6
+ const yargs = require('yargs');
7
+ const jscodeshiftPackage = require('jscodeshift/package.json');
8
+ const postcssCliPackage = require('postcss-cli/package.json');
9
+
10
+ const jscodeshiftDirectory = path.dirname(require.resolve('jscodeshift'));
11
+ const jscodeshiftExecutable = path.join(jscodeshiftDirectory, jscodeshiftPackage.bin.jscodeshift);
12
+
13
+ const postcssCliDirectory = path.dirname(require.resolve('postcss-cli'));
14
+ const postcssExecutable = path.join(postcssCliDirectory, postcssCliPackage.bin.postcss);
15
+
16
+ async function runJscodeshiftTransform(transform, files, flags, codemodFlags) {
17
+ const paths = [
18
+ path.resolve(__dirname, './src', `${transform}/index.js`),
19
+ path.resolve(__dirname, './src', `${transform}.js`),
20
+ path.resolve(__dirname, './node', `${transform}/index.js`),
21
+ path.resolve(__dirname, './node', `${transform}.js`),
22
+ ];
23
+
24
+ let transformerPath;
25
+ let error;
26
+ for (const item of paths) {
27
+ try {
28
+ await fs.stat(item);
29
+ error = undefined;
30
+ transformerPath = item;
31
+ break;
32
+ } catch (srcPathError) {
33
+ error = srcPathError;
34
+ continue;
35
+ }
36
+ }
37
+
38
+ if (error) {
39
+ if (error?.code === 'ENOENT') {
40
+ throw new Error(
41
+ `Transform '${transform}' not found. Check out ${path.resolve(__dirname, './README.md for a list of available codemods.')}`,
42
+ );
43
+ }
44
+ throw error;
45
+ }
46
+
47
+ const args = [
48
+ jscodeshiftExecutable,
49
+ '--transform',
50
+ transformerPath,
51
+ ...codemodFlags,
52
+ '--extensions',
53
+ 'js,ts,jsx,tsx,json',
54
+ '--parser',
55
+ flags.parser || 'tsx',
56
+ '--ignore-pattern',
57
+ '**/node_modules/**',
58
+ '--ignore-pattern',
59
+ '**/*.css'
60
+ ];
61
+
62
+ if (flags.dry) {
63
+ args.push('--dry');
64
+ }
65
+ if (flags.print) {
66
+ args.push('--print');
67
+ }
68
+ if (flags.jscodeshift) {
69
+ args.push(flags.jscodeshift);
70
+ }
71
+
72
+ args.push(...files);
73
+
74
+ console.log(`Executing command: jscodeshift ${args.join(' ')}`);
75
+ const jscodeshiftProcess = childProcess.spawnSync('node', args, { stdio: 'inherit' });
76
+
77
+ if (jscodeshiftProcess.error) {
78
+ throw jscodeshiftProcess.error;
79
+ }
80
+ }
81
+
82
+ const parseCssFilePaths = async (files) => {
83
+ const cssFiles = await Promise.all(files.map(async (filePath) => {
84
+ const stat = await fs.stat(filePath);
85
+ if (stat.isDirectory()) {
86
+ return `${filePath}/**/*.css`;
87
+ }
88
+ if (filePath.endsWith('.css')) {
89
+ return filePath;
90
+ }
91
+
92
+ return null;
93
+ }),);
94
+
95
+ return cssFiles.filter(Boolean);
96
+ }
97
+
98
+ async function runPostcssTransform(transform, files) {
99
+ const paths = [
100
+ path.resolve(__dirname, './src', `${transform}/postcss.config.js`),
101
+ path.resolve(__dirname, './node', `${transform}/postcss.config.js`),
102
+ ];
103
+
104
+ let configPath;
105
+ let error;
106
+ for (const item of paths) {
107
+ try {
108
+ await fs.stat(item);
109
+ error = undefined;
110
+ configPath = item;
111
+ break;
112
+ } catch (srcPathError) {
113
+ error = srcPathError;
114
+ continue;
115
+ }
116
+ }
117
+
118
+ if (error) {
119
+ if (error?.code !== 'ENOENT') {
120
+ throw error;
121
+ }
122
+ } else {
123
+ const cssPaths = await parseCssFilePaths(files);
124
+
125
+ if (cssPaths.length > 0) {
126
+ const args = [
127
+ postcssExecutable,
128
+ ...cssPaths,
129
+ '--config',
130
+ configPath,
131
+ '--replace',
132
+ '--verbose',
133
+ ];
134
+
135
+ console.log(`Executing command: postcss ${args.join(' ')}`);
136
+ const postCssProcess = childProcess.spawnSync('node', args, { stdio: 'inherit' });
137
+
138
+ if (postCssProcess.error) {
139
+ throw postCssProcess.error;
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ function run(argv) {
146
+ const { codemod, paths, ...flags } = argv;
147
+ const files = paths.map((filePath) => path.resolve(filePath));
148
+
149
+ runJscodeshiftTransform(codemod, files, flags, argv._);
150
+ runPostcssTransform(codemod, files);
151
+ }
152
+
153
+ yargs.command({
154
+ command: '$0 <codemod> <paths...>',
155
+ describe: 'Applies a `@mui/codemod` to the specified paths',
156
+ builder: (command) => {
157
+ return command
158
+ .positional('codemod', {
159
+ description: 'The name of the codemod',
160
+ type: 'string'
161
+ })
162
+ .positional('paths', {
163
+ array: true,
164
+ description: 'Paths forwarded to `jscodeshift`',
165
+ type: 'string',
166
+ })
167
+ .option('dry', {
168
+ description: 'dry run (no changes are made to files)',
169
+ default: false,
170
+ type: 'boolean'
171
+ })
172
+ .option('parser', {
173
+ description: 'which parser for jscodeshift to use',
174
+ default: 'tsx',
175
+ type: 'string'
176
+ })
177
+ .option('print', {
178
+ description: 'print transformed files to stdout, useful for development',
179
+ default: false,
180
+ type: 'boolean'
181
+ })
182
+ .option('jscodeshift', {
183
+ description: '(Advanced) Pass options directly to jscodeshift',
184
+ default: false,
185
+ type: 'string'
186
+ });
187
+ },
188
+ handler: run,
189
+ })
190
+ .scriptName('npx @availity/mui-codemod')
191
+ .example('$0 v4.0.0/theme-spacing-api src')
192
+ .example('$0 v5.0.0/component-rename-prop src -- --component=Grid --from=prop --to=newProp')
193
+ .help()
194
+ .parse();
@@ -0,0 +1,217 @@
1
+ const possibleDefaultImports = ['@availity/element', '@availity/mui-layout'];
2
+ const possibleNamedImports = {
3
+ '@availity/element': 'Grid',
4
+ '@availity/mui-layout': 'Grid',
5
+ };
6
+
7
+ const defaultBreakpoints = ['xs', 'sm', 'md', 'lg', 'xl'];
8
+
9
+ /**
10
+ * @param {import('jscodeshift').FileInfo} file
11
+ * @param {import('jscodeshift').API} api
12
+ */
13
+ export default function gridV2Props(file, api, options) {
14
+ if (file.path?.endsWith('.json') || file.path?.endsWith('.d.ts')) {
15
+ return file.source;
16
+ }
17
+ const j = api.jscodeshift;
18
+ const root = j(file.source);
19
+ const breakpoints = options.muiBreakpoints?.split(',') || defaultBreakpoints;
20
+ const printOptions = options.printOptions;
21
+
22
+ const gridLocalNames = [];
23
+
24
+ root
25
+ .find(j.ImportDeclaration, (decl) => possibleDefaultImports.includes(decl.source.value))
26
+ .forEach((decl) => {
27
+ decl.node.specifiers.forEach((spec) => {
28
+ if (spec.type === 'ImportDefaultSpecifier') {
29
+ gridLocalNames.push(spec.local.name);
30
+ }
31
+ });
32
+ });
33
+
34
+ root
35
+ .find(j.ImportDeclaration, (decl) =>
36
+ Object.keys(possibleNamedImports).includes(decl.source.value),
37
+ )
38
+ .forEach((decl) => {
39
+ decl.node.specifiers.forEach((spec) => {
40
+ if (spec.type === 'ImportSpecifier') {
41
+ if (possibleNamedImports[decl.node.source.value] === spec.imported.name) {
42
+ gridLocalNames.push(spec.local.name);
43
+ }
44
+ }
45
+ });
46
+ });
47
+
48
+ root
49
+ .find(j.JSXElement, {
50
+ openingElement: {
51
+ name: {
52
+ name: (name) => gridLocalNames.includes(name),
53
+ },
54
+ },
55
+ })
56
+ .forEach((el) => {
57
+ const size = j.objectExpression([]);
58
+
59
+ const spreadProps = [];
60
+ const attributesToPrune = [];
61
+
62
+ el.node.openingElement.attributes.forEach((attr) => {
63
+ if (attr.type === 'JSXSpreadAttribute') {
64
+ spreadProps.push(attr);
65
+ }
66
+ });
67
+
68
+ const breakpointNodes = j(el)
69
+ .find(j.JSXAttribute)
70
+ .filter(
71
+ (path) =>
72
+ path.parent.parent.node === el.node && breakpoints.includes(path.node.name.name),
73
+ );
74
+
75
+ breakpointNodes.nodes().forEach((node) => {
76
+ const breakpoint = node.name.name;
77
+ const nodeValue = node.value;
78
+ let value;
79
+
80
+ if (nodeValue === null) {
81
+ value = j.stringLiteral('grow');
82
+ } else if (nodeValue.type === 'JSXExpressionContainer') {
83
+ if (nodeValue.expression.value === true) {
84
+ value = j.stringLiteral('grow');
85
+ } else {
86
+ value = nodeValue.expression;
87
+ }
88
+ } else {
89
+ value = nodeValue;
90
+ }
91
+
92
+ size.properties.push(j.property('init', j.identifier(breakpoint), value));
93
+ });
94
+
95
+ spreadProps.forEach((spreadProp) => {
96
+ const spreadPropArgument = spreadProp.argument;
97
+ if (spreadPropArgument.type === 'ObjectExpression') {
98
+ const propertiesToPrune = [];
99
+ spreadPropArgument.properties.forEach((property) => {
100
+ if (breakpoints.includes(property.key.name)) {
101
+ size.properties.push(j.property('init', property.key, property.value));
102
+ propertiesToPrune.push(property.key.name);
103
+ }
104
+ });
105
+ spreadPropArgument.properties = spreadPropArgument.properties.filter(
106
+ (prop) => !propertiesToPrune.includes(prop.key.name),
107
+ );
108
+ if (spreadPropArgument.properties.length === 0) {
109
+ attributesToPrune.push(spreadProp);
110
+ }
111
+ }
112
+ });
113
+
114
+ if (size.properties.length) {
115
+ let sizePropValue = size;
116
+ if (size.properties.length === 1 && size.properties[0].key.name === 'xs') {
117
+ sizePropValue = size.properties[0].value;
118
+ }
119
+ if (sizePropValue.type !== 'StringLiteral') {
120
+ sizePropValue = j.jsxExpressionContainer(sizePropValue);
121
+ }
122
+
123
+ el.node.openingElement.attributes.push(
124
+ j.jsxAttribute(j.jsxIdentifier('size'), sizePropValue),
125
+ );
126
+ }
127
+
128
+ el.node.openingElement.attributes = el.node.openingElement.attributes.filter(
129
+ (attr) => !breakpoints.includes(attr?.name?.name),
130
+ );
131
+
132
+ const offset = j.objectExpression([]);
133
+
134
+ const offsetNodes = j(el)
135
+ .find(j.JSXAttribute)
136
+ .filter(
137
+ (path) =>
138
+ path.parent.parent.node === el.node &&
139
+ path.node.name.name.endsWith('Offset') &&
140
+ breakpoints.includes(path.node.name.name.replace('Offset', '')),
141
+ );
142
+
143
+ offsetNodes.nodes().forEach((node) => {
144
+ const breakpoint = node.name.name.replace('Offset', '');
145
+ const value =
146
+ node.value.type === 'JSXExpressionContainer' ? node.value.expression : node.value;
147
+
148
+ offset.properties.push(j.property('init', j.identifier(breakpoint), value));
149
+ });
150
+
151
+ spreadProps.forEach((spreadProp) => {
152
+ const spreadPropArgument = spreadProp.argument;
153
+ if (spreadPropArgument.type === 'ObjectExpression') {
154
+ const propertiesToPrune = [];
155
+ spreadPropArgument.properties.forEach((property) => {
156
+ const breakpoint = property.key.name.replace('Offset', '');
157
+ if (property.key.name.endsWith('Offset') && breakpoints.includes(breakpoint)) {
158
+ offset.properties.push(j.property('init', j.identifier(breakpoint), property.value));
159
+ propertiesToPrune.push(property.key.name);
160
+ }
161
+ });
162
+ spreadPropArgument.properties = spreadPropArgument.properties.filter(
163
+ (prop) => !propertiesToPrune.includes(prop.key.name),
164
+ );
165
+ if (spreadPropArgument.properties.length === 0) {
166
+ attributesToPrune.push(spreadProp);
167
+ }
168
+ }
169
+ });
170
+
171
+ if (offset.properties.length) {
172
+ let offsetPropValue = offset;
173
+
174
+ if (offset.properties.length === 1 && offset.properties[0].key.name === 'xs') {
175
+ offsetPropValue = offset.properties[0].value;
176
+ }
177
+
178
+ if (offsetPropValue.type !== 'StringLiteral') {
179
+ offsetPropValue = j.jsxExpressionContainer(offsetPropValue);
180
+ }
181
+
182
+ el.node.openingElement.attributes.push(
183
+ j.jsxAttribute(j.jsxIdentifier('offset'), offsetPropValue),
184
+ );
185
+ }
186
+
187
+ el.node.openingElement.attributes = el.node.openingElement.attributes.filter(
188
+ (attr) => !breakpoints.includes(attr?.name?.name.replace('Offset', '')),
189
+ );
190
+
191
+ el.node.openingElement.attributes = el.node.openingElement.attributes.filter(
192
+ (attr) => !attributesToPrune.includes(attr),
193
+ );
194
+
195
+ const itemProp = el.node.openingElement.attributes.find(
196
+ (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'item',
197
+ );
198
+
199
+ if (itemProp) {
200
+ el.node.openingElement.attributes = el.node.openingElement.attributes.filter(
201
+ (attr) => attr.type === 'JSXAttribute' && attr.name.name !== 'item',
202
+ );
203
+ }
204
+
205
+ const zeroMinWidthProp = el.node.openingElement.attributes.find(
206
+ (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'zeroMinWidth',
207
+ );
208
+
209
+ if (zeroMinWidthProp) {
210
+ el.node.openingElement.attributes = el.node.openingElement.attributes.filter(
211
+ (attr) => attr.type === 'JSXAttribute' && attr.name.name !== 'zeroMinWidth',
212
+ );
213
+ }
214
+ });
215
+
216
+ return root.toSource(printOptions);
217
+ }
@@ -0,0 +1,23 @@
1
+ import { describeJscodeshiftTransform } from "../../../testUtils";
2
+ import transform from './grid-v2-props';
3
+
4
+ describe('@availity/codemod', () => {
5
+ describe('deprecations', () => {
6
+ describeJscodeshiftTransform({
7
+ transform,
8
+ transformName: 'grid-props',
9
+ dirname: __dirname,
10
+ testCases: [
11
+ {
12
+ actual: '/test-cases/actual.js',
13
+ expected: '/test-cases/expected.js'
14
+ },
15
+ {
16
+ actual: '/test-cases/custom-breakpoints.actual.js',
17
+ expected: '/test-cases/custom-breakpoints.expected.js',
18
+ options: { muiBreakpoints: 'customXs,customSm,customMd'}
19
+ }
20
+ ]
21
+ })
22
+ });
23
+ });
@@ -0,0 +1,36 @@
1
+ /* eslint-disable @nx/enforce-module-boundaries */
2
+ import { Grid as GridA } from '@availity/element';
3
+ import { Grid as GridB } from '@availity/mui-layout';
4
+
5
+ // Transforms on all the possible imports
6
+ <GridA xs={2} />;
7
+ <GridB xs={2} />;
8
+
9
+ <GridA item />;
10
+ <GridA item={true} />;
11
+ <GridA item={false} />;
12
+
13
+ <GridA zeroMinWidth />;
14
+ <GridA zeroMinWidth={true} />;
15
+ <GridA zeroMinWidth={false} />;
16
+
17
+ // Transforms responsive sizes
18
+ <GridA xs={2} sm={4} md={6} lg={8} xl={10} />;
19
+
20
+ // Transforms all the possible size values
21
+ <GridA xs sm="auto" md={2} lg={true} xl={false} />;
22
+
23
+ // Doesn't add jsx object expression for single string values
24
+ <GridA xs="auto" />;
25
+
26
+ // Transforms offset
27
+ <GridA xsOffset={2} />;
28
+
29
+ // Transforms responsive offset
30
+ <GridA xsOffset={2} smOffset={4} mdOffset={6} lgOffset={8} xlOffset={10} />;
31
+
32
+ // Transforms all the possible offset values
33
+ <GridA xsOffset={2} smOffset="auto" />;
34
+
35
+ // Transforms spread props
36
+ <GridA {...{ xs: 2, sm: 4, xsOffset: 0, smOffset: 2 }} />;
@@ -0,0 +1,9 @@
1
+ import { Grid } from '@availity/mui-layout';
2
+
3
+ <>
4
+ // Transforms custom breakpoints
5
+ <Grid customXs={2} customSm={4} customMd={6} />
6
+
7
+ // Transforms custom breakpoints offset
8
+ <Grid customXsOffset={2} customSmOffset={4} customMdOffset={6} />
9
+ </>
@@ -0,0 +1,19 @@
1
+ import { Grid } from '@availity/mui-layout';
2
+
3
+ <>
4
+ // Transforms custom breakpoints
5
+ <Grid
6
+ size={{
7
+ customXs: 2,
8
+ customSm: 4,
9
+ customMd: 6
10
+ }} />
11
+
12
+ // Transforms custom breakpoints offset
13
+ <Grid
14
+ offset={{
15
+ customXs: 2,
16
+ customSm: 4,
17
+ customMd: 6
18
+ }} />
19
+ </>
@@ -0,0 +1,69 @@
1
+ /* eslint-disable @nx/enforce-module-boundaries */
2
+ import { Grid as GridA } from '@availity/element';
3
+ import { Grid as GridB } from '@availity/mui-layout';
4
+
5
+ // Transforms on all the possible imports
6
+ <GridA size={2} />;
7
+ <GridB size={2} />;
8
+
9
+ <GridA />;
10
+ <GridA />;
11
+ <GridA />;
12
+
13
+ <GridA />;
14
+ <GridA />;
15
+ <GridA />;
16
+
17
+ // Transforms responsive sizes
18
+ <GridA
19
+ size={{
20
+ xs: 2,
21
+ sm: 4,
22
+ md: 6,
23
+ lg: 8,
24
+ xl: 10
25
+ }} />;
26
+
27
+ // Transforms all the possible size values
28
+ <GridA
29
+ size={{
30
+ xs: "grow",
31
+ sm: "auto",
32
+ md: 2,
33
+ lg: "grow",
34
+ xl: false
35
+ }} />;
36
+
37
+ // Doesn't add jsx object expression for single string values
38
+ <GridA size="auto" />;
39
+
40
+ // Transforms offset
41
+ <GridA offset={2} />;
42
+
43
+ // Transforms responsive offset
44
+ <GridA
45
+ offset={{
46
+ xs: 2,
47
+ sm: 4,
48
+ md: 6,
49
+ lg: 8,
50
+ xl: 10
51
+ }} />;
52
+
53
+ // Transforms all the possible offset values
54
+ <GridA
55
+ offset={{
56
+ xs: 2,
57
+ sm: "auto"
58
+ }} />;
59
+
60
+ // Transforms spread props
61
+ <GridA
62
+ size={{
63
+ xs: 2,
64
+ sm: 4
65
+ }}
66
+ offset={{
67
+ xs: 0,
68
+ sm: 2
69
+ }} />;
@@ -0,0 +1,300 @@
1
+ // from `packages/mui-system/src/styleFunctionSx/defaultSxConfig.js`
2
+ const defaultSxConfig = {
3
+ // borders
4
+ border: {},
5
+ borderTop: {},
6
+ borderRight: {},
7
+ borderBottom: {},
8
+ borderLeft: {},
9
+ borderColor: {},
10
+ borderTopColor: {},
11
+ borderRightColor: {},
12
+ borderBottomColor: {},
13
+ borderLeftColor: {},
14
+ outline: {},
15
+ outlineColor: {},
16
+ borderRadius: {},
17
+ color: {},
18
+ bgcolor: {},
19
+ backgroundColor: {},
20
+ p: {},
21
+ pt: {},
22
+ pr: {},
23
+ pb: {},
24
+ pl: {},
25
+ px: {},
26
+ py: {},
27
+ padding: {},
28
+ paddingTop: {},
29
+ paddingRight: {},
30
+ paddingBottom: {},
31
+ paddingLeft: {},
32
+ paddingX: {},
33
+ paddingY: {},
34
+ paddingInline: {},
35
+ paddingInlineStart: {},
36
+ paddingInlineEnd: {},
37
+ paddingBlock: {},
38
+ paddingBlockStart: {},
39
+ paddingBlockEnd: {},
40
+
41
+ m: {},
42
+ mt: {},
43
+ mr: {},
44
+ mb: {},
45
+ ml: {},
46
+ mx: {},
47
+ my: {},
48
+ margin: {},
49
+ marginTop: {},
50
+ marginRight: {},
51
+ marginBottom: {},
52
+ marginLeft: {},
53
+ marginX: {},
54
+ marginY: {},
55
+ marginInline: {},
56
+ marginInlineStart: {},
57
+ marginInlineEnd: {},
58
+ marginBlock: {},
59
+ marginBlockStart: {},
60
+ marginBlockEnd: {},
61
+
62
+ // display
63
+ displayPrint: {},
64
+ display: {},
65
+ overflow: {},
66
+ textOverflow: {},
67
+ visibility: {},
68
+ whiteSpace: {},
69
+
70
+ // flexbox
71
+ flexBasis: {},
72
+ flexDirection: {},
73
+ flexWrap: {},
74
+ justifyContent: {},
75
+ alignItems: {},
76
+ alignContent: {},
77
+ order: {},
78
+ flex: {},
79
+ flexGrow: {},
80
+ flexShrink: {},
81
+ alignSelf: {},
82
+ justifyItems: {},
83
+ justifySelf: {},
84
+
85
+ // grid
86
+ gap: {},
87
+ rowGap: {},
88
+ columnGap: {},
89
+ gridColumn: {},
90
+ gridRow: {},
91
+ gridAutoFlow: {},
92
+ gridAutoColumns: {},
93
+ gridAutoRows: {},
94
+ gridTemplateColumns: {},
95
+ gridTemplateRows: {},
96
+ gridTemplateAreas: {},
97
+ gridArea: {},
98
+
99
+ // positions
100
+ position: {},
101
+ zIndex: {},
102
+ top: {},
103
+ right: {},
104
+ bottom: {},
105
+ left: {},
106
+
107
+ // shadows
108
+ boxShadow: {},
109
+
110
+ // sizing
111
+ width: {},
112
+ maxWidth: {},
113
+ minWidth: {},
114
+ height: {},
115
+ maxHeight: {},
116
+ minHeight: {},
117
+ boxSizing: {},
118
+
119
+ // typography
120
+ font: {},
121
+ fontFamily: {},
122
+ fontSize: {},
123
+ fontStyle: {},
124
+ fontWeight: {},
125
+ letterSpacing: {},
126
+ textTransform: {},
127
+ lineHeight: {},
128
+ textAlign: {},
129
+ typography: {},
130
+ };
131
+ const systemProps = Object.keys(defaultSxConfig);
132
+ const components = ['Box', 'Stack', 'Typography', 'Link', 'Grid'];
133
+
134
+ /**
135
+ * @param {import('jscodeshift').FileInfo} file
136
+ * @param {import('jscodeshift').API} api
137
+ */
138
+ export default function removeSystemProps(file, api, options) {
139
+ if (file.path?.endsWith('.json') || file.path?.endsWith('.d.ts')) {
140
+ return file.source;
141
+ }
142
+ const j = api.jscodeshift;
143
+ const root = j(file.source);
144
+ const printOptions = options.printOptions;
145
+
146
+ const deprecatedElements = [];
147
+ const customReplacement = {
148
+ Typography: {
149
+ matcher: (key, val) =>
150
+ key !== 'color' ||
151
+ (val.value?.includes('.') && val.value !== 'inherit') ||
152
+ val.value === 'divider' ||
153
+ val.value.startsWith('#') ||
154
+ val.value.match(/\(.*\)/),
155
+ },
156
+ Link: {
157
+ matcher: (key) => key !== 'color',
158
+ },
159
+ };
160
+ const elementReplacement = {};
161
+
162
+ root
163
+ .find(j.ImportDeclaration, (decl) => {
164
+ const { source: {value} } = decl;
165
+ return value.includes('@availity/element') || value.includes('@availity/mui')
166
+ })
167
+ .forEach((decl) => {
168
+ decl.node.specifiers.forEach((spec) => {
169
+ if (spec.type === 'ImportSpecifier') {
170
+ const name = spec.imported.name;
171
+ if (components.includes(name)) {
172
+ deprecatedElements.push(spec.local.name);
173
+ if (customReplacement[name]) {
174
+ elementReplacement[spec.local.name] = customReplacement[name];
175
+ }
176
+ }
177
+ }
178
+ if (spec.type === 'ImportDefaultSpecifier') {
179
+ const name = decl.node.source.value.split('/').pop();
180
+ if (components.includes(name)) {
181
+ deprecatedElements.push(spec.local.name);
182
+ if (customReplacement[name]) {
183
+ elementReplacement[spec.local.name] = customReplacement[name];
184
+ }
185
+ }
186
+ }
187
+ });
188
+ });
189
+
190
+ root
191
+ .find(j.JSXElement, {
192
+ openingElement: {
193
+ name: {
194
+ name: (name) => {
195
+ return deprecatedElements.includes(name);
196
+ },
197
+ },
198
+ },
199
+ })
200
+ .forEach((el) => {
201
+ const sx = j.objectExpression([]);
202
+ const elementName = el.value?.openingElement?.name?.name;
203
+
204
+ const sxNodes = j(el)
205
+ .find(j.JSXAttribute)
206
+ .filter((path) => path.parent.parent.node === el.node && path.node.name.name === 'sx');
207
+
208
+ const sxNodesArray = sxNodes.nodes() || [];
209
+ const existingSxValue = sxNodesArray[0]?.value?.expression;
210
+
211
+ let spreadElement = null;
212
+ el.node.openingElement.attributes.forEach((attr) => {
213
+ if (attr.type === 'JSXSpreadAttribute') {
214
+ spreadElement = attr;
215
+ }
216
+ });
217
+
218
+ const attrToPrune = ['sx'];
219
+ el.node.openingElement.attributes.forEach((attr) => {
220
+ if (
221
+ attr.type === 'JSXSpreadAttribute' ||
222
+ !attr.value ||
223
+ !systemProps.includes(attr?.name?.name)
224
+ ) {
225
+ return;
226
+ }
227
+ const key = attr?.name?.name;
228
+ const literal = attr?.value;
229
+ const val = literal.type === 'JSXExpressionContainer' ? literal.expression : literal;
230
+ const shouldPrune =
231
+ !elementReplacement[elementName] || elementReplacement[elementName].matcher(key, val);
232
+ if (key && val) {
233
+ if (shouldPrune) {
234
+ sx.properties.push(j.property('init', j.identifier(key), val));
235
+ attrToPrune.push(key);
236
+ }
237
+ }
238
+ });
239
+
240
+ if (sx.properties.length) {
241
+ el.node.openingElement.attributes = el.node.openingElement.attributes.filter(
242
+ (attr) => attr.type !== 'JSXAttribute' || !attrToPrune.includes(attr?.name?.name),
243
+ );
244
+
245
+ let finalSx;
246
+ if (!existingSxValue) {
247
+ finalSx = sx;
248
+ } else if (existingSxValue?.type === 'ObjectExpression') {
249
+ sx.properties.push(...existingSxValue.properties);
250
+ finalSx = sx;
251
+ } else if (existingSxValue?.type === 'ArrayExpression') {
252
+ existingSxValue.elements = [sx, ...existingSxValue.elements];
253
+ finalSx = existingSxValue;
254
+ } else {
255
+ finalSx = j.arrayExpression([
256
+ sx,
257
+ existingSxValue.type === 'Identifier'
258
+ ? j.spreadElement(
259
+ j.conditionalExpression(
260
+ j.callExpression(
261
+ j.memberExpression(j.identifier('Array'), j.identifier('isArray')),
262
+ [existingSxValue],
263
+ ),
264
+ existingSxValue,
265
+ j.arrayExpression([existingSxValue]),
266
+ ),
267
+ )
268
+ : existingSxValue,
269
+ ]);
270
+ }
271
+
272
+ if (spreadElement && spreadElement.argument.type === 'Identifier') {
273
+ if (finalSx.type === 'ObjectExpression') {
274
+ const propSx = j.memberExpression(spreadElement.argument, j.identifier('sx'));
275
+ finalSx = j.arrayExpression([
276
+ finalSx,
277
+ j.spreadElement(
278
+ j.conditionalExpression(
279
+ j.callExpression(
280
+ j.memberExpression(j.identifier('Array'), j.identifier('isArray')),
281
+ [propSx],
282
+ ),
283
+ propSx,
284
+ j.arrayExpression([propSx]),
285
+ ),
286
+ ),
287
+ ]);
288
+ } else if (finalSx.type === 'ArrayExpression') {
289
+ finalSx.elements.push(j.memberExpression(spreadElement.argument, j.identifier('sx')));
290
+ }
291
+ }
292
+
293
+ el.node.openingElement.attributes.push(
294
+ j.jsxAttribute(j.jsxIdentifier('sx'), j.jsxExpressionContainer(finalSx)),
295
+ );
296
+ }
297
+ });
298
+
299
+ return root.toSource(printOptions);
300
+ }
@@ -0,0 +1,34 @@
1
+ import path from 'path';
2
+ import { jscodeshift } from '../../../testUtils';
3
+ import transform from './removeSystemProps';
4
+ import {readFile} from '../../../utils';
5
+
6
+ function read(fileName) {
7
+ return readFile(path.join(__dirname, fileName));
8
+ }
9
+
10
+ describe('@mui/codemod', () => {
11
+ describe('v6.0.0 - removeSystemProps', () => {
12
+ it('transforms props as needed', () => {
13
+ const actual = transform(
14
+ { source: read('./test-cases/system-props.actual.js') },
15
+ { jscodeshift },
16
+ {},
17
+ );
18
+
19
+ const expected = read('./test-cases/system-props.expected.js');
20
+ expect(actual).toBe(expected);
21
+ });
22
+
23
+ it('should be idempotent', () => {
24
+ const actual = transform(
25
+ { source: read('./test-cases/system-props.expected.js') },
26
+ { jscodeshift },
27
+ {},
28
+ );
29
+
30
+ const expected = read('./test-cases/system-props.expected.js');
31
+ expect(actual).toBe(expected);
32
+ });
33
+ });
34
+ });
@@ -0,0 +1,47 @@
1
+ /* eslint-disable no-undef */
2
+ // eslint-disable-next-line @nx/enforce-module-boundaries
3
+ import { Box as Boxxx, Grid as Griddd } from '@availity/element';
4
+ import { Typography } from '@availity/mui-typography';
5
+ import { Typography as Typographyyy } from '@availity/mui-typography';
6
+ import { Stack as Stackkk } from '@availity/mui-layout';
7
+
8
+ <Boxxx typography="body1" />;
9
+ <Boxxx color="palette.main" sx={{ display: 'block' }} />;
10
+
11
+ <Griddd container flexDirection={`column`} />;
12
+
13
+ const sx = { display: 'flex' };
14
+ const ml = 2;
15
+ <Typography color="#fff" mb={5} />;
16
+ <Typography color="hsl(200 30% 30%)" mb={5} />;
17
+ <Typographyyy variant="body1" color="primary.main" ml={ml} sx={sx} />;
18
+ <Typographyyy variant="body1" color="divider" ml={ml} sx={sx} />;
19
+ <Typographyyy variant="body1" color="inherit" ml={ml} sx={sx} />;
20
+ <Typographyyy
21
+ fontSize="xl4"
22
+ lineHeight={1}
23
+ startDecorator={
24
+ <Typographyyy fontSize="lg" textColor="text.secondary">
25
+ $
26
+ </Typographyyy>
27
+ }
28
+ sx={{ alignItems: 'flex-start' }}
29
+ >
30
+ 25
31
+ </Typographyyy>;
32
+ function Copyright(props) {
33
+ return (
34
+ <Typographyyy variant="body2" color="text.secondary" align="center" {...props}>
35
+ {'Copyright © '}
36
+ <Link color="inherit" href="https://mui.com/">
37
+ Your Website
38
+ </Link>{' '}
39
+ {new Date().getFullYear()}
40
+ {'.'}
41
+ </Typographyyy>
42
+ );
43
+ }
44
+
45
+ <Stackkk flex="1" sx={[...(Array.isArray(sx) ? sx : [sx])]} />;
46
+
47
+ <Boxxx typography="body1" sx={foo.bar ? { opacity: 0 } : sx} />;
@@ -0,0 +1,93 @@
1
+ /* eslint-disable no-undef */
2
+ // eslint-disable-next-line @nx/enforce-module-boundaries
3
+ import { Box as Boxxx, Grid as Griddd } from '@availity/element';
4
+ import { Typography } from '@availity/mui-typography';
5
+ import { Typography as Typographyyy } from '@availity/mui-typography';
6
+ import { Stack as Stackkk } from '@availity/mui-layout';
7
+
8
+ <Boxxx sx={{
9
+ typography: "body1"
10
+ }} />;
11
+ <Boxxx
12
+ sx={{
13
+ color: "palette.main",
14
+ display: 'block'
15
+ }} />;
16
+
17
+ <Griddd container sx={{
18
+ flexDirection: `column`
19
+ }} />;
20
+
21
+ const sx = { display: 'flex' };
22
+ const ml = 2;
23
+ <Typography
24
+ sx={{
25
+ color: "#fff",
26
+ mb: 5
27
+ }} />;
28
+ <Typography
29
+ sx={{
30
+ color: "hsl(200 30% 30%)",
31
+ mb: 5
32
+ }} />;
33
+ <Typographyyy
34
+ variant="body1"
35
+ sx={[{
36
+ color: "primary.main",
37
+ ml: ml
38
+ }, ...(Array.isArray(sx) ? sx : [sx])]} />;
39
+ <Typographyyy
40
+ variant="body1"
41
+ sx={[{
42
+ color: "divider",
43
+ ml: ml
44
+ }, ...(Array.isArray(sx) ? sx : [sx])]} />;
45
+ <Typographyyy
46
+ variant="body1"
47
+ color="inherit"
48
+ sx={[{
49
+ ml: ml
50
+ }, ...(Array.isArray(sx) ? sx : [sx])]} />;
51
+ <Typographyyy
52
+ startDecorator={
53
+ <Typographyyy textColor="text.secondary" sx={{
54
+ fontSize: "lg"
55
+ }}>
56
+ $
57
+ </Typographyyy>
58
+ }
59
+ sx={{
60
+ fontSize: "xl4",
61
+ lineHeight: 1,
62
+ alignItems: 'flex-start'
63
+ }}>
64
+ 25
65
+ </Typographyyy>;
66
+ function Copyright(props) {
67
+ return (
68
+ (<Typographyyy
69
+ variant="body2"
70
+ align="center"
71
+ {...props}
72
+ sx={[{
73
+ color: "text.secondary"
74
+ }, ...(Array.isArray(props.sx) ? props.sx : [props.sx])]}>
75
+ {'Copyright © '}
76
+ <Link color="inherit" href="https://mui.com/">
77
+ Your Website
78
+ </Link>{' '}
79
+ {new Date().getFullYear()}
80
+ {'.'}
81
+ </Typographyyy>)
82
+ );
83
+ }
84
+
85
+ <Stackkk
86
+ sx={[{
87
+ flex: "1"
88
+ }, ...(Array.isArray(sx) ? sx : [sx])]} />;
89
+
90
+ <Boxxx
91
+ sx={[{
92
+ typography: "body1"
93
+ }, foo.bar ? { opacity: 0 } : sx]} />;
@@ -0,0 +1,49 @@
1
+ /* eslint-disable no-undef */
2
+ import j from 'jscodeshift';
3
+ import { EOL } from 'os';
4
+ import path from 'path';
5
+ import { readFile } from '../utils';
6
+
7
+ export const jscodeshift = j.withParser('tsx');
8
+
9
+ function read(dirname, fileName) {
10
+ return readFile(path.join(dirname, fileName));
11
+ }
12
+
13
+ export function describeJscodeshiftTransform({ transformName, transform, testCases, dirname }) {
14
+ describe(transformName, () => {
15
+ testCases.forEach((testCase) => {
16
+ it('transforms as needed', () => {
17
+ const actual = transform(
18
+ { source: read(dirname, testCase.actual) },
19
+ { jscodeshift },
20
+ {
21
+ ...testCase.options, printOptions: {
22
+ ...testCase.options?.printOptions,
23
+ lineTerminator: EOL
24
+ }
25
+ });
26
+
27
+ const expected = read(dirname, testCase.expected);
28
+ expect(actual).toBe(expected);
29
+ });
30
+
31
+ it('should be idempotent', () => {
32
+ const actual = transform(
33
+ { source: read(dirname, testCase.expected) },
34
+ { jscodeshift },
35
+ {
36
+ ...testCase.options,
37
+ printOptions: {
38
+ ...testCase.options?.printOptions,
39
+ lineTerminator: EOL,
40
+ }
41
+ }
42
+ );
43
+
44
+ const expected = read(dirname, testCase.expected);
45
+ expect(actual).toBe(expected);
46
+ });
47
+ })
48
+ })
49
+ }
package/src/utils.js ADDED
@@ -0,0 +1,117 @@
1
+ import fs from 'fs';
2
+ import { EOL } from 'os';
3
+
4
+ export function readFile(filePath) {
5
+ const fileContents = fs.readFileSync(filePath, 'utf8').toString();
6
+
7
+ if (EOL !== '\n') {
8
+ return fileContents.replace(/\n/g, EOL);
9
+ }
10
+
11
+ return fileContents;
12
+ }
13
+
14
+ export const getCreateBuildStyle = (j) =>
15
+ function createBuildStyle(key, upperBuildStyle, applyStylesMode) {
16
+ if (applyStylesMode) {
17
+ upperBuildStyle = (styleExpression) =>
18
+ j.objectExpression([
19
+ j.spreadElement(
20
+ j.callExpression(
21
+ j.memberExpression(j.identifier('theme'), j.identifier('applyStyles')),
22
+ [j.stringLiteral(applyStylesMode), styleExpression],
23
+ ),
24
+ ),
25
+ ]);
26
+ }
27
+ return function buildStyle(styleExpression) {
28
+ if (key) {
29
+ if (key.type === 'Identifier' || key.type === 'StringLiteral') {
30
+ return upperBuildStyle(j.objectExpression([j.objectProperty(key, styleExpression)]));
31
+ }
32
+ if (key.type === 'TemplateLiteral' || key.type === 'CallExpression') {
33
+ return upperBuildStyle(
34
+ j.objectExpression([
35
+ {
36
+ ...j.objectProperty(key, styleExpression),
37
+ computed: true,
38
+ },
39
+ ]),
40
+ );
41
+ }
42
+ }
43
+ return upperBuildStyle ? upperBuildStyle(styleExpression) : styleExpression;
44
+ };
45
+ };
46
+
47
+ export const getAppendPaletteModeStyles = (j) => function appendPaletteModeStyles(node, modeStyles) {
48
+ Object.entries(modeStyles).forEach(([mode, objectStyles]) => {
49
+ node.properties.push(
50
+ j.spreadElement(
51
+ j.callExpression(j.memberExpression(j.identifier('theme'), j.identifier('applyStyles')), [
52
+ j.stringLiteral(mode),
53
+ Array.isArray(objectStyles) ? j.objectExpression(objectStyles) : objectStyles,
54
+ ]),
55
+ ),
56
+ );
57
+ });
58
+ };
59
+
60
+ export const getBuildArrowFunctionAST = (j) => function buildArrowFunctionAST(params, body) {
61
+ const destructured = [...params].every((param) => typeof param === 'string');
62
+ return j.arrowFunctionExpression(
63
+ destructured ? [
64
+ j.objectPattern(
65
+ [...params].map((k) => ({
66
+ ...j.objectProperty(j.identifier(k), j.identifier(k)),
67
+ shorthand: true,
68
+ })),
69
+ ),
70
+ ]
71
+ : params,
72
+ body,
73
+ );
74
+ };
75
+
76
+ export function getReturnExpression(node) {
77
+ let body = node.body;
78
+ if (body === 'BlockStatement') {
79
+ body = body.body;
80
+ }
81
+
82
+ if (Array.isArray(body)) {
83
+ return body.find((statement) => statement.type === 'ReturnStatement')?.argument;
84
+ }
85
+ return body;
86
+ }
87
+
88
+ export function getObjectKey(node) {
89
+ let tempNode = { ...node };
90
+ while (tempNode.type === 'UnaryExpression') {
91
+ tempNode = tempNode.argument;
92
+ }
93
+ while (tempNode.type === 'MemberExpression' || tempNode.type === 'OptionMemberExpression') {
94
+ tempNode = tempNode.object;
95
+ }
96
+ return tempNode;
97
+ }
98
+
99
+ export function removeProperty(parentNode, child) {
100
+ if (parentNode) {
101
+ if (parentNode.type === 'ObjectExpression') {
102
+ parentNode.properties = parentNode.properties.filter(
103
+ (prop) => prop !== child && prop.value !== child,
104
+ );
105
+ }
106
+ }
107
+ }
108
+
109
+ export function isThemePaletteMode(node) {
110
+ return (
111
+ node?.type === 'MemberExpression' &&
112
+ node.object.type === 'MemberExpression' &&
113
+ node.object.object.name === 'theme' &&
114
+ node.object.property.name === 'palette' &&
115
+ node.property.name === 'mode'
116
+ );
117
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./jsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "module": "commonjs",
6
+ "types": ["jest", "node", "@testing-library/jest-dom"],
7
+ "allowJs": true
8
+ },
9
+ "include": ["**/*.test.js", "**/*.test.ts", "**/*.test.tsx", "**/*.d.ts"]
10
+ }