export function renamePropInSpread()

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');
}