blob: 73eb7d3e2ef70a4e06146272408d12492d69cfcc [file] [log] [blame]
// Copyright (c) 2016, 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.
import 'dart:collection';
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/file_system/file_system.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/context/context.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/file_system/file_system.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source_io.dart';
import 'package:analyzer/src/generated/testing/ast_test_factory.dart';
import 'package:analyzer/src/generated/testing/element_factory.dart';
import 'package:analyzer/src/generated/testing/test_type_provider.dart';
import 'package:analyzer/src/generated/utilities_dart.dart';
import 'package:analyzer/src/string_source.dart';
import 'package:test/test.dart';
/**
* The class `AnalysisContextFactory` defines utility methods used to create analysis contexts
* for testing purposes.
*/
class AnalysisContextFactory {
static String _DART_MATH = "dart:math";
static String _DART_INTERCEPTORS = "dart:_interceptors";
static String _DART_JS_HELPER = "dart:_js_helper";
/**
* Create and return an analysis context that has a fake core library already
* resolved. The given [resourceProvider] will be used when accessing the file
* system.
*/
static InternalAnalysisContext contextWithCore(
{ResourceProvider resourceProvider}) {
AnalysisContextForTests context = new AnalysisContextForTests();
return initContextWithCore(context, null, resourceProvider);
}
/**
* Create and return an analysis context that uses the given [options] and has
* a fake core library already resolved. The given [resourceProvider] will be
* used when accessing the file system.
*/
static InternalAnalysisContext contextWithCoreAndOptions(
AnalysisOptions options,
{ResourceProvider resourceProvider}) {
AnalysisContextForTests context = new AnalysisContextForTests();
context._internalSetAnalysisOptions(options);
return initContextWithCore(context, null, resourceProvider);
}
/**
* Create and return an analysis context that has a fake core library already
* resolved. If not `null`, the given [packages] map will be used to create a
* package URI resolver. The given [resourceProvider] will be used when
* accessing the file system.
*/
static InternalAnalysisContext contextWithCoreAndPackages(
Map<String, String> packages,
{ResourceProvider resourceProvider}) {
AnalysisContextForTests context = new AnalysisContextForTests();
return initContextWithCore(
context, new TestPackageUriResolver(packages), resourceProvider);
}
/**
* Initialize the given analysis [context] with a fake core library that has
* already been resolved. If not `null`, the given [contributedResolver] will
* be added to the context's source factory. The given [resourceProvider] will
* be used when accessing the file system.
*/
static InternalAnalysisContext initContextWithCore(
InternalAnalysisContext context,
[UriResolver contributedResolver,
ResourceProvider resourceProvider]) {
resourceProvider ??= PhysicalResourceProvider.INSTANCE;
DartSdk sdk = new _AnalysisContextFactory_initContextWithCore(
resourceProvider, '/fake/sdk');
List<UriResolver> resolvers = <UriResolver>[
new DartUriResolver(sdk),
new ResourceUriResolver(resourceProvider)
];
if (contributedResolver != null) {
resolvers.add(contributedResolver);
}
SourceFactory sourceFactory = new SourceFactory(resolvers);
context.sourceFactory = sourceFactory;
AnalysisContext coreContext = sdk.context;
(coreContext.analysisOptions as AnalysisOptionsImpl).strongMode =
context.analysisOptions.strongMode;
//
// dart:core
//
TestTypeProvider provider = new TestTypeProvider();
CompilationUnitElementImpl coreUnit =
new CompilationUnitElementImpl("core.dart");
Source coreSource = sourceFactory.forUri(DartSdk.DART_CORE);
coreContext.setContents(coreSource, "");
coreUnit.librarySource = coreUnit.source = coreSource;
ClassElementImpl overrideClassElement =
ElementFactory.classElement2("_Override");
ClassElementImpl proxyClassElement = ElementFactory.classElement2("_Proxy");
proxyClassElement.constructors = <ConstructorElement>[
ElementFactory.constructorElement(proxyClassElement, '', true)
..isCycleFree = true
..constantInitializers = <ConstructorInitializer>[]
];
ClassElement objectClassElement = provider.objectType.element;
coreUnit.types = <ClassElement>[
provider.boolType.element,
provider.deprecatedType.element,
provider.doubleType.element,
provider.functionType.element,
provider.intType.element,
provider.iterableType.element,
provider.iteratorType.element,
provider.listType.element,
provider.mapType.element,
provider.nullType.element,
provider.numType.element,
objectClassElement,
overrideClassElement,
proxyClassElement,
provider.stackTraceType.element,
provider.stringType.element,
provider.symbolType.element,
provider.typeType.element
];
coreUnit.functions = <FunctionElement>[
ElementFactory.functionElement3("identical", provider.boolType,
<ClassElement>[objectClassElement, objectClassElement], null),
ElementFactory.functionElement3("print", VoidTypeImpl.instance,
<ClassElement>[objectClassElement], null)
];
TopLevelVariableElement proxyTopLevelVariableElt = ElementFactory
.topLevelVariableElement3("proxy", true, false, proxyClassElement.type);
ConstTopLevelVariableElementImpl deprecatedTopLevelVariableElt =
ElementFactory.topLevelVariableElement3(
"deprecated", true, false, provider.deprecatedType);
TopLevelVariableElement overrideTopLevelVariableElt =
ElementFactory.topLevelVariableElement3(
"override", true, false, overrideClassElement.type);
{
ClassElement deprecatedElement = provider.deprecatedType.element;
InstanceCreationExpression initializer = AstTestFactory
.instanceCreationExpression2(
Keyword.CONST,
AstTestFactory.typeName(deprecatedElement),
[AstTestFactory.string2('next release')]);
ConstructorElement constructor = deprecatedElement.constructors.single;
initializer.staticElement = constructor;
initializer.constructorName.staticElement = constructor;
deprecatedTopLevelVariableElt.constantInitializer = initializer;
}
coreUnit.accessors = <PropertyAccessorElement>[
deprecatedTopLevelVariableElt.getter,
overrideTopLevelVariableElt.getter,
proxyTopLevelVariableElt.getter
];
coreUnit.topLevelVariables = <TopLevelVariableElement>[
deprecatedTopLevelVariableElt,
overrideTopLevelVariableElt,
proxyTopLevelVariableElt
];
LibraryElementImpl coreLibrary = new LibraryElementImpl.forNode(
coreContext, AstTestFactory.libraryIdentifier2(["dart", "core"]));
coreLibrary.definingCompilationUnit = coreUnit;
//
// dart:async
//
LibraryElementImpl asyncLibrary = new LibraryElementImpl.forNode(
coreContext, AstTestFactory.libraryIdentifier2(["dart", "async"]));
CompilationUnitElementImpl asyncUnit =
new CompilationUnitElementImpl("async.dart");
Source asyncSource = sourceFactory.forUri(DartSdk.DART_ASYNC);
coreContext.setContents(asyncSource, "");
asyncUnit.librarySource = asyncUnit.source = asyncSource;
asyncLibrary.definingCompilationUnit = asyncUnit;
// Future<T>
ClassElementImpl futureElement =
ElementFactory.classElement2("Future", ["T"]);
// FutureOr<T>
ClassElementImpl futureOrElement =
ElementFactory.classElement2("FutureOr", ["T"]);
futureElement.enclosingElement = asyncUnit;
// factory Future.value([value])
ConstructorElementImpl futureConstructor =
ElementFactory.constructorElement2(futureElement, "value");
futureConstructor.parameters = <ParameterElement>[
ElementFactory.positionalParameter2("value", provider.dynamicType)
];
futureConstructor.factory = true;
futureElement.constructors = <ConstructorElement>[futureConstructor];
// Future<R> then<R>(FutureOr<R> onValue(T value), { Function onError });
TypeDefiningElement futureThenR = DynamicElementImpl.instance;
DartType onValueReturnType = DynamicTypeImpl.instance;
if (context.analysisOptions.strongMode) {
futureThenR = ElementFactory.typeParameterWithType('R');
onValueReturnType = futureOrElement.type.instantiate([futureThenR.type]);
}
FunctionElementImpl thenOnValue = ElementFactory.functionElement3(
'onValue', onValueReturnType, [futureElement.typeParameters[0]], null);
thenOnValue.isSynthetic = true;
DartType futureRType = futureElement.type.instantiate([futureThenR.type]);
MethodElementImpl thenMethod = ElementFactory
.methodElementWithParameters(futureElement, "then", futureRType, [
ElementFactory.requiredParameter2("onValue", thenOnValue.type),
ElementFactory.namedParameter2("onError", provider.functionType)
]);
if (!futureThenR.type.isDynamic) {
thenMethod.typeParameters = <TypeParameterElement>[futureThenR];
}
thenOnValue.enclosingElement = thenMethod;
thenOnValue.type = new FunctionTypeImpl(thenOnValue);
(thenMethod.parameters[0] as ParameterElementImpl).type = thenOnValue.type;
thenMethod.type = new FunctionTypeImpl(thenMethod);
futureElement.methods = <MethodElement>[thenMethod];
// Completer
ClassElementImpl completerElement =
ElementFactory.classElement2("Completer", ["T"]);
ConstructorElementImpl completerConstructor =
ElementFactory.constructorElement2(completerElement, null);
completerElement.constructors = <ConstructorElement>[completerConstructor];
// StreamSubscription
ClassElementImpl streamSubscriptionElement =
ElementFactory.classElement2("StreamSubscription", ["T"]);
// Stream
ClassElementImpl streamElement =
ElementFactory.classElement2("Stream", ["T"]);
streamElement.abstract = true;
streamElement.constructors = <ConstructorElement>[
ElementFactory.constructorElement2(streamElement, null)
];
DartType returnType = streamSubscriptionElement.type
.instantiate(streamElement.type.typeArguments);
FunctionElementImpl listenOnData = ElementFactory.functionElement3(
'onData',
VoidTypeImpl.instance,
<TypeDefiningElement>[streamElement.typeParameters[0]],
null);
listenOnData.isSynthetic = true;
List<DartType> parameterTypes = <DartType>[
listenOnData.type,
];
// TODO(brianwilkerson) This is missing the optional parameters.
MethodElementImpl listenMethod =
ElementFactory.methodElement('listen', returnType, parameterTypes);
streamElement.methods = <MethodElement>[listenMethod];
listenMethod.type = new FunctionTypeImpl(listenMethod);
FunctionElementImpl listenParamFunction = parameterTypes[0].element;
listenParamFunction.enclosingElement = listenMethod;
listenParamFunction.type = new FunctionTypeImpl(listenParamFunction);
ParameterElementImpl listenParam = listenMethod.parameters[0];
listenParam.type = listenParamFunction.type;
asyncUnit.types = <ClassElement>[
completerElement,
futureElement,
futureOrElement,
streamElement,
streamSubscriptionElement
];
//
// dart:html
//
CompilationUnitElementImpl htmlUnit =
new CompilationUnitElementImpl("html_dartium.dart");
Source htmlSource = sourceFactory.forUri(DartSdk.DART_HTML);
coreContext.setContents(htmlSource, "");
htmlUnit.librarySource = htmlUnit.source = htmlSource;
ClassElementImpl elementElement = ElementFactory.classElement2("Element");
InterfaceType elementType = elementElement.type;
ClassElementImpl canvasElement =
ElementFactory.classElement("CanvasElement", elementType);
ClassElementImpl contextElement =
ElementFactory.classElement2("CanvasRenderingContext");
InterfaceType contextElementType = contextElement.type;
ClassElementImpl context2dElement = ElementFactory.classElement(
"CanvasRenderingContext2D", contextElementType);
canvasElement.methods = <MethodElement>[
ElementFactory.methodElement(
"getContext", contextElementType, [provider.stringType])
];
canvasElement.accessors = <PropertyAccessorElement>[
ElementFactory.getterElement("context2D", false, context2dElement.type)
];
canvasElement.fields = canvasElement.accessors
.map((PropertyAccessorElement accessor) => accessor.variable)
.cast<FieldElement>()
.toList();
ClassElementImpl documentElement =
ElementFactory.classElement("Document", elementType);
ClassElementImpl htmlDocumentElement =
ElementFactory.classElement("HtmlDocument", documentElement.type);
htmlDocumentElement.methods = <MethodElement>[
ElementFactory
.methodElement("query", elementType, <DartType>[provider.stringType])
];
htmlUnit.types = <ClassElement>[
ElementFactory.classElement("AnchorElement", elementType),
ElementFactory.classElement("BodyElement", elementType),
ElementFactory.classElement("ButtonElement", elementType),
canvasElement,
contextElement,
context2dElement,
ElementFactory.classElement("DivElement", elementType),
documentElement,
elementElement,
htmlDocumentElement,
ElementFactory.classElement("InputElement", elementType),
ElementFactory.classElement("SelectElement", elementType)
];
htmlUnit.functions = <FunctionElement>[
ElementFactory.functionElement3("query", elementElement.type,
<ClassElement>[provider.stringType.element], ClassElement.EMPTY_LIST)
];
TopLevelVariableElementImpl document =
ElementFactory.topLevelVariableElement3(
"document", false, true, htmlDocumentElement.type);
htmlUnit.topLevelVariables = <TopLevelVariableElement>[document];
htmlUnit.accessors = <PropertyAccessorElement>[document.getter];
LibraryElementImpl htmlLibrary = new LibraryElementImpl.forNode(coreContext,
AstTestFactory.libraryIdentifier2(["dart", "dom", "html"]));
htmlLibrary.definingCompilationUnit = htmlUnit;
//
// dart:math
//
CompilationUnitElementImpl mathUnit =
new CompilationUnitElementImpl("math.dart");
Source mathSource = sourceFactory.forUri(_DART_MATH);
coreContext.setContents(mathSource, "");
mathUnit.librarySource = mathUnit.source = mathSource;
FunctionElement cosElement = ElementFactory.functionElement3(
"cos",
provider.doubleType,
<ClassElement>[provider.numType.element],
ClassElement.EMPTY_LIST);
TopLevelVariableElement ln10Element = ElementFactory
.topLevelVariableElement3("LN10", true, false, provider.doubleType);
TypeParameterElement maxT =
ElementFactory.typeParameterWithType('T', provider.numType);
FunctionElementImpl maxElement = ElementFactory.functionElement3(
"max", maxT.type, [maxT, maxT], ClassElement.EMPTY_LIST);
maxElement.typeParameters = [maxT];
maxElement.type = new FunctionTypeImpl(maxElement);
TopLevelVariableElement piElement = ElementFactory.topLevelVariableElement3(
"PI", true, false, provider.doubleType);
ClassElementImpl randomElement = ElementFactory.classElement2("Random");
randomElement.abstract = true;
ConstructorElementImpl randomConstructor =
ElementFactory.constructorElement2(randomElement, null);
randomConstructor.factory = true;
ParameterElementImpl seedParam = new ParameterElementImpl("seed", 0);
seedParam.parameterKind = ParameterKind.POSITIONAL;
seedParam.type = provider.intType;
randomConstructor.parameters = <ParameterElement>[seedParam];
randomElement.constructors = <ConstructorElement>[randomConstructor];
FunctionElement sinElement = ElementFactory.functionElement3(
"sin",
provider.doubleType,
<ClassElement>[provider.numType.element],
ClassElement.EMPTY_LIST);
FunctionElement sqrtElement = ElementFactory.functionElement3(
"sqrt",
provider.doubleType,
<ClassElement>[provider.numType.element],
ClassElement.EMPTY_LIST);
mathUnit.accessors = <PropertyAccessorElement>[
ln10Element.getter,
piElement.getter
];
mathUnit.functions = <FunctionElement>[
cosElement,
maxElement,
sinElement,
sqrtElement
];
mathUnit.topLevelVariables = <TopLevelVariableElement>[
ln10Element,
piElement
];
mathUnit.types = <ClassElement>[randomElement];
LibraryElementImpl mathLibrary = new LibraryElementImpl.forNode(
coreContext, AstTestFactory.libraryIdentifier2(["dart", "math"]));
mathLibrary.definingCompilationUnit = mathUnit;
//
// Set empty sources for the rest of the libraries.
//
Source source = sourceFactory.forUri(_DART_INTERCEPTORS);
coreContext.setContents(source, "");
source = sourceFactory.forUri(_DART_JS_HELPER);
coreContext.setContents(source, "");
//
// Record the elements.
//
Map<Source, LibraryElement> elementMap =
new HashMap<Source, LibraryElement>();
elementMap[coreSource] = coreLibrary;
if (asyncSource != null) {
elementMap[asyncSource] = asyncLibrary;
}
elementMap[htmlSource] = htmlLibrary;
elementMap[mathSource] = mathLibrary;
//
// Set the public and export namespaces. We don't use exports in the fake
// core library so public and export namespaces are the same.
//
for (LibraryElementImpl library in elementMap.values) {
Namespace namespace =
new NamespaceBuilder().createPublicNamespaceForLibrary(library);
library.exportNamespace = namespace;
library.publicNamespace = namespace;
}
context.recordLibraryElements(elementMap);
// Create the synthetic element for `loadLibrary`.
for (LibraryElementImpl library in elementMap.values) {
library.createLoadLibraryFunction(context.typeProvider);
}
return context;
}
}
/**
* An analysis context that has a fake SDK that is much smaller and faster for
* testing purposes.
*/
class AnalysisContextForTests extends AnalysisContextImpl {
@override
void set analysisOptions(AnalysisOptions options) {
AnalysisOptions currentOptions = analysisOptions;
bool needsRecompute = currentOptions.analyzeFunctionBodiesPredicate !=
options.analyzeFunctionBodiesPredicate ||
currentOptions.generateImplicitErrors !=
options.generateImplicitErrors ||
currentOptions.generateSdkErrors != options.generateSdkErrors ||
currentOptions.dart2jsHint != options.dart2jsHint ||
(currentOptions.hint && !options.hint) ||
currentOptions.preserveComments != options.preserveComments;
if (needsRecompute) {
fail(
"Cannot set options that cause the sources to be reanalyzed in a test context");
}
super.analysisOptions = options;
}
@override
bool exists(Source source) =>
super.exists(source) || sourceFactory.dartSdk.context.exists(source);
@override
TimestampedData<String> getContents(Source source) {
if (source.isInSystemLibrary) {
return sourceFactory.dartSdk.context.getContents(source);
}
return super.getContents(source);
}
@override
int getModificationStamp(Source source) {
if (source.isInSystemLibrary) {
return sourceFactory.dartSdk.context.getModificationStamp(source);
}
return super.getModificationStamp(source);
}
/**
* Set the analysis options, even if they would force re-analysis. This method should only be
* invoked before the fake SDK is initialized.
*
* @param options the analysis options to be set
*/
void _internalSetAnalysisOptions(AnalysisOptions options) {
super.analysisOptions = options;
}
}
/**
* Helper for creating and managing single [AnalysisContext].
*/
class AnalysisContextHelper {
MemoryResourceProvider resourceProvider;
AnalysisContext context;
/**
* Creates new [AnalysisContext] using [AnalysisContextFactory].
*/
AnalysisContextHelper(
[AnalysisOptionsImpl options, MemoryResourceProvider provider]) {
resourceProvider = provider ?? new MemoryResourceProvider();
context = AnalysisContextFactory.contextWithCoreAndOptions(
options ?? new AnalysisOptionsImpl(),
resourceProvider: resourceProvider);
}
Source addSource(String path, String code) {
Source source = resourceProvider
.getFile(resourceProvider.convertPath(path))
.createSource();
if (path.endsWith(".dart") || path.endsWith(".html")) {
ChangeSet changeSet = new ChangeSet();
changeSet.addedSource(source);
context.applyChanges(changeSet);
}
context.setContents(source, code);
return source;
}
CompilationUnitElement getDefiningUnitElement(Source source) =>
context.getCompilationUnitElement(source, source);
CompilationUnit resolveDefiningUnit(Source source) {
LibraryElement libraryElement = context.computeLibraryElement(source);
return context.resolveCompilationUnit(source, libraryElement);
}
void runTasks() {
AnalysisResult result = context.performAnalysisTask();
while (result.changeNotices != null) {
result = context.performAnalysisTask();
}
}
}
class TestPackageUriResolver extends UriResolver {
Map<String, Source> sourceMap = new HashMap<String, Source>();
TestPackageUriResolver(Map<String, String> map) {
map.forEach((String uri, String contents) {
sourceMap[uri] = new StringSource(contents, '/test_pkg_source.dart');
});
}
@override
Source resolveAbsolute(Uri uri, [Uri actualUri]) {
String uriString = uri.toString();
return sourceMap[uriString];
}
@override
Uri restoreAbsolute(Source source) => throw new UnimplementedError();
}
class _AnalysisContextFactory_initContextWithCore extends FolderBasedDartSdk {
_AnalysisContextFactory_initContextWithCore(
ResourceProvider resourceProvider, String sdkPath)
: super(resourceProvider, resourceProvider.getFolder(sdkPath));
@override
LibraryMap initialLibraryMap(bool useDart2jsPaths) {
LibraryMap map = new LibraryMap();
_addLibrary(map, DartSdk.DART_ASYNC, false, "async.dart");
_addLibrary(map, DartSdk.DART_CORE, false, "core.dart");
_addLibrary(map, DartSdk.DART_HTML, false, "html_dartium.dart");
_addLibrary(map, AnalysisContextFactory._DART_MATH, false, "math.dart");
_addLibrary(map, AnalysisContextFactory._DART_INTERCEPTORS, true,
"_interceptors.dart");
_addLibrary(
map, AnalysisContextFactory._DART_JS_HELPER, true, "_js_helper.dart");
return map;
}
void _addLibrary(LibraryMap map, String uri, bool isInternal, String path) {
SdkLibraryImpl library = new SdkLibraryImpl(uri);
if (isInternal) {
library.category = "Internal";
}
library.path = path;
map.setLibrary(uri, library);
}
}