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

Unified Diff: pkg/analysis_server/lib/src/status/validator.dart

Issue 1503893002: Add more (incomplete) diagnostic support to the status pages (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Created 5 years 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: pkg/analysis_server/lib/src/status/validator.dart
diff --git a/pkg/analysis_server/lib/src/status/validator.dart b/pkg/analysis_server/lib/src/status/validator.dart
new file mode 100644
index 0000000000000000000000000000000000000000..dccf996732d8ff98366c0fcc6b68fa0b19a550d3
--- /dev/null
+++ b/pkg/analysis_server/lib/src/status/validator.dart
@@ -0,0 +1,1787 @@
+// Copyright (c) 2015, 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.
+
+library analysis_server.src.status.validator;
+
+import 'dart:collection';
+
+import 'package:analyzer/src/context/cache.dart';
+import 'package:analyzer/src/context/context.dart';
+import 'package:analyzer/src/generated/ast.dart';
+import 'package:analyzer/src/generated/constant.dart';
+import 'package:analyzer/src/generated/element.dart';
+import 'package:analyzer/src/generated/engine.dart'
+ show AnalysisEngine, AnalysisResult, CacheState, ChangeSet;
+import 'package:analyzer/src/generated/error.dart';
+import 'package:analyzer/src/generated/resolver.dart';
+import 'package:analyzer/src/generated/scanner.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/generated/utilities_collection.dart';
+import 'package:analyzer/src/task/dart.dart';
+import 'package:analyzer/src/task/html.dart';
+import 'package:analyzer/task/dart.dart';
+import 'package:analyzer/task/model.dart';
+import 'package:html/dom.dart' as html;
+
+/**
+ * A class used to compare two element models for equality.
+ */
+class ElementComparator {
+ /**
+ * The buffer to which any discovered differences will be recorded.
+ */
+ final StringBuffer _buffer = new StringBuffer();
+
+ /**
+ * A flag indicating whether a line break should be added the next time data
+ * is written to the [_buffer].
+ */
+ bool _needsLineBreak = false;
+
+ /**
+ * Initialize a newly created comparator.
+ */
+ ElementComparator();
+
+ /**
+ * A textual description of the differences that were found.
+ */
+ String get description => _buffer.toString();
+
+ /**
+ * Return `true` if at least one difference was found between the expected and
+ * actual elements.
+ */
+ bool get hasDifference => _buffer.length > 0;
+
+ /**
+ * Compare the [expected] and [actual] elements. The results of the comparison
+ * can be accessed via the [hasDifference] and [description] getters.
+ */
+ void compareElements(Element expected, Element actual) {
+ if (expected == null) {
+ if (actual != null) {
+ _writeMismatch(expected, actual, (Element element) {
+ return element == null ? 'null' : 'non null ${element.runtimeType}';
+ });
+ }
+ } else if (actual == null) {
+ _writeMismatch(expected, actual, (Element element) {
+ return element == null ? 'null' : 'non null ${element.runtimeType}';
+ });
+ } else if (expected is ClassElement && actual is ClassElement) {
+ _compareClassElements(expected, actual);
+ } else if (expected is CompilationUnitElement &&
+ actual is CompilationUnitElement) {
+ _compareCompilationUnitElements(expected, actual);
+ } else if (expected is ConstructorElement && actual is ConstructorElement) {
+ _compareConstructorElements(expected, actual);
+ } else if (expected is ExportElement && actual is ExportElement) {
+ _compareExportElements(expected, actual);
+ } else if (expected is FieldElement && actual is FieldElement) {
+ _compareFieldElements(expected, actual);
+ } else if (expected is FieldFormalParameterElement &&
+ actual is FieldFormalParameterElement) {
+ _compareFieldFormalParameterElements(expected, actual);
+ } else if (expected is FunctionElement && actual is FunctionElement) {
+ _compareFunctionElements(expected, actual);
+ } else if (expected is FunctionTypeAliasElement &&
+ actual is FunctionTypeAliasElement) {
+ _compareFunctionTypeAliasElements(expected, actual);
+ } else if (expected is ImportElement && actual is ImportElement) {
+ _compareImportElements(expected, actual);
+ } else if (expected is LabelElement && actual is LabelElement) {
+ _compareLabelElements(expected, actual);
+ } else if (expected is LibraryElement && actual is LibraryElement) {
+ _compareLibraryElements(expected, actual);
+ } else if (expected is LocalVariableElement &&
+ actual is LocalVariableElement) {
+ _compareLocalVariableElements(expected, actual);
+ } else if (expected is MethodElement && actual is MethodElement) {
+ _compareMethodElements(expected, actual);
+ } else if (expected is MultiplyDefinedElement &&
+ actual is MultiplyDefinedElement) {
+ _compareMultiplyDefinedElements(expected, actual);
+ } else if (expected is ParameterElement && actual is ParameterElement) {
+ _compareParameterElements(expected, actual);
+ } else if (expected is PrefixElement && actual is PrefixElement) {
+ _comparePrefixElements(expected, actual);
+ } else if (expected is PropertyAccessorElement &&
+ actual is PropertyAccessorElement) {
+ _comparePropertyAccessorElements(expected, actual);
+ } else if (expected is TopLevelVariableElement &&
+ actual is TopLevelVariableElement) {
+ _compareTopLevelVariableElements(expected, actual);
+ } else if (expected is TypeParameterElement &&
+ actual is TypeParameterElement) {
+ _compareTypeParameterElements(expected, actual);
+ } else {
+ _write('Expected an instance of ');
+ _write(expected.runtimeType);
+ _write('; found an instance of ');
+ _writeln(actual.runtimeType);
+ }
+ }
+
+ void _compareClassElements(ClassElement expected, ClassElement actual) {
+ _compareGenericElements(expected, actual);
+ //
+ // Compare attributes.
+ //
+ if (expected.hasReferenceToSuper != actual.hasReferenceToSuper) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ClassElement element) => element.hasReferenceToSuper
+ ? 'a class that references super'
+ : 'a class that does not reference super');
+ }
+ if (expected.isAbstract != actual.isAbstract) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ClassElement element) =>
+ element.isAbstract ? 'an abstract class' : 'a concrete class');
+ }
+ if (expected.isEnum != actual.isEnum ||
+ expected.isMixinApplication != actual.isMixinApplication) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ClassElement element) => element.isEnum
+ ? 'an enum'
+ : (element.isMixinApplication
+ ? 'a mixin application'
+ : 'a class'));
+ }
+ if (expected.isOrInheritsProxy != actual.isOrInheritsProxy) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ClassElement element) => element.isOrInheritsProxy
+ ? 'a class that is marked as a proxy'
+ : 'a class that is not marked as a proxy');
+ }
+ if (expected.isValidMixin != actual.isValidMixin) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ClassElement element) =>
+ element.isValidMixin ? 'a valid mixin' : 'an invalid mixin');
+ }
+ _compareTypes('supertype', expected.supertype, actual.supertype);
+ _compareTypeLists('mixin', expected.mixins, actual.mixins);
+ _compareTypeLists('interface', expected.interfaces, actual.interfaces);
+ //
+ // Compare children.
+ //
+ _compareElementLists(expected.accessors, actual.accessors);
+ _compareElementLists(expected.constructors, actual.constructors);
+ _compareElementLists(expected.fields, actual.fields);
+ _compareElementLists(expected.methods, actual.methods);
+ _compareElementLists(expected.typeParameters, actual.typeParameters);
+ }
+
+ void _compareCompilationUnitElements(
+ CompilationUnitElement expected, CompilationUnitElement actual) {
+ _compareGenericElements(expected, actual);
+ //
+ // Compare children.
+ //
+ _compareElementLists(expected.accessors, actual.accessors);
+ _compareElementLists(expected.enums, actual.enums);
+ _compareElementLists(expected.functions, actual.functions);
+ _compareElementLists(
+ expected.functionTypeAliases, actual.functionTypeAliases);
+ _compareElementLists(expected.topLevelVariables, actual.topLevelVariables);
+ _compareElementLists(expected.types, actual.types);
+ }
+
+ void _compareConstructorElements(
+ ConstructorElement expected, ConstructorElement actual) {
+ _compareExecutableElements(expected, actual, 'constructor');
+ //
+ // Compare attributes.
+ //
+ if (expected.isConst != actual.isConst) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ConstructorElement element) => element.isConst
+ ? 'a const constructor'
+ : 'a non-const constructor');
+ }
+ if (expected.isFactory != actual.isFactory) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ConstructorElement element) => element.isFactory
+ ? 'a factory constructor'
+ : 'a non-factory constructor');
+ }
+ if (expected.periodOffset != actual.periodOffset) {
+ _write('Expected a period offset of ');
+ _write(expected.periodOffset);
+ _write('; found ');
+ _writeln(actual.periodOffset);
+ }
+ if ((expected.redirectedConstructor == null) !=
+ (actual.redirectedConstructor == null)) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ConstructorElement element) => element.redirectedConstructor == null
+ ? 'a redirecting constructor'
+ : 'a non-redirecting constructor');
+ }
+ }
+
+ void _compareElementLists(List expected, List actual) {
+ Set<Element> extraElements = new HashSet<Element>();
+ Map<Element, Element> commonElements = new HashMap<Element, Element>();
+
+ Map<String, Element> expectedElements = new HashMap<String, Element>();
+ for (Element expectedElement in expected) {
+ expectedElements[expectedElement.name] = expectedElement;
+ }
+ for (Element actualElement in actual) {
+ String name = actualElement.name;
+ Element expectedElement = expectedElements[name];
+ if (expectedElement == null) {
+ extraElements.add(actualElement);
+ } else {
+ commonElements[expectedElement] = actualElement;
+ expectedElements.remove(name);
+ }
+ }
+
+ commonElements.forEach((Element expected, Element actual) {
+ compareElements(expected, actual);
+ });
+ void writeElement(Element element) {
+ _write('an instance of ');
+ _write(element.runtimeType);
+ if (element.name == null) {
+ _write(' with no name');
+ } else {
+ _write(' named ');
+ _write(element.name);
+ }
+ }
+ expectedElements.forEach((String name, Element element) {
+ _write('Expected ');
+ writeElement(element);
+ _writeln('; found no match');
+ });
+ extraElements.forEach((Element element) {
+ _write('Expected nothing; found ');
+ writeElement(element);
+ });
+ }
+
+ void _compareExecutableElements(
+ ExecutableElement expected, ExecutableElement actual, String kind) {
+ _compareGenericElements(expected, actual);
+ //
+ // Compare attributes.
+ //
+ if (expected.hasImplicitReturnType != actual.hasImplicitReturnType) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ExecutableElement element) => element.hasImplicitReturnType
+ ? 'an implicit return type'
+ : 'an explicit return type');
+ }
+ if (expected.isAbstract != actual.isAbstract) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ExecutableElement element) =>
+ element.isAbstract ? 'an abstract $kind' : 'a concrete $kind');
+ }
+ if (expected.isAsynchronous != actual.isAsynchronous) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ExecutableElement element) => element.isAsynchronous
+ ? 'an asynchronous $kind'
+ : 'a synchronous $kind');
+ }
+ if (expected.isExternal != actual.isExternal) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ExecutableElement element) => element.isExternal
+ ? 'an external $kind'
+ : 'a non-external $kind');
+ }
+ if (expected.isGenerator != actual.isGenerator) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ExecutableElement element) => element.isGenerator
+ ? 'a generator $kind'
+ : 'a non-generator $kind');
+ }
+ if (expected.isOperator != actual.isOperator) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ExecutableElement element) =>
+ element.isOperator ? 'an operator' : 'a non-operator $kind');
+ }
+ if (expected.isStatic != actual.isStatic) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ExecutableElement element) =>
+ element.isStatic ? 'a static $kind' : 'an instance $kind');
+ }
+ if ((expected.returnType == null) != (actual.returnType == null)) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ExecutableElement element) => element.returnType == null
+ ? 'a $kind with no return type'
+ : 'a $kind with a return type');
+ } else {
+ _compareTypes('return type', expected.returnType, actual.returnType);
+ }
+ //
+ // Compare children.
+ //
+ _compareElementLists(expected.functions, actual.functions);
+ _compareElementLists(expected.labels, actual.labels);
+ _compareElementLists(expected.localVariables, actual.localVariables);
+ _compareElementLists(expected.parameters, actual.parameters);
+ _compareElementLists(expected.typeParameters, actual.typeParameters);
+ }
+
+ void _compareExportElements(ExportElement expected, ExportElement actual) {
+ _compareUriReferencedElements(expected, actual);
+ //
+ // Compare attributes.
+ //
+ if ((expected.exportedLibrary == null) !=
+ (actual.exportedLibrary == null)) {
+ // TODO(brianwilkerson) Check for more than existence?
+ _writeMismatch(
+ expected,
+ actual,
+ (ExportElement element) => element.exportedLibrary == null
+ ? 'unresolved uri'
+ : 'uri resolved to ${element.exportedLibrary.source.fullName}');
+ }
+ //
+ // Compare children.
+ //
+ _compareElementLists(expected.combinators, actual.combinators);
+ }
+
+ void _compareFieldElements(FieldElement expected, FieldElement actual) {
+ _comparePropertyInducingElements(expected, actual, 'field');
+ //
+ // Compare attributes.
+ //
+ if (expected.isEnumConstant != actual.isEnumConstant) {
+ _writeMismatch(
+ expected,
+ actual,
+ (FieldElement element) =>
+ element.isEnumConstant ? 'an enum constant' : 'a normal field');
+ }
+ }
+
+ void _compareFieldFormalParameterElements(
+ FieldFormalParameterElement expected,
+ FieldFormalParameterElement actual) {
+ // TODO(brianwilkerson) Implement this
+ _compareGenericElements(expected, actual);
+ }
+
+ void _compareFunctionElements(
+ FunctionElement expected, FunctionElement actual) {
+ // TODO(brianwilkerson) Implement this
+ _compareGenericElements(expected, actual);
+ }
+
+ void _compareFunctionTypeAliasElements(
+ FunctionTypeAliasElement expected, FunctionTypeAliasElement actual) {
+ // TODO(brianwilkerson) Implement this
+ _compareGenericElements(expected, actual);
+ }
+
+ void _compareGenericElements(Element expected, Element actual) {
+ _compareMetadata(expected.metadata, actual.metadata);
+ if (expected.nameOffset != actual.nameOffset) {
+ _write('Expected name offset of ');
+ _write(expected.nameOffset);
+ _write('; found ');
+ _writeln(actual.nameOffset);
+ }
+ SourceRange expectedRange = expected.docRange;
+ SourceRange actualRange = actual.docRange;
+ if (expectedRange.offset != actualRange.offset ||
+ expectedRange.length != actualRange.length) {
+ _write('Expected documentation range of ');
+ _write(expectedRange);
+ _write('; found ');
+ _writeln(actualRange);
+ }
+ }
+
+ void _compareImportElements(ImportElement expected, ImportElement actual) {
+ _compareUriReferencedElements(expected, actual);
+ //
+ // Compare attributes.
+ //
+ if (expected.isDeferred != actual.isDeferred) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ImportElement element) => element.isDeferred
+ ? 'a deferred import'
+ : 'a non-deferred import');
+ }
+ if ((expected.importedLibrary == null) !=
+ (actual.importedLibrary == null)) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ImportElement element) => element.importedLibrary == null
+ ? 'unresolved uri'
+ : 'uri resolved to ${element.importedLibrary.source.fullName}');
+ }
+ if ((expected.prefix == null) != (actual.prefix == null)) {
+ _writeMismatch(
+ expected,
+ actual,
+ (ImportElement element) => element.prefix == null
+ ? 'no prefix'
+ : 'a prefix named ${element.prefix.name}');
+ }
+ if (expected.prefixOffset != actual.prefixOffset) {
+ _write('Expected a prefix offset of ');
+ _write(expected.prefixOffset);
+ _write('; found ');
+ _writeln(actual.prefixOffset);
+ }
+ //
+ // Compare children.
+ //
+ _compareElementLists(expected.combinators, actual.combinators);
+ }
+
+ void _compareLabelElements(LabelElement expected, LabelElement actual) {
+ // TODO(brianwilkerson) Implement this
+ _compareGenericElements(expected, actual);
+ }
+
+ void _compareLibraryElements(LibraryElement expected, LibraryElement actual) {
+ _compareGenericElements(expected, actual);
+ //
+ // Compare attributes.
+ //
+ // TODO(brianwilkerson) Implement this
+ expected.hasLoadLibraryFunction;
+ expected.name;
+ expected.source;
+ //
+ // Compare children.
+ //
+ _compareElementLists(expected.imports, actual.imports);
+ _compareElementLists(expected.exports, actual.exports);
+ _compareElementLists(expected.units, actual.units);
+ }
+
+ void _compareLocalVariableElements(
+ LocalVariableElement expected, LocalVariableElement actual) {
+ // TODO(brianwilkerson) Implement this
+ _compareGenericElements(expected, actual);
+ }
+
+ void _compareMetadata(
+ List<ElementAnnotation> expected, List<ElementAnnotation> actual) {
+ // TODO(brianwilkerson) Implement this
+ }
+
+ void _compareMethodElements(MethodElement expected, MethodElement actual) {
+ // TODO(brianwilkerson) Implement this
+ _compareExecutableElements(expected, actual, 'method');
+ //
+ // Compare attributes.
+ //
+ if (expected.isStatic != actual.isStatic) {
+ _writeMismatch(
+ expected,
+ actual,
+ (FieldElement element) =>
+ element.isStatic ? 'a static field' : 'an instance field');
+ }
+ }
+
+ void _compareMultiplyDefinedElements(
+ MultiplyDefinedElement expected, MultiplyDefinedElement actual) {
+ // TODO(brianwilkerson) Implement this
+ }
+
+ void _compareParameterElements(
+ ParameterElement expected, ParameterElement actual) {
+ // TODO(brianwilkerson) Implement this
+ _compareGenericElements(expected, actual);
+ }
+
+ void _comparePrefixElements(PrefixElement expected, PrefixElement actual) {
+ // TODO(brianwilkerson) Implement this
+ _compareGenericElements(expected, actual);
+ }
+
+ void _comparePropertyAccessorElements(
+ PropertyAccessorElement expected, PropertyAccessorElement actual) {
+ // TODO(brianwilkerson) Implement this
+ _compareGenericElements(expected, actual);
+ }
+
+ void _comparePropertyInducingElements(PropertyInducingElement expected,
+ PropertyInducingElement actual, String kind) {
+ _compareVariableElements(expected, actual, kind);
+ }
+
+ void _compareTopLevelVariableElements(
+ TopLevelVariableElement expected, TopLevelVariableElement actual) {
+ // TODO(brianwilkerson) Implement this
+ _compareGenericElements(expected, actual);
+ }
+
+ void _compareTypeLists(String descriptor, List<InterfaceType> expected,
+ List<InterfaceType> actual) {
+ int expectedLength = expected.length;
+ if (expectedLength != actual.length) {
+ _write('Expected ');
+ _write(expectedLength);
+ _write(' ');
+ _write(descriptor);
+ _write('s; found ');
+ _write(actual.length);
+ } else {
+ for (int i = 0; i < expectedLength; i++) {
+ _compareTypes(descriptor, expected[i], actual[i]);
+ }
+ }
+ }
+
+ void _compareTypeParameterElements(
+ TypeParameterElement expected, TypeParameterElement actual) {
+ // TODO(brianwilkerson) Implement this
+ _compareGenericElements(expected, actual);
+ expected.bound;
+ }
+
+ void _compareTypes(String descriptor, DartType expected, DartType actual) {
+ void compareNames() {
+ if (expected.name != actual.name) {
+ _write('Expected a ');
+ _write(descriptor);
+ _write(' named ');
+ _write(expected.name);
+ _write('; found a ');
+ _write(descriptor);
+ _write(' named ');
+ _write(actual.name);
+ }
+ }
+ void compareTypeArguments(
+ ParameterizedType expected, ParameterizedType actual) {
+ List<DartType> expectedArguments = expected.typeArguments;
+ List<DartType> actualArguments = actual.typeArguments;
+ int expectedLength = expectedArguments.length;
+ if (expectedLength != actualArguments.length) {
+ _write('Expected ');
+ _write(expectedLength);
+ _write(' type arguments; found ');
+ _write(actualArguments.length);
+ } else {
+ for (int i = 0; i < expectedLength; i++) {
+ _compareTypes(
+ 'type argument', expectedArguments[i], actualArguments[i]);
+ }
+ }
+ }
+
+ if (expected == null) {
+ if (actual != null) {
+ _write('Expected no ');
+ _write(descriptor);
+ _write('; found a ');
+ _write(descriptor);
+ _write(' named ');
+ _write(actual.name);
+ }
+ } else if (actual == null) {
+ _write('Expected a ');
+ _write(descriptor);
+ _write(' named ');
+ _write(expected.name);
+ _write('; found none');
+ } else if ((expected.isBottom && actual.isBottom) ||
+ (expected.isDynamic && actual.isDynamic) ||
+ (expected.isVoid && actual.isVoid)) {
+ // The types are the same
+ } else if (expected is InterfaceType && actual is InterfaceType) {
+ compareNames();
+ compareTypeArguments(expected, actual);
+ } else if (expected is FunctionType && actual is FunctionType) {
+ compareNames();
+ compareTypeArguments(expected, actual);
+ } else if (expected is TypeParameterType && actual is TypeParameterType) {
+ compareNames();
+ _compareTypes('bound', expected.element.bound, actual.element.bound);
+ } else {
+ _write('Expected an instance of ');
+ _write(expected.runtimeType);
+ _write(' named ');
+ _write(expected.name);
+ _write('; found an instance of ');
+ _writeln(actual.runtimeType);
+ _write(' named ');
+ _write(actual.name);
+ }
+ }
+
+ void _compareUriReferencedElements(
+ UriReferencedElement expected, UriReferencedElement actual) {
+ _compareGenericElements(expected, actual);
+ //
+ // Compare attributes.
+ //
+ if (expected.uri != actual.uri) {
+ _write('Expected a uri of ');
+ _write(expected.uri);
+ _write('; found ');
+ _writeln(actual.uri);
+ }
+ if (expected.uriOffset != actual.uriOffset) {
+ _write('Expected a uri offset of ');
+ _write(expected.uriOffset);
+ _write('; found ');
+ _writeln(actual.uriOffset);
+ }
+ }
+
+ void _compareVariableElements(
+ VariableElement expected, VariableElement actual, String kind) {
+ _compareGenericElements(expected, actual);
+ //
+ // Compare attributes.
+ //
+ if ((expected.constantValue == null) != (actual.constantValue == null)) {
+ // TODO(brianwilkerson) Check for more than existence.
+ _writeMismatch(
+ expected,
+ actual,
+ (VariableElement element) => element.constantValue == null
+ ? 'a $kind with no constant value'
+ : 'a $kind with a constant value');
+ }
+ if (expected.hasImplicitType != actual.hasImplicitType) {
+ _writeMismatch(
+ expected,
+ actual,
+ (VariableElement element) => element.hasImplicitType
+ ? 'a $kind with an implicit type'
+ : 'a $kind with an explicit type');
+ }
+ if (expected.isConst != actual.isConst) {
+ _writeMismatch(
+ expected,
+ actual,
+ (VariableElement element) =>
+ element.isConst ? 'a const $kind' : 'a non-const $kind');
+ }
+ if (expected.isFinal != actual.isFinal) {
+ _writeMismatch(
+ expected,
+ actual,
+ (VariableElement element) =>
+ element.isFinal ? 'a final $kind' : 'a non-final $kind');
+ }
+ if (expected.isPotentiallyMutatedInClosure !=
+ actual.isPotentiallyMutatedInClosure) {
+ _writeMismatch(
+ expected,
+ actual,
+ (VariableElement element) => element.isPotentiallyMutatedInClosure
+ ? 'a $kind that is potentially mutated in a closure'
+ : 'a $kind that is not mutated in a closure');
+ }
+ if (expected.isPotentiallyMutatedInScope !=
+ actual.isPotentiallyMutatedInScope) {
+ _writeMismatch(
+ expected,
+ actual,
+ (VariableElement element) => element.isPotentiallyMutatedInScope
+ ? 'a $kind that is potentially mutated in its scope'
+ : 'a $kind that is not mutated in its scope');
+ }
+ if (expected.isStatic != actual.isStatic) {
+ _writeMismatch(
+ expected,
+ actual,
+ (VariableElement element) =>
+ element.isStatic ? 'a static $kind' : 'an instance $kind');
+ }
+ //
+ // Compare children.
+ //
+ compareElements(expected.initializer, actual.initializer);
+ }
+
+ void _write(Object value) {
+ if (_needsLineBreak) {
+ _buffer.write('</p><p>');
+ _needsLineBreak = false;
+ }
+ _buffer.write(value);
+ }
+
+ void _writeln(Object value) {
+ _buffer.write(value);
+ _needsLineBreak = true;
+ }
+
+ /**
+ * Write a simple message explaining that the [expected] and [actual] values
+ * were different, using the [describe] function to describe the values.
+ */
+ void _writeMismatch /*<E>*/ (Object /*=E*/ expected, Object /*=E*/ actual,
+ String describe(Object /*=E*/ value)) {
+ _write('Expected ');
+ _write(describe(expected));
+ _write('; found ');
+ _writeln(describe(actual));
+ }
+}
+
+/**
+ * The comparison of two analyses of the same target.
+ */
+class EntryComparison {
+ /**
+ * The target that was analyzed.
+ */
+ final AnalysisTarget target;
+
+ /**
+ * The cache entry from the original context.
+ */
+ final CacheEntry originalEntry;
+
+ /**
+ * The cache entry from the re-analysis in a cloned context.
+ */
+ final CacheEntry cloneEntry;
+
+ /**
+ * A flag indicating whether the target is obsolete. A target is obsolete if
+ * it is an element in an element model that was replaced at a some point.
+ */
+ bool obsoleteTarget = false;
+
+ /**
+ * A table mapping the results that were computed for the target to
+ * comparisons of the values of those results. The table only contains entries
+ * for results for which the comparison produced interesting data.
+ */
+ Map<ResultDescriptor, ResultComparison> resultMap =
+ new HashMap<ResultDescriptor, ResultComparison>();
+
+ /**
+ * Initialize a newly created comparison of the given [target]'s analysis,
+ * given the [originalEntry] from the original context and the [cloneEntry]
+ * from the cloned context.
+ */
+ EntryComparison(this.target, this.originalEntry, this.cloneEntry) {
+ _performComparison();
+ }
+
+ /**
+ * Return `true` if there is something interesting about the analysis of this
+ * target that should be reported.
+ */
+ bool hasInterestingState() => obsoleteTarget || resultMap.isNotEmpty;
+
+ /**
+ * Write an HTML formatted description of the validation results to the given
+ * [buffer].
+ */
+ void writeOn(StringBuffer buffer) {
+ buffer.write('<p>');
+ buffer.write(target);
+ buffer.write('</p>');
+ buffer.write('<blockquote>');
+ if (obsoleteTarget) {
+ buffer.write('<p><b>This target is obsolete.</b></p>');
+ }
+ List<ResultDescriptor> results = resultMap.keys.toList();
+ results.sort((ResultDescriptor first, ResultDescriptor second) =>
+ first.toString().compareTo(second.toString()));
+ for (ResultDescriptor result in results) {
+ resultMap[result].writeOn(buffer);
+ }
+ buffer.write('</blockquote>');
+ }
+
+ /**
+ * Compare all of the results that were computed in the two contexts, adding
+ * the interesting comparisons to the [resultMap].
+ */
+ void _compareResults() {
+ Set<ResultDescriptor> results = new Set<ResultDescriptor>();
+ results.addAll(originalEntry.nonInvalidResults);
+ results.addAll(cloneEntry.nonInvalidResults);
+
+ for (ResultDescriptor result in results) {
+ ResultComparison difference = new ResultComparison(this, result);
+ if (difference.hasInterestingState()) {
+ resultMap[result] = difference;
+ }
+ }
+ }
+
+ /**
+ * Return `true` if the target of this entry is an obsolete element.
+ */
+ bool _isTargetObsolete() {
+ if (target is Element) {
+ LibraryElement library = (target as Element).library;
+ AnalysisContextImpl context = library.context;
+ CacheEntry entry = context.analysisCache.get(library.source);
+ LibraryElement value = entry.getValue(LIBRARY_ELEMENT);
+ return value != library;
+ }
+ return false;
+ }
+
+ /**
+ * Determine whether or not there is any interesting difference between the
+ * original and cloned contexts.
+ */
+ void _performComparison() {
+ obsoleteTarget = _isTargetObsolete();
+ _compareResults();
+ }
+}
+
+/**
+ * The comparison of the value of a single result computed for a single target.
+ */
+class ResultComparison {
+ /**
+ * The entry for the target for which the result was computed.
+ */
+ final EntryComparison entry;
+
+ /**
+ * The result that was computed for the target.
+ */
+ final ResultDescriptor result;
+
+ /**
+ * A flag indicating whether the state of the result is different.
+ */
+ bool differentStates = false;
+
+ /**
+ * The result of comparing the values of the results, or `null` if the states
+ * are different or if the values are the same.
+ */
+ ValueComparison valueComparison;
+
+ /**
+ * Initialize a newly created result comparison.
+ */
+ ResultComparison(this.entry, this.result) {
+ _performComparison();
+ }
+
+ /**
+ * Return `true` if this object represents a difference between the original
+ * and cloned contexts.
+ */
+ bool hasInterestingState() => differentStates || valueComparison != null;
+
+ /**
+ * Write an HTML formatted description of the validation results to the given
+ * [buffer].
+ */
+ void writeOn(StringBuffer buffer) {
+ buffer.write('<p>');
+ buffer.write(result);
+ buffer.write('</p>');
+ buffer.write('<blockquote>');
+ if (differentStates) {
+ CacheState originalState = entry.originalEntry.getState(result);
+ CacheState cloneState = entry.cloneEntry.getState(result);
+ buffer.write('<p>Original state = ');
+ buffer.write(originalState.name);
+ buffer.write('; clone state = ');
+ buffer.write(cloneState.name);
+ buffer.write('</p>');
+ }
+ if (valueComparison != null) {
+ valueComparison.writeOn(buffer);
+ }
+ buffer.write('</blockquote>');
+ }
+
+ /**
+ * Determine whether the state of the result is different between the
+ * original and cloned contexts.
+ */
+ bool _areStatesDifferent(CacheState originalState, CacheState cloneState) {
+ if (originalState == cloneState) {
+ return false;
+ } else if (originalState == CacheState.FLUSHED &&
+ cloneState == CacheState.VALID) {
+ return false;
+ } else if (originalState == CacheState.VALID &&
+ cloneState == CacheState.FLUSHED) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Determine whether the value of the result is different between the
+ * original and cloned contexts.
+ */
+ void _compareValues(CacheState originalState, CacheState cloneState) {
+ if (originalState != cloneState || originalState != CacheState.VALID) {
+ return null;
+ }
+ ValueComparison comparison = new ValueComparison(
+ entry.originalEntry.getValue(result),
+ entry.cloneEntry.getValue(result));
+ if (comparison.hasInterestingState()) {
+ valueComparison = comparison;
+ }
+ }
+
+ /**
+ * Determine whether or not there is any interesting difference between the
+ * original and cloned contexts.
+ */
+ void _performComparison() {
+ CacheState originalState = entry.originalEntry.getState(result);
+ CacheState cloneState = entry.cloneEntry.getState(result);
+ if (_areStatesDifferent(originalState, cloneState)) {
+ differentStates = true;
+ _compareValues(originalState, cloneState);
+ }
+ }
+}
+
+/**
+ * The results of validating an analysis context.
+ *
+ * Validation is done by re-analyzing all of the explicitly added source in a
+ * new analysis context that is configured to be the same as the original
+ * context.
+ */
+class ValidationResults {
+ /**
+ * A set of targets that were in the original context that were not included
+ * in the re-created context.
+ */
+ Set<AnalysisTarget> extraTargets;
+
+ /**
+ * A set of targets that were in the re-created context that were not included
+ * in the original context.
+ */
+ Set<AnalysisTarget> missingTargets;
+
+ /**
+ * A table, keyed by targets, whose values are comparisons of the analysis of
+ * those targets. The table only contains entries for targets for which the
+ * comparison produced interesting data.
+ */
+ Map<AnalysisTarget, EntryComparison> targetMap =
+ new HashMap<AnalysisTarget, EntryComparison>();
+
+ /**
+ * Initialize a newly created validation result by validating the given
+ * [context].
+ */
+ ValidationResults(AnalysisContextImpl context) {
+ _validate(context);
+ }
+
+ /**
+ * Write an HTML formatted description of the validation results to the given
+ * [buffer].
+ */
+ void writeOn(StringBuffer buffer) {
+ if (extraTargets.isEmpty && missingTargets.isEmpty && targetMap.isEmpty) {
+ buffer.write('<p>No interesting results.</p>');
+ return;
+ }
+ if (extraTargets.isNotEmpty) {
+ buffer.write('<h4>Extra Targets</h4>');
+ buffer.write('<p style="commentary">');
+ buffer.write('Targets that exist in the original context that were not ');
+ buffer.write('re-created in the cloned context.');
+ buffer.write('</p>');
+ _writeTargetList(buffer, extraTargets.toList());
+ }
+ if (missingTargets.isNotEmpty) {
+ buffer.write('<h4>Missing Targets</h4>');
+ buffer.write('<p style="commentary">');
+ buffer.write('Targets that do <b>not</b> exist in the original context ');
+ buffer.write('but do exist in the cloned context.');
+ buffer.write('</p>');
+ _writeTargetList(buffer, missingTargets.toList());
+ }
+ if (targetMap.isNotEmpty) {
+ buffer.write('<h4>Differing Targets</h4>');
+ // TODO(brianwilkerson) Sort the list of targets.
+ for (EntryComparison comparison in targetMap.values) {
+ comparison.writeOn(buffer);
+ }
+ }
+ }
+
+ /**
+ * Analyze all of the explicit sources in the given [context].
+ */
+ void _analyze(AnalysisContextImpl context) {
+ while (true) {
+ AnalysisResult result = context.performAnalysisTask();
+ if (!result.hasMoreWork) {
+ return;
+ }
+ }
+ }
+
+ /**
+ * Create and return a new analysis context that will analyze files in the
+ * same way as the given [context].
+ */
+ AnalysisContextImpl _clone(AnalysisContextImpl context) {
+ AnalysisContextImpl clone = AnalysisEngine.instance.createAnalysisContext();
+
+ clone.analysisOptions = context.analysisOptions;
+ //clone.declaredVariables = context.declaredVariables;
+ clone.sourceFactory = context.sourceFactory.clone();
+ // TODO(brianwilkerson) Check content cache. We either need to copy the
+ // cache into the clone or ensure that the context's cache is empty.
+
+ ChangeSet changeSet = new ChangeSet();
+ for (AnalysisTarget target in context.explicitTargets) {
+ if (target is Source) {
+ changeSet.addedSource(target);
+ }
+ }
+ clone.applyChanges(changeSet);
+ return clone;
+ }
+
+ /**
+ * Compare the results produced in the [original] context to those produced in
+ * the [clone].
+ */
+ void _compareContexts(
+ AnalysisContextImpl original, AnalysisContextImpl clone) {
+ AnalysisCache originalCache = original.analysisCache;
+ AnalysisCache cloneCache = clone.analysisCache;
+ List<AnalysisTarget> originalTargets = _getKeys(original, originalCache);
+ List<AnalysisTarget> cloneTargets = _getKeys(clone, cloneCache);
+
+ extraTargets =
+ new HashSet<AnalysisTarget>(equals: _equal, hashCode: _hashCode);
+ extraTargets.addAll(originalTargets);
+ extraTargets.removeAll(cloneTargets);
+
+ missingTargets =
+ new HashSet<AnalysisTarget>(equals: _equal, hashCode: _hashCode);
+ missingTargets.addAll(cloneTargets);
+ missingTargets.removeAll(originalTargets);
+
+ for (AnalysisTarget cloneTarget in cloneTargets) {
+ if (!missingTargets.contains(cloneTarget)) {
+ AnalysisTarget originalTarget = _find(originalTargets, cloneTarget);
+ CacheEntry originalEntry = originalCache.get(originalTarget);
+ CacheEntry cloneEntry = cloneCache.get(cloneTarget);
+ EntryComparison comparison =
+ new EntryComparison(cloneTarget, originalEntry, cloneEntry);
+ if (comparison.hasInterestingState()) {
+ targetMap[cloneTarget] = comparison;
+ }
+ }
+ }
+ }
+
+ /**
+ * Find the target in the list of [originalTargets] that is equal to the
+ * [cloneTarget].
+ */
+ AnalysisTarget _find(
+ List<AnalysisTarget> originalTargets, AnalysisTarget cloneTarget) {
+ for (AnalysisTarget originalTarget in originalTargets) {
+ if (_equal(originalTarget, cloneTarget)) {
+ return originalTarget;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return a list of the analysis targets in the given [cache] that are owned
+ * by the given [context].
+ */
+ List<AnalysisTarget> _getKeys(
+ AnalysisContextImpl context, AnalysisCache cache) {
+ List<AnalysisTarget> targets = <AnalysisTarget>[];
+ MapIterator<AnalysisTarget, CacheEntry> iterator =
+ cache.iterator(context: context);
+ while (iterator.moveNext()) {
+ targets.add(iterator.key);
+ }
+ return targets;
+ }
+
+ /**
+ * Validate the given [context].
+ */
+ void _validate(AnalysisContextImpl context) {
+ AnalysisContextImpl clone = _clone(context);
+ _analyze(clone);
+ _compareContexts(context, clone);
+ }
+
+ /**
+ * Write the list of [targets] to the [buffer].
+ */
+ void _writeTargetList(StringBuffer buffer, List<AnalysisTarget> targets) {
+ // TODO(brianwilkerson) Sort the list of targets.
+ //targets.sort();
+ for (AnalysisTarget target in targets) {
+ buffer.write('<p>');
+ buffer.write(target);
+ buffer.write(' (');
+ buffer.write(target.runtimeType);
+ buffer.write(')');
+ buffer.write('</p>');
+ }
+ }
+
+ /**
+ * Return `true` if the [first] and [second] objects are equal.
+ */
+ static bool _equal(Object first, Object second) {
+ //
+ // Compare possible null values.
+ //
+ if (first == null) {
+ return second == null;
+ } else if (second == null) {
+ return false;
+ }
+ //
+ // Handle special cases.
+ //
+ if (first is ConstantEvaluationTarget_Annotation &&
+ second is ConstantEvaluationTarget_Annotation) {
+ return _equal(first.source, second.source) &&
+ _equal(first.librarySource, second.librarySource) &&
+ _equal(first.annotation, second.annotation);
+ } else if (first is AstNode && second is AstNode) {
+ return first.runtimeType == second.runtimeType &&
+ first.offset == second.offset &&
+ first.length == second.length;
+ }
+ //
+ // Handle the general case.
+ //
+ return first == second;
+ }
+
+ /**
+ * Return a hash code for the given [object].
+ */
+ static int _hashCode(Object object) {
+ //
+ // Handle special cases.
+ //
+ if (object is ConstantEvaluationTarget_Annotation) {
+ return object.source.hashCode;
+ } else if (object is AstNode) {
+ return object.offset;
+ }
+ //
+ // Handle the general case.
+ //
+ return object.hashCode;
+ }
+}
+
+class ValueComparison {
+ /**
+ * The result value from the original context.
+ */
+ final Object originalValue;
+
+ /**
+ * The result value from the cloned context.
+ */
+ final Object cloneValue;
+
+ /**
+ * A description of the difference between the original and clone values, or
+ * `null` if the values are equal.
+ */
+ String description = null;
+
+ /**
+ * Initialize a newly created value comparison to represents the difference,
+ * if any, between the [originalValue] and the [cloneValue].
+ */
+ ValueComparison(this.originalValue, this.cloneValue) {
+ _performComparison();
+ }
+
+ /**
+ * Return `true` if this object represents a difference between the original
+ * and cloned values.
+ */
+ bool hasInterestingState() => description != null;
+
+ /**
+ * Write an HTML formatted description of the validation results to the given
+ * [buffer].
+ */
+ void writeOn(StringBuffer buffer) {
+ buffer.write('<p>');
+ buffer.write(description);
+ buffer.write('</p>');
+ }
+
+ bool _compareAnalysisErrors(
+ AnalysisError expected, AnalysisError actual, StringBuffer buffer) {
+ if (actual.errorCode == expected.errorCode &&
+ actual.source == expected.source &&
+ actual.offset == expected.offset) {
+ return true;
+ }
+ if (buffer != null) {
+ void write(AnalysisError originalError) {
+ buffer.write('a ');
+ buffer.write(originalError.errorCode.uniqueName);
+ buffer.write(' in ');
+ buffer.write(originalError.source.fullName);
+ buffer.write(' at ');
+ buffer.write(originalError.offset);
+ }
+
+ buffer.write('Expected ');
+ write(expected);
+ buffer.write('; found ');
+ write(actual);
+ }
+ return false;
+ }
+
+ bool _compareAstNodes(AstNode expected, AstNode actual, StringBuffer buffer) {
+ if (AstComparator.equalNodes(actual, expected)) {
+ return true;
+ }
+ if (buffer != null) {
+ // TODO(brianwilkerson) Compute where the difference is rather than just
+ // whether there is a difference.
+ buffer.write('Different AST nodes');
+ }
+ return false;
+ }
+
+ bool _compareConstantEvaluationTargets(ConstantEvaluationTarget expected,
+ ConstantEvaluationTarget actual, StringBuffer buffer) {
+ if (actual is ConstantEvaluationTarget_Annotation) {
+ ConstantEvaluationTarget_Annotation expectedAnnotation = expected;
+ ConstantEvaluationTarget_Annotation actualAnnotation = actual;
+ if (actualAnnotation.source == expectedAnnotation.source &&
+ actualAnnotation.librarySource == expectedAnnotation.librarySource &&
+ actualAnnotation.annotation == expectedAnnotation.annotation) {
+ return true;
+ }
+ if (buffer != null) {
+ void write(ConstantEvaluationTarget_Annotation target) {
+ Annotation annotation = target.annotation;
+ buffer.write(annotation);
+ buffer.write(' at ');
+ buffer.write(annotation.offset);
+ buffer.write(' in ');
+ buffer.write(target.source);
+ buffer.write(' in ');
+ buffer.write(target.librarySource);
+ }
+
+ buffer.write('Expected ');
+ write(expectedAnnotation);
+ buffer.write('; found ');
+ write(actualAnnotation);
+ }
+ return false;
+ }
+ if (buffer != null) {
+ buffer.write('Unknown class of ConstantEvaluationTarget: ');
+ buffer.write(actual.runtimeType);
+ }
+ return false;
+ }
+
+ bool _compareDartScripts(
+ DartScript expected, DartScript actual, StringBuffer buffer) {
+ // TODO(brianwilkerson) Implement this.
+ return true;
+ }
+
+ bool _compareDocuments(
+ html.Document expected, html.Document actual, StringBuffer buffer) {
+ // TODO(brianwilkerson) Implement this.
+ return true;
+ }
+
+ bool _compareElements(Element expected, Element actual, StringBuffer buffer) {
+ ElementComparator comparator = new ElementComparator();
+ comparator.compareElements(expected, actual);
+ if (comparator.hasDifference) {
+ if (buffer != null) {
+ buffer.write(comparator.description);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ bool _compareLibrarySpecificUnits(LibrarySpecificUnit expected,
+ LibrarySpecificUnit actual, StringBuffer buffer) {
+ if (actual.library.fullName == expected.library.fullName &&
+ actual.unit.fullName == expected.unit.fullName) {
+ return true;
+ }
+ if (buffer != null) {
+ buffer.write('Expected ');
+ buffer.write(expected);
+ buffer.write('; found ');
+ buffer.write(actual);
+ }
+ return false;
+ }
+
+ bool _compareLineInfos(
+ LineInfo expected, LineInfo actual, StringBuffer buffer) {
+ // TODO(brianwilkerson) Implement this.
+ return true;
+ }
+
+ bool _compareLists(List expected, List actual, StringBuffer buffer) {
+ int expectedLength = expected.length;
+ int actualLength = actual.length;
+ int left = 0;
+ while (left < expectedLength &&
+ left < actualLength &&
+ _compareObjects(expected[left], actual[left], null)) {
+ left++;
+ }
+ if (left == actualLength) {
+ if (left == expectedLength) {
+ // The lists are the same length and the elements are equal.
+ return true;
+ }
+ if (buffer != null) {
+ buffer.write('Expected a list of length ');
+ buffer.write(expectedLength);
+ buffer.write('; found a list of length ');
+ buffer.write(actualLength);
+ buffer.write(' that was a prefix of the expected list');
+ }
+ return false;
+ } else if (left == expectedLength) {
+ if (buffer != null) {
+ buffer.write('Expected a list of length ');
+ buffer.write(expectedLength);
+ buffer.write('; found a list of length ');
+ buffer.write(actualLength);
+ buffer.write(' that was an extension of the expected list');
+ }
+ return false;
+ }
+ int expectedRight = expectedLength - 1;
+ int actualRight = actualLength - 1;
+ while (expectedRight > left &&
+ actualRight > left &&
+ _compareObjects(expected[expectedRight], actual[actualRight], null)) {
+ actualRight--;
+ expectedRight--;
+ }
+ if (buffer != null) {
+ void write(int left, int right, int length) {
+ buffer.write('the elements (');
+ buffer.write(left);
+ buffer.write('..');
+ buffer.write(right);
+ buffer.write(') in a list of length ');
+ buffer.write(length);
+ }
+
+ buffer.write('Expected ');
+ write(left, expectedRight, expectedLength);
+ buffer.write(' to match ');
+ write(left, actualRight, actualLength);
+ buffer.write(' but they did not');
+ }
+ return false;
+ }
+
+ /**
+ * Return `true` if the [expected] and [actual] objects are equal. If they are
+ * not equal, and the given [buffer] is not `null`, then a description of the
+ * difference will be written to the [buffer].
+ */
+ bool _compareObjects(Object expected, Object actual, StringBuffer buffer) {
+ //
+ // Compare possible null values.
+ //
+ if (actual == null) {
+ if (expected == null) {
+ return true;
+ } else {
+ if (buffer != null) {
+ buffer.write('Expected an instance of ');
+ buffer.write(expected.runtimeType);
+ buffer.write('; found null');
+ }
+ return false;
+ }
+ }
+ Type actualType = actual.runtimeType;
+ if (expected == null) {
+ if (buffer != null) {
+ buffer.write('Expected null; found an instance of ');
+ buffer.write(actualType);
+ }
+ return false;
+ }
+ Type expectedType = expected.runtimeType;
+ //
+ // Compare the types.
+ //
+ if (expectedType != actualType) {
+ if (buffer != null) {
+ buffer.write('Expected an instance of ');
+ buffer.write(expectedType);
+ buffer.write('; found an instance of ');
+ buffer.write(actualType);
+ }
+ return false;
+ }
+ //
+ // Compare non-null values of the same type.
+ //
+ if (actual is bool) {
+ return _comparePrimitives(expected, actual, buffer);
+ } else if (actual is int) {
+ return _comparePrimitives(expected, actual, buffer);
+ } else if (actual is String) {
+ return _compareStrings(expected, actual, buffer);
+ } else if (actual is List) {
+ return _compareLists(expected, actual, buffer);
+ } else if (actual is AnalysisError) {
+ return _compareAnalysisErrors(expected, actual, buffer);
+ } else if (actual is AstNode) {
+ return _compareAstNodes(expected, actual, buffer);
+ } else if (actual is DartScript) {
+ return _compareDartScripts(expected, actual, buffer);
+ } else if (actual is html.Document) {
+ return _compareDocuments(expected, actual, buffer);
+ } else if (actual is Element) {
+ return _compareElements(expected, actual, buffer);
+ } else if (actual is LibrarySpecificUnit) {
+ return _compareLibrarySpecificUnits(expected, actual, buffer);
+ } else if (actual is LineInfo) {
+ return _compareLineInfos(expected, actual, buffer);
+ } else if (actual is ReferencedNames) {
+ return _compareReferencedNames(expected, actual, buffer);
+ } else if (actual is Source) {
+ return _compareSources(expected, actual, buffer);
+ } else if (actual is SourceKind) {
+ return _comparePrimitives(expected, actual, buffer);
+ } else if (actual is Token) {
+ return _compareTokenStreams(expected, actual, buffer);
+ } else if (actual is TypeProvider) {
+ return true;
+ } else if (actual is UsedLocalElements) {
+ return _compareUsedLocalElements(expected, actual, buffer);
+ } else if (actual is UsedImportedElements) {
+ return _compareUsedImportedElements(expected, actual, buffer);
+ } else if (actual is ConstantEvaluationTarget) {
+ return _compareConstantEvaluationTargets(expected, actual, buffer);
+ }
+ if (buffer != null) {
+ buffer.write('Cannot compare values of type ');
+ buffer.write(actualType);
+ }
+ return false;
+ }
+
+ bool _comparePrimitives(Object expected, Object actual, StringBuffer buffer) {
+ if (actual == expected) {
+ return true;
+ }
+ if (buffer != null) {
+ buffer.write('Expected ');
+ buffer.write(expected);
+ buffer.write('; found ');
+ buffer.write(actual);
+ }
+ return false;
+ }
+
+ bool _compareReferencedNames(
+ ReferencedNames expected, ReferencedNames actual, StringBuffer buffer) {
+ Set<String> expectedNames = expected.names;
+ Map<String, Set<String>> expectedUserToDependsOn = expected.userToDependsOn;
+ Set<String> expectedKeys = expectedUserToDependsOn.keys.toSet();
+
+ Set<String> actualNames = actual.names;
+ Map<String, Set<String>> actualUserToDependsOn = actual.userToDependsOn;
+ Set<String> actualKeys = actualUserToDependsOn.keys.toSet();
+
+ Set<String> missingNames = expectedNames.difference(actualNames);
+ Set<String> extraNames = actualNames.difference(expectedNames);
+ Set<String> missingKeys = expectedKeys.difference(actualKeys);
+ Set<String> extraKeys = actualKeys.difference(expectedKeys);
+ Map<String, List<Set<String>>> mismatchedDependencies =
+ new HashMap<String, List<Set<String>>>();
+ Set<String> commonKeys = expectedKeys.intersection(actualKeys);
+ for (String key in commonKeys) {
+ Set<String> expectedDependencies = expectedUserToDependsOn[key];
+ Set<String> actualDependencies = actualUserToDependsOn[key];
+ Set<String> missingDependencies =
+ expectedDependencies.difference(actualDependencies);
+ Set<String> extraDependencies =
+ actualDependencies.difference(expectedDependencies);
+ if (missingDependencies.isNotEmpty || extraDependencies.isNotEmpty) {
+ mismatchedDependencies[key] = [missingDependencies, extraDependencies];
+ }
+ }
+
+ if (missingNames.isEmpty &&
+ extraNames.isEmpty &&
+ missingKeys.isEmpty &&
+ extraKeys.isEmpty &&
+ mismatchedDependencies.isEmpty) {
+ return true;
+ }
+ if (buffer != null) {
+ void write(String title, Set<String> names) {
+ buffer.write(names.length);
+ buffer.write(' ');
+ buffer.write(title);
+ buffer.write(': {');
+ bool first = true;
+ for (String name in names) {
+ if (first) {
+ first = false;
+ } else {
+ buffer.write(', ');
+ }
+ buffer.write(name);
+ }
+ buffer.write('}');
+ }
+ bool needsNewline = false;
+ if (missingNames.isNotEmpty) {
+ buffer.write('Has ');
+ write('missing names', missingNames);
+ needsNewline = true;
+ }
+ if (extraNames.isNotEmpty) {
+ if (needsNewline) {
+ buffer.write('</p><p>');
+ }
+ buffer.write('Has ');
+ write('extra names', extraNames);
+ needsNewline = true;
+ }
+ if (missingKeys.isNotEmpty) {
+ if (needsNewline) {
+ buffer.write('</p><p>');
+ }
+ buffer.write('Has ');
+ write('missing keys', missingKeys);
+ needsNewline = true;
+ }
+ if (extraKeys.isNotEmpty) {
+ if (needsNewline) {
+ buffer.write('</p><p>');
+ }
+ buffer.write('Has ');
+ write('extra keys', extraKeys);
+ needsNewline = true;
+ }
+ mismatchedDependencies.forEach((String key, List<Set<String>> value) {
+ Set<String> missingDependencies = value[0];
+ Set<String> extraDependencies = value[1];
+ if (needsNewline) {
+ buffer.write('</p><p>');
+ }
+ buffer.write('The key ');
+ buffer.write(key);
+ buffer.write(' has ');
+ bool needsConjunction = false;
+ if (missingNames.isNotEmpty) {
+ write('missing dependencies', missingDependencies);
+ needsConjunction = true;
+ }
+ if (extraNames.isNotEmpty) {
+ if (needsConjunction) {
+ buffer.write(' and ');
+ }
+ write('extra dependencies', extraDependencies);
+ }
+ needsNewline = true;
+ });
+ }
+ return true;
+ }
+
+ bool _compareSources(Source expected, Source actual, StringBuffer buffer) {
+ if (actual.fullName == expected.fullName) {
+ return true;
+ }
+ if (buffer != null) {
+ buffer.write('Expected a source for ');
+ buffer.write(expected.fullName);
+ buffer.write('; found a source for ');
+ buffer.write(actual.fullName);
+ }
+ return false;
+ }
+
+ bool _compareStrings(String expected, String actual, StringBuffer buffer) {
+ if (actual == expected) {
+ return true;
+ }
+ int expectedLength = expected.length;
+ int actualLength = actual.length;
+ int left = 0;
+ while (left < actualLength &&
+ left < expectedLength &&
+ actual.codeUnitAt(left) == expected.codeUnitAt(left)) {
+ left++;
+ }
+ if (left == actualLength) {
+ if (buffer != null) {
+ buffer.write('Expected ...[');
+ buffer.write(expected.substring(left));
+ buffer.write(']; found ...[]');
+ }
+ return false;
+ } else if (left == expectedLength) {
+ if (buffer != null) {
+ buffer.write('Expected ...[]; found ...[');
+ buffer.write(actual.substring(left));
+ buffer.write(']');
+ }
+ return false;
+ }
+ int actualRight = actualLength - 1;
+ int expectedRight = expectedLength - 1;
+ while (actualRight > left &&
+ expectedRight > left &&
+ actual.codeUnitAt(actualRight) == expected.codeUnitAt(expectedRight)) {
+ actualRight--;
+ expectedRight--;
+ }
+ if (buffer != null) {
+ void write(String string, int left, int right) {
+ buffer.write('...[');
+ buffer.write(string.substring(left, right));
+ buffer.write(']... (');
+ buffer.write(left);
+ buffer.write('..');
+ buffer.write(right);
+ buffer.write(')');
+ }
+
+ buffer.write('Expected ');
+ write(expected, left, expectedRight);
+ buffer.write('; found ');
+ write(actual, left, actualRight);
+ }
+ return false;
+ }
+
+ bool _compareTokenStreams(Token expected, Token actual, StringBuffer buffer) {
+ bool equals(Token originalToken, Token cloneToken) {
+ return originalToken.type == cloneToken.type &&
+ originalToken.offset == cloneToken.offset &&
+ originalToken.lexeme == cloneToken.lexeme;
+ }
+
+ Token actualLeft = actual;
+ Token expectedLeft = expected;
+ while (actualLeft.type != TokenType.EOF &&
+ expectedLeft.type != TokenType.EOF &&
+ equals(actualLeft, expectedLeft)) {
+ actualLeft = actualLeft.next;
+ expectedLeft = expectedLeft.next;
+ }
+ if (actualLeft.type == TokenType.EOF &&
+ expectedLeft.type == TokenType.EOF) {
+ return true;
+ }
+ if (buffer != null) {
+ void write(Token token) {
+ buffer.write(token.type);
+ buffer.write(' at ');
+ buffer.write(token.offset);
+ buffer.write(' (');
+ buffer.write(token.lexeme);
+ buffer.write(')');
+ }
+
+ buffer.write('Expected ');
+ write(expectedLeft);
+ buffer.write('; found ');
+ write(actualLeft);
+ }
+ return false;
+ }
+
+ bool _compareUsedImportedElements(UsedImportedElements expected,
+ UsedImportedElements actual, StringBuffer buffer) {
+ // TODO(brianwilkerson) Implement this.
+ return true;
+ }
+
+ bool _compareUsedLocalElements(UsedLocalElements expected,
+ UsedLocalElements actual, StringBuffer buffer) {
+ // TODO(brianwilkerson) Implement this.
+ return true;
+ }
+
+ /**
+ * Determine whether or not there is any interesting difference between the
+ * original and cloned values.
+ */
+ void _performComparison() {
+ StringBuffer buffer = new StringBuffer();
+ if (!_compareObjects(cloneValue, originalValue, buffer)) {
+ description = buffer.toString();
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698