private void matchedShadowOf()

in errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/ShadowUsageCheck.java [140:387]


    private void matchedShadowOf(MethodInvocationTree shadowOfCall, VisitorState state) {
      ExpressionTree shadowOfArg = shadowOfCall.getArguments().get(0);
      Type shadowOfArgType = getExpressionType(shadowOfArg);

      Tree parent = state.getPath().getParentPath().getLeaf();
      CompilationUnitTree compilationUnit = state.getPath().getCompilationUnit();

      // pointless (ShadowX) shadowOf(x)? drop it.
      if (parent.getKind() == Kind.TYPE_CAST) {
        parent = removeCastIfUnnecessary((JCTypeCast) parent, state);
      }

      switch (parent.getKind()) {
        case VARIABLE: // ShadowType shadowType = shadowOf(type);
          {
            // shadow is being assigned to a variable; don't do that!
            JCVariableDecl variableDecl = (JCVariableDecl) parent;
            String oldVarName = variableDecl.getName().toString();

            // since it's being declared here, no danger of a collision on this var name...
            knownLocalVars.remove(oldVarName);

            String newVarName = pickNewName(shadowOfArg, oldVarName, this);
            varRemapping.put(getSymbol(variableDecl), newVarName);

            // ... but be careful not to collide with it later.
            knownLocalVars.add(newVarName);

            // replace shadow variable declaration with shadowed type and name
            if (!newVarName.equals(shadowOfArg.toString())) {
              Type shadowedType = getUpperBound(shadowOfArgType, state);
              String shadowedTypeName =
                  SuggestedFixes.prettyType(state, possibleFixes.fixBuilder, shadowedType);
              String newAssignment =
                  shadowedTypeName + " " + newVarName + " = " + shadowOfArg + ";";

              // avoid overlapping replacements:
              if (shadowOfArg instanceof JCMethodInvocation) {
                JCExpression jcExpression = ((JCMethodInvocation) shadowOfArg).meth;
                if (jcExpression instanceof JCFieldAccess) {
                  possibleFixes.removeFixFor(((JCFieldAccess) jcExpression).selected);
                }
              }

              possibleFixes.fixByReplacing(parent, newAssignment);
            } else {
              possibleFixes.fixByDeleting(parent);
            }

            // replace shadow variable reference with `nonShadowInstance` or
            // `shadowOf(nonShadowInstance)` as appropriate.
            new TreePathScanner<Void, Void>() {
              @Override
              public Void visitIdentifier(IdentifierTree identifierTreeX, Void aVoid) {
                JCIdent identifierTree = (JCIdent) identifierTreeX;

                Symbol symbol = getSymbol(identifierTree);
                if (variableDecl.sym.equals(symbol) && !isLeftSideOfAssignment(identifierTree)) {
                  TreePath idPath = TreePath.getPath(compilationUnit, identifierTree);
                  Tree parent = idPath.getParentPath().getLeaf();
                  boolean callDirectlyOnFramework = shouldCallDirectlyOnFramework(idPath);

                  JCTree replaceNode;
                  if (parent instanceof JCFieldAccess && !callDirectlyOnFramework) {
                    JCFieldAccess fieldAccess = (JCFieldAccess) parent;
                    JCMethodInvocation newShadowOfCall =
                        createSyntheticShadowAccess(shadowOfCall, newVarName, symbol, state);

                    replaceFieldSelected(fieldAccess, newShadowOfCall, state);
                    replaceNode = newShadowOfCall;
                  } else {
                    identifierTree.name = state.getName(newVarName);
                    identifierTree.sym.name = state.getName(newVarName);
                    replaceNode = identifierTree;
                  }

                  String replaceWith =
                      callDirectlyOnFramework
                          ? newVarName
                          : shadowOfCall.getMethodSelect() + "(" + newVarName + ")";

                  possibleFixes.put(
                      replaceNode, possibleFixes.new ReplacementFix(identifierTree, replaceWith));
                }
                return super.visitIdentifier(identifierTree, aVoid);
              }

              private boolean isLeftSideOfAssignment(IdentifierTree identifierTree) {
                Tree parent = getCurrentPath().getParentPath().getLeaf();
                if (parent instanceof AssignmentTree) {
                  return identifierTree.equals(((AssignmentTree) parent).getVariable());
                }
                return false;
              }
            }.scan(compilationUnit, null);
          }
          break;

        case ASSIGNMENT: // this.shadowType = shadowOf(type);
          {
            // shadow is being assigned to a field or variable; don't do that!
            JCAssign assignment = (JCAssign) parent;
            Symbol fieldSymbol = getSymbol(assignment.lhs);

            String oldFieldName = assignment.lhs.toString();
            String remappedName = varRemapping.get(fieldSymbol);

            // since it's being declared here, no danger of a collision on this var name...
            knownFields.remove(oldFieldName);

            String newFieldName =
                remappedName == null ? pickNewName(shadowOfArg, oldFieldName, this) : remappedName;
            varRemapping.put(fieldSymbol, newFieldName);

            // ... but be careful not to collide with it later.
            knownLocalVars.add(newFieldName);

            // local variable declaration should have been handled above in the VARIABLE case;
            // just strip shadowOf() and assign it to the de-shadowed variable.
            if (fieldSymbol.getKind() == ElementKind.LOCAL_VARIABLE) {
              if (newFieldName.equals(shadowOfArg.toString())) {
                // assigning to self, don't bother
                TreePath assignmentPath = TreePath.getPath(compilationUnit, assignment);
                Tree assignmentParent = assignmentPath.getParentPath().getLeaf();
                if (assignmentParent instanceof ExpressionStatementTree) {
                  possibleFixes.fixByDeleting(assignmentParent);
                }
              } else {
                possibleFixes.fixByReplacing(
                    assignment, newFieldName + " = " + shadowOfArg.toString());
              }
              break;
            }

            Symbol shadowOfArgSym = getSymbol(shadowOfArg);
            ElementKind shadowOfArgDomicile =
                shadowOfArgSym == null
                    ? ElementKind.OTHER // it's probably an expression, not a var...
                    : shadowOfArgSym.getKind();
            boolean namesAreSame = newFieldName.equals(shadowOfArg.toString());

            boolean useExistingField =
                shadowOfArgDomicile == ElementKind.FIELD
                    && namesAreSame
                    && !isMethodParam(ASTHelpers.getSymbol(shadowOfArg), state.getPath());

            if (useExistingField) {
              fixVar(fieldSymbol, state, possibleFixes).delete();

              ExpressionStatementTree enclosingNode =
                  ASTHelpers.findEnclosingNode(
                      TreePath.getPath(compilationUnit, assignment), ExpressionStatementTree.class);
              if (enclosingNode != null) {
                possibleFixes.fixByDeleting(enclosingNode);
              }
            } else {
              Type shadowedType = getUpperBound(shadowOfArgType, state);
              String shadowedTypeName =
                  SuggestedFixes.prettyType(state, possibleFixes.fixBuilder, shadowedType);
              fixVar(fieldSymbol, state, possibleFixes)
                  .setName(newFieldName)
                  .setTypeName(shadowedTypeName)
                  .setRenameUses(false)
                  .modify();

              String thisStr = "";
              if (assignment.lhs.toString().startsWith("this.")
                  || (shadowOfArgDomicile == ElementKind.LOCAL_VARIABLE && namesAreSame)) {
                thisStr = "this.";
              }

              possibleFixes.fixByReplacing(
                  assignment, thisStr + newFieldName + " = " + shadowOfArg);
            }

            TreePath containingBlock = findParentOfKind(state, Kind.BLOCK);
            if (containingBlock != null) {
              // replace shadow field reference with `nonShadowInstance` or
              // `shadowOf(nonShadowInstance)` as appropriate.
              new TreePathScanner<Void, Void>() {
                @Override
                public Void visitMemberSelect(MemberSelectTree memberSelectTree, Void aVoid) {
                  maybeReplaceFieldRef(memberSelectTree.getExpression());

                  return super.visitMemberSelect(memberSelectTree, aVoid);
                }

                @Override
                public Void visitIdentifier(IdentifierTree identifierTree, Void aVoid) {
                  maybeReplaceFieldRef(identifierTree);

                  return super.visitIdentifier(identifierTree, aVoid);
                }

                private void maybeReplaceFieldRef(ExpressionTree subject) {
                  Symbol symbol = getSymbol(subject);
                  if (symbol != null && symbol.getKind() == ElementKind.FIELD) {
                    TreePath subjectPath = TreePath.getPath(compilationUnit, subject);

                    if (symbol.equals(fieldSymbol) && isPartOfMethodInvocation(subjectPath)) {
                      String fieldRef =
                          subject.toString().startsWith("this.")
                              ? "this." + newFieldName
                              : newFieldName;

                      JCTree replaceNode = (JCTree) subject;
                      Tree container = subjectPath.getParentPath().getLeaf();
                      if (container instanceof JCFieldAccess) {
                        JCFieldAccess fieldAccess = (JCFieldAccess) container;
                        JCMethodInvocation newShadowOfCall =
                            createSyntheticShadowAccess(shadowOfCall, newFieldName, symbol, state);
                        replaceFieldSelected(fieldAccess, newShadowOfCall, state);
                        replaceNode = newShadowOfCall;
                      }

                      String replaceWith =
                          shouldCallDirectlyOnFramework(subjectPath)
                              ? fieldRef
                              : shadowOfCall.getMethodSelect() + "(" + fieldRef + ")";
                      possibleFixes.put(
                          replaceNode, possibleFixes.new ReplacementFix(subject, replaceWith));
                    }
                  }
                }
              }.scan(compilationUnit, null);
            }
          }
          break;

        case MEMBER_SELECT: // shadowOf(type).method();
          {
            if (shouldCallDirectlyOnFramework(state.getPath())) {
              if (!isInSyntheticShadowAccess(state)) {
                possibleFixes.fixByReplacing(shadowOfCall, shadowOfArg.toString());
              }
            }
          }
          break;

        case TYPE_CAST:
          System.out.println("WARN: not sure what to do with " + parent.getKind() + ": " + parent);
          break;

        default:
          throw new RuntimeException(
              "not sure what to do with " + parent.getKind() + ": " + parent);
      }
    }