// 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 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 resolvers = [ 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 = [ ElementFactory.constructorElement(proxyClassElement, '', true) ..isCycleFree = true ..constantInitializers = [] ]; ClassElement objectClassElement = provider.objectType.element; coreUnit.types = [ 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 = [ ElementFactory.functionElement3("identical", provider.boolType, [objectClassElement, objectClassElement], null), ElementFactory.functionElement3("print", VoidTypeImpl.instance, [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 = [ deprecatedTopLevelVariableElt.getter, overrideTopLevelVariableElt.getter, proxyTopLevelVariableElt.getter ]; coreUnit.topLevelVariables = [ 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 ClassElementImpl futureElement = ElementFactory.classElement2("Future", ["T"]); // FutureOr ClassElementImpl futureOrElement = ElementFactory.classElement2("FutureOr", ["T"]); futureElement.enclosingElement = asyncUnit; // factory Future.value([value]) ConstructorElementImpl futureConstructor = ElementFactory.constructorElement2(futureElement, "value"); futureConstructor.parameters = [ ElementFactory.positionalParameter2("value", provider.dynamicType) ]; futureConstructor.factory = true; futureElement.constructors = [futureConstructor]; // Future then(FutureOr 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 = [futureThenR]; } thenOnValue.enclosingElement = thenMethod; thenOnValue.type = new FunctionTypeImpl(thenOnValue); (thenMethod.parameters[0] as ParameterElementImpl).type = thenOnValue.type; thenMethod.type = new FunctionTypeImpl(thenMethod); futureElement.methods = [thenMethod]; // Completer ClassElementImpl completerElement = ElementFactory.classElement2("Completer", ["T"]); ConstructorElementImpl completerConstructor = ElementFactory.constructorElement2(completerElement, null); completerElement.constructors = [completerConstructor]; // StreamSubscription ClassElementImpl streamSubscriptionElement = ElementFactory.classElement2("StreamSubscription", ["T"]); // Stream ClassElementImpl streamElement = ElementFactory.classElement2("Stream", ["T"]); streamElement.abstract = true; streamElement.constructors = [ ElementFactory.constructorElement2(streamElement, null) ]; DartType returnType = streamSubscriptionElement.type .instantiate(streamElement.type.typeArguments); FunctionElementImpl listenOnData = ElementFactory.functionElement3( 'onData', VoidTypeImpl.instance, [streamElement.typeParameters[0]], null); listenOnData.isSynthetic = true; List parameterTypes = [ listenOnData.type, ]; // TODO(brianwilkerson) This is missing the optional parameters. MethodElementImpl listenMethod = ElementFactory.methodElement('listen', returnType, parameterTypes); streamElement.methods = [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 = [ 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 = [ ElementFactory.methodElement( "getContext", contextElementType, [provider.stringType]) ]; canvasElement.accessors = [ ElementFactory.getterElement("context2D", false, context2dElement.type) ]; canvasElement.fields = canvasElement.accessors .map((PropertyAccessorElement accessor) => accessor.variable) .cast() .toList(); ClassElementImpl documentElement = ElementFactory.classElement("Document", elementType); ClassElementImpl htmlDocumentElement = ElementFactory.classElement("HtmlDocument", documentElement.type); htmlDocumentElement.methods = [ ElementFactory .methodElement("query", elementType, [provider.stringType]) ]; htmlUnit.types = [ 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 = [ ElementFactory.functionElement3("query", elementElement.type, [provider.stringType.element], ClassElement.EMPTY_LIST) ]; TopLevelVariableElementImpl document = ElementFactory.topLevelVariableElement3( "document", false, true, htmlDocumentElement.type); htmlUnit.topLevelVariables = [document]; htmlUnit.accessors = [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, [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 = [seedParam]; randomElement.constructors = [randomConstructor]; FunctionElement sinElement = ElementFactory.functionElement3( "sin", provider.doubleType, [provider.numType.element], ClassElement.EMPTY_LIST); FunctionElement sqrtElement = ElementFactory.functionElement3( "sqrt", provider.doubleType, [provider.numType.element], ClassElement.EMPTY_LIST); mathUnit.accessors = [ ln10Element.getter, piElement.getter ]; mathUnit.functions = [ cosElement, maxElement, sinElement, sqrtElement ]; mathUnit.topLevelVariables = [ ln10Element, piElement ]; mathUnit.types = [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 elementMap = new HashMap(); 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 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 sourceMap = new HashMap(); TestPackageUriResolver(Map 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); } }