All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.CheckMissingReturn Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs. This binary checks for style issues such as incorrect or missing JSDoc usage, and missing goog.require() statements. It does not do more advanced checks such as typechecking.

There is a newer version: v20200830
Show newest version
/*
 * Copyright 2008 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;

import com.google.common.base.Predicate;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphEdge;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.TernaryValue;

/**
 * Checks functions for missing return statements. Return statements are only
 * expected for functions with return type information. Functions with empty
 * bodies are ignored.
 *
 *
 * NOTE(dimvar):
 * Do not convert this pass to use TypeI. The pass is only used with the old type checker.
 * The new type inference checks missing returns on its own.
 */
class CheckMissingReturn implements ScopedCallback {

  static final DiagnosticType MISSING_RETURN_STATEMENT =
      DiagnosticType.warning(
          "JSC_MISSING_RETURN_STATEMENT",
          "Missing return statement. Function expected to return {0}.");

  private final AbstractCompiler compiler;
  private final CodingConvention convention;

  private static final Predicate IS_RETURN = new Predicate() {
    @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.
      return input != null && input.isReturn();
    }
  };

  /* Skips all exception edges and impossible edges. */
  private static final Predicate>
      GOES_THROUGH_TRUE_CONDITION_PREDICATE =
        new Predicate>() {
    @Override
    public boolean apply(DiGraphEdge input) {
      // First skill all exceptions.
      Branch branch = input.getValue();
      if (branch == Branch.ON_EX) {
        return false;
      } else if (branch.isConditional()) {
        Node condition = NodeUtil.getConditionExpression(
            input.getSource().getValue());
        // TODO(user): We CAN make this bit smarter just looking at
        // constants. We DO have a full blown ReverseAbstractInterupter and
        // type system that can evaluate some impressions' boolean value but
        // for now we will keep this pass lightweight.
        if (condition != null) {
          TernaryValue val = NodeUtil.getImpureBooleanValue(condition);
          if (val != TernaryValue.UNKNOWN) {
            return val.toBoolean(true) == (Branch.ON_TRUE == branch);
          }
        }
      }
      return true;
    }
  };

  CheckMissingReturn(AbstractCompiler compiler) {
    this.compiler = compiler;
    this.convention = compiler.getCodingConvention();
  }

  @Override
  public void enterScope(NodeTraversal t) {
    JSType returnType = explicitReturnExpected(t.getScopeRoot());
    if (returnType == null) {
      return;
    }

    if (fastAllPathsReturnCheck(t.getControlFlowGraph())) {
      return;
    }

    CheckPathsBetweenNodes test =
        new CheckPathsBetweenNodes<>(
            t.getControlFlowGraph(),
            t.getControlFlowGraph().getEntry(),
            t.getControlFlowGraph().getImplicitReturn(),
            IS_RETURN, GOES_THROUGH_TRUE_CONDITION_PREDICATE);

    if (!test.allPathsSatisfyPredicate()) {
      compiler.report(
          t.makeError(t.getScopeRoot(), MISSING_RETURN_STATEMENT, returnType.toString()));
    }
  }

  /**
   * Fast check to see if all execution paths contain a return statement.
   * May spuriously report that a return statement is missing.
   *
   * @return true if all paths return, converse not necessarily true
   */
  private boolean fastAllPathsReturnCheck(ControlFlowGraph cfg) {
    for (DiGraphEdge s : cfg.getImplicitReturn().getInEdges()) {
      Node n = s.getSource().getValue();
      // NOTE(dimvar): it is possible to change ControlFlowAnalysis.java, so
      // that the calls that always throw are treated in the same way as THROW
      // in the CFG. Then, we would not need to use the coding convention here.
      if (!n.isReturn() && !convention.isFunctionCallThatAlwaysThrows(n)) {
        return false;
      }
    }
    return true;
  }

  @Override
  public void exitScope(NodeTraversal t) {
  }

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

  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
  }

  /**
   * Determines if the given scope should explicitly return. All functions
   * with non-void or non-unknown return types must have explicit returns.
   *
   * Exception: Constructors which specifically specify a return type are
   * used to allow invocation without requiring the "new" keyword. They
   * have an implicit return type. See unit tests.
   *
   * @return If a return type is expected, returns it. Otherwise, returns null.
   */
  private JSType explicitReturnExpected(Node scope) {
    FunctionType scopeType = JSType.toMaybeFunctionType(scope.getJSType());

    if (scopeType == null) {
      return null;
    }

    if (isEmptyFunction(scope)) {
      return null;
    }

    if (scopeType.isConstructor()) {
      return null;
    }

    JSType returnType = scopeType.getReturnType();

    if (returnType == null) {
      return null;
    }

    if (!isVoidOrUnknown(returnType)) {
      return returnType;
    }

    return null;
  }

  /**
   * @return {@code true} if function represents a JavaScript function
   *     with an empty body
   */
  private static boolean isEmptyFunction(Node function) {
    return function.getChildCount() == 3 &&
           !function.getSecondChild().getNext().hasChildren();
  }

  /**
   * @return {@code true} if returnType is void, unknown, or a union
   *     containing void or unknown
   */
  private boolean isVoidOrUnknown(JSType returnType) {
    final JSType voidType = compiler.getTypeIRegistry().getNativeType(JSTypeNative.VOID_TYPE);
    return voidType.isSubtype(returnType);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy