private boolean matchesNodeShape(Node template, Node ast) { if (isTemplateParameterNode(template)) { // Match the entire expression but only if it is an expression. return !NodeUtil.isStatement(ast); } else if (isTemplateLocalNameNode(template)) { // Match any name. Maybe match locals here. if (!ast.isName()) { return false; } } else if (template.isCall()) { // Loosely match CALL nodes. isEquivalentToShallow checks free calls against non-free calls, // but the template should ignore that distinction. if (ast == null || !ast.isCall() || ast.getChildCount() != template.getChildCount()) { return false; } // But check any children. } else if (!template.isEquivalentToShallow(ast)) { return false; } // isEquivalentToShallow guarantees the child counts match Node templateChild = template.getFirstChild(); Node astChild = ast.getFirstChild(); while (templateChild != null) { if (!matchesNodeShape(templateChild, astChild)) { return false; } templateChild = templateChild.getNext(); astChild = astChild.getNext(); } return true; }
private void validateMaximumChildCount(Node n, int i) { boolean valid = false; if (i == 1) { valid = !n.hasMoreThanOneChild(); } else if (i == -1) { valid = true; // Varying number of children. } else { valid = n.getChildCount() <= i; } if (!valid) { violation("Expected no more than " + i + " children, but was " + n.getChildCount(), n); } }
private void validateMinimumChildCount(Node n, int i) { boolean valid = false; if (i == 1) { valid = n.hasChildren(); } else if (i == 2) { valid = n.hasMoreThanOneChild(); } else { valid = n.getChildCount() >= i; } if (!valid) { violation("Expected at least " + i + " children, but was " + n.getChildCount(), n); } }
private void handleFor(Node forNode) { if (forNode.getChildCount() == 4) { // We have for (init; cond; iter) { body } Node init = forNode.getFirstChild(); Node cond = init.getNext(); Node iter = cond.getNext(); Node body = iter.getNext(); // After initialization, we transfer to the FOR which is in charge of // checking the condition (for the first time). createEdge(init, Branch.UNCOND, forNode); // The edge that transfer control to the beginning of the loop body. createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body)); // The edge to end of the loop. createEdge(forNode, Branch.ON_FALSE, computeFollowNode(forNode)); // The end of the body will have a unconditional branch to our iter // (handled by calling computeFollowNode of the last instruction of the // body. Our iter will jump to the forNode again to another condition // check. createEdge(iter, Branch.UNCOND, forNode); connectToPossibleExceptionHandler(init, init); connectToPossibleExceptionHandler(forNode, cond); connectToPossibleExceptionHandler(iter, iter); } else { // We have for (item in collection) { body } Node item = forNode.getFirstChild(); Node collection = item.getNext(); Node body = collection.getNext(); // The edge that transfer control to the beginning of the loop body. createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body)); // The edge to end of the loop. createEdge(forNode, Branch.ON_FALSE, computeFollowNode(forNode)); connectToPossibleExceptionHandler(forNode, collection); } }
@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 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); } }
private Node createAnonymWithParamCall( Set<String> closures, String anonymName, Node clonedorigParamNode, boolean thisChanged) { Node newAnonymNode = new Node(Token.FUNCTION); Node blockNode = new Node(Token.BLOCK); newAnonymNode.addChildrenToBack(Node.newString(Token.NAME, "")); newAnonymNode.addChildrenToBack(clonedorigParamNode); newAnonymNode.addChildrenToBack(blockNode); // to block add call with params as closure varibles Node returnNode = new Node(Token.RETURN); Node callNode = new Node(Token.CALL); callNode.addChildrenToBack(Node.newString(Token.NAME, anonymName)); // first add all original params to this call for (int i = 0; i < clonedorigParamNode.getChildCount(); i++) { Node temp = clonedorigParamNode.getChildAtIndex(i).cloneTree(); callNode.addChildrenToBack(temp); } addParamsToMethod(closures, callNode, thisChanged, "this"); returnNode.addChildrenToBack(callNode); blockNode.addChildrenToBack(returnNode); return newAnonymNode; }
/** Traverses a function. */ private void traverseFunction(Node n, Node parent) { Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.isFunction()); final Node fnName = n.getFirstChild(); boolean isFunctionExpression = (parent != null) && NodeUtil.isFunctionExpression(n); if (!isFunctionExpression) { // Functions declarations are in the scope containing the declaration. traverseBranch(fnName, n); } curNode = n; pushScope(n); if (isFunctionExpression) { // Function expression names are only accessible within the function // scope. traverseBranch(fnName, n); } final Node args = fnName.getNext(); final Node body = args.getNext(); // Args traverseBranch(args, n); // Body // ES6 "arrow" function may not have a block as a body. traverseBranch(body, n); popScope(); }
private void handleFunction(Node node) { // A block transfer control to its first child if it is not empty. Preconditions.checkState(node.getChildCount() >= 3); createEdge(node, Branch.UNCOND, computeFallThrough(node.getFirstChild().getNext().getNext())); Preconditions.checkState(exceptionHandler.peek() == node); exceptionHandler.pop(); }
/** Try to fold array-length. e.g [1, 2, 3].length ==> 3, [x, y].length ==> 2 */ private Node tryFoldGetProp(Node n, Node left, Node right) { Preconditions.checkArgument(n.isGetProp()); if (left.isObjectLit()) { return tryFoldObjectPropAccess(n, left, right); } if (right.isString() && right.getString().equals("length")) { int knownLength = -1; switch (left.getType()) { case ARRAYLIT: if (mayHaveSideEffects(left)) { // Nope, can't fold this, without handling the side-effects. return n; } knownLength = left.getChildCount(); break; case STRING: knownLength = left.getString().length(); break; default: // Not a foldable case, forget it. return n; } Preconditions.checkState(knownLength != -1); Node lengthNode = IR.number(knownLength); n.getParent().replaceChild(n, lengthNode); reportCodeChange(); return lengthNode; } return n; }
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); } }
/** @return whether the blockNode contains only a single "throw" child node. */ private static boolean hasSingleThrow(Node blockNode) { if (blockNode.getChildCount() == 1 && blockNode.getFirstChild().getType() == Token.THROW) { // Functions consisting of a single "throw FOO" can be actually abstract, // so do not check their return type nullability. return true; } return false; }
private void validateIf(Node n) { validateNodeType(Token.IF, n); validateChildCountIn(n, 2, 3); validateExpression(n.getFirstChild()); validateBlock(n.getChildAtIndex(1)); if (n.getChildCount() == 3) { validateBlock(n.getLastChild()); } }
/** Scans and gather variables declarations under a Node */ private void scanVars(Node n, Node parent) { switch (n.getType()) { case Token.VAR: // Declare all variables. e.g. var x = 1, y, z; for (Node child = n.getFirstChild(); child != null; ) { Node next = child.getNext(); Preconditions.checkState(child.getType() == Token.NAME); String name = child.getString(); declareVar(name, child, n, parent, null, n); child = next; } return; case Token.FUNCTION: if (NodeUtil.isFunctionExpression(n)) { return; } String fnName = n.getFirstChild().getString(); if (fnName.isEmpty()) { // This is invalid, but allow it so the checks can catch it. return; } declareVar(fnName, n.getFirstChild(), n, parent, null, n); return; // should not examine function's children case Token.CATCH: Preconditions.checkState(n.getChildCount() == 2); Preconditions.checkState(n.getFirstChild().getType() == Token.NAME); // the first child is the catch var and the third child // is the code block final Node var = n.getFirstChild(); final Node block = var.getNext(); declareVar(var.getString(), var, n, parent, null, n); scanVars(block, n); return; // only one child to scan case Token.SCRIPT: sourceName = (String) n.getProp(Node.SOURCENAME_PROP); break; } // Variables can only occur in statement-level nodes, so // we only need to traverse children in a couple special cases. if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) { for (Node child = n.getFirstChild(); child != null; ) { Node next = child.getNext(); scanVars(child, n); child = next; } } }
/** * 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; }
/** * Gets the maximum number of arguments that this function requires, or Integer.MAX_VALUE if this * is a variable argument function. */ public int getMaxArguments() { Node params = getParametersNode(); if (params != null) { Node lastParam = params.getLastChild(); if (lastParam == null || !lastParam.isVarArgs()) { return params.getChildCount(); } } return Integer.MAX_VALUE; }
/** Processes trailing default and rest parameters. */ private void visitParamList(Node paramList, Node function) { Node insertSpot = null; Node block = function.getLastChild(); for (int i = 0; i < paramList.getChildCount(); i++) { Node param = paramList.getChildAtIndex(i); if (param.isDefaultValue()) { Node nameOrPattern = param.removeFirstChild(); Node defaultValue = param.removeFirstChild(); Node newParam = nameOrPattern.isName() ? nameOrPattern : IR.name(DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++)); Node lhs = nameOrPattern.cloneTree(); Node rhs = defaultValueHook(newParam.cloneTree(), defaultValue); Node newStatement = nameOrPattern.isName() ? IR.exprResult(IR.assign(lhs, rhs)) : IR.var(lhs, rhs); newStatement.useSourceInfoIfMissingFromForTree(param); block.addChildAfter(newStatement, insertSpot); insertSpot = newStatement; paramList.replaceChild(param, newParam); newParam.setOptionalArg(true); compiler.reportCodeChange(); } else if (param.isRest()) { // rest parameter param.setType(Token.NAME); param.setVarArgs(true); // Transpile to: param = [].slice.call(arguments, i); Node newArr = IR.exprResult( IR.assign( IR.name(param.getString()), IR.call( IR.getprop( IR.getprop(IR.arraylit(), IR.string("slice")), IR.string("call")), IR.name("arguments"), IR.number(i)))); block.addChildAfter(newArr.useSourceInfoIfMissingFromForTree(param), insertSpot); compiler.reportCodeChange(); } else if (param.isDestructuringPattern()) { String tempVarName = DESTRUCTURING_TEMP_VAR + (destructuringVarCounter++); paramList.replaceChild(param, IR.name(tempVarName)); Node newDecl = IR.var(param, IR.name(tempVarName)); block.addChildAfter(newDecl, insertSpot); insertSpot = newDecl; } } // For now, we are running transpilation before type-checking, so we'll // need to make sure changes don't invalidate the JSDoc annotations. // Therefore we keep the parameter list the same length and only initialize // the values if they are set to undefined. }
/** * Traverses a function, which creates a new scope in javascript. * * <p>Note that CATCH blocks also create a new scope, but only for the catch variable. * Declarations within the block actually belong to the enclosing scope. Because we don't remove * catch variables, there's no need to treat CATCH blocks differently like we do functions. */ private void traverseFunction(Node n, Scope parentScope) { Preconditions.checkState(n.getChildCount() == 3); Preconditions.checkState(n.getType() == Token.FUNCTION); final Node body = n.getLastChild(); Preconditions.checkState(body.getNext() == null && body.getType() == Token.BLOCK); Scope fnScope = new SyntacticScopeCreator(compiler).createScope(n, parentScope); traverseNode(body, n, fnScope); collectMaybeUnreferencedVars(fnScope); allFunctionScopes.add(fnScope); }
private void validateTemplateLit(Node n) { validateEs6Feature("template literal", n); validateNodeType(Token.TEMPLATELIT, n); if (!n.hasChildren()) { return; } for (int i = 0; i < n.getChildCount(); i++) { Node child = n.getChildAtIndex(i); // If the first child is not a STRING, this is a tagged template. if (i == 0 && !child.isString()) { validateExpression(child); } else if (child.isString()) { validateString(child); } else { validateTemplateLitSub(child); } } }
/** * Gets an estimate of the cost in characters of making the function call: the sum of the * identifiers and the separators. * * @param referencesThis */ private static int estimateCallCost(Node fnNode, boolean referencesThis) { Node argsNode = NodeUtil.getFnParameters(fnNode); int numArgs = argsNode.getChildCount(); int callCost = NAME_COST_ESTIMATE + PAREN_COST; if (numArgs > 0) { callCost += (numArgs * NAME_COST_ESTIMATE) + ((numArgs - 1) * COMMA_COST); } if (referencesThis) { // TODO(johnlenz): Update this if we start supporting inlining // other functions that reference this. // The only functions that reference this that are currently inlined // are those that are called via ".call" with an explicit "this". callCost += 5 + 5; // ".call" + "this," } return callCost; }
@Override public FlowScope getPreciserScopeKnowingConditionOutcome( Node condition, FlowScope blindScope, boolean outcome) { if (condition.isCall() && condition.getChildCount() == 2) { Node callee = condition.getFirstChild(); Node param = condition.getLastChild(); if (callee.isGetProp() && param.isQualifiedName()) { JSType paramType = getTypeIfRefinable(param, blindScope); Node left = callee.getFirstChild(); Node right = callee.getLastChild(); if (left.isName() && "goog".equals(left.getString()) && right.isString()) { Function<TypeRestriction, JSType> restricter = restricters.get(right.getString()); if (restricter != null) { return restrictParameter(param, paramType, blindScope, restricter, outcome); } } } } return nextPreciserScopeKnowingConditionOutcome(condition, blindScope, outcome); }
private void validateExport(Node n) { validateNodeType(Token.EXPORT, n); if (n.getBooleanProp(Node.EXPORT_ALL_FROM)) { // export * from "mod" validateChildCount(n, 2); validateNodeType(Token.EMPTY, n.getFirstChild()); validateString(n.getChildAtIndex(1)); } else if (n.getBooleanProp(Node.EXPORT_DEFAULT)) { // export default foo = 2 validateChildCount(n, 1); validateExpression(n.getFirstChild()); } else { validateChildCountIn(n, 1, 2); if (n.getFirstChild().getType() == Token.EXPORT_SPECS) { validateExportSpecifiers(n.getFirstChild()); } else { validateStatement(n.getFirstChild()); } if (n.getChildCount() == 2) { validateString(n.getChildAtIndex(1)); } } }
/** Processes trailing default and rest parameters. */ private void visitParamList(Node paramList, Node function) { Node insertSpot = null; Node block = function.getLastChild(); for (int i = 0; i < paramList.getChildCount(); i++) { Node param = paramList.getChildAtIndex(i); if (param.hasChildren()) { // default parameter param.setOptionalArg(true); Node defaultValue = param.removeFirstChild(); // Transpile to: param === undefined && (param = defaultValue); Node name = IR.name(param.getString()); Node undefined = IR.name("undefined"); Node stm = IR.exprResult( IR.and(IR.sheq(name, undefined), IR.assign(name.cloneNode(), defaultValue))); block.addChildAfter(stm.useSourceInfoIfMissingFromForTree(param), insertSpot); insertSpot = stm; compiler.reportCodeChange(); } else if (param.isRest()) { // rest parameter param.setType(Token.NAME); param.setVarArgs(true); // Transpile to: param = [].slice.call(arguments, i); Node newArr = IR.exprResult( IR.assign( IR.name(param.getString()), IR.call( IR.getprop( IR.getprop(IR.arraylit(), IR.string("slice")), IR.string("call")), IR.name("arguments"), IR.number(i)))); block.addChildAfter(newArr.useSourceInfoIfMissingFromForTree(param), insertSpot); compiler.reportCodeChange(); } } // For now, we are running transpilation before type-checking, so we'll // need to make sure changes don't invalidate the JSDoc annotations. // Therefore we keep the parameter list the same length and only initialize // the values if they are set to undefined. }
private void checkRequireCall(NodeTraversal t, Node callNode, Node parent) { Preconditions.checkState(callNode.isCall()); switch (parent.getType()) { case Token.EXPR_RESULT: return; case Token.GETPROP: if (parent.getParent().isName()) { checkRequireCall(t, callNode, parent.getParent()); return; } break; case Token.NAME: case Token.OBJECT_PATTERN: { Node declaration = parent.getParent(); if (declaration.getChildCount() != 1) { t.report(declaration, ONE_REQUIRE_PER_DECLARATION); } return; } } t.report(callNode, REQUIRE_NOT_AT_TOP_LEVEL); }
/** * Expressions such as [foo() * 10 * 20] generate parse trees where no node has two const children * ((foo() * 10) * 20), so performArithmeticOp() won't fold it -- tryFoldLeftChildOp() will. * Specifically, it folds associative expressions where: - The left child is also an associative * expression of the same time. - The right child is a constant NUMBER constant. - The left * child's right child is a NUMBER constant. */ private Node tryFoldLeftChildOp(Node n, Node left, Node right) { Token opType = n.getType(); Preconditions.checkState( (NodeUtil.isAssociative(opType) && NodeUtil.isCommutative(opType)) || n.isAdd()); Preconditions.checkState(!n.isAdd() || !NodeUtil.mayBeString(n, shouldUseTypes)); // Use getNumberValue to handle constants like "NaN" and "Infinity" // other values are converted to numbers elsewhere. Double rightValObj = NodeUtil.getNumberValue(right, shouldUseTypes); if (rightValObj != null && left.getType() == opType) { Preconditions.checkState(left.getChildCount() == 2); Node ll = left.getFirstChild(); Node lr = ll.getNext(); Node valueToCombine = ll; Node replacement = performArithmeticOp(opType, valueToCombine, right); if (replacement == null) { valueToCombine = lr; replacement = performArithmeticOp(opType, valueToCombine, right); } if (replacement != null) { // Remove the child that has been combined left.removeChild(valueToCombine); // Replace the left op with the remaining child. n.replaceChild(left, left.removeFirstChild()); // New "-Infinity" node need location info explicitly // added. replacement.useSourceInfoIfMissingFromForTree(right); n.replaceChild(right, replacement); reportCodeChange(); } } return n; }
@Override public void visit(NodeTraversal t, Node n, Node parent) { // VOID nodes appear when there are extra semicolons at the BLOCK level. // I've been unable to think of any cases where this indicates a bug, // and apparently some people like keeping these semicolons around, // so we'll allow it. if (n.isEmpty() || n.isComma()) { return; } if (parent == null) { return; } int pt = parent.getType(); if (pt == Token.COMMA) { Node gramps = parent.getParent(); if (gramps.isCall() && parent == gramps.getFirstChild()) { // Semantically, a direct call to eval is different from an indirect // call to an eval. See Ecma-262 S15.1.2.1. So it's ok for the first // expression to a comma to be a no-op if it's used to indirect // an eval. if (n == parent.getFirstChild() && parent.getChildCount() == 2 && n.getNext().isName() && "eval".equals(n.getNext().getString())) { return; } } if (n == parent.getLastChild()) { for (Node an : parent.getAncestors()) { int ancestorType = an.getType(); if (ancestorType == Token.COMMA) continue; if (ancestorType != Token.EXPR_RESULT && ancestorType != Token.BLOCK) return; else break; } } } else if (pt != Token.EXPR_RESULT && pt != Token.BLOCK) { if (pt == Token.FOR && parent.getChildCount() == 4 && (n == parent.getFirstChild() || n == parent.getFirstChild().getNext().getNext())) { // Fall through and look for warnings for the 1st and 3rd child // of a for. } else { return; // it might be ok to not have a side-effect } } boolean isSimpleOp = NodeUtil.isSimpleOperatorType(n.getType()); if (isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler())) { if (n.isQualifiedName() && n.getJSDocInfo() != null) { // This no-op statement was there so that JSDoc information could // be attached to the name. This check should not complain about it. return; } else if (n.isExprResult()) { // we already reported the problem when we visited the child. return; } String msg = "This code lacks side-effects. Is there a bug?"; if (n.isString()) { msg = "Is there a missing '+' on the previous line?"; } else if (isSimpleOp) { msg = "The result of the '" + Token.name(n.getType()).toLowerCase() + "' operator is not being used."; } t.getCompiler().report(t.makeError(n, level, USELESS_CODE_ERROR, msg)); // TODO(johnlenz): determine if it is necessary to // try to protect side-effect free statements as well. if (!NodeUtil.isStatement(n)) { problemNodes.add(n); } } }
/** * Validates the class definition and if valid, destructively extracts the class definition from * the AST. */ private ClassDefinition extractClassDefinition(Node callNode) { Node descriptor = NodeUtil.getArgumentForCallOrNew(callNode, 0); if (descriptor == null || !descriptor.isObjectLit()) { // report bad class definition compiler.report(JSError.make(callNode, POLYMER_DESCRIPTOR_NOT_VALID)); return null; } int paramCount = callNode.getChildCount() - 1; if (paramCount != 1) { compiler.report(JSError.make(callNode, POLYMER_UNEXPECTED_PARAMS)); return null; } Node elName = NodeUtil.getFirstPropMatchingKey(descriptor, "is"); if (elName == null) { compiler.report(JSError.make(callNode, POLYMER_MISSING_IS)); return null; } String elNameString = CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, elName.getString()); elNameString += "Element"; Node target; if (NodeUtil.isNameDeclaration(callNode.getParent().getParent())) { target = IR.name(callNode.getParent().getString()); } else if (callNode.getParent().isAssign()) { target = callNode.getParent().getFirstChild().cloneTree(); } else { target = IR.name(elNameString); } target.useSourceInfoIfMissingFrom(callNode); JSDocInfo classInfo = NodeUtil.getBestJSDocInfo(target); JSDocInfo ctorInfo = null; Node constructor = NodeUtil.getFirstPropMatchingKey(descriptor, "factoryImpl"); if (constructor == null) { constructor = IR.function(IR.name(""), IR.paramList(), IR.block()); constructor.useSourceInfoFromForTree(callNode); } else { ctorInfo = NodeUtil.getBestJSDocInfo(constructor); } Node baseClass = NodeUtil.getFirstPropMatchingKey(descriptor, "extends"); String nativeBaseElement = baseClass == null ? null : baseClass.getString(); Node behaviorArray = NodeUtil.getFirstPropMatchingKey(descriptor, "behaviors"); List<BehaviorDefinition> behaviors = extractBehaviors(behaviorArray); List<MemberDefinition> allProperties = new LinkedList<>(); for (BehaviorDefinition behavior : behaviors) { overwriteMembersIfPresent(allProperties, behavior.props); } overwriteMembersIfPresent(allProperties, extractProperties(descriptor)); ClassDefinition def = new ClassDefinition( target, descriptor, classInfo, new MemberDefinition(ctorInfo, null, constructor), nativeBaseElement, allProperties, behaviors); return def; }
/** * Tries to optimize all the arguments array access in this scope by assigning a name to each * element. * * @param scope scope of the function * @return true if any modification has been done to the AST */ private boolean tryReplaceArguments(Scope scope) { Node parametersList = scope.getRootNode().getSecondChild(); Preconditions.checkState(parametersList.isParamList()); // Keep track of rather this function modified the AST and needs to be // reported back to the compiler later. boolean changed = false; // Number of parameter that can be accessed without using the arguments // array. int numNamedParameter = parametersList.getChildCount(); // We want to guess what the highest index that has been access from the // arguments array. We will guess that it does not use anything index higher // than the named parameter list first until we see other wise. int highestIndex = numNamedParameter - 1; // Iterate through all the references to arguments array in the function to // determine the real highestIndex. for (Node ref : currentArgumentsAccess) { Node getElem = ref.getParent(); // Bail on anything but argument[c] access where c is a constant. // TODO(user): We might not need to bail out all the time, there might // be more cases that we can cover. if (!getElem.isGetElem() || ref != getElem.getFirstChild()) { return false; } Node index = ref.getNext(); // We have something like arguments[x] where x is not a constant. That // means at least one of the access is not known. if (!index.isNumber() || index.getDouble() < 0) { // TODO(user): Its possible not to give up just yet. The type // inference did a 'semi value propagation'. If we know that string // is never a subclass of the type of the index. We'd know that // it is never 'callee'. return false; // Give up. } Node getElemParent = getElem.getParent(); // When we have argument[0](), replacing it with a() is semantically // different if argument[0] is a function call that refers to 'this' if (getElemParent.isCall() && getElemParent.getFirstChild() == getElem) { // TODO(user): We can consider using .call() if aliasing that // argument allows shorter alias for other arguments. return false; } // Replace the highest index if we see an access that has a higher index // than all the one we saw before. int value = (int) index.getDouble(); if (value > highestIndex) { highestIndex = value; } } // Number of extra arguments we need. // For example: function() { arguments[3] } access index 3 so // it will need 4 extra named arguments to changed into: // function(a,b,c,d) { d }. int numExtraArgs = highestIndex - numNamedParameter + 1; // Temporary holds the new names as string for quick access later. String[] argNames = new String[numExtraArgs]; // Insert the formal parameter to the method's signature. // Example: function() --> function(r0, r1, r2) for (int i = 0; i < numExtraArgs; i++) { String name = getNewName(); argNames[i] = name; parametersList.addChildToBack(IR.name(name).useSourceInfoIfMissingFrom(parametersList)); changed = true; } // This loop performs the replacement of arguments[x] -> a if x is known. for (Node ref : currentArgumentsAccess) { Node index = ref.getNext(); // Skip if it is unknown. if (!index.isNumber()) { continue; } int value = (int) index.getDouble(); // Unnamed parameter. if (value >= numNamedParameter) { ref.getGrandparent() .replaceChild(ref.getParent(), IR.name(argNames[value - numNamedParameter])); } else { // Here, for no apparent reason, the user is accessing a named parameter // with arguments[idx]. We can replace it with the actual name for them. Node name = parametersList.getFirstChild(); // This is a linear search for the actual name from the signature. // It is not necessary to make this fast because chances are the user // will not deliberately write code like this. for (int i = 0; i < value; i++) { name = name.getNext(); } ref.getGrandparent().replaceChild(ref.getParent(), IR.name(name.getString())); } changed = true; } return changed; }
/** * 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 static boolean isAlwaysInlinable(Node fn) { Preconditions.checkArgument(fn.isFunction()); Node body = NodeUtil.getFunctionBody(fn); int numOfStmsInBody = body.getChildCount(); return numOfStmsInBody == 0 || numOfStmsInBody == 1 && body.getFirstChild().isReturn(); }