@mui/x-codemod 9.0.0-alpha.4 → 9.0.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.
@@ -15,11 +15,27 @@ const addItemToObject = (path, value, object, j) => {
15
15
 
16
16
  // Final case where we have to add the property to the object.
17
17
  if (splittedPath.length === 1) {
18
- const propertyToAdd = j.objectProperty(j.identifier(path), value);
19
18
  if (object === null) {
19
+ const propertyToAdd = j.objectProperty(j.identifier(path), value);
20
20
  return j.objectExpression([propertyToAdd]);
21
21
  }
22
- return j.objectExpression([...(object.properties ?? []).filter(property => property.key.name !== path), propertyToAdd]);
22
+
23
+ // When both the existing and new values are ObjectExpressions, merge their properties
24
+ // (new properties win on key conflicts) instead of replacing the entire object.
25
+ const existingProperty = (object.properties ?? []).find(property => property.key?.name === path || property.key?.value === path);
26
+ if (existingProperty && existingProperty.value.type === 'ObjectExpression' && value.type === 'ObjectExpression') {
27
+ // Spread elements (e.g. `{ ...rest }`) have no `key`, so guard against `undefined`
28
+ // sneaking into the dedup set — otherwise existing spreads would be filtered out.
29
+ const newKeys = new Set(value.properties.map(p => p.key?.name ?? p.key?.value).filter(key => key !== undefined));
30
+ const mergedValue = j.objectExpression([...existingProperty.value.properties.filter(p => {
31
+ const key = p.key?.name ?? p.key?.value;
32
+ return key === undefined || !newKeys.has(key);
33
+ }), ...value.properties]);
34
+ const mergedProperty = j.objectProperty(j.identifier(path), mergedValue);
35
+ return j.objectExpression([...(object.properties ?? []).filter(property => (property.key?.name ?? property.key?.value) !== path), mergedProperty]);
36
+ }
37
+ const propertyToAdd = j.objectProperty(j.identifier(path), value);
38
+ return j.objectExpression([...(object.properties ?? []).filter(property => (property.key?.name ?? property.key?.value) !== path), propertyToAdd]);
23
39
  }
24
40
  const remainingPath = splittedPath.slice(1).join('.');
25
41
  const targetKey = splittedPath[0];
@@ -30,11 +46,12 @@ const addItemToObject = (path, value, object, j) => {
30
46
  }
31
47
 
32
48
  // Look if the object we got already contains the property we have to use.
33
- const correspondingObject = (object.properties ?? []).find(property => property.key.name === targetKey);
49
+ // `property.key` is missing on spread / rest elements, so guard the access.
50
+ const correspondingObject = (object.properties ?? []).find(property => (property.key?.name ?? property.key?.value) === targetKey);
34
51
  const propertyToAdd = j.objectProperty(j.identifier(targetKey),
35
52
  // Here we use recursion to mix the new value with the current one
36
53
  addItemToObject(remainingPath, value, correspondingObject?.value ?? null, j));
37
- return j.objectExpression([...(object.properties ?? []).filter(property => property.key.name !== targetKey), propertyToAdd]);
54
+ return j.objectExpression([...(object.properties ?? []).filter(property => (property.key?.name ?? property.key?.value) !== targetKey), propertyToAdd]);
38
55
  };
39
56
 
40
57
  /**
@@ -44,7 +44,14 @@ function renameClasses(parameters) {
44
44
  localNameToOldClassName[hasAlias ? localName : oldName] = oldName;
45
45
  renamedIdentifiersMap[oldName] = config.newClassName;
46
46
  if (!hasAlias && alreadyAvailableIdentifiersMap.has(config.newClassName)) {
47
+ const importDeclarationCollection = j(path).closest(j.ImportDeclaration);
47
48
  path.prune();
49
+ if (importDeclarationCollection.length > 0) {
50
+ const importDeclaration = importDeclarationCollection.nodes()[0];
51
+ if (importDeclaration.specifiers?.length === 0) {
52
+ importDeclarationCollection.remove();
53
+ }
54
+ }
48
55
  return;
49
56
  }
50
57
  alreadyAvailableIdentifiersMap.add(config.newClassName);
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = transformer;
8
+ exports.testConfig = void 0;
9
+ var _path = _interopRequireDefault(require("path"));
10
+ var _readFile = _interopRequireDefault(require("../../../util/readFile"));
11
+ var _addComponentsSlots = require("../../../util/addComponentsSlots");
12
+ var _removeProps = _interopRequireDefault(require("../../../util/removeProps"));
13
+ // @ts-ignore - JS file without types
14
+
15
+ /**
16
+ * Maps a legacy text-field prop name to the corresponding key inside `slotProps`.
17
+ */
18
+ const PROP_TO_SLOT = {
19
+ InputProps: 'input',
20
+ inputProps: 'htmlInput',
21
+ InputLabelProps: 'inputLabel',
22
+ FormHelperTextProps: 'formHelperText'
23
+ };
24
+ const LEGACY_PROP_NAMES = Object.keys(PROP_TO_SLOT);
25
+ const FIELD_AND_PICKER_NAMES = [
26
+ // Fields
27
+ 'DateField', 'DateTimeField', 'TimeField', 'DateRangeField', 'DateTimeRangeField', 'TimeRangeField', 'MultiInputDateRangeField', 'MultiInputDateTimeRangeField', 'MultiInputTimeRangeField', 'SingleInputDateRangeField', 'SingleInputDateTimeRangeField', 'SingleInputTimeRangeField',
28
+ // Pickers
29
+ 'DatePicker', 'DesktopDatePicker', 'MobileDatePicker', 'StaticDatePicker', 'DateTimePicker', 'DesktopDateTimePicker', 'MobileDateTimePicker', 'StaticDateTimePicker', 'TimePicker', 'DesktopTimePicker', 'MobileTimePicker', 'StaticTimePicker', 'DateRangePicker', 'DesktopDateRangePicker', 'MobileDateRangePicker', 'StaticDateRangePicker', 'DateTimeRangePicker', 'DesktopDateTimeRangePicker', 'MobileDateTimeRangePicker', 'TimeRangePicker', 'DesktopTimeRangePicker', 'MobileTimeRangePicker'];
30
+ const ALL_TARGET_NAMES = [...FIELD_AND_PICKER_NAMES, 'PickersTextField'];
31
+ const getKeyName = key => {
32
+ if (!key) {
33
+ return undefined;
34
+ }
35
+ if (key.type === 'Identifier') {
36
+ return key.name;
37
+ }
38
+ if (key.type === 'Literal' || key.type === 'StringLiteral') {
39
+ return String(key.value);
40
+ }
41
+ return undefined;
42
+ };
43
+ function transformer(file, api, options) {
44
+ const j = api.jscodeshift;
45
+ const root = j(file.source);
46
+ const printOptions = options.printOptions || {
47
+ quote: 'single',
48
+ trailingComma: true
49
+ };
50
+
51
+ // 1. Rewrite legacy props passed directly as JSX attributes on field / picker components.
52
+ root.find(j.JSXElement).filter(elementPath => {
53
+ const nameNode = elementPath.value.openingElement.name;
54
+ return nameNode && nameNode.type === 'JSXIdentifier' && ALL_TARGET_NAMES.includes(nameNode.name);
55
+ }).forEach(elementPath => {
56
+ const nameNode = elementPath.value.openingElement.name;
57
+ // For PickersTextField the new keys live directly on `slotProps`.
58
+ // For every other component they live on a nested `textField.slotProps` object.
59
+ const pathPrefix = nameNode.name === 'PickersTextField' ? '' : 'textField.slotProps.';
60
+ const attributesToTransform = j(elementPath).find(j.JSXAttribute).filter(attribute => {
61
+ const attributeParent = attribute.parentPath.parentPath;
62
+ if (attributeParent.value.type !== 'JSXOpeningElement' || attributeParent.value.name.name !== nameNode.name) {
63
+ return false;
64
+ }
65
+ return LEGACY_PROP_NAMES.includes(attribute.value.name.name);
66
+ });
67
+ attributesToTransform.forEach(attribute => {
68
+ const attributeName = attribute.value.name.name;
69
+ const value = attribute.value.value?.type === 'JSXExpressionContainer' ? attribute.value.value.expression : attribute.value.value || j.booleanLiteral(true);
70
+ (0, _addComponentsSlots.transformNestedProp)(elementPath, 'slotProps', `${pathPrefix}${PROP_TO_SLOT[attributeName]}`, value, j);
71
+ });
72
+ });
73
+
74
+ // Drop the now-orphaned legacy attributes from the targeted components.
75
+ (0, _removeProps.default)({
76
+ root,
77
+ componentNames: ALL_TARGET_NAMES,
78
+ props: LEGACY_PROP_NAMES,
79
+ j
80
+ });
81
+
82
+ // 2. Rewrite legacy props found inside `slotProps={{ field: { ... } }}` and
83
+ // `slotProps={{ textField: { ... } }}` regardless of which component they appear on.
84
+ root.find(j.JSXAttribute, {
85
+ name: {
86
+ name: 'slotProps'
87
+ }
88
+ }).forEach(attrPath => {
89
+ const openingElement = attrPath.parentPath?.parentPath?.value;
90
+ if (!openingElement || openingElement.type !== 'JSXOpeningElement' || openingElement.name?.type !== 'JSXIdentifier' || !FIELD_AND_PICKER_NAMES.includes(openingElement.name.name)) {
91
+ return;
92
+ }
93
+ const attrValue = attrPath.value.value;
94
+ if (!attrValue || attrValue.type !== 'JSXExpressionContainer') {
95
+ return;
96
+ }
97
+ const expression = attrValue.expression;
98
+ if (expression.type !== 'ObjectExpression') {
99
+ return;
100
+ }
101
+ // Collect legacy props found inside `field` / `textField` slot objects.
102
+ // - `textField` legacy props are migrated in-place under `textField.slotProps.<newKey>`.
103
+ // - `field` legacy props cannot be nested under `field.slotProps.textField.slotProps.<newKey>`
104
+ // because the `field` slotProps type does not allow it. Hoist them to the sibling
105
+ // `textField.slotProps.<newKey>` instead.
106
+ const fieldCollected = [];
107
+ expression.properties.forEach(prop => {
108
+ if (prop.type === 'SpreadElement' || prop.type === 'ExperimentalSpreadProperty') {
109
+ console.warn(`[migrate-text-field-props] ${file.path}: encountered a spread inside slotProps; ` + `cannot inspect for legacy text field props. Migrate manually if needed.`);
110
+ return;
111
+ }
112
+ if (prop.type !== 'Property' && prop.type !== 'ObjectProperty') {
113
+ return;
114
+ }
115
+ const keyName = getKeyName(prop.key);
116
+ if (keyName !== 'field' && keyName !== 'textField') {
117
+ return;
118
+ }
119
+ if (prop.value.type !== 'ObjectExpression') {
120
+ console.warn(`[migrate-text-field-props] ${file.path}: \`slotProps.${keyName}\` is set to a ` + `non-literal value (e.g. a variable or a function); cannot migrate legacy ` + `text field props automatically. Migrate manually if needed.`);
121
+ return;
122
+ }
123
+ let target = prop.value;
124
+ const remaining = [];
125
+ const collected = [];
126
+ target.properties.forEach(innerProp => {
127
+ if (innerProp.type !== 'Property' && innerProp.type !== 'ObjectProperty') {
128
+ remaining.push(innerProp);
129
+ return;
130
+ }
131
+ const innerKey = getKeyName(innerProp.key);
132
+ if (innerKey && LEGACY_PROP_NAMES.includes(innerKey)) {
133
+ collected.push({
134
+ newKey: PROP_TO_SLOT[innerKey],
135
+ value: innerProp.value
136
+ });
137
+ return;
138
+ }
139
+ remaining.push(innerProp);
140
+ });
141
+ if (collected.length === 0) {
142
+ return;
143
+ }
144
+ if (keyName === 'field') {
145
+ // Defer: hoist these to the sibling `textField` slot below.
146
+ target.properties = remaining;
147
+ fieldCollected.push(...collected);
148
+ return;
149
+ }
150
+ target.properties = remaining;
151
+ // Use the same recursive merge helper used by `transformNestedProp`.
152
+ collected.forEach(({
153
+ newKey,
154
+ value
155
+ }) => {
156
+ target = (0, _addComponentsSlots.addItemToObject)(`slotProps.${newKey}`, value, target, j);
157
+ });
158
+ prop.value = target;
159
+ });
160
+ if (fieldCollected.length > 0) {
161
+ // Drop `field` if it became empty.
162
+ expression.properties = expression.properties.filter(prop => {
163
+ if (prop.type !== 'Property' && prop.type !== 'ObjectProperty') {
164
+ return true;
165
+ }
166
+ if (getKeyName(prop.key) !== 'field') {
167
+ return true;
168
+ }
169
+ return prop.value.type !== 'ObjectExpression' || prop.value.properties.length > 0;
170
+ });
171
+ // Hoist the collected legacy props to `textField.slotProps.<newKey>`.
172
+ let merged = expression;
173
+ fieldCollected.forEach(({
174
+ newKey,
175
+ value
176
+ }) => {
177
+ merged = (0, _addComponentsSlots.addItemToObject)(`textField.slotProps.${newKey}`, value, merged, j);
178
+ });
179
+ expression.properties = merged.properties;
180
+ }
181
+ });
182
+ return root.toSource(printOptions);
183
+ }
184
+ const testConfig = () => ({
185
+ name: 'migrate-text-field-props',
186
+ specFiles: [{
187
+ name: 'migrate legacy text field props to slotProps',
188
+ actual: (0, _readFile.default)(_path.default.join(__dirname, 'actual.spec.tsx')),
189
+ expected: (0, _readFile.default)(_path.default.join(__dirname, 'expected.spec.tsx'))
190
+ }]
191
+ });
192
+ exports.testConfig = testConfig;
@@ -5,10 +5,26 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.default = transformer;
8
+ exports.testConfig = void 0;
8
9
  var _renameFieldRef = _interopRequireDefault(require("../rename-field-ref"));
10
+ var _removePickerDay = _interopRequireDefault(require("../remove-picker-day-2"));
11
+ var _renamePickerDay = _interopRequireDefault(require("../rename-picker-day-2"));
12
+ var _renamePickersDay = _interopRequireDefault(require("../rename-pickers-day"));
13
+ var _renamePickerClasses = _interopRequireDefault(require("../rename-picker-classes"));
14
+ var _removeDisableMargin = _interopRequireDefault(require("../remove-disable-margin"));
15
+ var _removeEnableAccessibleFieldDomStructure = _interopRequireDefault(require("../remove-enable-accessible-field-dom-structure"));
16
+ var _migrateTextFieldProps = _interopRequireDefault(require("../migrate-text-field-props"));
17
+ // Order matters: removePickerDay2 must run before renamePickerDay2
18
+ // because it looks for `PickerDay2` identifiers in slot objects.
19
+ // If renamePickerDay2 ran first, those identifiers would already be
20
+ // renamed to `PickerDay` and removePickerDay2 would not find them.
21
+ const allModules = [_renameFieldRef.default, _removePickerDay.default, _renamePickerDay.default, _renamePickersDay.default, _renamePickerClasses.default, _removeDisableMargin.default, _removeEnableAccessibleFieldDomStructure.default, _migrateTextFieldProps.default];
9
22
  function transformer(file, api, options) {
10
- [_renameFieldRef.default].forEach(transform => {
23
+ allModules.forEach(transform => {
11
24
  file.source = transform(file, api, options);
12
25
  });
13
26
  return file.source;
14
- }
27
+ }
28
+ const testConfig = exports.testConfig = {
29
+ allModules
30
+ };
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = transformer;
8
+ exports.testConfig = void 0;
9
+ var _path = _interopRequireDefault(require("path"));
10
+ var _readFile = _interopRequireDefault(require("../../../util/readFile"));
11
+ // Components on which disableMargin was a direct prop
12
+ const dayComponentNames = ['PickerDay', 'PickersDay', 'DateRangePickerDay'];
13
+
14
+ /**
15
+ * Returns true if a disableMargin JSX attribute is considered enabled (i.e. `disableMargin` or
16
+ * `disableMargin={true}`). Returns false for `disableMargin={false}`.
17
+ */
18
+ function isDisableMarginEnabled(attr) {
19
+ if (!attr.value) {
20
+ // bare `disableMargin` with no value — equivalent to true
21
+ return true;
22
+ }
23
+ if (attr.value.type === 'JSXExpressionContainer' && attr.value.expression.type === 'BooleanLiteral') {
24
+ return attr.value.expression.value;
25
+ }
26
+ // Any other expression (variable, etc.) — conservatively treat as enabled
27
+ return true;
28
+ }
29
+ const cssVarKey = '--PickerDay-horizontalMargin';
30
+
31
+ /**
32
+ * Merges `'--PickerDay-horizontalMargin': 0` into an existing ObjectExpression used as an `sx` value.
33
+ * Only merges when the expression is a plain object literal.
34
+ * Returns true if the merge succeeded.
35
+ */
36
+ function mergeCssVarIntoSxObject(j, sxExpr) {
37
+ if (sxExpr.type !== 'ObjectExpression') {
38
+ return false;
39
+ }
40
+ // Avoid adding the CSS variable if it's already there
41
+ const alreadyHasVar = sxExpr.properties.some(p => p.type === 'ObjectProperty' && p.key?.value === cssVarKey);
42
+ if (!alreadyHasVar) {
43
+ sxExpr.properties.push(j.objectProperty(j.stringLiteral(cssVarKey), j.numericLiteral(0)));
44
+ }
45
+ return true;
46
+ }
47
+ function transformer(file, api, options) {
48
+ const j = api.jscodeshift;
49
+ const root = j(file.source);
50
+ const printOptions = options.printOptions || {
51
+ quote: 'single',
52
+ trailingComma: true
53
+ };
54
+
55
+ // ─── Case 1: disableMargin directly on a day component JSX element ───────────
56
+ root.find(j.JSXOpeningElement).filter(p => {
57
+ const name = p.node.name;
58
+ return name.type === 'JSXIdentifier' && dayComponentNames.includes(name.name);
59
+ }).forEach(openingElPath => {
60
+ const attrs = openingElPath.node.attributes;
61
+ const dmIndex = attrs.findIndex(a => a.type === 'JSXAttribute' && a.name?.name === 'disableMargin');
62
+ if (dmIndex === -1) {
63
+ return;
64
+ }
65
+ const dmAttr = attrs[dmIndex];
66
+ const enabled = isDisableMarginEnabled(dmAttr);
67
+ attrs.splice(dmIndex, 1);
68
+ if (enabled) {
69
+ const sxIndex = attrs.findIndex(a => a.type === 'JSXAttribute' && a.name?.name === 'sx');
70
+ if (sxIndex === -1) {
71
+ attrs.push(j.jsxAttribute(j.jsxIdentifier('sx'), j.jsxExpressionContainer(j.objectExpression([j.objectProperty(j.stringLiteral(cssVarKey), j.numericLiteral(0))]))));
72
+ } else {
73
+ const sxAttr = attrs[sxIndex];
74
+ if (sxAttr.value?.type === 'JSXExpressionContainer') {
75
+ mergeCssVarIntoSxObject(j, sxAttr.value.expression);
76
+ }
77
+ }
78
+ }
79
+ });
80
+
81
+ // ─── Case 2: disableMargin inside a slotProps.day object ─────────────────────
82
+ root.find(j.JSXAttribute, {
83
+ name: {
84
+ name: 'slotProps'
85
+ }
86
+ }).forEach(slotPropsAttrPath => {
87
+ const container = slotPropsAttrPath.node.value;
88
+ if (container?.type !== 'JSXExpressionContainer') {
89
+ return;
90
+ }
91
+ const slotPropsObj = container.expression;
92
+ if (slotPropsObj.type !== 'ObjectExpression') {
93
+ return;
94
+ }
95
+ slotPropsObj.properties.forEach(prop => {
96
+ if (prop.type !== 'ObjectProperty') {
97
+ return;
98
+ }
99
+ const keyName = prop.key?.name ?? prop.key?.value;
100
+ if (keyName !== 'day') {
101
+ return;
102
+ }
103
+ const dayObj = prop.value;
104
+ if (dayObj.type !== 'ObjectExpression') {
105
+ return;
106
+ }
107
+ const dmIndex = dayObj.properties.findIndex(p => p.type === 'ObjectProperty' && (p.key?.name === 'disableMargin' || p.key?.value === 'disableMargin'));
108
+ if (dmIndex === -1) {
109
+ return;
110
+ }
111
+ const dmProp = dayObj.properties[dmIndex];
112
+ const enabled = dmProp.value.type === 'BooleanLiteral' ? dmProp.value.value : true;
113
+ dayObj.properties.splice(dmIndex, 1);
114
+ if (enabled) {
115
+ const sxIndex = dayObj.properties.findIndex(p => p.type === 'ObjectProperty' && (p.key?.name === 'sx' || p.key?.value === 'sx'));
116
+ if (sxIndex === -1) {
117
+ dayObj.properties.push(j.objectProperty(j.identifier('sx'), j.objectExpression([j.objectProperty(j.stringLiteral(cssVarKey), j.numericLiteral(0))])));
118
+ } else {
119
+ mergeCssVarIntoSxObject(j, dayObj.properties[sxIndex].value);
120
+ }
121
+ }
122
+ });
123
+ });
124
+ return root.toSource(printOptions);
125
+ }
126
+ const testConfig = () => ({
127
+ name: 'remove-disable-margin',
128
+ specFiles: [{
129
+ name: "remove disableMargin prop and replace with sx={{ '--PickerDay-horizontalMargin': 0 }}",
130
+ actual: (0, _readFile.default)(_path.default.join(__dirname, 'actual.spec.tsx')),
131
+ expected: (0, _readFile.default)(_path.default.join(__dirname, 'expected.spec.tsx'))
132
+ }]
133
+ });
134
+ exports.testConfig = testConfig;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = transformer;
8
+ exports.testConfig = void 0;
9
+ var _path = _interopRequireDefault(require("path"));
10
+ var _removeProps = _interopRequireDefault(require("../../../util/removeProps"));
11
+ var _readFile = _interopRequireDefault(require("../../../util/readFile"));
12
+ const componentNames = ['DateField', 'DateTimeField', 'TimeField', 'DateRangeField', 'DateTimeRangeField', 'TimeRangeField', 'MultiInputDateRangeField', 'MultiInputDateTimeRangeField', 'MultiInputTimeRangeField', 'SingleInputDateRangeField', 'SingleInputDateTimeRangeField', 'SingleInputTimeRangeField', 'DatePicker', 'DesktopDatePicker', 'MobileDatePicker', 'StaticDatePicker', 'DateTimePicker', 'DesktopDateTimePicker', 'MobileDateTimePicker', 'StaticDateTimePicker', 'TimePicker', 'DesktopTimePicker', 'MobileTimePicker', 'StaticTimePicker', 'DateRangePicker', 'DesktopDateRangePicker', 'MobileDateRangePicker', 'StaticDateRangePicker', 'DateTimeRangePicker', 'DesktopDateTimeRangePicker', 'MobileDateTimeRangePicker', 'TimeRangePicker', 'DesktopTimeRangePicker', 'MobileTimeRangePicker'];
13
+ function transformer(file, api, options) {
14
+ const j = api.jscodeshift;
15
+ const root = j(file.source);
16
+ const printOptions = options.printOptions || {
17
+ quote: 'single',
18
+ trailingComma: true
19
+ };
20
+ (0, _removeProps.default)({
21
+ root,
22
+ j,
23
+ componentNames,
24
+ props: ['enableAccessibleFieldDOMStructure']
25
+ });
26
+
27
+ // Also remove enableAccessibleFieldDOMStructure from slotProps.field
28
+ componentNames.forEach(componentName => {
29
+ root.find(j.JSXElement, {
30
+ openingElement: {
31
+ name: {
32
+ name: componentName
33
+ }
34
+ }
35
+ }).forEach(elementPath => {
36
+ j(elementPath).find(j.JSXAttribute, {
37
+ name: {
38
+ name: 'slotProps'
39
+ }
40
+ }).forEach(slotPropsAttr => {
41
+ const value = slotPropsAttr.node.value;
42
+ if (value?.type !== 'JSXExpressionContainer' || value.expression.type !== 'ObjectExpression') {
43
+ return;
44
+ }
45
+ const fieldProp = value.expression.properties.find(p => p.key?.name === 'field' || p.key?.value === 'field');
46
+ if (!fieldProp || fieldProp.value.type !== 'ObjectExpression') {
47
+ return;
48
+ }
49
+ fieldProp.value.properties = fieldProp.value.properties.filter(p => p.key?.name !== 'enableAccessibleFieldDOMStructure' && p.key?.value !== 'enableAccessibleFieldDOMStructure');
50
+
51
+ // If field object is now empty, remove it from slotProps
52
+ if (fieldProp.value.properties.length === 0) {
53
+ value.expression.properties = value.expression.properties.filter(p => p !== fieldProp);
54
+ // If slotProps object is now empty, remove the whole attribute
55
+ if (value.expression.properties.length === 0) {
56
+ j(slotPropsAttr).remove();
57
+ }
58
+ }
59
+ });
60
+ });
61
+ });
62
+ return root.toSource(printOptions);
63
+ }
64
+ const testConfig = () => ({
65
+ name: 'remove-enable-accessible-field-dom-structure',
66
+ specFiles: [{
67
+ name: 'remove enableAccessibleFieldDOMStructure prop',
68
+ actual: (0, _readFile.default)(_path.default.join(__dirname, 'actual.spec.tsx')),
69
+ expected: (0, _readFile.default)(_path.default.join(__dirname, 'expected.spec.tsx'))
70
+ }]
71
+ });
72
+ exports.testConfig = testConfig;
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.default = transformer;
8
+ exports.testConfig = void 0;
9
+ var _path = _interopRequireDefault(require("path"));
10
+ var _readFile = _interopRequireDefault(require("../../../util/readFile"));
11
+ const pickerNames = ['DatePicker', 'DesktopDatePicker', 'MobileDatePicker', 'StaticDatePicker', 'DateTimePicker', 'DesktopDateTimePicker', 'MobileDateTimePicker', 'StaticDateTimePicker', 'TimePicker', 'DesktopTimePicker', 'MobileTimePicker', 'StaticTimePicker', 'DateRangePicker', 'DesktopDateRangePicker', 'MobileDateRangePicker', 'StaticDateRangePicker', 'DateTimeRangePicker', 'DesktopDateTimeRangePicker', 'MobileDateTimeRangePicker', 'TimeRangePicker', 'DesktopTimeRangePicker', 'MobileTimeRangePicker', 'DateCalendar', 'DayCalendar'];
12
+ function transformer(file, api, options) {
13
+ const j = api.jscodeshift;
14
+ const root = j(file.source);
15
+ const printOptions = options.printOptions || {
16
+ quote: 'single',
17
+ trailingComma: true
18
+ };
19
+ const dayComponents = ['PickerDay2', 'DateRangePickerDay2'];
20
+ root.find(j.ObjectExpression).forEach(objectExpressionPath => {
21
+ const properties = objectExpressionPath.node.properties;
22
+ const dayPropIndex = properties.findIndex(prop => {
23
+ const keyName = prop.key?.name || prop.key?.value;
24
+ if (keyName !== 'day') {
25
+ return false;
26
+ }
27
+ const val = prop.value;
28
+ return val.type === 'Identifier' && dayComponents.includes(val.name);
29
+ });
30
+ if (dayPropIndex !== -1) {
31
+ properties.splice(dayPropIndex, 1);
32
+ if (properties.length === 0) {
33
+ // Case 1: inline slots attribute — remove the entire slots prop
34
+ const attrCollection = j(objectExpressionPath).closest(j.JSXAttribute, {
35
+ name: {
36
+ name: 'slots'
37
+ }
38
+ });
39
+ if (attrCollection.length > 0) {
40
+ const openingElement = j(objectExpressionPath).closest(j.JSXOpeningElement);
41
+ if (openingElement.length > 0) {
42
+ const nameNode = openingElement.get().value.name;
43
+ const componentName = nameNode.type === 'JSXIdentifier' ? nameNode.name : null;
44
+ if (componentName && pickerNames.includes(componentName)) {
45
+ attrCollection.remove();
46
+ }
47
+ }
48
+ }
49
+
50
+ // Case 2: object is in a variable — remove slots={varName} on picker components,
51
+ // then remove the variable declaration if it becomes unreferenced
52
+ const varDeclarator = j(objectExpressionPath).closest(j.VariableDeclarator);
53
+ if (varDeclarator.length > 0) {
54
+ const varId = varDeclarator.get().value.id;
55
+ const varName = varId?.type === 'Identifier' ? varId.name : null;
56
+ if (varName) {
57
+ const slotsOnPickers = root.find(j.JSXAttribute, {
58
+ name: {
59
+ name: 'slots'
60
+ }
61
+ }).filter(attrPath => {
62
+ const val = attrPath.node.value;
63
+ return val?.type === 'JSXExpressionContainer' && val.expression.type === 'Identifier' && val.expression.name === varName;
64
+ }).filter(attrPath => {
65
+ const openingElement = j(attrPath).closest(j.JSXOpeningElement);
66
+ if (openingElement.length === 0) {
67
+ return false;
68
+ }
69
+ const nameNode = openingElement.get().value.name;
70
+ const componentName = nameNode.type === 'JSXIdentifier' ? nameNode.name : null;
71
+ return componentName !== null && pickerNames.includes(componentName);
72
+ });
73
+
74
+ // Check (before any mutation) whether varName has references outside
75
+ // of picker slots and the declaration binding itself.
76
+ const hasNonPickerRefs = root.find(j.Identifier, {
77
+ name: varName
78
+ }).filter(idPath => {
79
+ // find(j.Identifier) also matches JSXIdentifier (attribute names) — skip them
80
+ if (idPath.node.type !== 'Identifier') {
81
+ return false;
82
+ }
83
+ // Exclude the declaration binding (the `id` in `const varName = ...`)
84
+ if (idPath.name === 'id' && idPath.parent.value.type === 'VariableDeclarator') {
85
+ return false;
86
+ }
87
+ // Exclude references inside picker slots attributes (those are being removed)
88
+ const closestAttr = j(idPath).closest(j.JSXAttribute, {
89
+ name: {
90
+ name: 'slots'
91
+ }
92
+ });
93
+ if (closestAttr.length > 0) {
94
+ const openingElement = j(closestAttr.get()).closest(j.JSXOpeningElement);
95
+ if (openingElement.length > 0) {
96
+ const nameNode = openingElement.get().value.name;
97
+ const componentName = nameNode.type === 'JSXIdentifier' ? nameNode.name : null;
98
+ if (componentName && pickerNames.includes(componentName)) {
99
+ return false;
100
+ }
101
+ }
102
+ }
103
+ return true;
104
+ }).length > 0;
105
+ slotsOnPickers.remove();
106
+ if (!hasNonPickerRefs) {
107
+ varDeclarator.closest(j.VariableDeclaration).remove();
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ });
114
+
115
+ // Remove imports if no longer used
116
+ dayComponents.forEach(componentName => {
117
+ const usages = root.find(j.Identifier, {
118
+ name: componentName
119
+ }).filter(componentPath => {
120
+ const {
121
+ parent
122
+ } = componentPath;
123
+ if (parent.value.type === 'ImportSpecifier') {
124
+ return false;
125
+ }
126
+ return true;
127
+ });
128
+ if (usages.length === 0) {
129
+ root.find(j.ImportSpecifier, {
130
+ imported: {
131
+ name: componentName
132
+ }
133
+ }).filter(componentPath => {
134
+ const importDeclaration = componentPath.parentPath.parentPath.value;
135
+ return importDeclaration.source.value.startsWith('@mui/x-date-pickers') || importDeclaration.source.value.startsWith('@mui/x-date-pickers-pro');
136
+ }).forEach(componentPath => {
137
+ const importDeclaration = componentPath.parentPath.parentPath;
138
+ j(componentPath).remove();
139
+ if (importDeclaration.value.specifiers.length === 0) {
140
+ j(importDeclaration).remove();
141
+ }
142
+ });
143
+ }
144
+ });
145
+ return root.toSource(printOptions);
146
+ }
147
+ const testConfig = () => ({
148
+ name: 'remove-picker-day-2',
149
+ specFiles: [{
150
+ name: 'remove PickerDay2 and DateRangePickerDay2 from slots',
151
+ actual: (0, _readFile.default)(_path.default.join(__dirname, 'actual.spec.tsx')),
152
+ expected: (0, _readFile.default)(_path.default.join(__dirname, 'expected.spec.tsx'))
153
+ }]
154
+ });
155
+ exports.testConfig = testConfig;