UNPKG

@mui/codemod

Version:
326 lines (322 loc) 14.9 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = transformer; var _movePropIntoSlots = _interopRequireDefault(require("../utils/movePropIntoSlots")); var _movePropIntoSlotProps = _interopRequireDefault(require("../utils/movePropIntoSlotProps")); var _replaceComponentsWithSlots = _interopRequireDefault(require("../utils/replaceComponentsWithSlots")); var _findComponentJSX = _interopRequireDefault(require("../../util/findComponentJSX")); var _findComponentDefaultProps = _interopRequireDefault(require("../../util/findComponentDefaultProps")); var _assignObject = _interopRequireDefault(require("../../util/assignObject")); var _appendAttribute = _interopRequireDefault(require("../../util/appendAttribute")); function getImportedNames(j, root, packageName, componentName) { const importNames = new Set(); root.find(j.ImportDeclaration).filter(path => path.node.source.value.match(new RegExp(`^${packageName}(/${componentName})?$`))).forEach(path => { path.node.specifiers.forEach(specifier => { if (specifier.type === 'ImportDefaultSpecifier') { importNames.add(specifier.local.name); } if (specifier.type === 'ImportSpecifier' && specifier.imported.name === componentName) { importNames.add(specifier.local.name); } }); }); return importNames; } function getAttributeIndex(element, attributeName) { return element.openingElement.attributes.findIndex(attr => attr.type === 'JSXAttribute' && attr.name.name === attributeName); } function upsertSlotPropsAttribute(j, element, callback) { const slotPropsIndex = getAttributeIndex(element, 'slotProps'); if (slotPropsIndex === -1) { const slotPropsExpression = j.objectExpression([]); callback(slotPropsExpression); (0, _appendAttribute.default)(j, { target: element, attributeName: 'slotProps', expression: slotPropsExpression }); return; } const slotPropsAttribute = element.openingElement.attributes[slotPropsIndex]; const slotPropsExpression = slotPropsAttribute.value.expression; if (slotPropsExpression.type === 'ObjectExpression') { callback(slotPropsExpression); return; } const nextSlotPropsExpression = j.objectExpression([j.spreadElement(slotPropsExpression)]); callback(nextSlotPropsExpression); slotPropsAttribute.value.expression = nextSlotPropsExpression; } function moveJsxPropIntoSlotProps(j, element, propName, slotName) { const propIndex = getAttributeIndex(element, propName); if (propIndex === -1) { return; } const removedAttribute = element.openingElement.attributes.splice(propIndex, 1)[0]; const removedValue = removedAttribute.value.type === 'StringLiteral' ? j.literal(removedAttribute.value.value) : removedAttribute.value.expression; upsertSlotPropsAttribute(j, element, slotPropsExpression => { const existingSlotIndex = slotPropsExpression.properties.findIndex(property => property.type !== 'SpreadElement' && property.key?.name === slotName); if (existingSlotIndex === -1) { slotPropsExpression.properties.push(j.objectProperty(j.identifier(slotName), removedValue)); return; } const existingSlot = slotPropsExpression.properties[existingSlotIndex].value; slotPropsExpression.properties[existingSlotIndex].value = j.objectExpression([j.spreadElement(removedValue), j.spreadElement(existingSlot)]); }); } function ensureParamsSlotPropsSpread(j, element, paramsName) { const hasParamsSpread = element.openingElement.attributes.some(attribute => attribute.type === 'JSXSpreadAttribute' && attribute.argument.type === 'Identifier' && attribute.argument.name === paramsName); if (!hasParamsSpread) { return; } upsertSlotPropsAttribute(j, element, slotPropsExpression => { const hasSlotPropsSpread = slotPropsExpression.properties.some(property => property.type === 'SpreadElement' && property.argument.type === 'MemberExpression' && property.argument.object.type === 'Identifier' && property.argument.object.name === paramsName && property.argument.property.type === 'Identifier' && property.argument.property.name === 'slotProps'); if (!hasSlotPropsSpread) { slotPropsExpression.properties.unshift(j.spreadElement(j.memberExpression(j.identifier(paramsName), j.identifier('slotProps')))); } }); } function replaceRenderInputParamsMembers(j, callbackRoot, paramsName) { const slotPropByDeprecatedName = { InputProps: 'input', inputProps: 'htmlInput', InputLabelProps: 'inputLabel' }; callbackRoot.find(j.MemberExpression).filter(path => path.node.object.type === 'Identifier' && path.node.object.name === paramsName && path.node.property.type === 'Identifier' && slotPropByDeprecatedName[path.node.property.name]).replaceWith(path => j.memberExpression(j.memberExpression(j.identifier(paramsName), j.identifier('slotProps')), j.identifier(slotPropByDeprecatedName[path.node.property.name]))); } function transformRenderInput(j, root, options) { const packageName = options.packageName || '@mui/material'; const textFieldNames = getImportedNames(j, root, packageName, 'TextField'); (0, _findComponentJSX.default)(j, { root, packageName: options.packageName, componentName: 'Autocomplete' }, elementPath => { const renderInputIndex = getAttributeIndex(elementPath.node, 'renderInput'); if (renderInputIndex === -1) { return; } const renderInputAttribute = elementPath.node.openingElement.attributes[renderInputIndex]; const renderInputExpression = renderInputAttribute.value?.expression; if (!renderInputExpression || !['ArrowFunctionExpression', 'FunctionExpression'].includes(renderInputExpression.type) || renderInputExpression.params.length === 0 || renderInputExpression.params[0].type !== 'Identifier') { return; } const paramsName = renderInputExpression.params[0].name; const callbackRoot = j(renderInputExpression.body); replaceRenderInputParamsMembers(j, callbackRoot, paramsName); textFieldNames.forEach(textFieldName => { const textFieldElements = []; if (renderInputExpression.body.type === 'JSXElement' && renderInputExpression.body.openingElement.name.type === 'JSXIdentifier' && renderInputExpression.body.openingElement.name.name === textFieldName) { textFieldElements.push(renderInputExpression.body); } callbackRoot.find(j.JSXElement).filter(textFieldPath => textFieldPath.node.openingElement.name.type === 'JSXIdentifier' && textFieldPath.node.openingElement.name.name === textFieldName).forEach(textFieldPath => { textFieldElements.push(textFieldPath.node); }); textFieldElements.forEach(element => { moveJsxPropIntoSlotProps(j, element, 'InputProps', 'input'); moveJsxPropIntoSlotProps(j, element, 'inputProps', 'htmlInput'); moveJsxPropIntoSlotProps(j, element, 'InputLabelProps', 'inputLabel'); moveJsxPropIntoSlotProps(j, element, 'SelectProps', 'select'); moveJsxPropIntoSlotProps(j, element, 'FormHelperTextProps', 'formHelperText'); if (getAttributeIndex(element, 'slotProps') !== -1) { ensureParamsSlotPropsSpread(j, element, paramsName); } }); }); }); } function isNonComputedKey(j, path) { const parent = path.parent.node; return (j.ObjectProperty.check(parent) || j.Property.check(parent)) && parent.key === path.node && !parent.computed; } function renameIdentifiersInScope(j, scopePath, oldName, newName) { const bindingScope = scopePath.scope.lookup(oldName); if (!bindingScope) { return; } j(bindingScope.path).find(j.Identifier, { name: oldName }).filter(path => { if (isNonComputedKey(j, path)) { return false; } return path.scope.lookup(oldName) === bindingScope; }).replaceWith(() => j.identifier(newName)); } function renameRenderTagsCallback(j, callbackPath) { const getTagPropsParam = callbackPath.node.params[1]; if (getTagPropsParam?.type === 'Identifier' && getTagPropsParam.name === 'getTagProps') { renameIdentifiersInScope(j, callbackPath, 'getTagProps', 'getItemProps'); } } function renameRenderTagsProp(j, propertyPath) { if (propertyPath.node.key.type === 'Identifier') { propertyPath.node.key.name = 'renderValue'; } if (propertyPath.node.value.type === 'ArrowFunctionExpression' || propertyPath.node.value.type === 'FunctionExpression') { renameRenderTagsCallback(j, propertyPath.get('value')); } } function renameUseAutocompleteReturnMembers(j, root) { const renamedMembers = new Map([['getTagProps', 'getItemProps'], ['focusedTag', 'focusedItem']]); root.find(j.VariableDeclarator).filter(path => { const { id, init } = path.node; return id.type === 'ObjectPattern' && init?.type === 'CallExpression' && init.callee.type === 'Identifier' && init.callee.name === 'useAutocomplete'; }).forEach(path => { path.node.id.properties.forEach(property => { if (property.type !== 'ObjectProperty' || property.key.type !== 'Identifier') { return; } const nextName = renamedMembers.get(property.key.name); if (!nextName) { return; } const isShorthand = property.shorthand === true; const localName = property.value.type === 'Identifier' ? property.value.name : null; property.key.name = nextName; if (isShorthand && localName) { renameIdentifiersInScope(j, path, localName, nextName); property.shorthand = true; } }); }); } /** * @param {import('jscodeshift').FileInfo} file * @param {import('jscodeshift').API} api */ function transformer(file, api, options) { const j = api.jscodeshift; const root = j(file.source); const printOptions = options.printOptions; (0, _movePropIntoSlots.default)(j, { root, packageName: options.packageName, componentName: 'Autocomplete', propName: 'PaperComponent', slotName: 'paper' }); (0, _movePropIntoSlots.default)(j, { root, packageName: options.packageName, componentName: 'Autocomplete', propName: 'PopperComponent', slotName: 'popper' }); (0, _movePropIntoSlotProps.default)(j, { root, packageName: options.packageName, componentName: 'Autocomplete', propName: 'ListboxProps', slotName: 'listbox' }); (0, _movePropIntoSlotProps.default)(j, { root, packageName: options.packageName, componentName: 'Autocomplete', propName: 'ChipProps', slotName: 'chip' }); (0, _replaceComponentsWithSlots.default)(j, { root, packageName: options.packageName, componentName: 'Autocomplete' }); transformRenderInput(j, root, options); // Move ListboxComponent JSX prop into slotProps.listbox.component (0, _findComponentJSX.default)(j, { root, packageName: options.packageName, componentName: 'Autocomplete' }, elementPath => { const element = elementPath.node; element.openingElement.attributes.forEach((attribute, index) => { if (attribute.type !== 'JSXAttribute' || attribute.name.name !== 'renderTags') { return; } attribute.name.name = 'renderValue'; if (attribute.value?.type === 'JSXExpressionContainer' && (attribute.value.expression.type === 'ArrowFunctionExpression' || attribute.value.expression.type === 'FunctionExpression')) { renameRenderTagsCallback(j, elementPath.get('openingElement', 'attributes', index, 'value', 'expression')); } }); const propIndex = element.openingElement.attributes.findIndex(attr => attr.type === 'JSXAttribute' && attr.name.name === 'ListboxComponent'); if (propIndex !== -1) { const removedValue = element.openingElement.attributes.splice(propIndex, 1)[0].value.expression; let hasSlotProps = false; element.openingElement.attributes.forEach(attr => { if (attr.name?.name === 'slotProps') { hasSlotProps = true; const slots = attr.value.expression; const slotIndex = slots.properties.findIndex(prop => prop?.key?.name === 'listbox'); if (slotIndex === -1) { (0, _assignObject.default)(j, { target: attr, key: 'listbox', expression: j.objectExpression([j.objectProperty(j.identifier('component'), removedValue)]) }); } else { const slotPropsSlotValue = slots.properties.splice(slotIndex, 1)[0].value; (0, _assignObject.default)(j, { target: attr, key: 'listbox', expression: j.objectExpression([j.objectProperty(j.identifier('component'), removedValue), j.spreadElement(slotPropsSlotValue)]) }); } } }); if (!hasSlotProps) { (0, _appendAttribute.default)(j, { target: element, attributeName: 'slotProps', expression: j.objectExpression([j.objectProperty(j.identifier('listbox'), j.objectExpression([j.objectProperty(j.identifier('component'), removedValue)]))]) }); } } }); // Move ListboxComponent default prop into slotProps.listbox.component const defaultPropsPathCollection = (0, _findComponentDefaultProps.default)(j, { root, packageName: options.packageName, componentName: 'Autocomplete' }); defaultPropsPathCollection.find(j.ObjectProperty, { key: { name: 'ListboxComponent' } }).forEach(path => { const removedValue = path.value.value; const defaultProps = path.parent.value; let hasSlotProps = false; defaultProps.properties.forEach(property => { if (property.key?.name === 'slotProps') { hasSlotProps = true; const slotIndex = property.value.properties.findIndex(prop => prop?.key?.name === 'listbox'); if (slotIndex === -1) { property.value.properties.push(j.objectProperty(j.identifier('listbox'), j.objectExpression([j.objectProperty(j.identifier('component'), removedValue)]))); } else { const slotPropsSlotValue = property.value.properties.splice(slotIndex, 1)[0].value; property.value.properties.push(j.objectProperty(j.identifier('listbox'), j.objectExpression([j.objectProperty(j.identifier('component'), removedValue), j.spreadElement(slotPropsSlotValue)]))); } } }); if (!hasSlotProps) { defaultProps.properties.push(j.objectProperty(j.identifier('slotProps'), j.objectExpression([j.objectProperty(j.identifier('listbox'), j.objectExpression([j.objectProperty(j.identifier('component'), removedValue)]))]))); } path.prune(); }); defaultPropsPathCollection.find(j.ObjectProperty, { key: { name: 'renderTags' } }).forEach(path => { renameRenderTagsProp(j, path); }); renameUseAutocompleteReturnMembers(j, root); return root.toSource(printOptions); }