/** * 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; }
/** 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()); } }
@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())); } } }
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)); } }
@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); }
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); } } }
/** 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; } } } }
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)); }
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)); } }
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); } } }
/** * 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); }
@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); } }
/** 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); } }
@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); }
/** * 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()); }
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)); } }
@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); } }
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); }
/** * 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())); }
/** * 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(); }
/** * 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; }
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(); }
/** @param requirement */ private static void reportInvalidRequirement( AbstractCompiler compiler, Requirement requirement, String reason) { compiler.report( JSError.make(INVALID_REQUIREMENT_SPEC, reason, TextFormat.printToString(requirement))); }
private void cannotConvert(Node n) { compiler.report(JSError.make(n, CANNOT_CONVERT)); }
/** * 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)); }
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(); }
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; }
/** * 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())); }
private void reportDuplicateRequireWarning(Node call, String require) { compiler.report(JSError.make(call, DUPLICATE_REQUIRE_WARNING, require)); }
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); } } }