blob: b25f241ab09df04ec9badaef611cb70a380e1996 [file] [log] [blame]
// Copyright (c) 2014, 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 analyzer.src.generated.incremental_resolution_validator;
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/member.dart';
/**
* Validates that the [actual] and the [expected] units have the same structure
* and resolution. Throws [IncrementalResolutionMismatch] otherwise.
*/
void assertSameResolution(CompilationUnit actual, CompilationUnit expected,
{bool validateTypes: false}) {
_SameResolutionValidator validator =
new _SameResolutionValidator(validateTypes);
validator.isEqualNodes(expected, actual);
}
/**
* This exception is thrown when a mismatch between actual and expected AST
* or resolution is found.
*/
class IncrementalResolutionMismatch {
final String message;
IncrementalResolutionMismatch(this.message);
@override
String toString() => "IncrementalResolutionMismatch: $message";
}
/**
* An [AstVisitor] that compares the structure of two [AstNode]s and their
* resolution to see whether they are equal.
*/
class _SameResolutionValidator extends AstComparator {
final bool validateTypes;
_SameResolutionValidator(this.validateTypes);
@override
bool failDifferentLength(List expectedList, List actualList) {
int expectedLength = expectedList.length;
int actualLength = actualList.length;
String message = '';
message += 'Expected length: $expectedLength\n';
message += 'but $actualLength found\n';
message += 'in $actualList';
_fail(message);
return false;
}
@override
bool failIfNotNull(Object expected, Object actual) {
if (actual != null) {
_fail('Expected null, but found $actual');
return false;
}
return true;
}
@override
bool failIsNull(Object expected, Object actual) {
_fail('Expected not null, but found null');
return false;
}
@override
bool failRuntimeType(Object expected, Object actual) {
_fail('Expected ${expected.runtimeType}, but found ${actual.runtimeType}');
return false;
}
@override
bool isEqualNodes(AstNode first, AstNode second) {
super.isEqualNodes(first, second);
if (first is SimpleIdentifier && second is SimpleIdentifier) {
int offset = first.offset;
_verifyElement(
first.staticElement, second.staticElement, 'staticElement[$offset]');
_verifyElement(first.propagatedElement, second.propagatedElement,
'propagatedElement[$offset]');
} else if (first is Declaration && second is Declaration) {
int offset = first.offset;
_verifyElement(first.element, second.element, 'declaration[$offset]');
} else if (first is Directive && second is Directive) {
int offset = first.offset;
_verifyElement(first.element, second.element, 'directive[$offset]');
} else if (first is Expression && second is Expression) {
int offset = first.offset;
_verifyType(first.staticType, second.staticType, 'staticType[$offset]');
_verifyType(first.propagatedType, second.propagatedType,
'propagatedType[$offset]');
_verifyElement(first.staticParameterElement,
second.staticParameterElement, 'staticParameterElement[$offset]');
_verifyElement(
first.propagatedParameterElement,
second.propagatedParameterElement,
'propagatedParameterElement[$offset]');
}
return true;
}
@override
bool isEqualTokensNotNull(Token expected, Token actual) {
_verifyEqual('lexeme', expected.lexeme, actual.lexeme);
_verifyEqual('offset', expected.offset, actual.offset);
_verifyEqual('offset', expected.length, actual.length);
return true;
}
void _fail(String message) {
throw new IncrementalResolutionMismatch(message);
}
void _verifyElement(Element a, Element b, String desc) {
if (a is Member && b is Member) {
a = (a as Member).baseElement;
b = (b as Member).baseElement;
}
String locationA = _getElementLocationWithoutUri(a);
String locationB = _getElementLocationWithoutUri(b);
if (locationA != locationB) {
_fail('$desc\nExpected: $b ($locationB)\n Actual: $a ($locationA)');
}
if (a == null && b == null) {
return;
}
_verifyEqual('nameOffset', a.nameOffset, b.nameOffset);
if (a is ElementImpl && b is ElementImpl) {
_verifyEqual('codeOffset', a.codeOffset, b.codeOffset);
_verifyEqual('codeLength', a.codeLength, b.codeLength);
}
if (a is LocalElement && b is LocalElement) {
_verifyEqual('visibleRange', a.visibleRange, b.visibleRange);
}
_verifyEqual(
'documentationComment', a.documentationComment, b.documentationComment);
}
void _verifyEqual(String name, actual, expected) {
if (actual != expected) {
_fail('$name\nExpected: $expected\n Actual: $actual');
}
}
void _verifyType(DartType a, DartType b, String desc) {
if (!validateTypes) {
return;
}
if (a != b) {
_fail('$desc\nExpected: $b\n Actual: $a');
}
}
/**
* Returns an URI scheme independent version of the [element] location.
*/
static String _getElementLocationWithoutUri(Element element) {
if (element == null) {
return '<null>';
}
if (element is UriReferencedElementImpl) {
return '<ignored>';
}
ElementLocation location = element.location;
List<String> components = location.components;
String uriPrefix = '';
Element unit = element is CompilationUnitElement
? element
: element.getAncestor((e) => e is CompilationUnitElement);
if (unit != null) {
String libComponent = components[0];
String unitComponent = components[1];
components = components.sublist(2);
uriPrefix = _getShortElementLocationUri(libComponent) +
':' +
_getShortElementLocationUri(unitComponent);
} else {
String libComponent = components[0];
components = components.sublist(1);
uriPrefix = _getShortElementLocationUri(libComponent);
}
return uriPrefix + ':' + components.join(':');
}
/**
* Returns a "short" version of the given [uri].
*
* For example:
* /User/me/project/lib/my_lib.dart -> my_lib.dart
* package:project/my_lib.dart -> my_lib.dart
*/
static String _getShortElementLocationUri(String uri) {
int index = uri.lastIndexOf('/');
if (index == -1) {
return uri;
}
return uri.substring(index + 1);
}
}