/*
 * Copyright 2014 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.javascript.jscomp.lint;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.javascript.jscomp.AbstractCompiler;
import com.google.javascript.jscomp.CheckPathsBetweenNodes;
import com.google.javascript.jscomp.ControlFlowGraph;
import com.google.javascript.jscomp.DiagnosticType;
import com.google.javascript.jscomp.HotSwapCompilerPass;
import com.google.javascript.jscomp.NodeTraversal;
import com.google.javascript.jscomp.NodeUtil;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphEdge;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.JSType;

/**
 * Checks when a function is annotated as returning {SomeType} (nullable)
 * but actually always returns {!SomeType}, i.e. never returns null.
 *
 */
public class CheckNullableReturn implements HotSwapCompilerPass, NodeTraversal.Callback {
  final AbstractCompiler compiler;

  public static final DiagnosticType NULLABLE_RETURN =
      DiagnosticType.disabled(
          "JSC_NULLABLE_RETURN",
          "This function''s return type is nullable, but it always returns a "
          + "non-null value. Consider making the return type non-nullable.");

  public static final DiagnosticType NULLABLE_RETURN_WITH_NAME =
      DiagnosticType.disabled(
          "JSC_NULLABLE_RETURN_WITH_NAME",
          "The return type of the function \"{0}\" is nullable, but it always "
          + "returns a non-null value. Consider making the return type "
          + "non-nullable.");

  private static final Predicate<Node> NULLABLE_RETURN_PREDICATE =
      new Predicate<Node>() {
    @Override
    public boolean apply(Node input) {
      // Check for null because the control flow graph's implicit return node is
      // represented by null, so this value might be input.
      if (input == null || !input.isReturn()) {
        return false;
      }
      Node returnValue = input.getFirstChild();
      return returnValue != null && isNullable(returnValue);
    }
  };

  public CheckNullableReturn(AbstractCompiler compiler) {
    this.compiler = compiler;
  }

  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    // Do the checks when 'n' is the block node and 'parent' is the function
    // node, so that getControlFlowGraph will return the graph inside
    // the function, rather than the graph of the enclosing scope.
    if (n.isBlock() && n.hasChildren() && isReturnTypeNullable(parent)
        && !hasSingleThrow(n) && !canReturnNull(t.getControlFlowGraph())) {
      String fnName = NodeUtil.getNearestFunctionName(parent);
      if (fnName != null && !fnName.isEmpty()) {
        compiler.report(t.makeError(parent, NULLABLE_RETURN_WITH_NAME, fnName));
      } else {
        compiler.report(t.makeError(parent, NULLABLE_RETURN));
      }
    }
  }

  /**
   * @return whether the blockNode contains only a single "throw" child node.
   */
  private static boolean hasSingleThrow(Node blockNode) {
    if (blockNode.getChildCount() == 1
        && blockNode.getFirstChild().getType() == Token.THROW) {
      // Functions consisting of a single "throw FOO" can be actually abstract,
      // so do not check their return type nullability.
      return true;
    }

    return false;
  }

  /**
   * @return True if n is a function node which is explicitly annotated
   * as returning a nullable type, other than {?}.
   */
  private static boolean isReturnTypeNullable(Node n) {
    if (n == null) {
      return false;
    }
    if (!n.isFunction()) {
      return false;
    }
    JSType returnType = n.getJSType().toMaybeFunctionType().getReturnType();
    if (returnType == null
        || returnType.isUnknownType() || !returnType.isNullable()) {
      return false;
    }
    JSDocInfo info = NodeUtil.getBestJSDocInfo(n);
    return info != null && info.hasReturnType();
  }

  /**
   * @return True if the given ControlFlowGraph could return null.
   */
  private static boolean canReturnNull(ControlFlowGraph<Node> graph) {
    CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch> test =
        new CheckPathsBetweenNodes<Node, ControlFlowGraph.Branch>(
            graph,
            graph.getEntry(),
            graph.getImplicitReturn(),
            NULLABLE_RETURN_PREDICATE,
            Predicates.<DiGraphEdge<Node, ControlFlowGraph.Branch>>alwaysTrue());

    return test.somePathsSatisfyPredicate();
  }

  /**
   * @return True if the node represents a nullable value. Essentially, this
   *     is just n.getJSType().isNullable(), but for purposes of this pass,
   *     the expression {@code x || null} is considered nullable even if
   *     x is always truthy. This often happens with expressions like
   *     {@code arr[i] || null}: The compiler doesn't know that arr[i] can
   *     be undefined.
   */
  private static boolean isNullable(Node n) {
    return n.getJSType().isNullable()
        || (n.isOr() && n.getLastChild().isNull());
  }

  @Override
  public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
    return true;
  }

  @Override
  public void process(Node externs, Node root) {
    NodeTraversal.traverse(compiler, root, this);
  }

  @Override
  public void hotSwapScript(Node scriptRoot, Node originalRoot) {
    NodeTraversal.traverse(compiler, originalRoot, this);
  }
}

@@ -2809,24 +2809,201 @@
 piler;%0A  %7D%0A%0A
+  public static boolean hasReturnDeclaredNullable(Node n) %7B%0A    return n.isBlock() && n.hasChildren() && isReturnTypeNullable(n.getParent())%0A        && !hasSingleThrow(n);%0A  %7D%0A%0A
   @Override%0A
@@ -3288,54 +3288,25 @@
 if (
-n.isBlock() && n.hasChildren() && isReturnType
+hasReturnDeclared
 Null
@@ -3310,22 +3310,17 @@
 ullable(
-parent
+n
 )%0A      
@@ -3320,38 +3320,16 @@
 %0A       
- 
-&& !hasSingleThrow(n)
  && !can
@@ -4763,38 +4763,37 @@
  null.%0A   */%0A  p
-rivate
+ublic
  static boolean 

Allow CheckNullableReturn check to be run from outside the pass. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=78219496