Пример #1
0
 /** Checks for illegal declarations. */
 private void checkDeclaration(NodeTraversal t, Node n) {
   if ("eval".equals(n.getString())) {
     t.report(n, EVAL_DECLARATION);
   } else if ("arguments".equals(n.getString())) {
     t.report(n, ARGUMENTS_DECLARATION);
   }
 }
  /**
   * Expand a jQuery.expandedEach call
   *
   * <p>Expanded jQuery.expandedEach calls will replace the GETELEM nodes of a property assignment
   * with GETPROP nodes to allow for renaming.
   */
  private void maybeExpandJqueryEachCall(NodeTraversal t, Node n) {
    Node objectToLoopOver = n.getChildAtIndex(1);

    if (objectToLoopOver == null) {
      return;
    }

    Node callbackFunction = objectToLoopOver.getNext();
    if (callbackFunction == null || !callbackFunction.isFunction()) {
      return;
    }

    // Run the peephole optimizations on the first argument to handle
    // cases like ("a " + "b").split(" ")
    peepholePasses.process(null, n.getChildAtIndex(1));

    // Create a reference tree
    Node nClone = n.cloneTree();

    objectToLoopOver = nClone.getChildAtIndex(1);

    // Check to see if the first argument is something we recognize and can
    // expand.
    if (!objectToLoopOver.isObjectLit()
        && !(objectToLoopOver.isArrayLit() && isArrayLitValidForExpansion(objectToLoopOver))) {
      t.report(n, JQUERY_UNABLE_TO_EXPAND_INVALID_LIT_ERROR, (String) null);
      return;
    }

    // Find all references to the callback function arguments
    List<Node> keyNodeReferences = new ArrayList<>();
    List<Node> valueNodeReferences = new ArrayList<>();

    new NodeTraversal(
            compiler,
            new FindCallbackArgumentReferences(
                callbackFunction,
                keyNodeReferences,
                valueNodeReferences,
                objectToLoopOver.isArrayLit()))
        .traverseInnerNode(
            NodeUtil.getFunctionBody(callbackFunction), callbackFunction, t.getScope());

    if (keyNodeReferences.isEmpty()) {
      // We didn't do anything useful ...
      t.report(n, JQUERY_USELESS_EACH_EXPANSION, (String) null);
      return;
    }

    Node fncBlock =
        tryExpandJqueryEachCall(
            t, nClone, callbackFunction, keyNodeReferences, valueNodeReferences);

    if (fncBlock != null && fncBlock.hasChildren()) {
      replaceOriginalJqueryEachCall(n, fncBlock);
    } else {
      // We didn't do anything useful ...
      t.report(n, JQUERY_USELESS_EACH_EXPANSION, (String) null);
    }
  }
 @Override
 public void visit(NodeTraversal t, Node n, Node parent) {
   switch (n.getType()) {
     case Token.CALL:
       Node callee = n.getFirstChild();
       if (callee.matchesQualifiedName("goog.module")) {
         if (currentModule == null) {
           currentModule = n;
         } else {
           t.report(n, MULTIPLE_MODULES_IN_FILE);
         }
       } else if (callee.matchesQualifiedName("goog.provide")) {
         t.report(n, MODULE_AND_PROVIDES);
       } else if (callee.matchesQualifiedName("goog.require")) {
         checkRequireCall(t, n, parent);
       }
       break;
     case Token.THIS:
       if (t.inGlobalHoistScope()) {
         t.report(n, GOOG_MODULE_REFERENCES_THIS);
       }
       break;
     case Token.THROW:
       if (t.inGlobalHoistScope()) {
         t.report(n, GOOG_MODULE_USES_THROW);
       }
       break;
     case Token.SCRIPT:
       currentModule = null;
       break;
   }
 }
Пример #4
0
 /** Checks that object literal keys or class method names are valid. */
 private static void checkObjectLiteralOrClass(NodeTraversal t, Node n) {
   Set<String> getters = new HashSet<>();
   Set<String> setters = new HashSet<>();
   for (Node key = n.getFirstChild(); key != null; key = key.getNext()) {
     if (!key.isSetterDef()) {
       // normal property and getter cases
       if (!getters.add(key.getString())) {
         if (n.isClassMembers()) {
           t.report(key, DUPLICATE_CLASS_METHODS);
         } else {
           t.report(key, DUPLICATE_OBJECT_KEY);
         }
       }
     }
     if (!key.isGetterDef()) {
       // normal property and setter cases
       if (!setters.add(key.getString())) {
         if (n.isClassMembers()) {
           t.report(key, DUPLICATE_CLASS_METHODS);
         } else {
           t.report(key, DUPLICATE_OBJECT_KEY);
         }
       }
     }
   }
 }
Пример #5
0
 /** Checks that an assignment is not to the "arguments" object. */
 private static void checkAssignment(NodeTraversal t, Node n) {
   if (n.getFirstChild().isName()) {
     if ("arguments".equals(n.getFirstChild().getString())) {
       t.report(n, ARGUMENTS_ASSIGNMENT);
     } else if ("eval".equals(n.getFirstChild().getString())) {
       // Note that assignment to eval is already illegal because any use of
       // that name is illegal.
       t.report(n, EVAL_ASSIGNMENT);
     }
   }
 }
Пример #6
0
 /** Reports a warning for with statements. */
 private static void checkWith(NodeTraversal t, Node n) {
   JSDocInfo info = n.getJSDocInfo();
   boolean allowWith = info != null && info.getSuppressions().contains("with");
   if (!allowWith) {
     t.report(n, USE_OF_WITH);
   }
 }
  public void testReport() {
    final List<JSError> errors = new ArrayList<JSError>();

    Compiler compiler =
        new Compiler(
            new BasicErrorManager() {

              @Override
              public void report(CheckLevel level, JSError error) {
                errors.add(error);
              }

              @Override
              public void println(CheckLevel level, JSError error) {}

              @Override
              protected void printSummary() {}
            });
    compiler.initCompilerOptionsIfTesting();

    NodeTraversal t = new NodeTraversal(compiler, null);
    DiagnosticType dt = DiagnosticType.warning("FOO", "{0}, {1} - {2}");

    t.report(null, dt, "Foo", "Bar", "Hello");
    assertEquals(1, errors.size());
    assertEquals("Foo, Bar - Hello", errors.get(0).description);
  }
 private static boolean reportIfNonObject(NodeTraversal t, Node n, DiagnosticType diagnosticType) {
   if (n.isAdd() || !NodeUtil.mayBeObect(n)) {
     t.report(n.getParent(), diagnosticType);
     return true;
   }
   return false;
 }
Пример #9
0
 /** Checks that the arguments.callee is not used. */
 private void checkGetProp(NodeTraversal t, Node n) {
   Node target = n.getFirstChild();
   Node prop = n.getLastChild();
   if (prop.getString().equals("callee")) {
     if (target.isName() && target.getString().equals("arguments")) {
       t.report(n, ARGUMENTS_CALLEE_FORBIDDEN);
     }
   } else if (prop.getString().equals("caller")) {
     if (target.isName() && target.getString().equals("arguments")) {
       t.report(n, ARGUMENTS_CALLER_FORBIDDEN);
     } else if (isFunctionType(target)) {
       t.report(n, FUNCTION_CALLER_FORBIDDEN);
     }
   } else if (prop.getString().equals("arguments") && isFunctionType(target)) {
     t.report(n, FUNCTION_ARGUMENTS_PROP_FORBIDDEN);
   }
 }
Пример #10
0
 /** Checks that variables, functions, and arguments are not deleted. */
 private static void checkDelete(NodeTraversal t, Node n) {
   if (n.getFirstChild().isName()) {
     Var v = t.getScope().getVar(n.getFirstChild().getString());
     if (v != null) {
       t.report(n, DELETE_VARIABLE);
     }
   }
 }
 @Override
 public void visit(NodeTraversal t, Node n, Node parent) {
   if (n.isSwitch()) {
     Set<String> cases = new HashSet<>();
     for (Node curr = n.getSecondChild(); curr != null; curr = curr.getNext()) {
       String source = compiler.toSource(curr.getFirstChild());
       if (!cases.add(source)) {
         t.report(curr, DUPLICATE_CASE);
       }
     }
   }
 }
 private void checkRequireCall(NodeTraversal t, Node callNode, Node parent) {
   Preconditions.checkState(callNode.isCall());
   switch (parent.getType()) {
     case Token.EXPR_RESULT:
       return;
     case Token.GETPROP:
       if (parent.getParent().isName()) {
         checkRequireCall(t, callNode, parent.getParent());
         return;
       }
       break;
     case Token.NAME:
     case Token.OBJECT_PATTERN:
       {
         Node declaration = parent.getParent();
         if (declaration.getChildCount() != 1) {
           t.report(declaration, ONE_REQUIRE_PER_DECLARATION);
         }
         return;
       }
   }
   t.report(callNode, REQUIRE_NOT_AT_TOP_LEVEL);
 }
    /**
     * Eliminates an assignment if the lvalue is: - A field name that's a strip name - A qualified
     * name that begins with a strip type
     *
     * @param t The traversal
     * @param n An ASSIGN node
     * @param parent {@code n}'s parent
     */
    void maybeEliminateAssignmentByLvalueName(NodeTraversal t, Node n, Node parent) {
      // ASSIGN
      //   lvalue
      //   rvalue
      Node lvalue = n.getFirstChild();
      if (nameEndsWithFieldNameToStrip(lvalue) || qualifiedNameBeginsWithStripType(lvalue)) {

        // Limit to EXPR_RESULT because it is not
        // safe to eliminate assignment in complex expressions,
        // e.g. in ((x = 7) + 8)
        if (NodeUtil.isExpressionNode(parent)) {
          Node gramps = parent.getParent();
          replaceWithEmpty(parent, gramps);
          compiler.reportCodeChange();
        } else {
          t.report(n, STRIP_ASSIGNMENT_ERROR, lvalue.getQualifiedName());
        }
      }
    }
    /**
     * Determines whether the given node helps to define a strip type. For example,
     * goog.inherits(stripType, Object) would be such a call.
     *
     * <p>Also reports an error if a non-strip type inherits from a strip type.
     *
     * @param t The current traversal
     * @param callNode The CALL node
     */
    private boolean actsOnStripType(NodeTraversal t, Node callNode) {
      SubclassRelationship classes =
          compiler.getCodingConvention().getClassesDefinedByCall(callNode);
      if (classes != null) {
        // It's okay to strip a type that inherits from a non-stripped type
        // e.g. goog.inherits(goog.debug.Logger, Object)
        if (qualifiedNameBeginsWithStripType(classes.subclassName)) {
          return true;
        }

        // report an error if a non-strip type inherits from a
        // strip type.
        if (qualifiedNameBeginsWithStripType(classes.superclassName)) {
          t.report(
              callNode, STRIP_TYPE_INHERIT_ERROR, classes.subclassName, classes.superclassName);
        }
      }

      return false;
    }
Пример #15
0
 /** Checks that the function is used legally. */
 private static void checkFunctionUse(NodeTraversal t, Node n) {
   if (NodeUtil.isFunctionDeclaration(n) && !NodeUtil.isHoistedFunctionDeclaration(n)) {
     t.report(n, BAD_FUNCTION_DECLARATION);
   }
 }
    @Override
    public void visit(NodeTraversal traversal, Node node, Node parent) {

      // Fix types in JSDoc.
      JSDocInfo doc = node.getJSDocInfo();
      if (doc != null) {
        fixJsdoc(traversal.getScope(), doc);
      }

      // Find qualified names that match static calls
      if (node.isQualifiedName()) {
        String name = node.getQualifiedName();
        Polyfill polyfill = null;

        if (polyfills.statics.containsKey(name)) {
          polyfill = polyfills.statics.get(name);
        }

        if (polyfill != null) {
          // Check the scope to make sure it's a global name.
          if (isRootInScope(node, traversal) || NodeUtil.isVarOrSimpleAssignLhs(node, parent)) {
            return;
          }

          if (!languageOutIsAtLeast(polyfill.polyfillVersion)) {
            traversal.report(
                node,
                INSUFFICIENT_OUTPUT_VERSION_ERROR,
                name,
                compiler.getOptions().getLanguageOut().toString(),
                polyfill.polyfillVersion.toString());
          }
          if (!languageOutIsAtLeast(polyfill.nativeVersion)) {

            if (!polyfill.installer.isEmpty()) {
              // Note: add the installer *before* replacing the node!
              addInstaller(node, polyfill.installer);
            }

            if (!polyfill.rewrite.isEmpty()) {
              changed = true;
              Node replacement = NodeUtil.newQName(compiler, polyfill.rewrite);
              replacement.useSourceInfoIfMissingFromForTree(node);
              parent.replaceChild(node, replacement);
            }
          }

          // TODO(sdh): consider warning if language_in is too low?  it's not really any
          // harm, and we can't do it consistently for the prototype methods, so maybe
          // it's not worth doing here, either.

          return; // isGetProp (below) overlaps, so just bail out now
        }
      }

      // Add any requires that *might* match method calls (but don't rewrite anything)
      if (node.isGetProp() && node.getLastChild().isString()) {
        for (Polyfill polyfill : polyfills.methods.get(node.getLastChild().getString())) {
          if (!languageOutIsAtLeast(polyfill.nativeVersion) && !polyfill.installer.isEmpty()) {
            // Check if this is a global function.
            if (!isStaticFunction(node, traversal)) {
              addInstaller(node, polyfill.installer);
            }
          }
        }
      }
    }
  private Node tryExpandJqueryEachCall(
      NodeTraversal t, Node n, Node callbackFunction, List<Node> keyNodes, List<Node> valueNodes) {

    Node callTarget = n.getFirstChild();
    Node objectToLoopOver = callTarget.getNext();

    // New block to contain the expanded statements
    Node fncBlock = IR.block().srcref(callTarget);

    boolean isValidExpansion = true;

    // Expand the jQuery.expandedEach call
    Node key = objectToLoopOver.getFirstChild(), val = null;
    for (int i = 0; key != null; key = key.getNext(), i++) {
      if (key != null) {
        if (objectToLoopOver.isArrayLit()) {
          // Arrays have a value of their index number
          val = IR.number(i).srcref(key);
        } else {
          val = key.getFirstChild();
        }
      }

      // Keep track of the replaced nodes so we can reset the tree
      List<Node> newKeys = new ArrayList<>();
      List<Node> newValues = new ArrayList<>();
      List<Node> origGetElems = new ArrayList<>();
      List<Node> newGetProps = new ArrayList<>();

      // Replace all of the key nodes with the prop name
      for (int j = 0; j < keyNodes.size(); j++) {
        Node origNode = keyNodes.get(j);
        Node ancestor = origNode.getParent();

        Node newNode = IR.string(key.getString()).srcref(key);
        newKeys.add(newNode);
        ancestor.replaceChild(origNode, newNode);

        // Walk up the tree to see if the key is used in a GETELEM
        // assignment
        while (ancestor != null && !NodeUtil.isStatement(ancestor) && !ancestor.isGetElem()) {
          ancestor = ancestor.getParent();
        }

        // Convert GETELEM nodes to GETPROP nodes so that they can be
        // renamed or removed.
        if (ancestor != null && ancestor.isGetElem()) {

          Node propObject = ancestor;
          while (propObject.isGetProp() || propObject.isGetElem()) {
            propObject = propObject.getFirstChild();
          }

          Node ancestorClone = ancestor.cloneTree();
          // Run the peephole passes to handle cases such as
          // obj['lit' + key] = val;
          peepholePasses.process(null, ancestorClone.getChildAtIndex(1));
          Node prop = ancestorClone.getChildAtIndex(1);

          if (prop.isString()
              && NodeUtil.isValidPropertyName(LanguageMode.ECMASCRIPT3, prop.getString())) {
            Node target = ancestorClone.getFirstChild();
            Node newGetProp = IR.getprop(target.detachFromParent(), prop.detachFromParent());
            newGetProps.add(newGetProp);
            origGetElems.add(ancestor);
            ancestor.getParent().replaceChild(ancestor, newGetProp);
          } else {
            if (prop.isString()
                && !NodeUtil.isValidPropertyName(LanguageMode.ECMASCRIPT3, prop.getString())) {
              t.report(n, JQUERY_UNABLE_TO_EXPAND_INVALID_NAME_ERROR, prop.getString());
            }
            isValidExpansion = false;
          }
        }
      }

      if (isValidExpansion) {
        // Replace all of the value nodes with the prop value
        for (int j = 0; val != null && j < valueNodes.size(); j++) {
          Node origNode = valueNodes.get(j);
          Node newNode = val.cloneTree();
          newValues.add(newNode);
          origNode.getParent().replaceChild(origNode, newNode);
        }

        // Wrap the new tree in an anonymous function call
        Node fnc =
            IR.function(
                    IR.name("").srcref(key),
                    IR.paramList().srcref(key),
                    callbackFunction.getChildAtIndex(2).cloneTree())
                .srcref(key);
        Node call = IR.call(fnc).srcref(key);
        call.putBooleanProp(Node.FREE_CALL, true);
        fncBlock.addChildToBack(IR.exprResult(call).srcref(call));
      }

      // Reset the source tree
      for (int j = 0; j < newGetProps.size(); j++) {
        newGetProps.get(j).getParent().replaceChild(newGetProps.get(j), origGetElems.get(j));
      }
      for (int j = 0; j < newKeys.size(); j++) {
        newKeys.get(j).getParent().replaceChild(newKeys.get(j), keyNodes.get(j));
      }
      for (int j = 0; j < newValues.size(); j++) {
        newValues.get(j).getParent().replaceChild(newValues.get(j), valueNodes.get(j));
      }

      if (!isValidExpansion) {
        return null;
      }
    }
    return fncBlock;
  }