Package com.google.javascript.jscomp.lint

Source Code of com.google.javascript.jscomp.lint.CheckNullableReturn

/*
* 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;
  }

  public static boolean hasReturnDeclaredNullable(Node n) {
    return n.isBlock() && n.hasChildren() && isReturnTypeNullable(n.getParent())
        && !hasSingleThrow(n);
  }

  @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 (hasReturnDeclaredNullable(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.
   */
  public 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);
  }
}
TOP

Related Classes of com.google.javascript.jscomp.lint.CheckNullableReturn

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.