blob: 43e390347db200606cb7320e5573a15feef867c1 [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.
library mock_compiler;
import 'dart:async';
import 'dart:collection';
import 'package:compiler/compiler_new.dart' as api;
import 'package:compiler/src/common/names.dart' show Uris;
import 'package:compiler/src/constants/expressions.dart';
import 'package:compiler/src/elements/resolution_types.dart'
show ResolutionDartType;
import 'package:compiler/src/diagnostics/diagnostic_listener.dart';
import 'package:compiler/src/diagnostics/source_span.dart';
import 'package:compiler/src/diagnostics/spannable.dart';
import 'package:compiler/src/elements/elements.dart';
import 'package:compiler/src/elements/visitor.dart';
import 'package:compiler/src/library_loader.dart' show LoadedLibraries;
import 'package:compiler/src/io/source_file.dart';
import 'package:compiler/src/options.dart' show CompilerOptions;
import 'package:compiler/src/resolution/members.dart';
import 'package:compiler/src/resolution/registry.dart';
import 'package:compiler/src/resolution/scope.dart';
import 'package:compiler/src/resolution/tree_elements.dart';
import 'package:compiler/src/resolved_uri_translator.dart';
import 'package:compiler/src/script.dart';
import 'package:compiler/src/tree/tree.dart';
import 'package:compiler/src/old_to_new_api.dart';
import 'parser_helper.dart';
import 'package:compiler/src/elements/modelx.dart'
show ErroneousElementX, FunctionElementX;
import 'package:compiler/src/compiler.dart';
import 'package:compiler/src/common/tasks.dart' show Measurer;
import 'package:compiler/src/deferred_load.dart'
show DeferredLoadTask, OutputUnit;
import 'mock_libraries.dart';
import 'diagnostic_helper.dart';
export 'diagnostic_helper.dart';
final Uri PATCH_CORE = new Uri(scheme: 'patch', path: 'core');
typedef String LibrarySourceProvider(Uri uri);
class MockCompiler extends Compiler {
api.CompilerDiagnostics diagnosticHandler;
final api.CompilerInput provider = null;
/// Expected number of warnings. If `null`, the number of warnings is
/// not checked.
final int expectedWarnings;
/// Expected number of errors. If `null`, the number of errors is not checked.
final int expectedErrors;
final Map<String, SourceFile> sourceFiles;
Node parsedTree;
final LibrarySourceProvider librariesOverride;
final DiagnosticCollector diagnosticCollector = new DiagnosticCollector();
final ResolvedUriTranslator resolvedUriTranslator =
new MockResolvedUriTranslator();
final Measurer measurer = new Measurer();
LibraryElement mainApp;
MockCompiler.internal(
{Map<String, String> coreSource,
bool enableTypeAssertions: false,
bool enableUserAssertions: false,
bool enableMinification: false,
bool disableTypeInference: false,
bool analyzeAll: false,
bool analyzeOnly: false,
bool preserveComments: false,
// Our unit tests check code generation output that is
// affected by inlining support.
bool disableInlining: true,
bool trustTypeAnnotations: false,
bool trustJSInteropTypeAnnotations: false,
bool enableAsyncAwait: false,
int this.expectedWarnings,
int this.expectedErrors,
api.CompilerOutput outputProvider,
LibrarySourceProvider this.librariesOverride})
: sourceFiles = new Map<String, SourceFile>(),
super(
options: new CompilerOptions(
entryPoint: new Uri(scheme: 'mock'),
libraryRoot: Uri.parse('placeholder_library_root_for_mock/'),
enableTypeAssertions: enableTypeAssertions,
enableUserAssertions: enableUserAssertions,
disableInlining: disableInlining,
enableAssertMessage: true,
enableMinification: enableMinification,
disableTypeInference: disableTypeInference,
analyzeAll: analyzeAll,
analyzeOnly: analyzeOnly,
preserveComments: preserveComments,
trustTypeAnnotations: trustTypeAnnotations,
trustJSInteropTypeAnnotations: trustJSInteropTypeAnnotations,
shownPackageWarnings: const []),
outputProvider: outputProvider) {
deferredLoadTask = new MockDeferredLoadTask(this);
registerSource(
Uris.dart_core, buildLibrarySource(DEFAULT_CORE_LIBRARY, coreSource));
registerSource(PATCH_CORE, DEFAULT_PATCH_CORE_SOURCE);
registerSource(
Uris.dart__internal, buildLibrarySource(DEFAULT_INTERNAL_LIBRARY));
registerSource(
Uris.dart__js_helper, buildLibrarySource(DEFAULT_JS_HELPER_LIBRARY));
registerSource(Uris.dart__foreign_helper,
buildLibrarySource(DEFAULT_FOREIGN_HELPER_LIBRARY));
registerSource(Uris.dart__interceptors,
buildLibrarySource(DEFAULT_INTERCEPTORS_LIBRARY));
registerSource(Uris.dart__isolate_helper,
buildLibrarySource(DEFAULT_ISOLATE_HELPER_LIBRARY));
registerSource(Uris.dart_mirrors, DEFAULT_MIRRORS_SOURCE);
registerSource(Uris.dart__js_mirrors, DEFAULT_JS_MIRRORS_SOURCE);
Map<String, String> asyncLibrarySource = <String, String>{};
asyncLibrarySource.addAll(DEFAULT_ASYNC_LIBRARY);
if (enableAsyncAwait) {
asyncLibrarySource.addAll(ASYNC_AWAIT_LIBRARY);
}
registerSource(Uris.dart_async, buildLibrarySource(asyncLibrarySource));
}
/// Initialize the mock compiler with an empty main library.
Future<Uri> init([String mainSource = ""]) {
Uri uri = new Uri(scheme: "mock");
registerSource(uri, mainSource);
return libraryLoader
.loadLibrary(uri)
.then((LoadedLibraries loadedLibraries) {
processLoadedLibraries(loadedLibraries);
mainApp = loadedLibraries.rootLibrary;
startResolution();
// We need to make sure the Object class is resolved. When registering a
// dynamic invocation the ArgumentTypesRegistry eventually iterates over
// the interfaces of the Object class which would be 'null' if the class
// wasn't resolved.
ClassElement objectClass = resolution.commonElements.objectClass;
objectClass.ensureResolved(resolution);
}).then((_) => uri);
}
Future<bool> run(Uri uri, [String mainSource = ""]) {
return init(mainSource).then((Uri mainUri) {
return super.run(uri == null ? mainUri : uri);
}).then((result) {
if (expectedErrors != null &&
expectedErrors != diagnosticCollector.errors.length) {
throw "unexpected error during compilation "
"${diagnosticCollector.errors}";
} else if (expectedWarnings != null &&
expectedWarnings != diagnosticCollector.warnings.length) {
throw "unexpected warnings during compilation "
"${diagnosticCollector.warnings}";
} else {
return result;
}
});
}
/**
* Registers the [source] with [uri] making it possible load [source] as a
* library. If an override has been provided in [librariesOverride], that
* is used instead.
*/
void registerSource(Uri uri, String source) {
if (librariesOverride != null) {
String override = librariesOverride(uri);
if (override != null) {
source = override;
}
}
sourceFiles[uri.toString()] = new MockFile(source);
}
void reportDiagnostic(DiagnosticMessage message,
List<DiagnosticMessage> infoMessages, api.Diagnostic kind) {
void processMessage(DiagnosticMessage message, api.Diagnostic kind) {
SourceSpan span = message.sourceSpan;
Uri uri;
int begin;
int end;
String text = '${message.message}';
if (span != null) {
uri = span.uri;
begin = span.begin;
end = span.end;
}
diagnosticCollector.report(message.message, uri, begin, end, text, kind);
if (diagnosticHandler != null) {
diagnosticHandler.report(message.message, uri, begin, end, text, kind);
}
}
processMessage(message, kind);
infoMessages.forEach((i) => processMessage(i, api.Diagnostic.INFO));
}
CollectingTreeElements resolveStatement(String text) {
parsedTree = parseStatement(text);
LibraryElement library = mainApp;
return resolveNodeStatement(parsedTree, new MockElement(library));
}
TreeElementMapping resolveNodeStatement(
Node tree, ExecutableElement element) {
ResolverVisitor visitor = new ResolverVisitor(
this.resolution,
element,
new ResolutionRegistry(
this.backend.target, new CollectingTreeElements(element)),
scope:
new MockTypeVariablesScope(element.enclosingElement.buildScope()));
if (visitor.scope is LibraryScope ||
visitor.scope is MockTypeVariablesScope) {
visitor.scope = new MethodScope(visitor.scope, element);
}
visitor.visit(tree);
visitor.scope = new LibraryScope(element.library);
return visitor.registry.mapping;
}
resolverVisitor() {
LibraryElement library = mainApp;
Element mockElement = new MockElement(library.entryCompilationUnit);
ResolverVisitor visitor = new ResolverVisitor(
this.resolution,
mockElement,
new ResolutionRegistry(
this.backend.target, new CollectingTreeElements(mockElement)),
scope: mockElement.enclosingElement.buildScope());
visitor.scope = new MethodScope(visitor.scope, mockElement);
return visitor;
}
parseScript(String text, [LibraryElement library]) {
if (library == null) library = mainApp;
parseUnit(text, this, library, registerSource);
}
Future scanBuiltinLibraries() {
// Do nothing. The mock core library is already handled in the constructor.
return new Future.value();
}
Future<LibraryElement> scanBuiltinLibrary(String name) {
// Do nothing. The mock core library is already handled in the constructor.
return new Future.value();
}
// The mock library doesn't need any patches.
Uri resolvePatchUri(String dartLibraryName) {
if (dartLibraryName == 'core') {
return PATCH_CORE;
}
return null;
}
Future<Script> readScript(Uri uri, [Spannable spannable]) {
SourceFile sourceFile = sourceFiles[uri.toString()];
if (sourceFile == null) throw new ArgumentError(uri);
return new Future.value(new Script(uri, uri, sourceFile));
}
Element lookupElementIn(ScopeContainerElement container, name) {
Element element = container.localLookup(name);
return element != null
? element
: new ErroneousElementX(null, null, name, container);
}
/// Create a new [MockCompiler] and apply it asynchronously to [f].
static Future create(f(MockCompiler compiler)) {
MockCompiler compiler = new MockCompiler.internal();
return compiler.init().then((_) => f(compiler));
}
}
class MockResolvedUriTranslator implements ResolvedUriTranslator {
static final dynamic _emptySet = new Set();
Uri translate(LibraryElement importingLibrary, Uri resolvedUri,
Spannable spannable) =>
resolvedUri;
Set<Uri> get disallowedLibraryUris => _emptySet;
bool get mockableLibraryUsed => false;
Map<String, Uri> get sdkLibraries => const <String, Uri>{};
}
class CollectingTreeElements extends TreeElementMapping {
final Map<Node, Element> map = new LinkedHashMap<Node, Element>();
CollectingTreeElements(Element currentElement) : super(currentElement);
operator []=(Node node, Element element) {
map[node] = element;
}
operator [](Node node) => map[node];
void remove(Node node) {
map.remove(node);
}
List<ConstantExpression> get constants {
List<ConstantExpression> list = <ConstantExpression>[];
forEachConstantNode((_, c) => list.add(c));
return list;
}
}
class MockTypeVariablesScope extends TypeVariablesScope {
@override
List<ResolutionDartType> get typeVariables => <ResolutionDartType>[];
MockTypeVariablesScope(Scope parent) : super(parent);
String toString() => 'MockTypeVariablesScope($parent)';
}
// The mock compiler does not split the program in output units.
class MockDeferredLoadTask extends DeferredLoadTask {
MockDeferredLoadTask(Compiler compiler) : super(compiler);
OutputUnit getElementOutputUnit(dynamic dependency) {
return mainOutputUnit;
}
}
api.CompilerDiagnostics createHandler(MockCompiler compiler, String text,
{bool verbose: false}) {
return new LegacyCompilerDiagnostics(
(uri, int begin, int end, String message, kind) {
if (kind == api.Diagnostic.VERBOSE_INFO && !verbose) return;
SourceFile sourceFile;
if (uri == null) {
sourceFile = new StringSourceFile.fromName('analysis', text);
} else {
sourceFile = compiler.sourceFiles[uri.toString()];
}
if (sourceFile != null && begin != null && end != null) {
print('${kind}: ${sourceFile.getLocationMessage(message, begin, end)}');
} else {
print('${kind}: $message');
}
});
}
class MockElement extends FunctionElementX {
MockElement(Element enclosingElement)
: super('', ElementKind.FUNCTION, Modifiers.EMPTY, enclosingElement);
get node => null;
parseNode(_) => null;
bool get hasNode => false;
accept(ElementVisitor visitor, arg) {
return visitor.visitMethodElement(this, arg);
}
}
// TODO(herhut): Disallow warnings and errors during compilation by default.
MockCompiler compilerFor(String code, Uri uri,
{bool analyzeAll: false,
bool analyzeOnly: false,
Map<String, String> coreSource,
bool disableInlining: true,
bool minify: false,
bool trustTypeAnnotations: false,
bool enableTypeAssertions: false,
bool enableUserAssertions: false,
int expectedErrors,
int expectedWarnings,
api.CompilerOutput outputProvider}) {
MockCompiler compiler = new MockCompiler.internal(
analyzeAll: analyzeAll,
analyzeOnly: analyzeOnly,
coreSource: coreSource,
disableInlining: disableInlining,
enableMinification: minify,
trustTypeAnnotations: trustTypeAnnotations,
enableTypeAssertions: enableTypeAssertions,
enableUserAssertions: enableUserAssertions,
expectedErrors: expectedErrors,
expectedWarnings: expectedWarnings,
outputProvider: outputProvider);
compiler.registerSource(uri, code);
compiler.diagnosticHandler = createHandler(compiler, code);
return compiler;
}