Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(379)

Unified Diff: sdk/lib/_internal/compiler/implementation/dart_backend/placeholder_collector.dart

Issue 694353007: Move dart2js from sdk/lib/_internal/compiler to pkg/compiler (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: sdk/lib/_internal/compiler/implementation/dart_backend/placeholder_collector.dart
diff --git a/sdk/lib/_internal/compiler/implementation/dart_backend/placeholder_collector.dart b/sdk/lib/_internal/compiler/implementation/dart_backend/placeholder_collector.dart
deleted file mode 100644
index 2b98939b1390dde257d3a52d36c1612da639ab46..0000000000000000000000000000000000000000
--- a/sdk/lib/_internal/compiler/implementation/dart_backend/placeholder_collector.dart
+++ /dev/null
@@ -1,701 +0,0 @@
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-part of dart_backend;
-
-class LocalPlaceholder {
- final String identifier;
- final Set<Node> nodes;
- LocalPlaceholder(this.identifier) : nodes = new Set<Node>();
- int get hashCode => identifier.hashCode;
- String toString() =>
- 'local_placeholder[id($identifier), nodes($nodes)]';
-}
-
-class FunctionScope {
- final Set<String> parameterIdentifiers;
- final Set<LocalPlaceholder> localPlaceholders;
- FunctionScope()
- : parameterIdentifiers = new Set<String>(),
- localPlaceholders = new Set<LocalPlaceholder>();
- void registerParameter(Identifier node) {
- parameterIdentifiers.add(node.source);
- }
-}
-
-class ConstructorPlaceholder {
- final Identifier node;
- final ConstructorElement element;
-
- ConstructorPlaceholder(this.node, this.element);
-}
-
-class DeclarationTypePlaceholder {
- final TypeAnnotation typeNode;
- final bool requiresVar;
- DeclarationTypePlaceholder(this.typeNode, this.requiresVar);
-}
-
-class SendVisitor extends ResolvedVisitor {
- final PlaceholderCollector collector;
-
- SendVisitor(this.collector, TreeElements elements)
- : super(elements);
-
- visitOperatorSend(Send node) {
- }
-
- visitForeignSend(Send node) {}
-
- visitSuperSend(Send node) {
- Element element = elements[node];
- if (element != null && element.isConstructor) {
- collector.tryMakeConstructorPlaceholder(node, element);
- } else {
- collector.tryMakeMemberPlaceholder(node.selector);
- }
- }
-
- visitDynamicSend(Send node) {
- final element = elements[node];
- if (element == null || !element.isErroneous) {
- collector.tryMakeMemberPlaceholder(node.selector);
- }
- }
-
- visitClosureSend(Send node) {
- final element = elements[node];
- if (element != null) {
- collector.tryMakeLocalPlaceholder(element, node.selector);
- }
- }
-
- visitGetterSend(Send node) {
- final element = elements[node];
- // element == null means dynamic property access.
- if (element == null) {
- collector.tryMakeMemberPlaceholder(node.selector);
- } else if (element.isErroneous) {
- collector.makeUnresolvedPlaceholder(node);
- return;
- } else if (element.isPrefix) {
- // Node is prefix part in case of source 'lib.somesetter = 5;'
- collector.makeErasePrefixPlaceholder(node);
- } else if (Elements.isStaticOrTopLevel(element)) {
- // Unqualified or prefixed top level or static.
- collector.makeElementPlaceholder(node.selector, element);
- } else if (!element.isTopLevel) {
- if (element.isInstanceMember) {
- collector.tryMakeMemberPlaceholder(node.selector);
- } else {
- // May get FunctionExpression here in selector
- // in case of A(int this.f());
- if (node.selector is Identifier) {
- collector.tryMakeLocalPlaceholder(element, node.selector);
- } else {
- assert(node.selector is FunctionExpression);
- }
- }
- }
- }
-
- visitAssert(node) {
- visitStaticSend(node);
- }
-
- visitStaticSend(Send node) {
- Element element = elements[node];
- collector.mirrorRenamer.registerStaticSend(
- collector.currentElement, element, node);
-
- if (Elements.isUnresolved(element)
- || elements.isAssert(node)
- || element.isDeferredLoaderGetter) {
- return;
- }
- if (element.isConstructor || element.isFactoryConstructor) {
- // Rename named constructor in redirection position:
- // class C { C.named(); C.redirecting() : this.named(); }
- if (node.receiver is Identifier
- && node.receiver.asIdentifier().isThis()) {
- assert(node.selector is Identifier);
- collector.tryMakeConstructorPlaceholder(node, element);
- }
- return;
- }
- collector.makeElementPlaceholder(node.selector, element);
- // Another ugly case: <lib prefix>.<top level> is represented as
- // receiver: lib prefix, selector: top level.
- if (element.isTopLevel && node.receiver != null) {
- assert(elements[node.receiver].isPrefix);
- // Hack: putting null into map overrides receiver of original node.
- collector.makeErasePrefixPlaceholder(node.receiver);
- }
- }
-
- internalError(String reason, {Node node}) {
- collector.internalError(reason, node: node);
- }
-
- visitTypePrefixSend(Send node) {
- collector.makeElementPlaceholder(node, elements[node]);
- }
-
- visitTypeLiteralSend(Send node) {
- DartType type = elements.getTypeLiteralType(node);
- if (!type.isDynamic) {
- if (type is TypeVariableType) {
- collector.makeTypeVariablePlaceholder(node.selector, type);
- } else {
- collector.makeTypePlaceholder(node.selector, type);
- }
- }
- }
-}
-
-class PlaceholderCollector extends Visitor {
- final DiagnosticListener listener;
- final MirrorRenamer mirrorRenamer;
- final FunctionElement mainFunction;
- final Set<String> fixedMemberNames; // member names which cannot be renamed.
- final Map<Element, ElementAst> elementAsts;
- final Set<Node> prefixNodesToErase = new Set<Node>();
- final Set<Node> unresolvedNodes = new Set<Node>();
- final Map<Element, Set<Node>> elementNodes = new Map<Element, Set<Node>>();
- final Map<FunctionElement, FunctionScope> functionScopes
- = new Map<FunctionElement, FunctionScope>();
- final Map<LibraryElement, Set<Identifier>> privateNodes =
- new Map<LibraryElement, Set<Identifier>>();
- final List<DeclarationTypePlaceholder> declarationTypePlaceholders
- = new List<DeclarationTypePlaceholder>();
- final Map<String, Set<Identifier>> memberPlaceholders
- = new Map<String, Set<Identifier>>();
- final List<ConstructorPlaceholder> constructorPlaceholders
- = new List<ConstructorPlaceholder>();
- Map<String, LocalPlaceholder> currentLocalPlaceholders;
- Element currentElement;
- FunctionElement topmostEnclosingFunction;
- TreeElements treeElements;
-
- get currentFunctionScope => functionScopes.putIfAbsent(
- topmostEnclosingFunction, () => new FunctionScope());
-
- PlaceholderCollector(this.listener, this.mirrorRenamer,
- this.fixedMemberNames, this.elementAsts,
- this.mainFunction);
-
- void collectFunctionDeclarationPlaceholders(
- FunctionElement element, FunctionExpression node) {
- if (element.isConstructor) {
- ConstructorElement constructor = element;
- DartType type = element.enclosingClass.thisType.asRaw();
- tryMakeConstructorPlaceholder(node.name, element);
- RedirectingFactoryBody bodyAsRedirectingFactoryBody =
- node.body.asRedirectingFactoryBody();
- if (bodyAsRedirectingFactoryBody != null) {
- // Factory redirection.
- FunctionElement redirectTarget = constructor.immediateRedirectionTarget;
- assert(redirectTarget != null && redirectTarget != element);
- type = redirectTarget.enclosingClass.thisType.asRaw();
- tryMakeConstructorPlaceholder(
- bodyAsRedirectingFactoryBody.constructorReference,
- redirectTarget);
- }
- } else if (Elements.isStaticOrTopLevel(element)) {
- // Note: this code should only rename private identifiers for class'
- // fields/getters/setters/methods. Top-level identifiers are renamed
- // just to escape conflicts and that should be enough as we shouldn't
- // be able to resolve private identifiers for other libraries.
- makeElementPlaceholder(node.name, element);
- } else if (element.isClassMember) {
- if (node.name is Identifier) {
- tryMakeMemberPlaceholder(node.name);
- } else {
- assert(node.name.asSend().isOperator);
- }
- }
- }
-
- void collectFieldDeclarationPlaceholders(Element element, Node node) {
- Identifier name = node is Identifier ? node : node.asSend().selector;
- if (Elements.isStaticOrTopLevel(element)) {
- makeElementPlaceholder(name, element);
- } else if (Elements.isInstanceField(element)) {
- tryMakeMemberPlaceholder(name);
- }
- }
-
- void collect(Element element) {
- this.currentElement = element;
- this.topmostEnclosingFunction = null;
- final ElementAst elementAst = elementAsts[element];
- this.treeElements = elementAst.treeElements;
- Node elementNode = elementAst.ast;
- if (element is FunctionElement) {
- collectFunctionDeclarationPlaceholders(element, elementNode);
- } else if (element is VariableElement) {
- VariableDefinitions definitions = elementNode;
- Node definition = definitions.definitions.nodes.head;
- collectFieldDeclarationPlaceholders(element, definition);
- makeVarDeclarationTypePlaceholder(definitions);
- } else {
- assert(element is ClassElement || element is TypedefElement);
- }
- currentLocalPlaceholders = new Map<String, LocalPlaceholder>();
- if (!(element is ConstructorElement && element.isRedirectingFactory)) {
- // Do not visit the body of redirecting factories.
- listener.withCurrentElement(element, () {
- elementNode.accept(this);
- });
- }
- }
-
- // TODO(karlklose): should we create placeholders for these?
- bool isTypedefParameter(Element element) {
- return element != null &&
- element.enclosingElement != null &&
- element.enclosingElement.isTypedef;
- }
-
- void tryMakeLocalPlaceholder(Element element, Identifier node) {
- bool isNamedOptionalParameter() {
- FunctionTypedElement function = element.enclosingElement;
- FunctionSignature signature = function.functionSignature;
- if (!signature.optionalParametersAreNamed) return false;
- for (Element parameter in signature.optionalParameters) {
- if (identical(parameter, element)) return true;
- }
- return false;
- }
- if (element.isParameter && !isTypedefParameter(element) &&
- isNamedOptionalParameter()) {
- currentFunctionScope.registerParameter(node);
- } else if (Elements.isLocal(element) && !isTypedefParameter(element)) {
- makeLocalPlaceholder(node);
- }
- }
-
- void tryMakeMemberPlaceholder(Identifier node) {
- assert(node != null);
- if (node is Operator) return;
- final identifier = node.source;
- if (fixedMemberNames.contains(identifier)) return;
- memberPlaceholders.putIfAbsent(
- identifier, () => new Set<Identifier>()).add(node);
- }
-
- void makeTypePlaceholder(Node node, DartType type) {
- Send send = node.asSend();
- if (send != null) {
- // Prefix.
- assert(send.receiver is Identifier);
- assert(send.selector is Identifier);
- makeErasePrefixPlaceholder(send.receiver);
- node = send.selector;
- }
- makeElementPlaceholder(node, type.element);
- }
-
- void makeTypeVariablePlaceholder(Node node, TypeVariableType type) {
- Send send = node.asSend();
- if (send != null) {
- // Prefix.
- assert(send.receiver is Identifier);
- assert(send.selector is Identifier);
- makeErasePrefixPlaceholder(send.receiver);
- node = send.selector;
- }
- tryMakeMemberPlaceholder(node);
- }
-
- void makeOmitDeclarationTypePlaceholder(TypeAnnotation type) {
- if (type == null) return;
- declarationTypePlaceholders.add(
- new DeclarationTypePlaceholder(type, false));
- }
-
- void makeVarDeclarationTypePlaceholder(VariableDefinitions node) {
- // TODO(smok): Maybe instead of calling this method and
- // makeDeclaratioTypePlaceholder have type declaration placeholder
- // collector logic in visitVariableDefinitions when resolver becomes better
- // and/or catch syntax changes.
- if (node.type == null) return;
- Element definitionElement = treeElements[node.definitions.nodes.head];
- bool requiresVar = !node.modifiers.isFinalOrConst;
- declarationTypePlaceholders.add(
- new DeclarationTypePlaceholder(node.type, requiresVar));
- }
-
- /// Marks [node] to be erased in the output.
- /// This is done for library prefixes because they are not used in the output
- /// because all imports are flattened and conflicts are renamed away.
- void makeErasePrefixPlaceholder(Node node) {
- assert(node is Identifier || node is Send);
- prefixNodesToErase.add(node);
- }
-
- void makeElementPlaceholder(Node node, Element element) {
- assert(node != null);
- assert(element != null);
- LibraryElement library = element.library;
- if (identical(element, mainFunction)) return;
- if (library.isDartCore) return;
-
- if (library.isPlatformLibrary && !element.isTopLevel) {
- return;
- }
- if (element.isGetter || element.isSetter) {
- element = (element as FunctionElement).abstractField;
- }
- elementNodes.putIfAbsent(element, () => new Set<Node>()).add(node);
- }
-
- /// Marks [node] to be renamed per-library if it names an instance member
- /// and has a private name.
- void tryMakePrivateIdentifier(Node node, Element element) {
- if (node is Identifier &&
- !Elements.isStaticOrTopLevel(element) &&
- !Elements.isLocal(element) &&
- isPrivateName(node.source)) {
- privateNodes.putIfAbsent(
- currentElement.library, () => new Set<Identifier>()).add(node);
- }
- }
-
- void makeUnresolvedPlaceholder(Node node) {
- unresolvedNodes.add(node);
- }
-
- void makeLocalPlaceholder(Identifier identifier) {
- LocalPlaceholder getLocalPlaceholder() {
- String name = identifier.source;
- return currentLocalPlaceholders.putIfAbsent(name, () {
- LocalPlaceholder localPlaceholder = new LocalPlaceholder(name);
- currentFunctionScope.localPlaceholders.add(localPlaceholder);
- return localPlaceholder;
- });
- }
- getLocalPlaceholder().nodes.add(identifier);
- }
-
- /// Finds the first constructor on the chain of definingConstructor from
- /// [element] that is not in a synthetic class.
- Element findDefiningConstructor(ConstructorElement element) {
- while (element.definingConstructor != null) {
- element = element.definingConstructor;
- }
- return element;
- }
-
- void tryMakeConstructorPlaceholder(Node node, ConstructorElement element) {
- if (Elements.isUnresolved(element)) {
- makeUnresolvedPlaceholder(node);
- return;
- }
- // A library prefix.
- Node prefix;
- // The name of the class with the constructor.
- Node className;
- // Will be null for unnamed constructors.
- Identifier constructorName;
- // First deconstruct the constructor, there are 4 possibilities:
- // ClassName()
- // prefix.ClassName()
- // ClassName.constructorName()
- // prefix.ClassName.constructorName()
- if (node is Send) {
- if (node.receiver is Send) {
- Send receiver = node.receiver;
- // prefix.ClassName.constructorName()
- assert(treeElements[receiver.receiver] != null &&
- treeElements[receiver.receiver].isPrefix);
- prefix = receiver.receiver;
- className = receiver.selector;
- constructorName = node.selector;
- } else {
- Element receiverElement = treeElements[node.receiver];
- if (receiverElement != null && receiverElement.isPrefix) {
- // prefix.ClassName()
- prefix = node.receiver;
- className = node.selector;
- } else {
- // ClassName.constructorName()
- className = node.receiver;
- constructorName = node.selector;
- }
- }
- } else {
- // ClassName()
- className = node;
- }
-
- if (prefix != null) {
- makeErasePrefixPlaceholder(prefix);
- }
-
- if (className is TypeAnnotation) {
- visitTypeAnnotation(className);
- } else if (Elements.isUnresolved(element)) {
- // We handle unresolved nodes elsewhere.
- } else if (className.isThis() || className.isSuper()) {
- // Do not rename super and this.
- } else if (className is Identifier) {
- makeElementPlaceholder(className, element.contextClass);
- } else {
- throw "Bad type of constructor name $className";
- }
-
- if (constructorName != null) {
- Element definingConstructor = findDefiningConstructor(element);
- constructorPlaceholders.add(new ConstructorPlaceholder(constructorName,
- definingConstructor));
- tryMakePrivateIdentifier(constructorName, element);
- }
- }
-
- void internalError(String reason, {Node node}) {
- listener.internalError(node, reason);
- }
-
- visit(Node node) => (node == null) ? null : node.accept(this);
-
- visitNode(Node node) { node.visitChildren(this); } // We must go deeper.
-
- visitNewExpression(NewExpression node) {
- Send send = node.send;
- DartType type = treeElements.getType(node);
- assert(type != null);
- Element constructor = treeElements[send];
- assert(constructor != null);
- assert(send.receiver == null);
- if (!Elements.isErroneousElement(constructor)) {
- tryMakeConstructorPlaceholder(node.send.selector, constructor);
- // TODO(smok): Should this be in visitNamedArgument?
- // Field names can be exposed as names of optional arguments, e.g.
- // class C {
- // final field;
- // C([this.field]);
- // }
- // Do not forget to rename them as well.
- FunctionElement constructorFunction = constructor;
- Link<Element> optionalParameters =
- constructorFunction.functionSignature.optionalParameters;
- for (final argument in send.argumentsNode) {
- NamedArgument named = argument.asNamedArgument();
- if (named == null) continue;
- Identifier name = named.name;
- String nameAsString = name.source;
- for (final parameter in optionalParameters) {
- if (parameter.isInitializingFormal) {
- if (parameter.name == nameAsString) {
- tryMakeMemberPlaceholder(name);
- break;
- }
- }
- }
- }
- } else {
- makeUnresolvedPlaceholder(node.send.selector);
- }
- visit(node.send.argumentsNode);
- }
-
- visitSend(Send send) {
- Element element = treeElements[send];
- tryMakePrivateIdentifier(send.selector, element);
- new SendVisitor(this, treeElements).visitSend(send);
- send.visitChildren(this);
- }
-
- visitSendSet(SendSet send) {
- Element element = treeElements[send];
- if (Elements.isErroneousElement(element)) {
- // Complicated case: constructs like receiver.selector++ can resolve
- // to ErroneousElement. Fortunately, receiver.selector still
- // can be resoved via treeElements[send.selector], that's all
- // that is needed to rename the construct properly.
- element = treeElements[send.selector];
- }
- tryMakePrivateIdentifier(send.selector, element);
- if (element == null) {
- if (send.receiver != null) tryMakeMemberPlaceholder(send.selector);
- } else if (!element.isErroneous) {
- if (Elements.isStaticOrTopLevel(element)) {
- // TODO(smok): Worth investigating why sometimes we get getter/setter
- // here and sometimes abstract field.
- assert(element.isClass || element is VariableElement ||
- element.isAccessor || element.isAbstractField ||
- element.isFunction || element.isTypedef ||
- element is TypeVariableElement);
- makeElementPlaceholder(send.selector, element);
- } else {
- Identifier identifier = send.selector.asIdentifier();
- if (identifier == null) {
- // Handle optional function expression parameters with default values.
- identifier = send.selector.asFunctionExpression().name;
- }
- if (Elements.isInstanceField(element)) {
- tryMakeMemberPlaceholder(identifier);
- } else {
- tryMakeLocalPlaceholder(element, identifier);
- }
- }
- }
- send.visitChildren(this);
- }
-
- visitTypeAnnotation(TypeAnnotation node) {
- final type = treeElements.getType(node);
- assert(invariant(node, type != null,
- message: "Missing type for type annotation: $treeElements"));
- if (!type.isVoid) {
- if (!type.treatAsDynamic) {
- if (type is TypeVariableType) {
- makeTypeVariablePlaceholder(node.typeName, type);
- } else {
- makeTypePlaceholder(node.typeName, type);
- }
- } else if (!type.isDynamic) {
- makeUnresolvedPlaceholder(node.typeName);
- }
- }
- // Visit only type arguments, otherwise in case of lib.Class type
- // annotation typeName is Send and we go to visitGetterSend, as a result
- // "Class" is added to member placeholders.
- visit(node.typeArguments);
- }
-
- visitVariableDefinitions(VariableDefinitions node) {
- // Collect only local placeholders.
- for (Node definition in node.definitions.nodes) {
- Element definitionElement = treeElements[definition];
- // definitionElement may be null if we're inside variable definitions
- // of a function that is a parameter of another function.
- // TODO(smok): Fix this when resolver correctly deals with
- // such cases.
- if (definitionElement == null) continue;
-
- Send send = definition.asSend();
- Identifier identifier = definition is Identifier
- ? definition
- : definition is Send
- ? (send.selector is Identifier
- ? send.selector
- : null)
- : null;
-
- tryMakePrivateIdentifier(identifier, definitionElement);
-
- if (send != null) {
- // May get FunctionExpression here in definition.selector
- // in case of A(int this.f());
- if (send.selector is Identifier) {
- if (definitionElement.isInitializingFormal) {
- tryMakeMemberPlaceholder(send.selector);
- } else {
- tryMakeLocalPlaceholder(definitionElement, send.selector);
- }
- } else {
- assert(send.selector is FunctionExpression);
- if (definitionElement.isInitializingFormal) {
- tryMakeMemberPlaceholder(
- send.selector.asFunctionExpression().name);
- }
- }
- } else if (definition is Identifier) {
- tryMakeLocalPlaceholder(definitionElement, definition);
- } else if (definition is FunctionExpression) {
- // Skip, it will be processed in visitFunctionExpression.
- } else {
- internalError('Unexpected definition structure $definition');
- }
- }
- node.visitChildren(this);
- }
-
- visitFunctionExpression(FunctionExpression node) {
- bool isKeyword(Identifier id) =>
- id != null && Keyword.keywords[id.source] != null;
-
- Element element = treeElements[node];
- // May get null here in case of A(int this.f());
- if (element != null) {
- tryMakePrivateIdentifier(node.name, element);
-
- // Rename only local functions.
- if (topmostEnclosingFunction == null) {
- topmostEnclosingFunction = element;
- }
- if (!identical(element, currentElement)) {
- if (node.name != null) {
- assert(node.name is Identifier);
- tryMakeLocalPlaceholder(element, node.name);
- }
- }
- }
-
- node.visitChildren(this);
-
- // Make sure we don't omit return type of methods which names are
- // identifiers, because the following works fine:
- // int interface() => 1;
- // But omitting 'int' makes VM unhappy.
- // TODO(smok): Remove it when http://dartbug.com/5278 is fixed.
- if (node.name == null || !isKeyword(node.name.asIdentifier())) {
- makeOmitDeclarationTypePlaceholder(node.returnType);
- }
- collectFunctionParameters(node.parameters);
- }
-
- void collectFunctionParameters(NodeList parameters) {
- if (parameters == null) return;
- for (Node parameter in parameters.nodes) {
- if (parameter is NodeList) {
- // Optional parameter list.
- collectFunctionParameters(parameter);
- } else {
- assert(parameter is VariableDefinitions);
- makeOmitDeclarationTypePlaceholder(
- parameter.asVariableDefinitions().type);
- }
- }
- }
-
- visitClassNode(ClassNode node) {
- ClassElement classElement = currentElement;
- makeElementPlaceholder(node.name, classElement);
- node.visitChildren(this);
- }
-
- visitNamedMixinApplication(NamedMixinApplication node) {
- ClassElement classElement = currentElement;
- makeElementPlaceholder(node.name, classElement);
- node.visitChildren(this);
- }
-
- visitTypeVariable(TypeVariable node) {
- DartType type = treeElements.getType(node);
- assert(invariant(node, type != null,
- message: "Missing type for type variable: $treeElements"));
- makeTypeVariablePlaceholder(node.name, type);
- node.visitChildren(this);
- }
-
- visitTypedef(Typedef node) {
- assert(currentElement is TypedefElement);
- makeElementPlaceholder(node.name, currentElement);
- node.visitChildren(this);
- makeOmitDeclarationTypePlaceholder(node.returnType);
- collectFunctionParameters(node.formals);
- }
-
- visitBlock(Block node) {
- for (Node statement in node.statements.nodes) {
- if (statement is VariableDefinitions) {
- makeVarDeclarationTypePlaceholder(statement);
- }
- }
- node.visitChildren(this);
- }
-}

Powered by Google App Engine
This is Rietveld 408576698