@Override
  public CheckLevel level(JSError error) {
    Node node = error.node;
    if (node != null) {
      boolean visitedFunction = false;
      for (Node current = node; current != null; current = current.getParent()) {
        Token type = current.getToken();
        JSDocInfo info = null;

        if (type == Token.FUNCTION) {
          info = NodeUtil.getBestJSDocInfo(current);
          visitedFunction = true;
        } else if (type == Token.SCRIPT) {
          info = current.getJSDocInfo();
        } else if (current.isVar() || current.isAssign()) {
          // There's one edge case we're worried about:
          // if the warning points to an assignment to a function, we
          // want the suppressions on that function to apply.
          // It's OK if we double-count some cases.
          Node rhs = NodeUtil.getRValueOfLValue(current.getFirstChild());
          if (rhs != null) {
            if (rhs.isCast()) {
              rhs = rhs.getFirstChild();
            }

            if (rhs.isFunction() && !visitedFunction) {
              info = NodeUtil.getBestJSDocInfo(current);
            }
          }
        }

        if (info != null) {
          for (String suppressor : info.getSuppressions()) {
            WarningsGuard guard = suppressors.get(suppressor);

            // Some @suppress tags are for other tools, and
            // may not have a warnings guard.
            if (guard != null) {
              CheckLevel newLevel = guard.level(error);
              if (newLevel != null) {
                return newLevel;
              }
            }
          }
        }
      }
    }
    return null;
  }
  /**
   * Extracts all Behaviors from an array recursively. The array must be an array literal whose
   * value is known at compile-time. Entries in the array can be object literals or array literals
   * (of other behaviors). Behavior names must be global, fully qualified names.
   *
   * @see https://github.com/Polymer/polymer/blob/0.8-preview/PRIMER.md#behaviors
   * @return A list of all {@code BehaviorDefinitions} in the array.
   */
  private List<BehaviorDefinition> extractBehaviors(Node behaviorArray) {
    if (behaviorArray == null) {
      return ImmutableList.of();
    }

    if (!behaviorArray.isArrayLit()) {
      compiler.report(JSError.make(behaviorArray, POLYMER_INVALID_BEHAVIOR_ARRAY));
      return ImmutableList.of();
    }

    ImmutableList.Builder<BehaviorDefinition> behaviors = ImmutableList.builder();
    for (Node behaviorName : behaviorArray.children()) {
      if (behaviorName.isObjectLit()) {
        this.switchDollarSignPropsToBrackets(behaviorName);
        this.quoteListenerAndHostAttributeKeys(behaviorName);
        behaviors.add(
            new BehaviorDefinition(
                extractProperties(behaviorName),
                getBehaviorFunctionsToCopy(behaviorName),
                getNonPropertyMembersToCopy(behaviorName),
                !NodeUtil.isInFunction(behaviorName)));
        continue;
      }

      Name behaviorGlobalName = globalNames.getSlot(behaviorName.getQualifiedName());
      boolean isGlobalDeclaration = true;
      if (behaviorGlobalName == null) {
        compiler.report(JSError.make(behaviorName, POLYMER_UNQUALIFIED_BEHAVIOR));
        continue;
      }

      Ref behaviorDeclaration = behaviorGlobalName.getDeclaration();

      // Use any set as a backup declaration, even if it's local.
      if (behaviorDeclaration == null) {
        List<Ref> behaviorRefs = behaviorGlobalName.getRefs();
        for (Ref ref : behaviorRefs) {
          if (ref.isSet()) {
            isGlobalDeclaration = false;
            behaviorDeclaration = ref;
            break;
          }
        }
      }

      if (behaviorDeclaration == null) {
        compiler.report(JSError.make(behaviorName, POLYMER_UNQUALIFIED_BEHAVIOR));
        continue;
      }

      Node behaviorDeclarationNode = behaviorDeclaration.getNode();
      JSDocInfo behaviorInfo = NodeUtil.getBestJSDocInfo(behaviorDeclarationNode);
      if (behaviorInfo == null || !behaviorInfo.isPolymerBehavior()) {
        compiler.report(JSError.make(behaviorDeclarationNode, POLYMER_UNANNOTATED_BEHAVIOR));
      }

      Node behaviorValue = NodeUtil.getRValueOfLValue(behaviorDeclarationNode);

      if (behaviorValue == null) {
        compiler.report(JSError.make(behaviorName, POLYMER_UNQUALIFIED_BEHAVIOR));
      } else if (behaviorValue.isArrayLit()) {
        // Individual behaviors can also be arrays of behaviors. Parse them recursively.
        behaviors.addAll(extractBehaviors(behaviorValue));
      } else if (behaviorValue.isObjectLit()) {
        this.switchDollarSignPropsToBrackets(behaviorValue);
        this.quoteListenerAndHostAttributeKeys(behaviorValue);
        behaviors.add(
            new BehaviorDefinition(
                extractProperties(behaviorValue),
                getBehaviorFunctionsToCopy(behaviorValue),
                getNonPropertyMembersToCopy(behaviorValue),
                isGlobalDeclaration));
      } else {
        compiler.report(JSError.make(behaviorName, POLYMER_UNQUALIFIED_BEHAVIOR));
      }
    }

    return behaviors.build();
  }