in packages/codemods/src/codeMods/utilities/helpers/propHelpers.ts [28:202]
export function renamePropInSpread(
element: JsxOpeningElement | JsxSelfClosingElement,
toRename: string,
replacementName: string,
changeValueMap?: ValueMap<string>,
replacementValue?: string,
): Result<string, NoOp> {
/* Step 1: Figure out which attribute contains the spread prop. */
const allAttributes = element.getAttributes();
allAttributes.forEach(attribute => {
if (!element.getAttribute(replacementName)) {
if (attribute.getKind() === SyntaxKind.JsxSpreadAttribute) {
const firstIdentifier = attribute.getFirstChildByKind(SyntaxKind.Identifier);
const propertyAccess = attribute.getFirstChildByKind(SyntaxKind.PropertyAccessExpression);
if (!attribute || (!firstIdentifier && !propertyAccess)) {
return Err({ logs: ['Invalid spread prop. Could not access internal identifiers successfully.'] });
}
/* SPREADISIDENTIFIER tells us whether we should look at an Identifier or a P.A.E. node. */
const spreadIsIdentifier = firstIdentifier !== undefined;
/* Verify this attribute contains the name of our desired prop and DOES NOT
contain the name of the replacement name. */
if (spreadContains(toRename, replacementName, spreadIsIdentifier, attribute)) {
/* Step 3: Create names for your new potential objects. */
const componentName = element.getFirstChildByKind(SyntaxKind.Identifier)?.getText();
const propSpreadName = spreadIsIdentifier ? firstIdentifier!.getText() : propertyAccess!.getText();
let newSpreadName = `__mig${componentName}Props`;
const newMapName = '__migEnumMap';
/* Metadata in case we need to reacquire the current element (AST modification). */
let newJSXFlag = false;
const elementType = element.getKind();
/* Step 4: Get the container node who is the direct child of the closest SyntaxKind.Block.
If we need to insert auxiliary variables, we'll insert them before this node. */
let blockContainer = getBlockContainer(element);
if (blockContainer === undefined) {
/* In the case there was NO code block, the following function will create one for you.
If successful, initialize the newJSXFlag because you'll need to reaquire the spread element. */
const containerMaybe = createBlockContainer(element);
if (containerMaybe.something) {
blockContainer = containerMaybe.value;
newJSXFlag = true;
} else {
return Err({ logs: ['Attempted to create a new block around prop failed.'] });
}
}
/* Step 5: Get the parent of BLOCKCONTAINER so that we can insert our own variable statements. */
const parentContainer = blockContainer!.getParentIfKind(SyntaxKind.Block);
if (parentContainer === undefined) {
return Err({ logs: ['Unable to get parent container from block.'] });
}
/* Step 6: Get the index of BLOCKCONTAINER within PARENTCONTAINER that we'll use to insert our variables. */
const insertIndex = blockContainer!.getChildIndex();
if (insertIndex === undefined) {
return Err({ logs: ['unable to find child index'] });
}
/* Step 7: Look to see if the prop we're looking for exists already. Manually
deconstruct it from the spread prop if not. */
const variableStatementWithSpreadProp = parentContainer.getVariableStatement(
(declaration: VariableStatement) => {
const elem = declaration.getFirstDescendantByKind(SyntaxKind.VariableDeclaration);
return (
elem !== undefined &&
(elem.getText().includes(`...${propSpreadName}`) ||
elem.getText().includes(`...${newSpreadName}`) ||
elem.getText().includes(propSpreadName))
);
},
);
/* If a variable statement with the spread prop in question exists, try and use it. */
if (variableStatementWithSpreadProp) {
/* Depending on where this spread prop was in the statement, there are different things we need to do. */
switch (locateSpreadPropInStatement(variableStatementWithSpreadProp, propSpreadName, newSpreadName)) {
case SpreadPropInStatement.PropLeft: {
/* If the spread prop was on the left with no '...' in front of it, try and create a new variable
statement, deconstructing this spread prop. */
if (!propAlreadyExists(parentContainer, toRename)) {
parentContainer.insertVariableStatement(
insertIndex,
createDeconstructedProp(newSpreadName, toRename, propSpreadName),
);
} else {
newSpreadName = propSpreadName;
}
break;
}
case SpreadPropInStatement.SpreadPropLeft:
case SpreadPropInStatement.PropRight: {
/* If the prop is on the right or it's in spread form on the left,
we can try and insert out TORENAME prop into its detruction. */
newSpreadName = propSpreadName;
if (!propAlreadyExists(parentContainer, toRename)) {
tryInsertExistingDecomposedProp(toRename, variableStatementWithSpreadProp);
}
break;
}
case SpreadPropInStatement.NotFound:
default: {
/* We shoulnd't make any intermediate steps if we coulnd't find the spread prop. */
newSpreadName = propSpreadName;
break;
}
}
} else {
/* If a viable spread prop wasn't found, make a new one. */
if (!propAlreadyExists(parentContainer, toRename)) {
parentContainer.insertVariableStatement(
insertIndex,
createDeconstructedProp(newSpreadName, toRename, propSpreadName),
);
} else {
newSpreadName = propSpreadName;
}
}
/* Step 8: Declare other auxiliary objects if necessary (i.e. value mapping case). */
if (changeValueMap && !parentContainer.getVariableStatement(newMapName)) {
parentContainer.insertVariableStatement(
insertIndex,
createAuxiliaryVariable(VariableDeclarationKind.Const, newMapName, JSON.stringify(changeValueMap)),
);
}
/* Step 9: Handle any last auxiliary cases (i.e. component rendered with no body). */
let attrToRename = attribute;
/* attribute is an iterator variable in the forEach function. */
if (newJSXFlag) {
const newSpreadProp = Maybe(blockContainer!.getFirstDescendantByKind(SyntaxKind.JsxSpreadAttribute));
const newJSXElem = Maybe(
blockContainer!.getFirstDescendantByKind(
elementType as SyntaxKind.JsxOpeningElement | SyntaxKind.JsxSelfClosingElement,
),
);
if (newSpreadProp.something && newJSXElem.something) {
attrToRename = newSpreadProp.value;
element = newJSXElem.value;
}
}
/* Step 10: Replace the props in the component with your new ones! */
if (
element.getAttributes().some(attr => {
if (attr.getKind() === SyntaxKind.JsxSpreadAttribute) {
const child = attr.getChildAtIndex(2);
if (child) {
return child.getText() === newSpreadName;
}
}
return false;
})
) {
/* Check to make sure that you're not renaming attrToRename to an extant prop. */
if (attrToRename.getChildAtIndex(2)!.getText() !== newSpreadName) {
attrToRename.remove(); // Replace old spread name.
}
} else {
attrToRename.replaceWithText(`{...${newSpreadName}}`); // Replace old spread name.
}
element.addAttribute({
name: replacementName,
initializer: changeValueMap
? `{${newMapName}[${toRename}]}`
: replacementValue
? `{${replacementValue}}`
: `{${toRename}}`,
}); // Add the updated prop name and set its value.
}
}
}
});
return Ok('Successfully modified the given prop in the spread operator');
}