code: public static boolean isLValue(Node n) {
      switch (n.getToken()) {
        case NAME:
        case GETPROP:
        case GETELEM:
          break;
        default:
          return false;
      }
  
      Node parent = n.getParent();
      if (parent == null) {
        return false;
      }
  
      switch (parent.getToken()) {
        case IMPORT_SPEC:
          return parent.getLastChild() == n;
        case VAR:
        case LET:
        case CONST:
        case REST:
        case PARAM_LIST:
        case IMPORT:
        case INC:
        case DEC:
        case CATCH:
          return true;
        case CLASS:
        case FUNCTION:
        case DEFAULT_VALUE:
        case FOR:
        case FOR_IN:
        case FOR_OF:
        case FOR_AWAIT_OF:
          return parent.getFirstChild() == n;
        case ARRAY_PATTERN:
        case STRING_KEY:
        case COMPUTED_PROP:
          return isLhsByDestructuring(n);
        default:
          return NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n;
      }
    }
docs: Determines whether this node is used as an L-value. Notice that sometimes
  names are used as both L-values and R-values.
  
  <p>We treat "var x;" and "let x;" as an L-value because it's syntactically similar to
  "var x = undefined", even though it's technically not an L-value. But it kind of makes
  sense if you treat it as "assignment to 'undefined' at the top of the scope".
  
  @param n The node
  @return True if n is an L-value.