コード例 #1
0
  /**
   * Sanity checks code generation by performing it once, parsing the result, then generating code
   * from the second parse tree to verify that it matches the code generated from the first parse
   * tree.
   *
   * @return The regenerated parse tree. Null on error.
   */
  private Node sanityCheckCodeGeneration(Node root) {
    if (compiler.hasHaltingErrors()) {
      // Don't even bother checking code generation if we already know the
      // the code is bad.
      return null;
    }

    String source = compiler.toSource(root);
    Node root2 = compiler.parseSyntheticCode(source);
    if (compiler.hasHaltingErrors()) {
      compiler.report(
          JSError.make(
              CANNOT_PARSE_GENERATED_CODE, Strings.truncateAtMaxLength(source, 100, true)));

      // Throw an exception, so that the infrastructure will tell us
      // which pass violated the sanity check.
      throw new IllegalStateException("Sanity Check failed");
    }

    String source2 = compiler.toSource(root2);
    if (!source.equals(source2)) {
      compiler.report(
          JSError.make(
              GENERATED_BAD_CODE,
              Strings.truncateAtMaxLength(source, 1000, true),
              Strings.truncateAtMaxLength(source2, 1000, true)));

      // Throw an exception, so that the infrastructure will tell us
      // which pass violated the sanity check.
      throw new IllegalStateException("Sanity Check failed");
    }

    return root2;
  }
コード例 #2
0
  /** Add an @this annotation to all functions in the objLit. */
  private void addTypesToFunctions(Node objLit, String thisType) {
    Preconditions.checkState(objLit.isObjectLit());
    for (Node keyNode : objLit.children()) {
      Node value = keyNode.getLastChild();
      if (value != null && value.isFunction()) {
        JSDocInfoBuilder fnDoc = JSDocInfoBuilder.maybeCopyFrom(keyNode.getJSDocInfo());
        fnDoc.recordThisType(
            new JSTypeExpression(new Node(Token.BANG, IR.string(thisType)), VIRTUAL_FILE));
        keyNode.setJSDocInfo(fnDoc.build());
      }
    }

    // Add @this and @return to default property values.
    for (MemberDefinition property : extractProperties(objLit)) {
      if (!property.value.isObjectLit()) {
        continue;
      }
      if (hasShorthandAssignment(property.value)) {
        compiler.report(JSError.make(property.value, POLYMER_SHORTHAND_NOT_SUPPORTED));
        return;
      }

      Node defaultValue = NodeUtil.getFirstPropMatchingKey(property.value, "value");
      if (defaultValue == null || !defaultValue.isFunction()) {
        continue;
      }
      Node defaultValueKey = defaultValue.getParent();
      JSDocInfoBuilder fnDoc = JSDocInfoBuilder.maybeCopyFrom(defaultValueKey.getJSDocInfo());
      fnDoc.recordThisType(
          new JSTypeExpression(new Node(Token.BANG, IR.string(thisType)), VIRTUAL_FILE));
      fnDoc.recordReturnType(getTypeFromProperty(property));
      defaultValueKey.setJSDocInfo(fnDoc.build());
    }
  }
コード例 #3
0
  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    if (n.isString() && !parent.isGetProp() && !parent.isRegExp()) {
      String s = n.getString();

      for (blacklist.reset(s); blacklist.find(); ) {
        if (parent.isTemplateLit()) {
          if (parent.getChildCount() > 1) {
            // Ignore template string with substitutions
            continue;
          } else {
            n = parent;
          }
        }
        if (insideGetCssNameCall(n)) {
          continue;
        }
        if (insideGetUniqueIdCall(n)) {
          continue;
        }
        if (insideAssignmentToIdConstant(n)) {
          continue;
        }
        compiler.report(t.makeError(n, level, MISSING_GETCSSNAME, blacklist.group()));
      }
    }
  }
コード例 #4
0
 private void checkClassSuperReferences(Node classNode) {
   Node className = classNode.getFirstChild();
   Node superClassName = className.getNext();
   if (NodeUtil.referencesSuper(classNode) && !superClassName.isQualifiedName()) {
     compiler.report(JSError.make(classNode, NO_SUPERTYPE));
   }
 }
コード例 #5
0
 @Override
 public void visit(NodeTraversal t, Node n, Node parent) {
   if (n.isCall() && isGoogDefineClass(n)) {
     if (!validateUsage(n)) {
       compiler.report(JSError.make(n, GOOG_CLASS_TARGET_INVALID));
     }
   }
   maybeRewriteClassDefinition(n);
 }
コード例 #6
0
 private void maybeRewriteClassDefinition(Node n, Node target, Node value) {
   if (isGoogDefineClass(value)) {
     if (!target.isQualifiedName()) {
       compiler.report(JSError.make(n, GOOG_CLASS_TARGET_INVALID));
     }
     ClassDefinition def = extractClassDefinition(target, value);
     if (def != null) {
       value.detachFromParent();
       target.detachFromParent();
       rewriteGoogDefineClass(n, def);
     }
   }
 }
コード例 #7
0
 /** Sets the default values of tweaks based on compiler options. */
 private void applyCompilerDefaultValueOverrides(Map<String, TweakInfo> tweakInfos) {
   for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) {
     String tweakId = entry.getKey();
     TweakInfo tweakInfo = tweakInfos.get(tweakId);
     if (tweakInfo == null) {
       compiler.report(JSError.make(UNKNOWN_TWEAK_WARNING, tweakId));
     } else {
       TweakFunction registerFunc = tweakInfo.registerCall.tweakFunc;
       Node value = entry.getValue();
       if (!registerFunc.isValidNodeType(value.getType())) {
         compiler.report(
             JSError.make(
                 INVALID_TWEAK_DEFAULT_VALUE_WARNING,
                 tweakId,
                 registerFunc.getName(),
                 registerFunc.getExpectedTypeName()));
       } else {
         tweakInfo.defaultValueNode = value;
       }
     }
   }
 }
コード例 #8
0
 private void reportExtraRequireWarning(Node call, String require) {
   if (DEFAULT_EXTRA_NAMESPACES.contains(require)) {
     return;
   }
   JSDocInfo jsDoc = call.getJSDocInfo();
   if (jsDoc != null && jsDoc.getSuppressions().contains("extraRequire")) {
     // There is a @suppress {extraRequire} on the call node. Even though the compiler generally
     // doesn't understand @suppress in that position, respect it in this case,
     // since lots of people put it there to suppress the closure-linter's extraRequire check.
     return;
   }
   compiler.report(JSError.make(call, EXTRA_REQUIRE_WARNING, require));
 }
コード例 #9
0
  void inferScope(Node n, Scope scope) {
    TypeInference typeInference =
        new TypeInference(
            compiler, computeCfg(n), reverseInterpreter, scope, assertionFunctionsMap);
    try {
      typeInference.analyze();

      // Resolve any new type names found during the inference.
      compiler.getTypeRegistry().resolveTypesInScope(scope);

    } catch (DataFlowAnalysis.MaxIterationsExceededException e) {
      compiler.report(JSError.make(n, DATAFLOW_ERROR));
    }
  }
コード例 #10
0
 private void rewriteClassDefinition(Node n, Node parent, NodeTraversal t) {
   if (parent.getParent().isConst() || parent.getParent().isLet()) {
     compiler.report(JSError.make(n, POLYMER_INVALID_DECLARATION));
     return;
   }
   ClassDefinition def = extractClassDefinition(n);
   if (def != null) {
     if (NodeUtil.isNameDeclaration(parent.getParent()) || parent.isAssign()) {
       rewritePolymerClass(parent.getParent(), def, t);
     } else {
       rewritePolymerClass(parent, def, t);
     }
   }
 }
コード例 #11
0
  /**
   * Gets the JSTypeExpression for a given property using its "type" key.
   *
   * @see https://github.com/Polymer/polymer/blob/0.8-preview/PRIMER.md#configuring-properties
   */
  private JSTypeExpression getTypeFromProperty(MemberDefinition property) {
    if (property.info != null && property.info.hasType()) {
      return property.info.getType();
    }

    String typeString = "";
    if (property.value.isObjectLit()) {
      Node typeValue = NodeUtil.getFirstPropMatchingKey(property.value, "type");
      if (typeValue == null || !typeValue.isName()) {
        compiler.report(JSError.make(property.name, POLYMER_INVALID_PROPERTY));
        return null;
      }
      typeString = typeValue.getString();
    } else if (property.value.isName()) {
      typeString = property.value.getString();
    }

    Node typeNode = null;
    switch (typeString) {
      case "Boolean":
      case "String":
      case "Number":
        typeNode = IR.string(typeString.toLowerCase());
        break;
      case "Array":
      case "Function":
      case "Object":
      case "Date":
        typeNode = new Node(Token.BANG, IR.string(typeString));
        break;
      default:
        compiler.report(JSError.make(property.name, POLYMER_INVALID_PROPERTY));
        return null;
    }

    return new JSTypeExpression(typeNode, VIRTUAL_FILE);
  }
コード例 #12
0
  @Override
  public void process(Node externs, Node root) {
    // Find and validate the markers.
    NodeTraversal.traverseEs6(compiler, root, new Callback());

    // Complain about any unmatched markers.
    for (Node node : markerStack) {
      compiler.report(JSError.make(node, UNMATCHED_START_MARKER, startMarkerName));
    }

    // Add the block for the valid marker sets.
    for (Marker marker : validMarkers) {
      addBlocks(marker);
    }
  }
コード例 #13
0
    /** Strip property type annotations and add suppress checkTypes and globalThis on functions. */
    private void suppressBehavior(Node behaviorValue) {
      if (behaviorValue == null) {
        compiler.report(JSError.make(behaviorValue, POLYMER_UNQUALIFIED_BEHAVIOR));
        return;
      }

      if (behaviorValue.isArrayLit()) {
        for (Node child : behaviorValue.children()) {
          suppressBehavior(child);
        }
      } else if (behaviorValue.isObjectLit()) {
        stripPropertyTypes(behaviorValue);
        addBehaviorSuppressions(behaviorValue);
      }
    }
コード例 #14
0
  @Override
  public void process(Node externs, Node root) {
    FindPolymerExterns externsCallback = new FindPolymerExterns();
    NodeTraversal.traverse(compiler, externs, externsCallback);
    polymerElementExterns = externsCallback.polymerElementExterns;
    polymerElementProps = externsCallback.getpolymerElementProps();

    if (polymerElementExterns == null) {
      compiler.report(JSError.make(externs, POLYMER_MISSING_EXTERNS));
      return;
    }

    globalNames = new GlobalNamespace(compiler, externs, root);

    hotSwapScript(root, null);
  }
コード例 #15
0
  /**
   * Creates an intrument functions compiler pass.
   *
   * @param compiler The JSCompiler
   * @param functionNames Assigned function identifiers.
   * @param templateFilename Template filename; for use during error reporting only.
   * @param appNameStr String to pass to appNameSetter.
   * @param readable Instrumentation template protobuf text.
   */
  InstrumentFunctions(
      AbstractCompiler compiler,
      FunctionNames functionNames,
      String templateFilename,
      String appNameStr,
      Readable readable) {
    this.compiler = compiler;
    this.functionNames = functionNames;
    this.templateFilename = templateFilename;
    this.appNameStr = appNameStr;

    Instrumentation.Builder builder = Instrumentation.newBuilder();
    try {
      TextFormat.merge(readable, builder);
    } catch (IOException e) {
      compiler.report(
          JSError.make(
              RhinoErrorReporter.PARSE_ERROR,
              "Error reading instrumentation template protobuf at " + templateFilename));
      this.initCodeSource = "";
      this.definedFunctionName = "";
      this.reportFunctionName = "";
      this.reportFunctionExitName = "";
      this.appNameSetter = "";
      this.declarationsToRemove = Lists.newArrayList();
      return;
    }

    Instrumentation template = builder.build();

    StringBuilder initCodeSourceBuilder = new StringBuilder();
    for (String line : template.getInitList()) {
      initCodeSourceBuilder.append(line).append("\n");
    }
    this.initCodeSource = initCodeSourceBuilder.toString();

    this.definedFunctionName = template.getReportDefined();
    this.reportFunctionName = template.getReportCall();
    this.reportFunctionExitName = template.getReportExit();
    this.appNameSetter = template.getAppNameSetter();

    this.declarationsToRemove = ImmutableList.copyOf(template.getDeclarationToRemoveList());
  }
コード例 #16
0
  void inferTypes(NodeTraversal t, Node n, Scope scope) {
    TypeInference typeInference =
        new TypeInference(
            compiler,
            computeCfg(n),
            reverseInterpreter,
            scope,
            assertionFunctionsMap,
            getUnflowableVars(scope));
    try {
      typeInference.analyze();
      escapedLocalVars.putAll(typeInference.getAssignedOuterLocalVars());

      // Resolve any new type names found during the inference.
      compiler.getTypeRegistry().resolveTypesInScope(scope);

    } catch (DataFlowAnalysis.MaxIterationsExceededException e) {
      compiler.report(t.makeError(n, DATAFLOW_ERROR));
    }
  }
コード例 #17
0
    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (isBehavior(n)) {
        if (!n.isVar() && !n.isAssign()) {
          compiler.report(JSError.make(n, POLYMER_UNQUALIFIED_BEHAVIOR));
          return;
        }

        // Add @nocollapse.
        JSDocInfoBuilder newDocs = JSDocInfoBuilder.maybeCopyFrom(n.getJSDocInfo());
        newDocs.recordNoCollapse();
        n.setJSDocInfo(newDocs.build());

        Node behaviorValue = n.getChildAtIndex(1);
        if (n.isVar()) {
          behaviorValue = n.getFirstChild().getFirstChild();
        }
        suppressBehavior(behaviorValue);
      }
    }
コード例 #18
0
  private JSDocInfo mergeJsDocFor(ClassDefinition cls, Node associatedNode) {
    // avoid null checks
    JSDocInfo classInfo = (cls.classInfo != null) ? cls.classInfo : new JSDocInfo(true);

    JSDocInfo ctorInfo =
        (cls.constructor.info != null) ? cls.constructor.info : new JSDocInfo(true);

    Node superNode = cls.superClass;

    // Start with a clone of the constructor info if there is one.
    JSDocInfoBuilder mergedInfo =
        cls.constructor.info != null
            ? JSDocInfoBuilder.copyFrom(ctorInfo)
            : new JSDocInfoBuilder(true);

    // merge block description
    String blockDescription =
        Joiner.on("\n")
            .skipNulls()
            .join(classInfo.getBlockDescription(), ctorInfo.getBlockDescription());
    if (!blockDescription.isEmpty()) {
      mergedInfo.recordBlockDescription(blockDescription);
    }

    // merge suppressions
    Set<String> suppressions = Sets.newHashSet();
    suppressions.addAll(classInfo.getSuppressions());
    suppressions.addAll(ctorInfo.getSuppressions());
    if (!suppressions.isEmpty()) {
      mergedInfo.recordSuppressions(suppressions);
    }

    // Use class deprecation if set.
    if (classInfo.isDeprecated()) {
      mergedInfo.recordDeprecated();
    }

    String deprecationReason = null;
    if (classInfo.getDeprecationReason() != null) {
      deprecationReason = classInfo.getDeprecationReason();
      mergedInfo.recordDeprecationReason(deprecationReason);
    }

    // Use class visibility if specifically set
    Visibility visibility = classInfo.getVisibility();
    if (visibility != null && visibility != JSDocInfo.Visibility.INHERITED) {
      mergedInfo.recordVisibility(classInfo.getVisibility());
    }

    if (classInfo.isConstant()) {
      mergedInfo.recordConstancy();
    }

    if (classInfo.isExport()) {
      mergedInfo.recordExport();
    }

    // If @ngInject is on the ctor, it's already been copied above.
    if (classInfo.isNgInject()) {
      compiler.report(JSError.make(associatedNode, GOOG_CLASS_NG_INJECT_ON_CLASS));
      mergedInfo.recordNgInject(true);
    }

    // @constructor is implied, @interface must be explicit
    boolean isInterface = classInfo.isInterface() || ctorInfo.isInterface();
    if (isInterface) {
      mergedInfo.recordInterface();
      List<JSTypeExpression> extendedInterfaces = null;
      if (classInfo.getExtendedInterfacesCount() > 0) {
        extendedInterfaces = classInfo.getExtendedInterfaces();
      } else if (ctorInfo.getExtendedInterfacesCount() == 0 && superNode != null) {
        extendedInterfaces =
            ImmutableList.of(
                new JSTypeExpression(
                    new Node(Token.BANG, IR.string(superNode.getQualifiedName())), VIRTUAL_FILE));
      }
      if (extendedInterfaces != null) {
        for (JSTypeExpression extend : extendedInterfaces) {
          mergedInfo.recordExtendedInterface(extend);
        }
      }
    } else {
      // @constructor by default
      mergedInfo.recordConstructor();
      if (classInfo.makesUnrestricted() || ctorInfo.makesUnrestricted()) {
        mergedInfo.recordUnrestricted();
      } else if (classInfo.makesDicts() || ctorInfo.makesDicts()) {
        mergedInfo.recordDict();
      } else {
        // @struct by default
        mergedInfo.recordStruct();
      }

      if (classInfo.getBaseType() != null) {
        mergedInfo.recordBaseType(classInfo.getBaseType());
      } else if (superNode != null) {
        // a "super" implies @extends, build a default.
        JSTypeExpression baseType =
            new JSTypeExpression(
                new Node(Token.BANG, IR.string(superNode.getQualifiedName())), VIRTUAL_FILE);
        mergedInfo.recordBaseType(baseType);
      }

      // @implements from the class if they exist
      List<JSTypeExpression> interfaces = classInfo.getImplementedInterfaces();
      for (JSTypeExpression implemented : interfaces) {
        mergedInfo.recordImplementedInterface(implemented);
      }
    }

    // merge @template types if they exist
    List<String> templateNames = new ArrayList<>();
    templateNames.addAll(classInfo.getTemplateTypeNames());
    templateNames.addAll(ctorInfo.getTemplateTypeNames());
    for (String typeName : templateNames) {
      mergedInfo.recordTemplateTypeName(typeName);
    }
    return mergedInfo.build(associatedNode);
  }
コード例 #19
0
 /**
  * Reports a warning because a namespace was redefined.
  *
  * @param nameObj A namespace that is being redefined
  * @param ref The reference that set the namespace
  */
 private void warnAboutNamespaceRedefinition(Name nameObj, Ref ref) {
   compiler.report(JSError.make(ref.node, NAMESPACE_REDEFINED_WARNING, nameObj.getFullName()));
 }
コード例 #20
0
  /**
   * 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();
  }
コード例 #21
0
  /**
   * Validates the class definition and if valid, destructively extracts the class definition from
   * the AST.
   */
  private ClassDefinition extractClassDefinition(Node targetName, Node callNode) {

    JSDocInfo classInfo = NodeUtil.getBestJSDocInfo(targetName);

    // name = goog.defineClass(superClass, {...}, [modifier, ...])
    Node superClass = NodeUtil.getArgumentForCallOrNew(callNode, 0);
    if (superClass == null || (!superClass.isNull() && !superClass.isQualifiedName())) {
      compiler.report(JSError.make(callNode, GOOG_CLASS_SUPER_CLASS_NOT_VALID));
      return null;
    }

    if (NodeUtil.isNullOrUndefined(superClass) || superClass.matchesQualifiedName("Object")) {
      superClass = null;
    }

    Node description = NodeUtil.getArgumentForCallOrNew(callNode, 1);
    if (description == null || !description.isObjectLit() || !validateObjLit(description)) {
      // report bad class definition
      compiler.report(JSError.make(callNode, GOOG_CLASS_DESCRIPTOR_NOT_VALID));
      return null;
    }

    int paramCount = callNode.getChildCount() - 1;
    if (paramCount > 2) {
      compiler.report(JSError.make(callNode, GOOG_CLASS_UNEXPECTED_PARAMS));
      return null;
    }

    Node constructor = extractProperty(description, "constructor");
    if (classInfo != null && classInfo.isInterface()) {
      if (constructor != null) {
        compiler.report(JSError.make(description, GOOG_CLASS_CONSTRUCTOR_ON_INTERFACE));
        return null;
      }
    } else if (constructor == null) {
      // report missing constructor
      compiler.report(JSError.make(description, GOOG_CLASS_CONSTRUCTOR_MISSING));
      return null;
    }
    if (constructor == null) {
      constructor =
          IR.function(
              IR.name("").srcref(callNode),
              IR.paramList().srcref(callNode),
              IR.block().srcref(callNode));
      constructor.srcref(callNode);
    }

    JSDocInfo info = NodeUtil.getBestJSDocInfo(constructor);

    Node classModifier = null;
    Node statics = null;
    Node staticsProp = extractProperty(description, "statics");
    if (staticsProp != null) {
      if (staticsProp.isObjectLit() && validateObjLit(staticsProp)) {
        statics = staticsProp;
      } else if (staticsProp.isFunction()) {
        classModifier = staticsProp;
      } else {
        compiler.report(JSError.make(staticsProp, GOOG_CLASS_STATICS_NOT_VALID));
        return null;
      }
    }

    if (statics == null) {
      statics = IR.objectlit();
    }

    // Ok, now rip apart the definition into its component pieces.
    // Remove the "special" property key nodes.
    maybeDetach(constructor.getParent());
    maybeDetach(statics.getParent());
    if (classModifier != null) {
      maybeDetach(classModifier.getParent());
    }
    ClassDefinition def =
        new ClassDefinition(
            targetName,
            classInfo,
            maybeDetach(superClass),
            new MemberDefinition(info, null, maybeDetach(constructor)),
            objectLitToList(maybeDetach(statics)),
            objectLitToList(description),
            maybeDetach(classModifier));
    return def;
  }
コード例 #22
0
  private void rewritePolymerClass(Node exprRoot, final ClassDefinition cls, NodeTraversal t) {
    // Add {@code @lends} to the object literal.
    Node call = exprRoot.getFirstChild();
    if (call.isAssign()) {
      call = call.getChildAtIndex(1);
    } else if (call.isName()) {
      call = call.getFirstChild();
    }

    Node objLit = cls.descriptor;
    if (hasShorthandAssignment(objLit)) {
      compiler.report(JSError.make(objLit, POLYMER_SHORTHAND_NOT_SUPPORTED));
      return;
    }

    JSDocInfoBuilder objLitDoc = new JSDocInfoBuilder(true);
    objLitDoc.recordLends(cls.target.getQualifiedName() + ".prototype");
    objLit.setJSDocInfo(objLitDoc.build());

    this.addTypesToFunctions(objLit, cls.target.getQualifiedName());
    this.switchDollarSignPropsToBrackets(objLit);
    this.quoteListenerAndHostAttributeKeys(objLit);

    // For simplicity add everything into a block, before adding it to the AST.
    Node block = IR.block();

    if (cls.nativeBaseElement != null) {
      this.appendPolymerElementExterns(cls);
    }
    JSDocInfoBuilder constructorDoc = this.getConstructorDoc(cls);

    // Remove the original constructor JS docs from the objlit.
    Node ctorKey = cls.constructor.value.getParent();
    if (ctorKey != null) {
      ctorKey.removeProp(Node.JSDOC_INFO_PROP);
    }

    if (cls.target.isGetProp()) {
      // foo.bar = Polymer({...});
      Node assign = IR.assign(cls.target.cloneTree(), cls.constructor.value.cloneTree());
      assign.setJSDocInfo(constructorDoc.build());
      Node exprResult = IR.exprResult(assign);
      block.addChildToBack(exprResult);
    } else {
      // var foo = Polymer({...}); OR Polymer({...});
      Node var = IR.var(cls.target.cloneTree(), cls.constructor.value.cloneTree());
      var.setJSDocInfo(constructorDoc.build());
      block.addChildToBack(var);
    }

    appendPropertiesToBlock(cls, block, cls.target.getQualifiedName() + ".prototype.");
    appendBehaviorMembersToBlock(cls, block);
    List<MemberDefinition> readOnlyProps = parseReadOnlyProperties(cls, block);
    addInterfaceExterns(cls, readOnlyProps);
    removePropertyDocs(objLit);

    block.useSourceInfoIfMissingFromForTree(exprRoot);
    Node stmts = block.removeChildren();
    Node parent = exprRoot.getParent();

    // If the call to Polymer() is not in the global scope and the assignment target is not
    // namespaced (which likely means it's exported to the global scope), put the type declaration
    // into the global scope at the start of the current script.
    //
    // This avoids unknown type warnings which are a result of the compiler's poor understanding of
    // types declared inside IIFEs or any non-global scope. We should revisit this decision after
    // moving to the new type inference system which should be able to infer these types better.
    if (!t.getScope().isGlobal() && !cls.target.isGetProp()) {
      Node scriptNode = NodeUtil.getEnclosingScript(exprRoot);
      scriptNode.addChildrenToFront(stmts);
    } else {
      Node beforeRoot = parent.getChildBefore(exprRoot);
      if (beforeRoot == null) {
        parent.addChildrenToFront(stmts);
      } else {
        parent.addChildrenAfter(stmts, beforeRoot);
      }
    }

    if (exprRoot.isVar()) {
      Node assignExpr = varToAssign(exprRoot);
      parent.replaceChild(exprRoot, assignExpr);
    }

    compiler.reportCodeChange();
  }
コード例 #23
0
 /** @param requirement */
 private static void reportInvalidRequirement(
     AbstractCompiler compiler, Requirement requirement, String reason) {
   compiler.report(
       JSError.make(INVALID_REQUIREMENT_SPEC, reason, TextFormat.printToString(requirement)));
 }
コード例 #24
0
 private void cannotConvert(Node n) {
   compiler.report(JSError.make(n, CANNOT_CONVERT));
 }
コード例 #25
0
 /**
  * Warns the user that the given ES6 feature cannot be converted to ES3 because the transpilation
  * is not yet implemented. A call to this method is essentially a "TODO(tbreisacher): Implement
  * {@code feature}" comment.
  */
 private void cannotConvertYet(Node n, String feature) {
   compiler.report(JSError.make(n, CANNOT_CONVERT_YET, feature));
 }
コード例 #26
0
  private void visitClass(Node classNode, Node parent) {
    Node className = classNode.getFirstChild();
    Node superClassName = className.getNext();
    Node classMembers = classNode.getLastChild();

    // This is a statement node. We insert methods of the
    // transpiled class after this node.
    Node insertionPoint;

    // The fully qualified name of the class, which will be used in the output.
    // May come from the class itself or the LHS of an assignment.
    String fullClassName = null;

    // Whether the constructor function in the output should be anonymous.
    boolean anonymous;

    // If this is a class statement, or a class expression in a simple
    // assignment or var statement, convert it. In any other case, the
    // code is too dynamic, so just call cannotConvert.
    if (NodeUtil.isStatement(classNode)) {
      fullClassName = className.getString();
      anonymous = false;
      insertionPoint = classNode;
    } else if (parent.isAssign() && parent.getParent().isExprResult()) {
      // Add members after the EXPR_RESULT node:
      // example.C = class {}; example.C.prototype.foo = function() {};
      fullClassName = parent.getFirstChild().getQualifiedName();
      if (fullClassName == null) {
        cannotConvert(parent);
        return;
      }
      anonymous = true;
      insertionPoint = parent.getParent();
    } else if (parent.isName()) {
      // Add members after the 'var' statement.
      // var C = class {}; C.prototype.foo = function() {};
      fullClassName = parent.getString();
      anonymous = true;
      insertionPoint = parent.getParent();
    } else {
      cannotConvert(parent);
      return;
    }

    Verify.verify(NodeUtil.isStatement(insertionPoint));

    className.detachFromParent();
    Node constructor = null;
    JSDocInfo ctorJSDocInfo = null;
    for (Node member : classMembers.children()) {
      if (member.getString().equals("constructor")) {
        ctorJSDocInfo = member.getJSDocInfo();
        constructor = member.getFirstChild().detachFromParent();
        if (!anonymous) {
          constructor.replaceChild(constructor.getFirstChild(), className);
        }
      } else {
        String qualifiedMemberName;
        if (member.isStaticMember()) {
          if (NodeUtil.referencesThis(member.getFirstChild())) {
            compiler.report(JSError.make(member, STATIC_METHOD_REFERENCES_THIS));
          }
          qualifiedMemberName = Joiner.on(".").join(fullClassName, member.getString());
        } else {
          qualifiedMemberName = Joiner.on(".").join(fullClassName, "prototype", member.getString());
        }
        Node assign =
            IR.assign(
                NodeUtil.newQualifiedNameNode(
                    compiler.getCodingConvention(),
                    qualifiedMemberName,
                    /* basis node */ member,
                    /* original name */ member.getString()),
                member.getFirstChild().detachFromParent());
        assign.srcref(member);

        JSDocInfo info = member.getJSDocInfo();
        if (info != null) {
          info.setAssociatedNode(assign);
          assign.setJSDocInfo(info);
        }

        Node newNode = NodeUtil.newExpr(assign);
        insertionPoint.getParent().addChildAfter(newNode, insertionPoint);
        insertionPoint = newNode;
      }
    }

    if (constructor == null) {
      Node name = anonymous ? IR.name("").srcref(className) : className;
      constructor =
          IR.function(name, IR.paramList().srcref(classNode), IR.block().srcref(classNode));
    }
    JSDocInfo classJSDoc = classNode.getJSDocInfo();
    JSDocInfoBuilder newInfo =
        (classJSDoc != null) ? JSDocInfoBuilder.copyFrom(classJSDoc) : new JSDocInfoBuilder(true);

    newInfo.recordConstructor();
    if (!superClassName.isEmpty()) {
      if (!superClassName.isQualifiedName()) {
        compiler.report(JSError.make(superClassName, DYNAMIC_EXTENDS_TYPE));
        return;
      }

      Node superClassString = IR.string(superClassName.getQualifiedName());
      if (newInfo.isInterfaceRecorded()) {
        newInfo.recordExtendedInterface(
            new JSTypeExpression(
                new Node(Token.BANG, superClassString), superClassName.getSourceFileName()));
      } else {
        // TODO(mattloring) Remove dependency on Closure Library.
        Node inherits =
            NodeUtil.newQualifiedNameNode(compiler.getCodingConvention(), "goog.inherits");
        Node inheritsCall =
            IR.exprResult(IR.call(inherits, className.cloneTree(), superClassName.cloneTree()));
        inheritsCall.useSourceInfoIfMissingFromForTree(classNode);
        parent.addChildAfter(inheritsCall, classNode);
        newInfo.recordBaseType(
            new JSTypeExpression(
                new Node(Token.BANG, superClassString), superClassName.getSourceFileName()));
      }
    }

    // Classes are @struct by default.
    if (!newInfo.isUnrestrictedRecorded()
        && !newInfo.isDictRecorded()
        && !newInfo.isStructRecorded()) {
      newInfo.recordStruct();
    }

    if (ctorJSDocInfo != null) {
      newInfo.recordSuppressions(ctorJSDocInfo.getSuppressions());
      for (String param : ctorJSDocInfo.getParameterNames()) {
        newInfo.recordParameter(param, ctorJSDocInfo.getParameterType(param));
      }
    }
    parent.replaceChild(classNode, constructor);

    if (NodeUtil.isStatement(constructor)) {
      constructor.setJSDocInfo(newInfo.build(constructor));
    } else if (parent.isName()) {
      // The constructor function is the RHS of a var statement.
      // Add the JSDoc to the VAR node.
      Node var = parent.getParent();
      var.setJSDocInfo(newInfo.build(var));
    } else if (parent.isAssign()) {
      // The constructor function is the RHS of an assignment.
      // Add the JSDoc to the ASSIGN node.
      parent.setJSDocInfo(newInfo.build(parent));
    } else {
      throw new IllegalStateException("Unexpected parent node " + parent);
    }

    compiler.reportCodeChange();
  }
コード例 #27
0
  private boolean inlineAliasIfPossible(Name name, Ref alias, GlobalNamespace namespace) {
    // Ensure that the alias is assigned to a local variable at that
    // variable's declaration. If the alias's parent is a NAME,
    // then the NAME must be the child of a VAR node, and we must
    // be in a VAR assignment.
    Node aliasParent = alias.node.getParent();
    if (aliasParent.isName()) {
      // Ensure that the local variable is well defined and never reassigned.
      Scope scope = alias.scope;
      String aliasVarName = aliasParent.getString();
      Var aliasVar = scope.getVar(aliasVarName);

      ReferenceCollectingCallback collector =
          new ReferenceCollectingCallback(
              compiler,
              ReferenceCollectingCallback.DO_NOTHING_BEHAVIOR,
              Predicates.equalTo(aliasVar));
      collector.processScope(scope);

      ReferenceCollection aliasRefs = collector.getReferences(aliasVar);
      Set<AstChange> newNodes = new LinkedHashSet<>();

      if (aliasRefs.isWellDefined() && aliasRefs.firstReferenceIsAssigningDeclaration()) {
        if (!aliasRefs.isAssignedOnceInLifetime()) {
          // Static properties of constructors are always collapsed.
          // So, if a constructor is aliased and its properties are accessed from
          // the alias, we would like to inline the alias here to access the
          // properties correctly.
          // But if the aliased variable is assigned more than once, we can't
          // inline, so we warn.
          if (name.isConstructor()) {
            boolean accessPropsAfterAliasing = false;
            for (Reference ref : aliasRefs.references) {
              if (ref.getNode().getParent().isGetProp()) {
                accessPropsAfterAliasing = true;
                break;
              }
            }
            if (accessPropsAfterAliasing) {
              compiler.report(JSError.make(aliasParent, UNSAFE_CTOR_ALIASING, aliasVarName));
            }
          }
          return false;
        }

        // The alias is well-formed, so do the inlining now.
        int size = aliasRefs.references.size();
        for (int i = 1; i < size; i++) {
          ReferenceCollectingCallback.Reference aliasRef = aliasRefs.references.get(i);

          Node newNode = alias.node.cloneTree();
          aliasRef.getParent().replaceChild(aliasRef.getNode(), newNode);
          newNodes.add(new AstChange(getRefModule(aliasRef), aliasRef.getScope(), newNode));
        }

        // just set the original alias to null.
        aliasParent.replaceChild(alias.node, IR.nullNode());
        compiler.reportCodeChange();

        // Inlining the variable may have introduced new references
        // to descendants of {@code name}. So those need to be collected now.
        namespace.scanNewNodes(newNodes);
        return true;
      }
    }

    return false;
  }
コード例 #28
0
 /**
  * Reports a warning because a namespace was aliased.
  *
  * @param nameObj A namespace that is being aliased
  * @param ref The reference that forced the alias
  */
 private void warnAboutNamespaceAliasing(Name nameObj, Ref ref) {
   compiler.report(JSError.make(ref.node, UNSAFE_NAMESPACE_WARNING, nameObj.getFullName()));
 }
コード例 #29
0
 private void reportDuplicateRequireWarning(Node call, String require) {
   compiler.report(JSError.make(call, DUPLICATE_REQUIRE_WARNING, require));
 }
コード例 #30
0
  private void visitScriptNode(NodeTraversal t) {
    if (mode == Mode.SINGLE_FILE && requires.isEmpty()) {
      // Likely a file that isn't using Closure at all.
      return;
    }

    Set<String> namespaces = new HashSet<>();

    // For every usage, check that there is a goog.require, and warn if not.
    for (Map.Entry<String, Node> entry : usages.entrySet()) {
      String namespace = entry.getKey();
      if (namespace.endsWith(".call") || namespace.endsWith(".apply")) {
        namespace = namespace.substring(0, namespace.lastIndexOf('.'));
      }
      if (namespace.startsWith("goog.global.")) {
        continue;
      }

      Node node = entry.getValue();
      JSDocInfo info = NodeUtil.getBestJSDocInfo(NodeUtil.getEnclosingStatement(node));
      if (info != null && info.getSuppressions().contains("missingRequire")) {
        continue;
      }

      String outermostClassName = getOutermostClassName(namespace);
      // The parent namespace is also checked as part of the requires so that classes
      // used by goog.module are still checked properly. This may cause missing requires
      // to be missed but in practice that should happen rarely.
      String nonNullClassName = outermostClassName != null ? outermostClassName : namespace;
      String parentNamespace = null;
      int separatorIndex = nonNullClassName.lastIndexOf('.');
      if (separatorIndex > 0) {
        parentNamespace = nonNullClassName.substring(0, separatorIndex);
      }
      boolean notProvidedByConstructors =
          !providedNames.contains(namespace)
              && !providedNames.contains(outermostClassName)
              && !providedNames.contains(parentNamespace);
      boolean notProvidedByRequires =
          !requires.containsKey(namespace)
              && !requires.containsKey(outermostClassName)
              && !requires.containsKey(parentNamespace);
      if (notProvidedByConstructors
          && notProvidedByRequires
          && !namespaces.contains(namespace)
          && !"goog".equals(parentNamespace)) {
        // TODO(mknichel): If the symbol is not explicitly provided, find the next best
        // symbol from the provides in the same file.
        String rootName = Splitter.on('.').split(namespace).iterator().next();
        if (mode != Mode.SINGLE_FILE || closurizedNamespaces.contains(rootName)) {
          if (node.isCall()) {
            compiler.report(t.makeError(node, MISSING_REQUIRE_CALL_WARNING, namespace));
          } else {
            compiler.report(t.makeError(node, MISSING_REQUIRE_WARNING, namespace));
          }
          namespaces.add(namespace);
        }
      }
    }

    // For every goog.require, check that there is a usage (in either usages or weakUsages)
    // and warn if there is not.
    for (Map.Entry<String, Node> entry : requires.entrySet()) {
      String require = entry.getKey();
      Node call = entry.getValue();
      Node parent = call.getParent();
      if (parent.isAssign()) {
        // var baz = goog.require('foo.bar.baz');
        // Assume that the var 'baz' is used somewhere, and don't warn.
        continue;
      }
      if (!usages.containsKey(require) && !weakUsages.containsKey(require)) {
        reportExtraRequireWarning(call, require);
      }
    }
  }