private void addVarDecls(NodeTraversal t, boolean addThis, boolean addArguments) { Scope scope = t.getScope(); if (scope.isDeclared(THIS_VAR, false)) { addThis = false; } if (scope.isDeclared(ARGUMENTS_VAR, false)) { addArguments = false; } Node parent = t.getScopeRoot(); if (parent.isFunction()) { // Add the new node at the beginning of the function body. parent = parent.getLastChild(); } if (parent.isSyntheticBlock() && parent.getFirstChild().isScript()) { // Add the new node inside the SCRIPT node instead of the // synthetic block that contains it. parent = parent.getFirstChild(); } CompilerInput input = compiler.getInput(parent.getInputId()); if (addArguments) { Node name = IR.name(ARGUMENTS_VAR).srcref(parent); Node argumentsVar = IR.var(name, IR.name("arguments").srcref(parent)); argumentsVar.srcref(parent); parent.addChildToFront(argumentsVar); scope.declare(ARGUMENTS_VAR, name, null, input); } if (addThis) { Node name = IR.name(THIS_VAR).srcref(parent); Node thisVar = IR.var(name, IR.thisNode().srcref(parent)); thisVar.srcref(parent); parent.addChildToFront(thisVar); scope.declare(THIS_VAR, name, null, input); } }
private void addThisVar(NodeTraversal t) { Scope scope = t.getScope(); if (scope.isDeclared(THIS_VAR, false)) { return; } Node parent = t.getScopeRoot(); if (parent.isFunction()) { // Add the new node at the beginning of the function body. parent = parent.getLastChild(); } if (parent.isSyntheticBlock()) { // Add the new node inside the SCRIPT node instead of the // synthetic block that contains it. parent = parent.getFirstChild(); } Node name = IR.name(THIS_VAR).srcref(parent); Node thisVar = IR.var(name, IR.thisNode().srcref(parent)); thisVar.srcref(parent); parent.addChildToFront(thisVar); scope.declare(THIS_VAR, name, null, compiler.getInput(parent.getInputId())); }
private static Node fixupSrcref(Node node) { node.srcref(node.getFirstChild()); return node; }
/** * 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 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(); }