Initial implementation of front_end hot reload API.

This implementation is based on the existing kernel and analysis
driver logic, using proxy classes to account for interface
incompatibilities between those components.  In future CLs, I
hope we can (a) move the necessary analysis driver logic into
front_end, and (b) reduce or eliminate the number of proxy
classes needed.

This CL represents the very first inklings of functionality; all that
is confirmed to work at this point is compilation of a single source
file containing an empty `main` method.

R=danrubel@google.com, scheglov@google.com

Review-Url: https://codereview.chromium.org/2624193003 .
This commit is contained in:
Paul Berry
2017-01-17 06:10:02 -08:00
parent 83f8ca4652
commit 05a417034a
9 changed files with 812 additions and 25 deletions
@@ -18,7 +18,7 @@ import 'package:analyzer/src/util/fast_uri.dart';
import 'package:analyzer/task/dart.dart';
import 'package:analyzer/task/general.dart';
import 'package:analyzer/task/model.dart';
import 'package:path/path.dart' as pathos;
import 'package:front_end/src/base/source.dart';
/**
* The [ResultProvider] that provides results from input package summaries.
@@ -66,43 +66,23 @@ class InSummaryPackageUriResolver extends UriResolver {
* are served from its summary. This source uses its URI as [fullName] and has
* empty contents.
*/
class InSummarySource extends Source {
final Uri uri;
class InSummarySource extends BasicSource {
/**
* The summary file where this source was defined.
*/
final String summaryPath;
InSummarySource(this.uri, this.summaryPath);
InSummarySource(Uri uri, this.summaryPath) : super(uri);
@override
TimestampedData<String> get contents => new TimestampedData<String>(0, '');
@override
String get encoding => uri.toString();
@override
String get fullName => encoding;
@override
int get hashCode => uri.hashCode;
@override
bool get isInSystemLibrary => uri.scheme == DartUriResolver.DART_SCHEME;
@override
int get modificationStamp => 0;
@override
String get shortName => pathos.basename(fullName);
@override
UriKind get uriKind => UriKind.PACKAGE_URI;
@override
bool operator ==(Object object) => object is Source && object.uri == uri;
@override
bool exists() => true;
@@ -0,0 +1,92 @@
// Copyright (c) 2017, 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:async';
import 'package:front_end/src/base/processed_options.dart';
import 'package:front_end/src/incremental_kernel_generator_impl.dart';
import 'package:kernel/kernel.dart';
import 'compiler_options.dart';
/// Represents the difference between "old" and "new" states of a program.
///
/// Not intended to be implemented or extended by clients.
class DeltaProgram {
/// The new state of the program.
///
/// Libraries whose kernel representation is known to be unchanged since the
/// last [DeltaProgram] are not included.
final Map<Uri, Program> newState;
DeltaProgram(this.newState);
/// TODO(paulberry): add information about libraries that were removed.
}
/// Interface for generating an initial kernel representation of a program and
/// keeping it up to date as incremental changes are made.
///
/// This class maintains an internal "previous program state"; each
/// time [computeDelta] is called, it updates the previous program state and
/// produces a representation of what has changed. When there are few changes,
/// a call to [computeDelta] should be much faster than compiling the whole
/// program from scratch.
///
/// This class also maintains a set of "valid sources", which is a (possibly
/// empty) subset of the sources constituting the previous program state. Files
/// in this set are assumed to be unchanged since the last call to
/// [computeDelta].
///
/// Behavior is undefined if the client does not obey the following concurrency
/// restrictions:
/// - no two invocations of [computeDelta] may be outstanding at any given time.
/// - neither [invalidate] nor [invalidateAll] may be called while an invocation
/// of [computeDelta] is outstanding.
///
/// Not intended to be implemented or extended by clients.
abstract class IncrementalKernelGenerator {
/// Creates an [IncrementalKernelGenerator] which is prepared to generate
/// kernel representations of the program whose main library is in the given
/// [source].
///
/// No file system access is performed by this constructor; the initial
/// "previous program state" is an empty program containing no code, and the
/// initial set of valid sources is empty. To obtain a kernel representation
/// of the program, call [computeDelta].
factory IncrementalKernelGenerator(Uri source, CompilerOptions options) =>
new IncrementalKernelGeneratorImpl(source, new ProcessedOptions(options));
/// Generates a kernel representation of the changes to the program, assuming
/// that all valid sources are unchanged since the last call to
/// [computeDelta].
///
/// Source files in the set of valid sources are guaranteed not to be re-read
/// from disk; they are assumed to be unchanged regardless of the state of the
/// filesystem.
///
/// If the future completes successfully, the previous file state is updated
/// and the set of valid sources is set to the set of all sources in the
/// program.
///
/// If the future completes with an error (due to errors in the compiled
/// source code), the caller may consider the previous file state and the set
/// of valid sources to be unchanged; this means that once the user fixes the
/// errors, it is safe to call [computeDelta] again.
Future<DeltaProgram> computeDelta();
/// Remove any source file(s) associated with the given file path from the set
/// of valid sources. This guarantees that those files will be re-read on the
/// next call to [computeDelta]).
void invalidate(String path);
/// Remove all source files from the set of valid sources. This guarantees
/// that all files will be re-read on the next call to [computeDelta].
///
/// Note that this does not erase the previous program state; the next time
/// [computeDelta] is called, if parts of the program are discovered to be
/// unchanged, parts of the previous program state will still be re-used to
/// speed up compilation.
void invalidateAll();
}
@@ -0,0 +1,104 @@
// Copyright (c) 2017, 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:async';
import 'package:front_end/src/base/processed_options.dart';
import 'package:front_end/src/incremental_resolved_ast_generator_impl.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'compiler_options.dart';
/// Represents the difference between "old" and "new" states of a program.
///
/// Not intended to be implemented or extended by clients.
class DeltaLibraries {
/// The new state of the program, as a map from Uri to [ResolvedLibrary].
///
/// Libraries whose resolved AST is known to be unchanged since the last
/// [DeltaLibraries] are not included.
final Map<Uri, ResolvedLibrary> newState;
DeltaLibraries(this.newState);
/// TODO(paulberry): add information about libraries that were removed.
}
/// Represents the resolved ASTs for all the compilation units in a single
/// library.
///
/// Not intended to be implemented or extended by clients.
class ResolvedLibrary {
final CompilationUnit definingCompilationUnit;
ResolvedLibrary(this.definingCompilationUnit);
// TODO(paulberry): add support for parts.
}
/// Interface for generating an initial resolved representation of a program and
/// keeping it up to date as incremental changes are made.
///
/// This class maintains an internal "previous program state"; each
/// time [computeDelta] is called, it updates the previous program state and
/// produces a representation of what has changed. When there are few changes,
/// a call to [computeDelta] should be much faster than compiling the whole
/// program from scratch.
///
/// This class also maintains a set of "valid sources", which is a (possibly
/// empty) subset of the sources constituting the previous program state. Files
/// in this set are assumed to be unchanged since the last call to
/// [computeDelta].
///
/// Behavior is undefined if the client does not obey the following concurrency
/// restrictions:
/// - no two invocations of [computeDelta] may be outstanding at any given time.
/// - neither [invalidate] nor [invalidateAll] may be called while an invocation
/// of [computeDelta] is outstanding.
///
/// Not intended to be implemented or extended by clients.
abstract class IncrementalResolvedAstGenerator {
/// Creates an [IncrementalResolvedAstGenerator] which is prepared to generate
/// resolved ASTs for the program whose main library is in the given
/// [source].
///
/// No file system access is performed by this constructor; the initial
/// "previous program state" is an empty program containing no code, and the
/// initial set of valid sources is empty. To obtain a resolved AST
/// representation of the program, call [computeDelta].
factory IncrementalResolvedAstGenerator(Uri source, CompilerOptions options) =>
new IncrementalResolvedAstGeneratorImpl(source, new ProcessedOptions(options));
/// Generates a resolved AST representation of the changes to the program,
/// assuming that all valid sources are unchanged since the last call to
/// [computeDelta].
///
/// Source files in the set of valid sources are guaranteed not to be re-read
/// from disk; they are assumed to be unchanged regardless of the state of the
/// filesystem.
///
/// If the future completes successfully, the previous file state is updated
/// and the set of valid sources is set to the set of all sources in the
/// program.
///
/// If the future completes with an error (due to errors in the compiled
/// source code), the caller may consider the previous file state and the set
/// of valid sources to be unchanged; this means that once the user fixes the
/// errors, it is safe to call [computeDelta] again.
Future<DeltaLibraries> computeDelta();
/// Remove any source file(s) associated with the given file path from the set
/// of valid sources. This guarantees that those files will be re-read on the
/// next call to [computeDelta]).
void invalidate(String path);
/// Remove all source files from the set of valid sources. This guarantees
/// that all files will be re-read on the next call to [computeDelta].
///
/// Note that this does not erase the previous program state; the next time
/// [computeDelta] is called, if parts of the program are discovered to be
/// unchanged, parts of the previous program state will still be re-used to
/// speed up compilation.
void invalidateAll();
}
+27
View File
@@ -5,6 +5,33 @@
import 'package:front_end/src/base/analysis_target.dart';
import 'package:front_end/src/base/timestamped_data.dart';
import 'package:front_end/src/base/uri_kind.dart';
import 'package:path/path.dart' as pathos;
/// Base class providing implementations for the methods in [Source] that don't
/// require filesystem access.
abstract class BasicSource extends Source {
final Uri uri;
BasicSource(this.uri);
@override
String get encoding => uri.toString();
@override
String get fullName => encoding;
@override
int get hashCode => uri.hashCode;
@override
bool get isInSystemLibrary => uri.scheme == 'dart';
@override
String get shortName => pathos.basename(fullName);
@override
bool operator ==(Object object) => object is Source && object.uri == uri;
}
/**
* The interface `Source` defines the behavior of objects representing source code that can be
@@ -0,0 +1,143 @@
// Copyright (c) 2017, 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:async';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/standard_resolution_map.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:front_end/incremental_kernel_generator.dart';
import 'package:front_end/incremental_resolved_ast_generator.dart';
import 'package:front_end/src/base/processed_options.dart';
import 'package:front_end/src/base/source.dart';
import 'package:front_end/src/incremental_resolved_ast_generator_impl.dart';
import 'package:kernel/analyzer/loader.dart';
import 'package:kernel/kernel.dart' hide Source;
import 'package:kernel/repository.dart';
dynamic unimplemented() {
// TODO(paulberry): get rid of this.
throw new UnimplementedError();
}
DartOptions _convertOptions(ProcessedOptions options) {
// TODO(paulberry): make sure options.compileSdk is handled correctly.
return new DartOptions(
strongMode: true, // TODO(paulberry): options.strongMode,
sdk: null, // TODO(paulberry): _uriToPath(options.sdkRoot, options),
sdkSummary:
null, // TODO(paulberry): options.compileSdk ? null : _uriToPath(options.sdkSummary, options),
packagePath:
null, // TODO(paulberry): _uriToPath(options.packagesFileUri, options),
declaredVariables: null // TODO(paulberry): options.declaredVariables
);
}
/// Implementation of [IncrementalKernelGenerator].
///
/// Theory of operation: an instance of [IncrementalResolvedAstGenerator] is
/// used to obtain resolved ASTs, and these are fed into kernel code generation
/// logic.
///
/// Note that the kernel doesn't expect to take resolved ASTs as a direct input;
/// it expects to request resolved ASTs from an [AnalysisContext]. To deal with
/// this, we create [_AnalysisContextProxy] which returns the resolved ASTs when
/// requested. TODO(paulberry): Make this unnecessary.
class IncrementalKernelGeneratorImpl implements IncrementalKernelGenerator {
final IncrementalResolvedAstGenerator _resolvedAstGenerator;
final ProcessedOptions _options;
IncrementalKernelGeneratorImpl(Uri source, ProcessedOptions options)
: _resolvedAstGenerator =
new IncrementalResolvedAstGeneratorImpl(source, options),
_options = options;
@override
Future<DeltaProgram> computeDelta() async {
var deltaLibraries = await _resolvedAstGenerator.computeDelta();
var kernelOptions = _convertOptions(_options);
var packages = null; // TODO(paulberry)
var kernels = <Uri, Program>{};
deltaLibraries.newState.forEach((uri, resolvedLibrary) {
// The kernel generation code doesn't currently support building a kernel
// directly from resolved ASTs--it wants to query an analysis context. So
// we provide it with a proxy analysis context that feeds it the resolved
// ASTs.
var strongMode = true; // TODO(paulberry): set this correctly
var analysisOptions = new _AnalysisOptionsProxy(strongMode);
var context =
new _AnalysisContextProxy(deltaLibraries.newState, analysisOptions);
var repository = new Repository();
var loader =
new DartLoader(repository, kernelOptions, packages, context: context);
loader.loadLibrary(uri);
kernels[uri] = new Program(repository.libraries);
});
return new DeltaProgram(kernels);
}
@override
void invalidate(String path) => _resolvedAstGenerator.invalidate(path);
@override
void invalidateAll() => _resolvedAstGenerator.invalidateAll();
}
class _AnalysisContextProxy implements AnalysisContext {
final Map<Uri, ResolvedLibrary> _resolvedLibraries;
@override
final _SourceFactoryProxy sourceFactory = new _SourceFactoryProxy();
@override
final AnalysisOptions analysisOptions;
_AnalysisContextProxy(this._resolvedLibraries, this.analysisOptions);
List<AnalysisError> computeErrors(Source source) {
// TODO(paulberry): do we need to return errors sometimes?
return [];
}
LibraryElement computeLibraryElement(Source source) {
assert(_resolvedLibraries.containsKey(source.uri));
return resolutionMap
.elementDeclaredByCompilationUnit(
_resolvedLibraries[source.uri].definingCompilationUnit)
.library;
}
noSuchMethod(Invocation invocation) => unimplemented();
CompilationUnit resolveCompilationUnit(
Source unitSource, LibraryElement library) {
assert(_resolvedLibraries.containsKey(library.source.uri));
// TODO(paulberry): support parts.
assert(unitSource == library.source);
return _resolvedLibraries[library.source.uri].definingCompilationUnit;
}
}
class _AnalysisOptionsProxy implements AnalysisOptions {
final bool strongMode;
_AnalysisOptionsProxy(this.strongMode);
noSuchMethod(Invocation invocation) => unimplemented();
}
class _SourceFactoryProxy implements SourceFactory {
Source forUri2(Uri absoluteUri) => new _SourceProxy(absoluteUri);
noSuchMethod(Invocation invocation) => unimplemented();
}
class _SourceProxy extends BasicSource {
_SourceProxy(Uri uri) : super(uri);
noSuchMethod(Invocation invocation) => unimplemented();
}
@@ -0,0 +1,279 @@
// Copyright (c) 2017, 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:async';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/context/context.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/driver.dart' as driver;
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/summary/idl.dart';
import 'package:analyzer/src/summary/summary_sdk.dart';
import 'package:analyzer/src/util/absolute_path.dart';
import 'package:front_end/incremental_resolved_ast_generator.dart';
import 'package:front_end/src/base/processed_options.dart';
import 'package:front_end/src/base/source.dart';
import 'package:front_end/src/dependency_grapher_impl.dart';
import 'package:path/src/context.dart';
dynamic unimplemented() {
// TODO(paulberry): get rid of this.
throw new UnimplementedError();
}
/// Implementation of [IncrementalKernelGenerator].
///
/// Theory of operation: this class is a thin wrapper around
/// [driver.AnalysisDriver]. When the client requests a new delta, we forward
/// the request to the analysis driver. When the client calls an invalidate
/// method, we ensure that the proper files will be re-read next time a delta is
/// requested.
///
/// Note that the analysis driver expects to be able to read file contents
/// synchronously based on filesystem path rather than asynchronously based on
/// URI, so the file contents are first read into memory using the asynchronous
/// FileSystem API, and then these are fed into the analysis driver using a
/// proxy implementation of [ResourceProvider]. TODO(paulberry): make this (and
/// other proxies in this file) unnecessary.
class IncrementalResolvedAstGeneratorImpl
implements IncrementalResolvedAstGenerator {
driver.AnalysisDriverScheduler _scheduler;
final _pathToUriMap = <String, Uri>{};
final _uriToPathMap = <Uri, String>{};
final _fileContents = <String, String>{};
_ResourceProviderProxy _resourceProvider;
driver.AnalysisDriver _driver;
bool _isInitialized = false;
final ProcessedOptions _options;
final Uri _source;
IncrementalResolvedAstGeneratorImpl(this._source, this._options);
@override
Future<DeltaLibraries> computeDelta() async {
if (!_isInitialized) {
await init();
}
// The analysis driver doesn't currently support an asynchronous file API,
// so we have to find all the files first to read their contents.
// TODO(paulberry): this is an unnecessary source of duplicate work and
// should be eliminated ASAP.
var graph = await graphForProgram([_source], _options);
var libraries = <Uri, ResolvedLibrary>{};
// TODO(paulberry): it should be possible to seed the driver using a URI,
// not a file path.
// TODO(paulberry): only start the scheduler the first time.
_scheduler.start();
_driver.addFile(_source.path);
for (var libraryCycle in graph.topologicallySortedCycles) {
for (var uri in libraryCycle.libraries.keys) {
var contents =
await _options.fileSystem.entityForUri(uri).readAsString();
_storeVirtualFile(uri, uri.path, contents);
}
// The driver will request files from dart:, even though it actually uses
// the data from the summary. TODO(paulberry): fix this.
_storeVirtualFile(_DartSdkProxy._dartCoreSource.uri, 'core.dart', '');
for (var uri in libraryCycle.libraries.keys) {
var result = await _driver.getResult(uri.path);
// TODO(paulberry): handle errors.
libraries[uri] = new ResolvedLibrary(result.unit);
}
}
// TODO(paulberry): stop the scheduler
return new DeltaLibraries(libraries);
}
Future<Null> init() async {
// TODO(paulberry): can we just use null?
var performanceLog = new driver.PerformanceLog(new _NullStringSink());
_scheduler = new driver.AnalysisDriverScheduler(performanceLog);
_resourceProvider =
new _ResourceProviderProxy(_fileContents, _pathToUriMap);
// TODO(paulberry): MemoryByteStore leaks memory (it never discards data).
// Do something better here.
var byteStore = new MemoryByteStore();
// TODO(paulberry): can we just use null?
var fileContentOverlay = new FileContentOverlay();
var sdkContext = new AnalysisContextImpl();
var dartSdk = new _DartSdkProxy(await _options.getSdkSummary(), sdkContext);
sdkContext.sourceFactory =
new SourceFactory([new DartUriResolver(dartSdk)]);
bool strongMode = true; // TODO(paulberry): support strong mode flag.
sdkContext.resultProvider = new SdkSummaryResultProvider(
sdkContext, await _options.getSdkSummary(), strongMode);
var sourceFactory =
new _SourceFactoryProxy(dartSdk, _pathToUriMap, _uriToPathMap);
var analysisOptions = new AnalysisOptionsImpl();
_driver = new driver.AnalysisDriver(
_scheduler,
performanceLog,
_resourceProvider,
byteStore,
fileContentOverlay,
sourceFactory,
analysisOptions);
_isInitialized = true;
}
@override
void invalidate(String path) {
throw new UnimplementedError();
}
@override
void invalidateAll() {
throw new UnimplementedError();
}
void _storeVirtualFile(Uri uri, String path, String contents) {
_pathToUriMap[path] = uri;
_uriToPathMap[uri] = path;
_fileContents[path] = contents;
}
}
class _DartSdkProxy implements DartSdk {
static final _dartCoreSource =
new _SourceProxy(Uri.parse('dart:core'), 'core.dart');
final PackageBundle summary;
final AnalysisContext context;
_DartSdkProxy(this.summary, this.context);
@override
PackageBundle getLinkedBundle() => summary;
@override
Source mapDartUri(String uri) {
// TODO(paulberry): this seems hacky.
return new _SourceProxy(Uri.parse(uri), '$uri.dart');
}
noSuchMethod(Invocation invocation) => unimplemented();
}
class _FileProxy implements File {
final _SourceProxy _source;
final _ResourceProviderProxy _resourceProvider;
_FileProxy(this._source, this._resourceProvider);
@override
String get path => _source.fullName;
@override
String get shortName => path;
@override
Source createSource([Uri uri]) {
assert(uri == null);
return _source;
}
noSuchMethod(Invocation invocation) => unimplemented();
@override
String readAsStringSync() {
assert(_resourceProvider.fileContents.containsKey(path));
return _resourceProvider.fileContents[path];
}
}
/// A string sink that ignores everything written to it.
class _NullStringSink implements StringSink {
void write(Object obj) {}
void writeAll(Iterable objects, [String separator = ""]) {}
void writeCharCode(int charCode) {}
void writeln([Object obj = ""]) {}
}
class _ResourceProviderProxy implements ResourceProvider {
final Map<String, String> fileContents;
final Map<String, Uri> pathToUriMap;
_ResourceProviderProxy(this.fileContents, this.pathToUriMap);
@override
AbsolutePathContext get absolutePathContext => throw new UnimplementedError();
@override
Context get pathContext => throw new UnimplementedError();
@override
File getFile(String path) {
assert(fileContents.containsKey(path));
assert(pathToUriMap.containsKey(path));
return new _FileProxy(new _SourceProxy(pathToUriMap[path], path), this);
}
@override
Folder getFolder(String path) => throw new UnimplementedError();
@override
Future<List<int>> getModificationTimes(List<Source> sources) =>
throw new UnimplementedError();
@override
Resource getResource(String path) => throw new UnimplementedError();
@override
Folder getStateLocation(String pluginId) => throw new UnimplementedError();
}
class _SourceFactoryProxy implements SourceFactory {
@override
final DartSdk dartSdk;
final Map<String, Uri> pathToUriMap;
final Map<Uri, String> uriToPathMap;
@override
AnalysisContext context;
_SourceFactoryProxy(this.dartSdk, this.pathToUriMap, this.uriToPathMap);
@override
SourceFactory clone() => this;
@override
Source forUri(String absoluteUri) {
if (absoluteUri == 'dart:core') return _DartSdkProxy._dartCoreSource;
Uri uri = Uri.parse(absoluteUri);
assert(uriToPathMap.containsKey(uri));
return new _SourceProxy(uri, uriToPathMap[uri]);
}
noSuchMethod(Invocation invocation) => unimplemented();
Source resolveUri(Source containingSource, String containedUri) {
// TODO(paulberry): re-use code from dependency_grapher_impl, and support
// SDK URI resolution logic.
var absoluteUri = containingSource.uri.resolve(containedUri);
return forUri(absoluteUri.toString());
}
@override
Uri restoreUri(Source source) => source.uri;
}
class _SourceProxy extends BasicSource {
@override
final String fullName;
_SourceProxy(Uri uri, this.fullName) : super(uri);
int get modificationStamp => 0;
noSuchMethod(Invocation invocation) => unimplemented();
}
@@ -0,0 +1,83 @@
// Copyright (c) 2017, 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:async';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:front_end/compiler_options.dart';
import 'package:front_end/incremental_kernel_generator.dart';
import 'package:front_end/memory_file_system.dart';
import 'package:kernel/ast.dart';
import 'package:path/path.dart' as pathos;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(IncrementalKernelGeneratorTest);
});
}
final _sdkSummary = _readSdkSummary();
List<int> _readSdkSummary() {
var resourceProvider = PhysicalResourceProvider.INSTANCE;
var sdk = new FolderBasedDartSdk(resourceProvider,
FolderBasedDartSdk.defaultSdkDirectory(resourceProvider))
..useSummary = true;
var path = resourceProvider.pathContext
.join(sdk.directory.path, 'lib', '_internal', 'strong.sum');
return resourceProvider.getFile(path).readAsBytesSync();
}
@reflectiveTest
class IncrementalKernelGeneratorTest {
static final sdkSummaryUri = Uri.parse('special:sdk_summary');
/// Virtual filesystem for testing.
final fileSystem = new MemoryFileSystem(pathos.posix, Uri.parse('file:///'));
/// The object under test.
IncrementalKernelGenerator incrementalKernelGenerator;
Future<Map<Uri, Program>> getInitialState(Uri startingUri) async {
fileSystem.entityForUri(sdkSummaryUri).writeAsBytesSync(_sdkSummary);
incrementalKernelGenerator = new IncrementalKernelGenerator(
startingUri,
new CompilerOptions()
..fileSystem = fileSystem
..chaseDependencies = true
..sdkSummary = sdkSummaryUri
..packagesFileUri = new Uri());
return (await incrementalKernelGenerator.computeDelta()).newState;
}
test_emptyProgram() async {
writeFiles({'/foo.dart': 'main() {}'});
var fileUri = Uri.parse('file:///foo.dart');
var initialState = await getInitialState(fileUri);
expect(initialState.keys, unorderedEquals([fileUri]));
var program = initialState[fileUri];
expect(program.libraries, hasLength(1));
var library = program.libraries[0];
expect(library.importUri, fileUri);
expect(library.classes, isEmpty);
expect(library.procedures, hasLength(1));
expect(library.procedures[0].name.name, 'main');
var body = library.procedures[0].function.body;
expect(body, new isInstanceOf<Block>());
var block = body as Block;
expect(block.statements, isEmpty);
}
/// Write the given file contents to the virtual filesystem.
void writeFiles(Map<String, String> contents) {
contents.forEach((path, text) {
fileSystem
.entityForUri(Uri.parse('file://$path'))
.writeAsStringSync(text);
});
}
}
@@ -0,0 +1,79 @@
// Copyright (c) 2017, 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:async';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:front_end/compiler_options.dart';
import 'package:front_end/incremental_resolved_ast_generator.dart';
import 'package:front_end/memory_file_system.dart';
import 'package:path/path.dart' as pathos;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(IncrementalResolvedAstGeneratorTest);
});
}
final _sdkSummary = _readSdkSummary();
List<int> _readSdkSummary() {
var resourceProvider = PhysicalResourceProvider.INSTANCE;
var sdk = new FolderBasedDartSdk(resourceProvider,
FolderBasedDartSdk.defaultSdkDirectory(resourceProvider))
..useSummary = true;
var path = resourceProvider.pathContext
.join(sdk.directory.path, 'lib', '_internal', 'strong.sum');
return resourceProvider.getFile(path).readAsBytesSync();
}
@reflectiveTest
class IncrementalResolvedAstGeneratorTest {
static final sdkSummaryUri = Uri.parse('special:sdk_summary');
/// Virtual filesystem for testing.
final fileSystem = new MemoryFileSystem(pathos.posix, Uri.parse('file:///'));
/// The object under test.
IncrementalResolvedAstGenerator incrementalResolvedAstGenerator;
Future<Map<Uri, ResolvedLibrary>> getInitialProgram(Uri startingUri) async {
fileSystem.entityForUri(sdkSummaryUri).writeAsBytesSync(_sdkSummary);
incrementalResolvedAstGenerator = new IncrementalResolvedAstGenerator(
startingUri,
new CompilerOptions()
..fileSystem = fileSystem
..chaseDependencies = true
..sdkSummary = sdkSummaryUri
..packagesFileUri = new Uri());
return (await incrementalResolvedAstGenerator.computeDelta()).newState;
}
test_emptyProgram() async {
writeFiles({'/foo.dart': 'main() {}'});
var fooUri = Uri.parse('file:///foo.dart');
var initialProgram = await getInitialProgram(fooUri);
expect(initialProgram.keys, unorderedEquals([fooUri]));
var unit = initialProgram[fooUri].definingCompilationUnit;
expect(unit.declarations, hasLength(1));
expect(unit.declarations[0], new isInstanceOf<FunctionDeclaration>());
var main = unit.declarations[0] as FunctionDeclaration;
expect(main.name.name, 'main');
// TODO(paulberry): test that stuff is actually resolved.
// TODO(paulberry): test parts.
}
/// Write the given file contents to the virtual filesystem.
void writeFiles(Map<String, String> contents) {
contents.forEach((path, text) {
fileSystem
.entityForUri(Uri.parse('file://$path'))
.writeAsStringSync(text);
});
}
}
+2 -2
View File
@@ -108,8 +108,8 @@ class DartLoader implements ReferenceLevelLoader {
bool get strongMode => context.analysisOptions.strongMode;
DartLoader(this.repository, DartOptions options, Packages packages,
{DartSdk dartSdk})
: this.context = createContext(options, packages, dartSdk: dartSdk),
{DartSdk dartSdk, AnalysisContext context})
: this.context = context ?? createContext(options, packages, dartSdk: dartSdk),
this.applicationRoot = options.applicationRoot;
String getLibraryName(LibraryElement element) {