@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.
- package/CHANGELOG.md +374 -2
- package/README.md +211 -4
- package/package.json +3 -3
- package/util/addComponentsSlots.js +21 -4
- package/util/renameClasses.js +7 -0
- package/v9.0.0/pickers/migrate-text-field-props/index.js +192 -0
- package/v9.0.0/pickers/preset-safe/index.js +18 -2
- package/v9.0.0/pickers/remove-disable-margin/index.js +134 -0
- package/v9.0.0/pickers/remove-enable-accessible-field-dom-structure/index.js +72 -0
- package/v9.0.0/pickers/remove-picker-day-2/index.js +155 -0
- package/v9.0.0/pickers/rename-picker-classes/index.js +126 -0
- package/v9.0.0/pickers/rename-picker-day-2/index.js +105 -0
- package/v9.0.0/pickers/rename-pickers-day/index.js +90 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
54
|
+
return j.objectExpression([...(object.properties ?? []).filter(property => (property.key?.name ?? property.key?.value) !== targetKey), propertyToAdd]);
|
|
38
55
|
};
|
|
39
56
|
|
|
40
57
|
/**
|
package/util/renameClasses.js
CHANGED
|
@@ -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
|
-
|
|
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;
|