Add hook for listening to implicitly analyzed files

R=paulberry@google.com

Review URL: https://codereview.chromium.org//1239863002 .
This commit is contained in:
Brian Wilkerson 2015-07-16 08:10:38 -07:00
parent e4881c55d0
commit 503e2b68f1
6 changed files with 276 additions and 52 deletions

View file

@ -216,8 +216,10 @@ class AnalysisCache {
/**
* Remove all information related to the given [target] from this cache.
* Return the entry associated with the target, or `null` if there was cache
* entry for the target.
*/
void remove(AnalysisTarget target) {
CacheEntry remove(AnalysisTarget target) {
int count = _partitions.length;
for (int i = 0; i < count; i++) {
CachePartition partition = _partitions[i];
@ -226,10 +228,10 @@ class AnalysisCache {
AnalysisEngine.instance.logger
.logInformation('Removed the cache entry for $target.');
}
partition.remove(target);
return;
return partition.remove(target);
}
}
return null;
}
/**
@ -896,9 +898,11 @@ abstract class CachePartition {
}
/**
* Remove all information related to the given [target] from this cache.
* Remove all information related to the given [target] from this partition.
* Return the entry associated with the target, or `null` if there was cache
* entry for the target.
*/
void remove(AnalysisTarget target) {
CacheEntry remove(AnalysisTarget target) {
for (CacheFlushManager flushManager in _flushManagerMap.values) {
flushManager.targetRemoved(target);
}
@ -907,6 +911,7 @@ abstract class CachePartition {
entry._invalidateAll();
}
_removeIfSource(target);
return entry;
}
/**
@ -981,19 +986,17 @@ abstract class CachePartition {
}
/**
* If the given [target] is a [Source], removes it from [_sources].
* If the given [target] is a [Source], remove it from the list of [_sources].
*/
void _removeIfSource(AnalysisTarget target) {
if (target is Source) {
_sources.remove(target);
{
String fullName = target.fullName;
List<Source> sources = _pathToSources[fullName];
if (sources != null) {
sources.remove(target);
if (sources.isEmpty) {
_pathToSources.remove(fullName);
}
String fullName = target.fullName;
List<Source> sources = _pathToSources[fullName];
if (sources != null) {
sources.remove(target);
if (sources.isEmpty) {
_pathToSources.remove(fullName);
}
}
}

View file

@ -166,6 +166,12 @@ class AnalysisContextImpl implements InternalAnalysisContext {
*/
StreamController<SourcesChangedEvent> _onSourcesChangedController;
/**
* A subscription for a stream of events indicating when files are (and are
* not) being implicitly analyzed.
*/
StreamController<ImplicitAnalysisEvent> _implicitAnalysisEventsController;
/**
* The listeners that are to be notified when various analysis results are
* produced in this context.
@ -220,6 +226,8 @@ class AnalysisContextImpl implements InternalAnalysisContext {
_taskManager, <WorkManager>[dartWorkManager, htmlWorkManager], this);
_onSourcesChangedController =
new StreamController<SourcesChangedEvent>.broadcast();
_implicitAnalysisEventsController =
new StreamController<ImplicitAnalysisEvent>.broadcast();
}
@override
@ -303,6 +311,10 @@ class AnalysisContextImpl implements InternalAnalysisContext {
@override
List<Source> get htmlSources => _getSources(SourceKind.HTML);
@override
Stream<ImplicitAnalysisEvent> get implicitAnalysisEvents =>
_implicitAnalysisEventsController.stream;
@override
bool get isDisposed => _disposed;
@ -728,6 +740,10 @@ class AnalysisContextImpl implements InternalAnalysisContext {
entry.modificationTime = getModificationStamp(target);
}
_cache.put(entry);
if (target is Source) {
_implicitAnalysisEventsController
.add(new ImplicitAnalysisEvent(target, true));
}
}
return entry;
}
@ -1219,7 +1235,7 @@ class AnalysisContextImpl implements InternalAnalysisContext {
for (Source source in missingSources) {
if (getLibrariesContaining(source).isEmpty &&
getLibrariesDependingOn(source).isEmpty) {
_cache.remove(source);
_removeFromCache(source);
removalCount++;
}
}
@ -1427,6 +1443,10 @@ class AnalysisContextImpl implements InternalAnalysisContext {
entry.modificationTime = getModificationStamp(source);
entry.explicitlyAdded = explicitlyAdded;
_cache.put(entry);
if (!explicitlyAdded) {
_implicitAnalysisEventsController
.add(new ImplicitAnalysisEvent(source, true));
}
return entry;
}
@ -1637,6 +1657,14 @@ class AnalysisContextImpl implements InternalAnalysisContext {
}
}
void _removeFromCache(Source source) {
CacheEntry entry = _cache.remove(source);
if (entry != null && entry.explicitlyAdded) {
_implicitAnalysisEventsController
.add(new ImplicitAnalysisEvent(source, false));
}
}
/**
* Remove the given [source] from the priority order if it is in the list.
*/
@ -1658,6 +1686,10 @@ class AnalysisContextImpl implements InternalAnalysisContext {
* that referenced the source before it existed.
*/
void _sourceAvailable(Source source) {
// TODO(brianwilkerson) This method needs to check whether the source was
// previously being implicitly analyzed. If so, the cache entry needs to be
// update to reflect the new status and an event needs to be generated to
// inform clients that it is no longer being implicitly analyzed.
CacheEntry entry = _cache.get(source);
if (entry == null) {
_createCacheEntry(source, true);
@ -1738,7 +1770,7 @@ class AnalysisContextImpl implements InternalAnalysisContext {
* Record that the give [source] has been deleted.
*/
void _sourceDeleted(Source source) {
// TODO(brianwilkerson) Implement this.
// TODO(brianwilkerson) Implement or remove this.
// SourceEntry sourceEntry = _cache.get(source);
// if (sourceEntry is HtmlEntry) {
// HtmlEntry htmlEntry = sourceEntry;
@ -1769,7 +1801,7 @@ class AnalysisContextImpl implements InternalAnalysisContext {
* Record that the given [source] has been removed.
*/
void _sourceRemoved(Source source) {
_cache.remove(source);
_removeFromCache(source);
_removeFromPriorityOrder(source);
}

View file

@ -209,8 +209,10 @@ class AnalysisCache {
/**
* Remove all information related to the given [source] from this cache.
* Return the entry associated with the source, or `null` if there was cache
* entry for the source.
*/
void remove(Source source) {
SourceEntry remove(Source source) {
int count = _partitions.length;
for (int i = 0; i < count; i++) {
if (_partitions[i].contains(source)) {
@ -223,10 +225,10 @@ class AnalysisCache {
JavaSystem.currentTimeMillis();
}
}
_partitions[i].remove(source);
return;
return _partitions[i].remove(source);
}
}
return null;
}
/**
@ -352,6 +354,12 @@ abstract class AnalysisContext {
*/
List<Source> get htmlSources;
/**
* The stream that is notified when a source either starts or stops being
* analyzed implicitly.
*/
Stream<ImplicitAnalysisEvent> get implicitAnalysisEvents;
/**
* Returns `true` if this context was disposed using [dispose].
*/
@ -1033,6 +1041,12 @@ class AnalysisContextImpl implements InternalAnalysisContext {
*/
StreamController<SourcesChangedEvent> _onSourcesChangedController;
/**
* A subscription for a stream of events indicating when files are (and are
* not) being implicitly analyzed.
*/
StreamController<ImplicitAnalysisEvent> _implicitAnalysisEventsController;
/**
* The listeners that are to be notified when various analysis results are
* produced in this context.
@ -1084,6 +1098,8 @@ class AnalysisContextImpl implements InternalAnalysisContext {
_cache = createCacheFromSourceFactory(null);
_onSourcesChangedController =
new StreamController<SourcesChangedEvent>.broadcast();
_implicitAnalysisEventsController =
new StreamController<ImplicitAnalysisEvent>.broadcast();
}
@override
@ -1197,6 +1213,10 @@ class AnalysisContextImpl implements InternalAnalysisContext {
@override
List<Source> get htmlSources => _getSources(SourceKind.HTML);
@override
Stream<ImplicitAnalysisEvent> get implicitAnalysisEvents =>
_implicitAnalysisEventsController.stream;
@override
bool get isDisposed => _disposed;
@ -2410,7 +2430,7 @@ class AnalysisContextImpl implements InternalAnalysisContext {
} else {
unitEntry.recordResolutionError(thrownException);
}
_cache.remove(unitSource);
_removeFromCache(unitSource);
if (thrownException != null) {
throw new AnalysisException('<rethrow>', thrownException);
}
@ -2483,7 +2503,7 @@ class AnalysisContextImpl implements InternalAnalysisContext {
} else {
unitEntry.recordResolutionError(thrownException);
}
_cache.remove(unitSource);
_removeFromCache(unitSource);
if (thrownException != null) {
throw new AnalysisException('<rethrow>', thrownException);
}
@ -2513,7 +2533,7 @@ class AnalysisContextImpl implements InternalAnalysisContext {
} else {
dartEntry.recordResolutionErrorInLibrary(
librarySource, thrownException);
_cache.remove(source);
_removeFromCache(source);
}
if (source != librarySource) {
_workManager.add(source, SourcePriority.PRIORITY_PART);
@ -2615,7 +2635,7 @@ class AnalysisContextImpl implements InternalAnalysisContext {
for (Source source in missingSources) {
if (getLibrariesContaining(source).isEmpty &&
getLibrariesDependingOn(source).isEmpty) {
_cache.remove(source);
_removeFromCache(source);
removalCount++;
}
}
@ -3385,12 +3405,20 @@ class AnalysisContextImpl implements InternalAnalysisContext {
htmlEntry.modificationTime = getModificationStamp(source);
htmlEntry.explicitlyAdded = explicitlyAdded;
_cache.put(source, htmlEntry);
if (!explicitlyAdded) {
_implicitAnalysisEventsController
.add(new ImplicitAnalysisEvent(source, true));
}
return htmlEntry;
} else {
DartEntry dartEntry = new DartEntry();
dartEntry.modificationTime = getModificationStamp(source);
dartEntry.explicitlyAdded = explicitlyAdded;
_cache.put(source, dartEntry);
if (!explicitlyAdded) {
_implicitAnalysisEventsController
.add(new ImplicitAnalysisEvent(source, true));
}
return dartEntry;
}
}
@ -4108,6 +4136,16 @@ class AnalysisContextImpl implements InternalAnalysisContext {
AnalysisEngine.instance.logger.logInformation(message);
}
/**
* Notify all of the analysis listeners that a task is about to be performed.
*/
void _notifyAboutToPerformTask(String taskDescription) {
int count = _listeners.length;
for (int i = 0; i < count; i++) {
_listeners[i].aboutToPerformTask(this, taskDescription);
}
}
// /**
// * Notify all of the analysis listeners that the given source is no longer included in the set of
// * sources that are being analyzed.
@ -4186,16 +4224,6 @@ class AnalysisContextImpl implements InternalAnalysisContext {
// }
// }
/**
* Notify all of the analysis listeners that a task is about to be performed.
*/
void _notifyAboutToPerformTask(String taskDescription) {
int count = _listeners.length;
for (int i = 0; i < count; i++) {
_listeners[i].aboutToPerformTask(this, taskDescription);
}
}
/**
* Notify all of the analysis listeners that the errors associated with the
* given [source] has been updated to the given [errors].
@ -4569,6 +4597,14 @@ class AnalysisContextImpl implements InternalAnalysisContext {
return dartEntry;
}
void _removeFromCache(Source source) {
SourceEntry entry = _cache.remove(source);
if (entry != null && entry.explicitlyAdded) {
_implicitAnalysisEventsController
.add(new ImplicitAnalysisEvent(source, false));
}
}
/**
* Remove the given [librarySource] from the list of containing libraries for
* all of the parts referenced by the given [dartEntry].
@ -4581,7 +4617,7 @@ class AnalysisContextImpl implements InternalAnalysisContext {
if (partEntry != null && !identical(partEntry, dartEntry)) {
partEntry.removeContainingLibrary(librarySource);
if (partEntry.containingLibraries.length == 0 && !exists(partSource)) {
_cache.remove(partSource);
_removeFromCache(partSource);
}
}
}
@ -4601,7 +4637,7 @@ class AnalysisContextImpl implements InternalAnalysisContext {
partEntry.removeContainingLibrary(librarySource);
if (partEntry.containingLibraries.length == 0 &&
!exists(partSource)) {
_cache.remove(partSource);
_removeFromCache(partSource);
}
}
}
@ -4630,6 +4666,10 @@ class AnalysisContextImpl implements InternalAnalysisContext {
* that referenced the source before it existed.
*/
void _sourceAvailable(Source source) {
// TODO(brianwilkerson) This method needs to check whether the source was
// previously being implicitly analyzed. If so, the cache entry needs to be
// update to reflect the new status and an event needs to be generated to
// inform clients that it is no longer being implicitly analyzed.
SourceEntry sourceEntry = _cache.get(source);
if (sourceEntry == null) {
sourceEntry = _createSourceEntry(source, true);
@ -4718,7 +4758,7 @@ class AnalysisContextImpl implements InternalAnalysisContext {
_invalidateLibraryResolution(librarySource);
}
}
_cache.remove(source);
_removeFromCache(source);
_workManager.remove(source);
_removeFromPriorityOrder(source);
}
@ -6752,11 +6792,13 @@ abstract class CachePartition {
}
/**
* Remove all information related to the given [source] from this cache.
* Remove all information related to the given [source] from this partition.
* Return the entry associated with the source, or `null` if there was cache
* entry for the source.
*/
void remove(Source source) {
SourceEntry remove(Source source) {
_recentlyUsed.remove(source);
_sourceMap.remove(source);
return _sourceMap.remove(source);
}
/**
@ -8852,6 +8894,32 @@ class HtmlEntry extends SourceEntry {
}
}
/**
* An event indicating when a source either starts or stops being implicitly
* analyzed.
*/
class ImplicitAnalysisEvent {
/**
* The source whose status has changed.
*/
final Source source;
/**
* A flag indicating whether the source is now being analyzed.
*/
final bool isAnalyzed;
/**
* Initialize a newly created event to indicate that the given [source] has
* changed it status to match the [isAnalyzed] flag.
*/
ImplicitAnalysisEvent(this.source, this.isAnalyzed);
@override
String toString() =>
'${isAnalyzed ? '' : 'not '}analyzing ${source.fullName}';
}
/**
* Instances of the class `IncrementalAnalysisCache` hold information used to perform
* incremental analysis.

View file

@ -245,6 +245,21 @@ class AnalysisContextImplTest extends EngineTestCase {
super.tearDown();
}
Future test_analyzedSources_added() async {
AnalyzedSourcesListener listener = new AnalyzedSourcesListener();
_context.implicitAnalysisEvents.listen(listener.onData);
//
// Create a file that references an file that is not explicitly being
// analyzed and fully analyze it. Ensure that the listener is told about
// the implicitly analyzed file.
//
Source sourceA = _addSource('/a.dart', "library a; import 'b.dart';");
Source sourceB = _createSource('/b.dart', "library b;");
_context.computeErrors(sourceA);
await pumpEventQueue();
listener.expectAnalyzed(sourceB);
}
Future test_applyChanges_add() {
SourcesChangedListener listener = new SourcesChangedListener();
_context.onSourcesChanged.listen(listener.onData);
@ -2151,6 +2166,12 @@ library test2;''');
_context.applyChanges(changeSet);
}
Source _createSource(String fileName, String contents) {
Source source = new FileBasedSource(FileUtilities2.createFile(fileName));
_context.setContents(source, contents);
return source;
}
/**
* Search the given compilation unit for a class with the given name. Return the class with the
* given name, or `null` if the class cannot be found.
@ -2343,6 +2364,48 @@ class AnalysisTaskTest extends EngineTestCase {
}
}
/**
* A listener used to gather the [ImplicitAnalysisEvent]s that are produced
* during analysis.
*/
class AnalyzedSourcesListener {
/**
* The events that have been gathered.
*/
List<ImplicitAnalysisEvent> actualEvents = <ImplicitAnalysisEvent>[];
/**
* The sources that are being implicitly analyzed.
*/
List<Source> analyzedSources = <Source>[];
/**
* Assert that the given source is currently being implicitly analyzed.
*/
void expectAnalyzed(Source source) {
expect(analyzedSources, contains(source));
}
/**
* Assert that the given source is not currently being implicitly analyzed.
*/
void expectNotAnalyzed(Source source) {
expect(analyzedSources, isNot(contains(source)));
}
/**
* Record that the given event was produced.
*/
void onData(ImplicitAnalysisEvent event) {
actualEvents.add(event);
if (event.isAnalyzed) {
analyzedSources.add(event.source);
} else {
analyzedSources.remove(event.source);
}
}
}
class CompilationUnitMock extends TypedMock implements CompilationUnit {
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
@ -5468,6 +5531,11 @@ class TestAnalysisContext implements InternalAnalysisContext {
return null;
}
@override
Stream<ImplicitAnalysisEvent> get implicitAnalysisEvents {
fail("Unexpected invocation of analyzedSources");
return null;
}
@override
bool get isDisposed {
fail("Unexpected invocation of isDisposed");
return false;
@ -5511,6 +5579,7 @@ class TestAnalysisContext implements InternalAnalysisContext {
fail("Unexpected invocation of getPrioritySources");
return null;
}
@override
List<AnalysisTarget> get priorityTargets {
fail("Unexpected invocation of visitCacheItems");
@ -5528,7 +5597,6 @@ class TestAnalysisContext implements InternalAnalysisContext {
fail("Unexpected invocation of getResolverVisitorFactory");
return null;
}
@override
SourceFactory get sourceFactory {
fail("Unexpected invocation of getSourceFactory");
@ -5790,6 +5858,7 @@ class TestAnalysisContext implements InternalAnalysisContext {
fail("Unexpected invocation of parseHtmlUnit");
return null;
}
@override
AnalysisResult performAnalysisTask() {
fail("Unexpected invocation of performAnalysisTask");
@ -5832,11 +5901,11 @@ class TestAnalysisContext implements InternalAnalysisContext {
int oldLength, int newLength) {
fail("Unexpected invocation of setChangedContents");
}
@override
void setContents(Source source, String contents) {
fail("Unexpected invocation of setContents");
}
@override
bool shouldErrorsBeAnalyzed(Source source, Object entry) {
fail("Unexpected invocation of shouldErrorsBeAnalyzed");

View file

@ -176,7 +176,7 @@ class AnalysisCacheTest extends AbstractCacheTest {
expect(entry2.getValue(result2), 222);
expect(entry3.getValue(result3), 333);
// remove entry1, invalidate result2 and remove empty entry2
cache.remove(target1);
expect(cache.remove(target1), entry1);
expect(cache.get(target1), isNull);
expect(cache.get(target2), isNull);
expect(cache.get(target3), entry3);
@ -197,7 +197,7 @@ class AnalysisCacheTest extends AbstractCacheTest {
expect(entry.getValue(result1), 111);
expect(entry.getValue(result2), 222);
// remove target, invalidate result2
cache.remove(target);
expect(cache.remove(target), entry);
expect(cache.get(target), isNull);
expect(entry.getState(result2), CacheState.INVALID);
}
@ -945,13 +945,21 @@ abstract class CachePartitionTest extends EngineTestCase {
expect(partition.get(target), entry);
}
void test_remove() {
void test_remove_absent() {
CachePartition partition = createPartition();
AnalysisTarget target = new TestSource();
expect(partition.get(target), isNull);
expect(partition.remove(target), isNull);
expect(partition.get(target), isNull);
}
void test_remove_present() {
CachePartition partition = createPartition();
AnalysisTarget target = new TestSource();
CacheEntry entry = new CacheEntry(target);
partition.put(entry);
expect(partition.get(target), entry);
partition.remove(target);
expect(partition.remove(target), entry);
expect(partition.get(target), isNull);
}
}
@ -1004,6 +1012,12 @@ class UniversalCachePartitionTest extends CachePartitionTest {
return new UniversalCachePartition(null);
}
void test_contains() {
UniversalCachePartition partition = new UniversalCachePartition(null);
TestSource source = new TestSource();
expect(partition.isResponsibleFor(source), isTrue);
}
test_dispose() {
InternalAnalysisContext context = new _InternalAnalysisContextMock();
CachePartition partition1 = new UniversalCachePartition(context);
@ -1034,12 +1048,6 @@ class UniversalCachePartitionTest extends CachePartitionTest {
// result2 is removed from result1
expect(entry1.getResultData(descriptor1).dependentResults, isEmpty);
}
void test_contains() {
UniversalCachePartition partition = new UniversalCachePartition(null);
TestSource source = new TestSource();
expect(partition.isResponsibleFor(source), isTrue);
}
}
class _InternalAnalysisContextMock extends TypedMock

View file

@ -55,6 +55,32 @@ main() {
@reflectiveTest
class AnalysisContextImplTest extends AbstractContextTest {
Future fail_analyzedSources_removed() async {
AnalyzedSourcesListener listener = new AnalyzedSourcesListener();
context.implicitAnalysisEvents.listen(listener.onData);
//
// Create a file that references an file that is not explicitly being
// analyzed and fully analyze it. Ensure that the listener is told about
// the implicitly analyzed file.
//
Source sourceA = newSource('/a.dart', "library a; import 'b.dart';");
Source sourceB = newSource('/b.dart', "library b;");
ChangeSet changeSet = new ChangeSet();
changeSet.addedSource(sourceA);
context.applyChanges(changeSet);
context.computeErrors(sourceA);
await pumpEventQueue();
listener.expectAnalyzed(sourceB);
//
// Remove the reference and ensure that the listener is told that we're no
// longer implicitly analyzing the file.
//
context.setContents(sourceA, "library a;");
context.computeErrors(sourceA);
await pumpEventQueue();
listener.expectNotAnalyzed(sourceB);
}
void fail_performAnalysisTask_importedLibraryDelete_html() {
// NOTE: This was failing before converting to the new task model.
Source htmlSource = addSource("/page.html", r'''
@ -90,6 +116,24 @@ class AnalysisContextImplTest extends AbstractContextTest {
super.tearDown();
}
Future test_analyzedSources_added() async {
AnalyzedSourcesListener listener = new AnalyzedSourcesListener();
context.implicitAnalysisEvents.listen(listener.onData);
//
// Create a file that references an file that is not explicitly being
// analyzed and fully analyze it. Ensure that the listener is told about
// the implicitly analyzed file.
//
Source sourceA = newSource('/a.dart', "library a; import 'b.dart';");
Source sourceB = newSource('/b.dart', "library b;");
ChangeSet changeSet = new ChangeSet();
changeSet.addedSource(sourceA);
context.applyChanges(changeSet);
context.computeErrors(sourceA);
await pumpEventQueue();
listener.expectAnalyzed(sourceB);
}
Future test_applyChanges_add() {
SourcesChangedListener listener = new SourcesChangedListener();
context.onSourcesChanged.listen(listener.onData);