private void handleContinue(Node node) { String label = null; if (node.hasChildren()) { label = node.getFirstChild().getString(); } Node cur; Node lastJump; // Similar to handBreak's logic with a few minor variation. Node parent = node.getParent(); for (cur = node, lastJump = node; !isContinueTarget(cur, parent, label); cur = parent, parent = parent.getParent()) { if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)) { if (lastJump == node) { createEdge(lastJump, Branch.UNCOND, cur.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(cur.getLastChild())); } lastJump = cur; } Preconditions.checkState(parent != null, "Cannot find continue target."); } Node iter = cur; if (cur.getChildCount() == 4) { iter = cur.getFirstChild().getNext().getNext(); } if (lastJump == node) { createEdge(node, Branch.UNCOND, iter); } else { finallyMap.put(lastJump, iter); } }
/** * Connects cfgNode to the proper CATCH block if target subtree might throw an exception. If there * are FINALLY blocks reached before a CATCH, it will make the corresponding entry in finallyMap. */ private void connectToPossibleExceptionHandler(Node cfgNode, Node target) { if (mayThrowException(target) && !exceptionHandler.isEmpty()) { Node lastJump = cfgNode; for (Node handler : exceptionHandler) { if (NodeUtil.isFunction(handler)) { return; } Preconditions.checkState(handler.getType() == Token.TRY); Node catchBlock = NodeUtil.getCatchBlock(handler); if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, handler.getLastChild()); } else { finallyMap.put(lastJump, handler.getLastChild()); } } else { // Has a catch. if (lastJump == cfgNode) { createEdge(cfgNode, Branch.ON_EX, catchBlock); return; } else { finallyMap.put(lastJump, catchBlock); } } lastJump = handler; } } }
/** Replace the current assign with its right hand side. */ void remove() { Node parent = assignNode.getParent(); if (mayHaveSecondarySideEffects) { Node replacement = assignNode.getLastChild().detachFromParent(); // Aggregate any expressions in GETELEMs. for (Node current = assignNode.getFirstChild(); current.getType() != Token.NAME; current = current.getFirstChild()) { if (current.getType() == Token.GETELEM) { replacement = new Node(Token.COMMA, current.getLastChild().detachFromParent(), replacement); replacement.copyInformationFrom(current); } } parent.replaceChild(assignNode, replacement); } else { Node gramps = parent.getParent(); if (parent.getType() == Token.EXPR_RESULT) { gramps.removeChild(parent); } else { parent.replaceChild(assignNode, assignNode.getLastChild().detachFromParent()); } } }
private JSType getRecordTypeHelper( Node n, DeclaredTypeRegistry registry, ImmutableList<String> typeParameters) throws UnknownTypeException { Map<String, Property> props = new LinkedHashMap<>(); for (Node propNode = n.getFirstFirstChild(); propNode != null; propNode = propNode.getNext()) { boolean isPropDeclared = propNode.getType() == Token.COLON; Node propNameNode = isPropDeclared ? propNode.getFirstChild() : propNode; String propName = propNameNode.getString(); if (propName.startsWith("'") || propName.startsWith("\"")) { propName = propName.substring(1, propName.length() - 1); } JSType propType = !isPropDeclared ? JSType.UNKNOWN : getTypeFromCommentHelper(propNode.getLastChild(), registry, typeParameters); Property prop; if (propType.equals(JSType.UNDEFINED) || isUnionWithUndefined(propNode.getLastChild())) { prop = Property.makeOptional(null, propType, propType); } else { prop = Property.make(propType, propType); } props.put(propName, prop); } return JSType.fromObjectType(ObjectType.fromProperties(props)); }
private void handleReturn(Node node) { Node lastJump = null; for (Iterator<Node> iter = exceptionHandler.iterator(); iter.hasNext(); ) { Node curHandler = iter.next(); if (NodeUtil.isFunction(curHandler)) { break; } if (NodeUtil.hasFinally(curHandler)) { if (lastJump == null) { createEdge(node, Branch.UNCOND, curHandler.getLastChild()); } else { finallyMap.put(lastJump, computeFallThrough(curHandler.getLastChild())); } lastJump = curHandler; } } if (node.hasChildren()) { connectToPossibleExceptionHandler(node, node.getFirstChild()); } if (lastJump == null) { createEdge(node, Branch.UNCOND, null); } else { finallyMap.put(lastJump, null); } }
private void validateObjectPatternComputedPropKey(int type, Node n) { validateNodeType(Token.COMPUTED_PROP, n); validateChildCount(n); validateExpression(n.getFirstChild()); if (n.getLastChild().isDefaultValue()) { validateDefaultValue(type, n.getLastChild()); } else { validateExpression(n.getLastChild()); } }
Node tryFuseStatements(Node n) { if (!n.getParent().isFunction() && canFuseIntoOneStatement(n)) { Node start = n.getFirstChild(); Node end = n.getLastChild(); Node result = fuseIntoOneStatement(n, start, end); fuseExpressionIntoControlFlowStatement(result, n.getLastChild()); reportCodeChange(); } return n; }
/** * Finds set of all defs in function given (defSites). Adds parameters of function to this set: * defSites. Finds all variables that are referenced in the function given (alf.names). * windowProps are all global properties available. Closures = alf.names - defSites - windowProps * * @param n root node of the function who's closures are to be found * @return set of closure names */ private Set<String> findClosures(Node n) { Set<String> closureVars = null; SimpleDefinitionFinder defFinder = new SimpleDefinitionFinder(compiler); defFinder.process(externs, n.getLastChild()); Collection<DefinitionSite> defSites = defFinder.getDefinitionSites(); Set<String> localDefs = new HashSet<String>(); for (DefinitionSite site : defSites) { if (site.node.getType() == Token.GETPROP) continue; String def = site.node.getString(); if (def.length() > 0) { localDefs.add(def); } } // adding params to function as defs Node origParamNode = n.getChildAtIndex(1); for (int i = 0; i < origParamNode.getChildCount(); i++) { String temp = origParamNode.getChildAtIndex(i).getString(); localDefs.add(temp); } // System.out.println("\nPrinting LOCAL def sites:" + defs); /*SimpleDefinitionFinder defFinder1 = new SimpleDefinitionFinder(compiler); defFinder1.process(externs, root); Collection<DefinitionSite> defSites1 = defFinder1.getDefinitionSites(); Set<String> defs1 = new HashSet<String>(); for(DefinitionSite site1: defSites1){ if (site1.node.getType() == Token.GETPROP) continue; String def = site1.node.getString(); if (def.length() > 0){ defs1.add(def); } } System.out.println("\nPrinting Global def sites:" + defs1);*/ AllNamesFinder alf = new AllNamesFinder(compiler); NodeTraversal.traverse(compiler, n.getLastChild(), alf); // System.out.println("all names: " + alf.names); closureVars = alf.names; closureVars.removeAll(localDefs); closureVars.removeAll(Props.windowProps); closureVars.remove( "this"); // since 'this' is later modified to $$_self we don't need to consider this as // closure return closureVars; }
public void validateRoot(Node n) { validateNodeType(Token.BLOCK, n); validateIsSynthetic(n); validateChildCount(n, 2); validateCodeRoot(n.getFirstChild()); validateCodeRoot(n.getLastChild()); }
private void addToDefinePropertiesObject(ClassDeclarationMetadata metadata, Node member) { Node obj = member.isStaticMember() ? metadata.definePropertiesObjForClass : metadata.definePropertiesObjForPrototype; Node prop = NodeUtil.getFirstPropMatchingKey(obj, member.getString()); if (prop == null) { prop = IR.objectlit( IR.stringKey("configurable", IR.trueNode()), IR.stringKey("enumerable", IR.trueNode())); obj.addChildToBack(IR.stringKey(member.getString(), prop)); } Node function = member.getLastChild(); JSDocInfoBuilder info = JSDocInfoBuilder.maybeCopyFrom(NodeUtil.getBestJSDocInfo(function)); info.recordThisType( new JSTypeExpression( new Node(Token.BANG, IR.string(metadata.fullClassName)), member.getSourceFileName())); Node stringKey = IR.stringKey(member.isGetterDef() ? "get" : "set", function.detachFromParent()); stringKey.setJSDocInfo(info.build()); prop.addChildToBack(stringKey); prop.useSourceInfoIfMissingFromForTree(member); }
/** 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()); } }
/** * Removes unreferenced arguments from a function declaration and when possible the function's * callSites. * * @param fnScope The scope inside the function */ private void removeUnreferencedFunctionArgs(Scope fnScope) { // TODO(johnlenz): Update type registry for function signature changes. Node function = fnScope.getRootNode(); Preconditions.checkState(function.getType() == Token.FUNCTION); if (NodeUtil.isGetOrSetKey(function.getParent())) { // The parameters object literal setters can not be removed. return; } Node argList = getFunctionArgList(function); boolean modifyCallers = modifyCallSites && callSiteOptimizer.canModifyCallers(function); if (!modifyCallers) { // Strip unreferenced args off the end of the function declaration. Node lastArg; while ((lastArg = argList.getLastChild()) != null) { Var var = fnScope.getVar(lastArg.getString()); if (!referenced.contains(var)) { Preconditions.checkNotNull(var == null); argList.removeChild(lastArg); compiler.reportCodeChange(); } else { break; } } } else { callSiteOptimizer.optimize(fnScope, referenced); } }
protected void testExternChanges(String extern, String input, String expectedExtern) { Compiler compiler = createCompiler(); CompilerOptions options = getOptions(); compiler.init( ImmutableList.of(SourceFile.fromCode("extern", extern)), ImmutableList.of(SourceFile.fromCode("input", input)), options); compiler.parseInputs(); assertFalse(compiler.hasErrors()); Node externsAndJs = compiler.getRoot(); Node root = externsAndJs.getLastChild(); Node externs = externsAndJs.getFirstChild(); Node expected = compiler.parseTestCode(expectedExtern); assertFalse(compiler.hasErrors()); (getProcessor(compiler)).process(externs, root); String externsCode = compiler.toSource(externs); String expectedCode = compiler.toSource(expected); assertEquals(expectedCode, externsCode); }
/** * Replaces a GETPROP a.b.c with a NAME a$b$c. * * @param alias A flattened prefix name (e.g. "a$b") * @param n The GETPROP node corresponding to the original name (e.g. "a.b") * @param parent {@code n}'s parent * @param originalName String version of the property name. */ private void flattenNameRef(String alias, Node n, Node parent, String originalName) { Preconditions.checkArgument( n.isGetProp(), "Expected GETPROP, found %s. Node: %s", Token.name(n.getType()), n); // BEFORE: // getprop // getprop // name a // string b // string c // AFTER: // name a$b$c Node ref = NodeUtil.newName(compiler, alias, n, originalName); NodeUtil.copyNameAnnotations(n.getLastChild(), ref); if (parent.isCall() && n == parent.getFirstChild()) { // The node was a call target, we are deliberately flatten these as // we node the "this" isn't provided by the namespace. Mark it as such: parent.putBooleanProp(Node.FREE_CALL, true); } TypeI type = n.getTypeI(); if (type != null) { ref.setTypeI(type); } parent.replaceChild(n, ref); compiler.reportCodeChange(); }
private boolean isFusableControlStatement(Node n) { switch (n.getToken()) { case IF: case THROW: case SWITCH: case EXPR_RESULT: return true; case RETURN: // We don't want to add a new return value. return n.hasChildren(); case FOR: if (NodeUtil.isForIn(n)) { // Avoid cases where we have for(var x = foo() in a) { .... return !mayHaveSideEffects(n.getFirstChild()); } else { // Avoid cases where we have for(var x;_;_) { .... return !n.getFirstChild().isVar(); } case LABEL: return isFusableControlStatement(n.getLastChild()); case BLOCK: return !n.isSyntheticBlock() && isFusableControlStatement(n.getFirstChild()); default: break; } return false; }
/** * Prepare an template AST to use when performing matches. * * @param templateFunctionNode The template declaration function to extract the template AST from. * @return The first node of the template AST sequence to use when matching. */ private Node initTemplate(Node templateFunctionNode) { Node prepped = templateFunctionNode.cloneTree(); prepTemplatePlaceholders(prepped); Node body = prepped.getLastChild(); Node startNode; if (body.hasOneChild() && body.getFirstChild().isExprResult()) { // When matching an expression, don't require it to be a complete // statement. startNode = body.getFirstFirstChild(); } else { startNode = body.getFirstChild(); } for (int i = 0; i < templateLocals.size(); i++) { // reserve space in the locals array. this.localVarMatches.add(null); } for (int i = 0; i < templateParams.size(); i++) { // reserve space in the params array. this.paramNodeMatches.add(null); } return startNode; }
public void testJSDocAttachment18() { Node fn = parse("function f() { " + " var x = /** @type {string} */ (y);" + "};").getFirstChild(); assertEquals(Token.FUNCTION, fn.getType()); Node cast = fn.getLastChild().getFirstChild().getFirstChild().getFirstChild(); assertEquals(Token.CAST, cast.getType()); }
private void validateFunctionStatement(Node n) { validateNodeType(Token.FUNCTION, n); validateChildCount(n); validateName(n.getFirstChild()); validateParameters(n.getChildAtIndex(1)); validateBlock(n.getLastChild()); }
private void validateTrinaryOp(Node n) { validateChildCount(n); Node first = n.getFirstChild(); validateExpression(first); validateExpression(first.getNext()); validateExpression(n.getLastChild()); }
private static void fuseExpressionIntoControlFlowStatement(Node before, Node control) { Preconditions.checkArgument(before.isExprResult(), "before must be expression result"); // Now we are just left with two statements. The comma tree of the first // n - 1 statements (which can be used in an expression) and the last // statement. We perform specific fusion based on the last statement's type. switch (control.getToken()) { case IF: case RETURN: case THROW: case SWITCH: case EXPR_RESULT: before.getParent().removeChild(before); fuseExpressionIntoFirstChild(before.removeFirstChild(), control); return; case FOR: before.getParent().removeChild(before); if (NodeUtil.isForIn(control)) { fuseExpressionIntoSecondChild(before.removeFirstChild(), control); } else { fuseExpressionIntoFirstChild(before.removeFirstChild(), control); } return; case LABEL: fuseExpressionIntoControlFlowStatement(before, control.getLastChild()); return; case BLOCK: fuseExpressionIntoControlFlowStatement(before, control.getFirstChild()); return; default: throw new IllegalStateException("Statement fusion missing."); } }
@Override public void visit(NodeTraversal t, Node n, Node parent) { // Verify the source file is annotated. if (doSanityChecks && sourceFile != null) { Preconditions.checkState(sourceFile.equals(n.getProp(Node.SOURCENAME_PROP))); } // Annotate the original name. switch (n.getType()) { case Token.GETPROP: Node propNode = n.getLastChild(); setOriginalName(n, propNode.getString()); break; case Token.FUNCTION: String functionName = NodeUtil.getNearestFunctionName(n); if (functionName != null) { setOriginalName(n, functionName); } break; case Token.NAME: setOriginalName(n, n.getString()); break; case Token.OBJECTLIT: for (Node key = n.getFirstChild(); key != null; key = key.getNext()) { // We only want keys were unquoted. if (!key.isQuotedString()) { setOriginalName(key, key.getString()); } } break; } }
private void validateForOf(Node n) { validateNodeType(Token.FOR_OF, n); validateChildCount(n); validateVarOrAssignmentTarget(n.getFirstChild()); validateExpression(n.getChildAtIndex(1)); validateBlock(n.getLastChild()); }
/** * Rename or remove labels. * * @param node The label node. * @param parent The parent of the label node. */ private void visitLabel(Node node, Node parent) { Node nameNode = node.getFirstChild(); Preconditions.checkState(nameNode != null); String name = nameNode.getString(); LabelInfo li = getLabelInfo(name); // This is a label... if (li.referenced || !removeUnused) { String newName = getNameForId(li.id); if (!name.equals(newName)) { // ... and it is used, give it the short name. nameNode.setString(newName); compiler.reportCodeChange(); } } else { // ... and it is not referenced, just remove it. Node newChild = node.getLastChild(); node.removeChild(newChild); parent.replaceChild(node, newChild); if (newChild.isBlock()) { NodeUtil.tryMergeBlock(newChild); } compiler.reportCodeChange(); } // Remove the label from the current stack of labels. namespaceStack.peek().renameMap.remove(name); }
private void addExportMethod( Map<String, GenerateNodeContext> exports, String export, GenerateNodeContext context) { CodingConvention convention = compiler.getCodingConvention(); // Emit the proper CALL expression. // This is an optimization to avoid exporting everything as a symbol // because exporting a property is significantly simpler/faster. // Only export the property if the parent is being exported or // if the parent is "prototype" and the grandparent is being exported. String parent = null; String grandparent = null; Node node = context.getNode().getFirstChild(); if (node.isGetProp()) { Node parentNode = node.getFirstChild(); parent = parentNode.getQualifiedName(); if (parentNode.isGetProp() && parentNode.getLastChild().getString().equals(PROTOTYPE_PROPERTY)) { grandparent = parentNode.getFirstChild().getQualifiedName(); } } boolean useExportSymbol = true; if (grandparent != null && exports.containsKey(grandparent)) { // grandparent is only set for properties exported off a prototype obj. useExportSymbol = false; } else if (parent != null && exports.containsKey(parent)) { useExportSymbol = false; } Node call; if (useExportSymbol) { // exportSymbol(publicPath, object); call = IR.call( NodeUtil.newQualifiedNameNode( convention, exportSymbolFunction, context.getNode(), export), IR.string(export), NodeUtil.newQualifiedNameNode(convention, export, context.getNode(), export)); } else { // exportProperty(object, publicName, symbol); String property = getPropertyName(node); call = IR.call( NodeUtil.newQualifiedNameNode( convention, exportPropertyFunction, context.getNode(), exportPropertyFunction), NodeUtil.newQualifiedNameNode( convention, parent, context.getNode(), exportPropertyFunction), IR.string(property), NodeUtil.newQualifiedNameNode( convention, export, context.getNode(), exportPropertyFunction)); } Node expression = IR.exprResult(call); annotate(expression); addStatement(context, expression); compiler.reportCodeChange(); }
private void validateTry(Node n) { validateNodeType(Token.TRY, n); validateChildCountIn(n, 2, 3); validateBlock(n.getFirstChild()); boolean seenCatchOrFinally = false; // Validate catch Node catches = n.getChildAtIndex(1); validateNodeType(Token.BLOCK, catches); validateMaximumChildCount(catches, 1); if (catches.hasChildren()) { validateCatch(catches.getFirstChild()); seenCatchOrFinally = true; } // Validate finally if (n.getChildCount() == 3) { validateBlock(n.getLastChild()); seenCatchOrFinally = true; } if (!seenCatchOrFinally) { violation("Missing catch or finally for try statement.", n); } }
/** If this is an assign to a variable or its property, return it. Otherwise, return null. */ static Assign maybeCreateAssign(Node assignNode) { Preconditions.checkState(NodeUtil.isAssignmentOp(assignNode)); // Skip one level of GETPROPs or GETELEMs. // // Don't skip more than one level, because then we get into // situations where assigns to properties of properties will always // trigger side-effects, and the variable they're on cannot be removed. boolean isPropAssign = false; Node current = assignNode.getFirstChild(); if (NodeUtil.isGet(current)) { current = current.getFirstChild(); isPropAssign = true; if (current.getType() == Token.GETPROP && current.getLastChild().getString().equals("prototype")) { // Prototype properties sets should be considered like normal // property sets. current = current.getFirstChild(); } } if (current.getType() == Token.NAME) { return new Assign(assignNode, current, isPropAssign); } return null; }
/** * Inline a function that fulfills the requirements of canInlineReferenceDirectly into the call * site, replacing only the CALL node. */ private Node inlineReturnValue(Node callNode, Node fnNode) { Node block = fnNode.getLastChild(); Node callParentNode = callNode.getParent(); // NOTE: As the normalize pass guarantees globals aren't being // shadowed and an expression can't introduce new names, there is // no need to check for conflicts. // Create an argName -> expression map, checking for side effects. Map<String, Node> argMap = FunctionArgumentInjector.getFunctionCallParameterMap( fnNode, callNode, this.safeNameIdSupplier); Node newExpression; if (!block.hasChildren()) { Node srcLocation = block; newExpression = NodeUtil.newUndefinedNode(srcLocation); } else { Node returnNode = block.getFirstChild(); Preconditions.checkArgument(returnNode.getType() == Token.RETURN); // Clone the return node first. Node safeReturnNode = returnNode.cloneTree(); Node inlineResult = FunctionArgumentInjector.inject(safeReturnNode, null, argMap); Preconditions.checkArgument(safeReturnNode == inlineResult); newExpression = safeReturnNode.removeFirstChild(); } callParentNode.replaceChild(callNode, newExpression); return newExpression; }
@Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getType()) { case Token.OBJECTLIT: for (Node child : n.children()) { if (child.isComputedProp()) { visitObjectWithComputedProperty(n, parent); break; } } break; case Token.MEMBER_DEF: if (parent.isObjectLit()) { visitMemberDefInObjectLit(n, parent); } break; case Token.FOR_OF: visitForOf(n, parent); break; case Token.SUPER: visitSuper(n, parent); break; case Token.STRING_KEY: visitStringKey(n); break; case Token.CLASS: for (Node member = n.getLastChild().getFirstChild(); member != null; member = member.getNext()) { if (member.isGetterDef() || member.isSetterDef() || member.getBooleanProp(Node.COMPUTED_PROP_GETTER) || member.getBooleanProp(Node.COMPUTED_PROP_SETTER)) { cannotConvert(member, "getters or setters in class definitions"); return; } } visitClass(n, parent); break; case Token.ARRAYLIT: case Token.NEW: case Token.CALL: for (Node child : n.children()) { if (child.isSpread()) { visitArrayLitOrCallWithSpread(n, parent); break; } } break; case Token.TEMPLATELIT: Es6TemplateLiterals.visitTemplateLiteral(t, n); break; case Token.ARRAY_PATTERN: visitArrayPattern(t, n, parent); break; case Token.OBJECT_PATTERN: visitObjectPattern(t, n, parent); break; } }
private void validateGetProp(Node n) { validateNodeType(Token.GETPROP, n); validateChildCount(n); validateExpression(n.getFirstChild()); Node prop = n.getLastChild(); validateNodeType(Token.STRING, prop); validateNonEmptyString(prop); }
/** * Gets whether a name ends with a field name that should be stripped. For example, this * function would return true when passed "this.logger" or "a.b.c.myLogger" if "logger" is a * strip name. * * @param n A node (typically a GETPROP node) * @return Whether the name ends with a field name that should be stripped */ boolean nameEndsWithFieldNameToStrip(@Nullable Node n) { if (n != null && n.getType() == Token.GETPROP) { Node propNode = n.getLastChild(); return propNode != null && propNode.getType() == Token.STRING && isStripName(propNode.getString()); } return false; }