anchor.diff:          print('  ${object1}');
           print('  found ${object2}, expected ${expectedValue}');
           print('at');
  -        print('  ${js.nodeToString(stack1.head)}');
  -        print('  ${js.nodeToString(stack2.head)}');
  +        print('  ${js.nodeToString(node1)}');
  +        print('  ${js.nodeToString(node2)}');
         }
         return expectedValue == object2;
       } else {
  
positive.code:   }
  
    @override
    bool visitProgram(js.Program node) {
      if (peek() is! js.Program) return failAt(node, peek());
      js.Program other = peek();
      return testNodeLists(node.body, other.body);
    }
  
    @override
    bool visitInterpolatedDeclaration(js.InterpolatedDeclaration node) {
      if (peek() is! js.InterpolatedDeclaration) return failAt(node, peek());
      js.InterpolatedDeclaration other = peek();
      return testValues(node.nameOrPosition, other.nameOrPosition);
    }
  
    @override
    bool visitInterpolatedStatement(js.InterpolatedStatement node) {
      if (peek() is! js.InterpolatedStatement) return failAt(node, peek());
      js.InterpolatedStatement other = peek();
      return testValues(node.nameOrPosition, other.nameOrPosition);
    }
  
    @override
    bool visitInterpolatedSelector(js.InterpolatedSelector node) {
      if (peek() is! js.InterpolatedSelector) return failAt(node, peek());
      js.InterpolatedSelector other = peek();
      return testValues(node.nameOrPosition, other.nameOrPosition);
    }
  
    @override
    bool visitInterpolatedParameter(js.InterpolatedParameter node) {
      if (peek() is! js.InterpolatedParameter) return failAt(node, peek());
      js.InterpolatedParameter other = peek();
      return testValues(node.nameOrPosition, other.nameOrPosition);
    }
  
    @override
    bool visitInterpolatedLiteral(js.InterpolatedLiteral node) {
      if (peek() is! js.InterpolatedLiteral) return failAt(node, peek());
      js.InterpolatedLiteral other = peek();
      return testValues(node.nameOrPosition, other.nameOrPosition);
    }
  
    @override
    bool visitInterpolatedExpression(js.InterpolatedExpression node) {
      if (peek() is! js.InterpolatedExpression) return failAt(node, peek());
      js.InterpolatedExpression other = peek();
      return testValues(node.nameOrPosition, other.nameOrPosition);
    }
  
    @override
    bool visitComment(js.Comment node) {
      if (peek() is! js.Comment) return failAt(node, peek());
      js.Comment other = peek();
      return testValues(node.comment, other.comment);
    }
  
    @override
    bool visitAwait(js.Await node) {
      if (peek() is! js.Await) return failAt(node, peek());
      js.Await other = peek();
      return testNodes(node.expression, other.expression);
    }
  
    @override
    bool visitRegExpLiteral(js.RegExpLiteral node) {
      if (peek() is! js.RegExpLiteral) return failAt(node, peek());
      js.RegExpLiteral other = peek();
      return testValues(node.pattern, other.pattern);
    }
  
    @override
    bool visitProperty(js.Property node) {
      if (peek() is! js.Property) return failAt(node, peek());
      js.Property other = peek();
      return testNodes(node.name, other.name) &&
          testNodes(node.value, other.value);
    }
  
    @override
    bool visitObjectInitializer(js.ObjectInitializer node) {
      if (peek() is! js.ObjectInitializer) return failAt(node, peek());
      js.ObjectInitializer other = peek();
      return testNodeLists(node.properties, other.properties);
    }
  
    @override
    bool visitArrayHole(js.ArrayHole node) {
      if (peek() is! js.ArrayHole) return failAt(node, peek());
      return true;
    }
  
    @override
    bool visitArrayInitializer(js.ArrayInitializer node) {
      if (peek() is! js.ArrayInitializer) return failAt(node, peek());
      js.ArrayInitializer other = peek();
      return testNodeLists(node.elements, other.elements);
    }
  
    @override
    bool visitName(js.Name node) {
      if (peek() is! js.Name) return failAt(node, peek());
      js.Name other = peek();
      return testValues(node.key, other.key);
    }
  
    @override
    bool visitStringConcatenation(js.StringConcatenation node) {
      if (peek() is! js.StringConcatenation) return failAt(node, peek());
      js.StringConcatenation other = peek();
      return testNodeLists(node.parts, other.parts);
    }
  
    @override
    bool visitLiteralNull(js.LiteralNull node) {
      if (peek() is! js.LiteralNull) return failAt(node, peek());
      return true;
    }
  
    @override
    bool visitLiteralNumber(js.LiteralNumber node) {
      if (peek() is! js.LiteralNumber) return failAt(node, peek());
      js.LiteralNumber other = peek();
      return testValues(node.value, other.value);
    }
  
    @override
    bool visitLiteralString(js.LiteralString node) {
      if (peek() is! js.LiteralString) return failAt(node, peek());
      js.LiteralString other = peek();
      return testValues(node.value, other.value);
    }
  
    @override
    bool visitLiteralBool(js.LiteralBool node) {
      if (peek() is! js.LiteralBool) return failAt(node, peek());
      js.LiteralBool other = peek();
      return testValues(node.value, other.value);
    }
  
    @override
    bool visitDeferredString(js.DeferredString node) {
      if (peek() is! js.DeferredString) return failAt(node, peek());
      js.DeferredString other = peek();
      return testValues(node.value, other.value);
    }
  
    @override
    bool visitDeferredNumber(js.DeferredNumber node) {
      if (peek() is! js.DeferredNumber) return failAt(node, peek());
      js.DeferredNumber other = peek();
      return testValues(node.value, other.value);
    }
  
    @override
    bool visitDeferredExpression(js.DeferredExpression node) {
      if (peek() is! js.DeferredExpression) return failAt(node, peek());
      js.DeferredExpression other = peek();
      return testNodes(node.value, other.value);
    }
  
    @override
    bool visitFun(js.Fun node) {
      if (peek() is! js.Fun) return failAt(node, peek());
      js.Fun other = peek();
      return testNodeLists(node.params, other.params) &&
          testNodes(node.body, other.body) &&
          testValues(node.asyncModifier, other.asyncModifier);
    }
  
    @override
    bool visitNamedFunction(js.NamedFunction node) {
      if (peek() is! js.NamedFunction) return failAt(node, peek());
      js.NamedFunction other = peek();
      return testNodes(node.name, other.name) &&
          testNodes(node.function, other.function);
    }
  
    @override
    bool visitAccess(js.PropertyAccess node) {
      if (peek() is! js.PropertyAccess) return failAt(node, peek());
      js.PropertyAccess other = peek();
      return testNodes(node.receiver, other.receiver) &&
          testNodes(node.selector, other.selector);
    }
  
    @override
    bool visitParameter(js.Parameter node) {
      if (peek() is! js.Parameter) return failAt(node, peek());
      js.Parameter other = peek();
      return testValues(node.name, other.name);
    }
  
    @override
    bool visitVariableDeclaration(js.VariableDeclaration node) {
      if (peek() is! js.VariableDeclaration) return failAt(node, peek());
      js.VariableDeclaration other = peek();
      return testValues(node.name, other.name) &&
          testValues(node.allowRename, other.allowRename);
    }
  
    @override
    bool visitThis(js.This node) {
      if (peek() is! js.This) return failAt(node, peek());
      return true;
    }
  
    @override
    bool visitVariableUse(js.VariableUse node) {
      if (peek() is! js.VariableUse) return failAt(node, peek());
      js.VariableUse other = peek();
      return testValues(node.name, other.name);
    }
  
    @override
    bool visitPostfix(js.Postfix node) {
      if (peek() is! js.Postfix) return failAt(node, peek());
      js.Postfix other = peek();
      return testValues(node.op, other.op) &&
          testNodes(node.argument, other.argument);
    }
  
    @override
    bool visitPrefix(js.Prefix node) {
      if (peek() is! js.Prefix) return failAt(node, peek());
      js.Prefix other = peek();
      return testValues(node.op, other.op) &&
          testNodes(node.argument, other.argument);
    }
  
    @override
    bool visitBinary(js.Binary node) {
      if (peek() is! js.Binary) return failAt(node, peek());
      js.Binary other = peek();
      return testNodes(node.left, other.left) &&
          testValues(node.op, other.op) &&
          testNodes(node.right, other.right);
    }
  
    @override
    bool visitCall(js.Call node) {
      if (peek() is! js.Call) return failAt(node, peek());
      js.Call other = peek();
      return testNodes(node.target, other.target) &&
          testNodeLists(node.arguments, other.arguments);
    }
  
    @override
    bool visitNew(js.New node) {
      if (peek() is! js.New) return failAt(node, peek());
      js.New other = peek();
      return testNodes(node.target, other.target) &&
          testNodeLists(node.arguments, other.arguments);
    }
  
    @override
    bool visitConditional(js.Conditional node) {
      if (peek() is! js.Conditional) return failAt(node, peek());
      js.Conditional other = peek();
      return testNodes(node.condition, other.condition) &&
          testNodes(node.then, other.then) &&
          testNodes(node.otherwise, other.otherwise);
    }
  
    @override
    bool visitVariableInitialization(js.VariableInitialization node) {
      if (peek() is! js.VariableInitialization) return failAt(node, peek());
      js.VariableInitialization other = peek();
      return testNodes(node.declaration, other.declaration) &&
          testNodes(node.leftHandSide, other.leftHandSide) &&
          testValues(node.op, other.op) &&
          testNodes(node.value, other.value);
    }
  
    @override
    bool visitAssignment(js.Assignment node) {
      if (peek() is! js.Assignment) return failAt(node, peek());
      js.Assignment other = peek();
      return testNodes(node.leftHandSide, other.leftHandSide) &&
          testValues(node.op, other.op) &&
          testNodes(node.value, other.value);
    }
  
    @override
    bool visitVariableDeclarationList(js.VariableDeclarationList node) {
      if (peek() is! js.VariableDeclarationList) return failAt(node, peek());
      js.VariableDeclarationList other = peek();
      return testNodeLists(node.declarations, other.declarations);
    }
  
    @override
    bool visitLiteralExpression(js.LiteralExpression node) {
      if (peek() is! js.LiteralExpression) return failAt(node, peek());
      js.LiteralExpression other = peek();
      return testValues(node.template, other.template) &&
          testNodeLists(node.inputs, other.inputs);
    }
  
    @override
    bool visitDartYield(js.DartYield node) {
      if (peek() is! js.DartYield) return failAt(node, peek());
      js.DartYield other = peek();
      return testNodes(node.expression, other.expression) &&
          testValues(node.hasStar, other.hasStar);
    }
  
    @override
    bool visitLiteralStatement(js.LiteralStatement node) {
      if (peek() is! js.LiteralStatement) return failAt(node, peek());
      js.LiteralStatement other = peek();
      return testValues(node.code, other.code);
    }
  
    @override
    bool visitLabeledStatement(js.LabeledStatement node) {
      if (peek() is! js.LabeledStatement) return failAt(node, peek());
      js.LabeledStatement other = peek();
      return testLabels(node.label, other.label) &&
          testNodes(node.body, other.body);
    }
  
    @override
    bool visitFunctionDeclaration(js.FunctionDeclaration node) {
      if (peek() is! js.FunctionDeclaration) return failAt(node, peek());
      js.FunctionDeclaration other = peek();
      return testNodes(node.name, other.name) &&
          testNodes(node.function, other.function);
    }
  
    @override
    bool visitDefault(js.Default node) {
      if (peek() is! js.Default) return failAt(node, peek());
      js.Default other = peek();
      return testNodes(node.body, other.body);
    }
  
    @override
    bool visitCase(js.Case node) {
      if (peek() is! js.Case) return failAt(node, peek());
      js.Case other = peek();
      return testNodes(node.expression, other.expression) &&
          testNodes(node.body, other.body);
    }
  
    @override
    bool visitSwitch(js.Switch node) {
      if (peek() is! js.Switch) return failAt(node, peek());
      js.Switch other = peek();
      return testNodes(node.key, other.key) &&
          testNodeLists(node.cases, other.cases);
    }
  
    @override
    bool visitCatch(js.Catch node) {
      if (peek() is! js.Catch) return failAt(node, peek());
      js.Catch other = peek();
      return testNodes(node.declaration, other.declaration) &&
          testNodes(node.body, other.body);
    }
  
    @override
    bool visitTry(js.Try node) {
      if (peek() is! js.Try) return failAt(node, peek());
      js.Try other = peek();
      return testNodes(node.body, other.body) &&
          testNodes(node.catchPart, other.catchPart) &&
          testNodes(node.finallyPart, other.finallyPart);
    }
  
    @override
    bool visitThrow(js.Throw node) {
      if (peek() is! js.Throw) return failAt(node, peek());
      js.Throw other = peek();
      return testNodes(node.expression, other.expression);
    }
  
    @override
    bool visitReturn(js.Return node) {
      if (peek() is! js.Return) return failAt(node, peek());
      js.Return other = peek();
      return testNodes(node.value, other.value);
    }
  
    @override
    bool visitBreak(js.Break node) {
      if (peek() is! js.Break) return failAt(node, peek());
      js.Break other = peek();
      return testLabels(node.targetLabel, other.targetLabel);
    }
  
    @override
    bool visitContinue(js.Continue node) {
      if (peek() is! js.Continue) return failAt(node, peek());
      js.Continue other = peek();
      return testLabels(node.targetLabel, other.targetLabel);
    }
  
    @override
    bool visitDo(js.Do node) {
      if (peek() is! js.Do) return failAt(node, peek());
      js.Do other = peek();
      return testNodes(node.condition, other.condition) &&
          testNodes(node.body, other.body);
    }
  
    @override
    bool visitWhile(js.While node) {
      if (peek() is! js.While) return failAt(node, peek());
      js.While other = peek();
      return testNodes(node.condition, other.condition) &&
          testNodes(node.body, other.body);
    }
  
    @override
    bool visitForIn(js.ForIn node) {
      if (peek() is! js.ForIn) return failAt(node, peek());
      js.ForIn other = peek();
      return testNodes(node.leftHandSide, other.leftHandSide) &&
          testNodes(node.object, other.object) &&
          testNodes(node.body, other.body);
    }
  
    @override
    bool visitFor(js.For node) {
      if (peek() is! js.For) return failAt(node, peek());
      js.For other = peek();
      return testNodes(node.init, other.init) &&
          testNodes(node.condition, other.condition) &&
          testNodes(node.update, other.update) &&
  
negative.code:   return new JsEquivalenceVisitor().testNodes(node1, node2);
  }
  
  class JsEquivalenceVisitor implements js.NodeVisitor1<bool, js.Node> {
    Map<String, String> labelsMap = <String, String>{};
  
    bool failAt(js.Node node1, js.Node node2) {
      print('Node mismatch:');
  