Version 2.15.0-151.0.dev
Merge commit '667e879ded7d5cb0a927199cbdb138fae04fd3ee' into 'dev'
diff --git a/pkg/analysis_server/test/lsp/server_abstract.dart b/pkg/analysis_server/test/lsp/server_abstract.dart
index b4d14d7..26cd616 100644
--- a/pkg/analysis_server/test/lsp/server_abstract.dart
+++ b/pkg/analysis_server/test/lsp/server_abstract.dart
@@ -1467,7 +1467,7 @@
text: content)),
);
await sendNotificationToServer(notification);
- await pumpEventQueue();
+ await pumpEventQueue(times: 128);
}
int positionCompare(Position p1, Position p2) {
diff --git a/pkg/analyzer/lib/src/dart/analysis/driver.dart b/pkg/analyzer/lib/src/dart/analysis/driver.dart
index fcf5d4e..3e16e13 100644
--- a/pkg/analyzer/lib/src/dart/analysis/driver.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/driver.dart
@@ -222,7 +222,7 @@
final bool enableIndex;
/// The current analysis session.
- late AnalysisSessionImpl _currentSession;
+ late final AnalysisSessionImpl _currentSession = AnalysisSessionImpl(this);
/// The current library context, consistent with the [_currentSession].
///
@@ -299,7 +299,6 @@
_sourceFactory = sourceFactory,
_externalSummaries = externalSummaries,
testingData = retainDataForTesting ? TestingData() : null {
- _createNewSession();
_onResults = _resultController.stream.asBroadcastStream();
_testView = AnalysisDriverTestView(this);
_createFileTracker();
@@ -461,14 +460,10 @@
return;
}
if (file_paths.isDart(resourceProvider.pathContext, path)) {
+ _priorityResults.clear();
+ _removePotentiallyAffectedLibraries(path);
_fileTracker.addFile(path);
- // If the file is known, it has already been read, even if it did not
- // exist. Now we are notified that the file exists, so we need to
- // re-read it and make sure that we invalidate signature of the files
- // that reference it.
- if (_fsState.knownFilePaths.contains(path)) {
- _changeFile(path);
- }
+ _scheduler.notify(this);
}
}
@@ -490,7 +485,15 @@
/// [changeFile] invocation.
void changeFile(String path) {
_throwIfNotAbsolutePath(path);
- _changeFile(path);
+ if (!_fsState.hasUri(path)) {
+ return;
+ }
+ if (file_paths.isDart(resourceProvider.pathContext, path)) {
+ _priorityResults.clear();
+ _removePotentiallyAffectedLibraries(path);
+ _fileTracker.changeFile(path);
+ _scheduler.notify(this);
+ }
}
/// Clear the library context and any related data structures. Mostly we do
@@ -500,6 +503,7 @@
/// periodically.
@visibleForTesting
void clearLibraryContext() {
+ _libraryContext?.invalidAllLibraries();
_libraryContext = null;
_currentSession.clearHierarchies();
}
@@ -528,9 +532,15 @@
if (sourceFactory != null) {
_sourceFactory = sourceFactory;
}
+
+ _priorityResults.clear();
+ clearLibraryContext();
+
Iterable<String> addedFiles = _fileTracker.addedFiles;
_createFileTracker();
_fileTracker.addFiles(addedFiles);
+
+ _scheduler.notify(this);
}
/// Return a [Future] that completes when discovery of all files that are
@@ -1197,17 +1207,25 @@
/// but does not guarantee this.
void removeFile(String path) {
_throwIfNotAbsolutePath(path);
- _fileTracker.removeFile(path);
- clearLibraryContext();
- _priorityResults.clear();
+ if (!_fsState.hasUri(path)) {
+ return;
+ }
+ if (file_paths.isDart(resourceProvider.pathContext, path)) {
+ _priorityResults.clear();
+ _removePotentiallyAffectedLibraries(path);
+ _fileTracker.removeFile(path);
+ _scheduler.notify(this);
+ }
}
/// Reset URI resolution, read again all files, build files graph, and ensure
/// that for all added files new results are reported.
void resetUriResolution() {
+ _priorityResults.clear();
+ clearLibraryContext();
_fsState.resetUriResolution();
_fileTracker.scheduleAllAddedFiles();
- _changeHook();
+ _scheduler.notify(this);
}
void _addDeclaredVariablesToSignature(ApiSignature buffer) {
@@ -1221,22 +1239,6 @@
}
}
- /// Implementation for [changeFile].
- void _changeFile(String path) {
- _fileTracker.changeFile(path);
- clearLibraryContext();
- _priorityResults.clear();
- }
-
- /// Handles a notification from the [FileTracker] that there has been a change
- /// of state.
- void _changeHook() {
- _createNewSession();
- clearLibraryContext();
- _priorityResults.clear();
- _scheduler.notify(this);
- }
-
/// There was an exception during a file analysis, we don't know why.
/// But it might have been caused by an inconsistency of files state, and
/// the library context state. Reset the library context, and hope that
@@ -1483,7 +1485,7 @@
externalSummaries: _externalSummaries,
fileContentCache: _fileContentCache,
);
- _fileTracker = FileTracker(_logger, _fsState, _changeHook);
+ _fileTracker = FileTracker(_logger, _fsState);
}
/// Return the context in which the [library] should be analyzed.
@@ -1516,11 +1518,6 @@
return libraryContext;
}
- /// Create a new analysis session, so invalidating the current one.
- void _createNewSession() {
- _currentSession = AnalysisSessionImpl(this);
- }
-
/// If this has not been done yet, schedule discovery of all files that are
/// potentially available, so that they are included in [knownFiles].
void _discoverAvailableFiles() {
@@ -1682,6 +1679,18 @@
'missing', errorsResult, AnalysisDriverUnitIndexBuilder());
}
+ void _removePotentiallyAffectedLibraries(String path) {
+ _logger.run('Invalidate affected by $path.', () {
+ _logger.writeln('Work in $name');
+ var affected = <FileState>{};
+ _fsState.collectAffected(path, affected);
+ _logger.writeln('Remove ${affected.length} libraries.');
+ _libraryContext?.elementFactory.removeLibraries(
+ affected.map((e) => e.uriStr).toSet(),
+ );
+ });
+ }
+
void _reportException(String path, Object exception, StackTrace stackTrace) {
String? contextKey;
if (exception is _ExceptionState) {
@@ -2053,6 +2062,9 @@
FileTracker get fileTracker => driver._fileTracker;
+ /// TODO(scheglov) Rename this, and [libraryContext].
+ LibraryContext? get libraryContext2 => driver._libraryContext;
+
Map<String, ResolvedUnitResult> get priorityResults {
return driver._priorityResults;
}
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index e3387ae..eff19a9 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -781,6 +781,26 @@
@visibleForTesting
FileSystemStateTestView get test => _testView;
+ /// Collected files that transitively reference a file with the [path].
+ /// These files are potentially affected by the change.
+ void collectAffected(String path, Set<FileState> affected) {
+ final knownFiles = this.knownFiles.toList();
+
+ collectAffected(FileState file) {
+ if (affected.add(file)) {
+ for (var other in knownFiles) {
+ if (other.directReferencedFiles.contains(file)) {
+ collectAffected(other);
+ }
+ }
+ }
+ }
+
+ for (var file in _pathToFiles[path] ?? <FileState>[]) {
+ collectAffected(file);
+ }
+ }
+
FeatureSet contextFeatureSet(
String path,
Uri uri,
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_tracker.dart b/pkg/analyzer/lib/src/dart/analysis/file_tracker.dart
index 0494153..45ea739 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_tracker.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_tracker.dart
@@ -16,10 +16,6 @@
///
/// Provides methods for updating the file system state in response to changes.
class FileTracker {
- /// Callback invoked whenever a change occurs that may require the client to
- /// perform analysis.
- final void Function() _changeHook;
-
/// The logger to write performed operations and performance to.
final PerformanceLog _logger;
@@ -49,7 +45,7 @@
/// have any special relation with changed files.
var _pendingFiles = <String>{};
- FileTracker(this._logger, this._fsState, this._changeHook);
+ FileTracker(this._logger, this._fsState);
/// Returns the path to exactly one that needs analysis. Throws a
/// [StateError] if no files need analysis.
@@ -100,27 +96,24 @@
/// Adds the given [path] to the set of "added files".
void addFile(String path) {
- _fsState.markFileForReading(path);
addedFiles.add(path);
- _pendingFiles.add(path);
- _changeHook();
+ changeFile(path);
}
/// Adds the given [paths] to the set of "added files".
void addFiles(Iterable<String> paths) {
addedFiles.addAll(paths);
_pendingFiles.addAll(paths);
- _changeHook();
}
/// Adds the given [path] to the set of "changed files".
void changeFile(String path) {
+ _fsState.markFileForReading(path);
_changedFiles.add(path);
+
if (addedFiles.contains(path)) {
_pendingChangedFiles.add(path);
}
- _fsState.markFileForReading(path);
- _changeHook();
}
/// Removes the given [path] from the set of "pending files".
@@ -165,7 +158,6 @@
// files seems extreme.
_fsState.removeFile(path);
_pendingFiles.addAll(addedFiles);
- _changeHook();
}
/// Schedule all added files for analysis.
diff --git a/pkg/analyzer/lib/src/dart/analysis/library_context.dart b/pkg/analyzer/lib/src/dart/analysis/library_context.dart
index f80a744..cab5839 100644
--- a/pkg/analyzer/lib/src/dart/analysis/library_context.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/library_context.dart
@@ -89,6 +89,11 @@
return elementFactory.libraryOfUri2('$uri');
}
+ /// We are about to discard this context, mark all libraries invalid.
+ void invalidAllLibraries() {
+ elementFactory.invalidateAllLibraries();
+ }
+
/// Load data required to access elements of the given [targetLibrary].
void load2(FileState targetLibrary) {
timerLoad2.start();
diff --git a/pkg/analyzer/lib/src/dart/element/element.dart b/pkg/analyzer/lib/src/dart/element/element.dart
index 766ace4..994614b 100644
--- a/pkg/analyzer/lib/src/dart/element/element.dart
+++ b/pkg/analyzer/lib/src/dart/element/element.dart
@@ -3723,6 +3723,12 @@
@override
final AnalysisSession session;
+ /// If `true`, then this library is valid in the session.
+ ///
+ /// A library becomes invalid when one of its files, or one of its
+ /// dependencies, changes.
+ bool isValid = true;
+
/// The language version for the library.
LibraryLanguageVersion? _languageVersion;
diff --git a/pkg/analyzer/lib/src/summary2/linked_element_factory.dart b/pkg/analyzer/lib/src/summary2/linked_element_factory.dart
index e83e492..456548e 100644
--- a/pkg/analyzer/lib/src/summary2/linked_element_factory.dart
+++ b/pkg/analyzer/lib/src/summary2/linked_element_factory.dart
@@ -164,6 +164,13 @@
return libraryReaders[uriStr] != null;
}
+ /// We are about to discard this factory, mark all libraries invalid.
+ void invalidateAllLibraries() {
+ for (var libraryReference in rootReference.children) {
+ _invalidateLibrary(libraryReference);
+ }
+ }
+
LibraryElementImpl? libraryOfUri(String uriStr) {
var reference = rootReference.getChild(uriStr);
return elementOfReference(reference) as LibraryElementImpl?;
@@ -189,7 +196,8 @@
for (var uriStr in uriStrSet) {
_exportsOfLibrary.remove(uriStr);
libraryReaders.remove(uriStr);
- rootReference.removeChild(uriStr);
+ var libraryReference = rootReference.removeChild(uriStr);
+ _invalidateLibrary(libraryReference);
}
analysisSession.classHierarchy.removeOfLibraries(uriStrSet);
@@ -239,4 +247,11 @@
libraryElement.createLoadLibraryFunction();
}
+
+ void _invalidateLibrary(Reference? libraryReference) {
+ var libraryElement = libraryReference?.element;
+ if (libraryElement is LibraryElementImpl) {
+ libraryElement.isValid = false;
+ }
+ }
}
diff --git a/pkg/analyzer/lib/src/summary2/reference.dart b/pkg/analyzer/lib/src/summary2/reference.dart
index e73b634..8c305bd 100644
--- a/pkg/analyzer/lib/src/summary2/reference.dart
+++ b/pkg/analyzer/lib/src/summary2/reference.dart
@@ -65,8 +65,8 @@
return map[name] ??= Reference._(this, name);
}
- void removeChild(String name) {
- _children?.remove(name);
+ Reference? removeChild(String name) {
+ return _children?.remove(name);
}
@override
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index 29a974a..f2b9cde 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -727,6 +727,128 @@
expect(driver.fsState.knownFilePaths, isNot(contains(b)));
}
+ test_changeFile_potentiallyAffected_imported() async {
+ newFile('/test/lib/a.dart', content: '');
+ var b = newFile('/test/lib/b.dart', content: '''
+import 'a.dart';
+''');
+ newFile('/test/lib/c.dart', content: '''
+import 'b.dart';
+''');
+ newFile('/test/lib/d.dart', content: '''
+import 'c.dart';
+''');
+ newFile('/test/lib/e.dart', content: '');
+
+ Future<LibraryElementImpl> getLibrary(String shortName) async {
+ var uriStr = 'package:test/$shortName';
+ var result = await driver.getLibraryByUriValid(uriStr);
+ return result.element as LibraryElementImpl;
+ }
+
+ var a_element = await getLibrary('a.dart');
+ var b_element = await getLibrary('b.dart');
+ var c_element = await getLibrary('c.dart');
+ var d_element = await getLibrary('d.dart');
+ var e_element = await getLibrary('e.dart');
+
+ // We have all libraries loaded after analysis.
+ driver.assertLoadedLibraryUriSet(
+ included: [
+ 'package:test/a.dart',
+ 'package:test/b.dart',
+ 'package:test/c.dart',
+ 'package:test/d.dart',
+ 'package:test/e.dart',
+ ],
+ );
+
+ // All libraries are valid.
+ expect(a_element.isValid, true);
+ expect(b_element.isValid, true);
+ expect(c_element.isValid, true);
+ expect(d_element.isValid, true);
+ expect(e_element.isValid, true);
+
+ // Change `b.dart`, also removes `c.dart` and `d.dart` that import it.
+ // But `a.dart` and `d.dart` is not affected.
+ driver.changeFile(b.path);
+ driver.assertLoadedLibraryUriSet(
+ excluded: [
+ 'package:test/b.dart',
+ 'package:test/c.dart',
+ 'package:test/d.dart',
+ ],
+ included: [
+ 'package:test/a.dart',
+ 'package:test/e.dart',
+ ],
+ );
+
+ // Only `a.dart` and `e.dart` are still valid.
+ expect(a_element.isValid, true);
+ expect(b_element.isValid, false);
+ expect(c_element.isValid, false);
+ expect(d_element.isValid, false);
+ expect(e_element.isValid, true);
+ }
+
+ test_changeFile_potentiallyAffected_part() async {
+ var a = newFile('/test/lib/a.dart', content: '''
+part of 'b.dart';
+''');
+ newFile('/test/lib/b.dart', content: '''
+part 'a.dart';
+''');
+ newFile('/test/lib/c.dart', content: '''
+import 'b.dart';
+''');
+ newFile('/test/lib/d.dart', content: '');
+
+ Future<LibraryElementImpl> getLibrary(String shortName) async {
+ var uriStr = 'package:test/$shortName';
+ var result = await driver.getLibraryByUriValid(uriStr);
+ return result.element as LibraryElementImpl;
+ }
+
+ var b_element = await getLibrary('b.dart');
+ var c_element = await getLibrary('c.dart');
+ var d_element = await getLibrary('d.dart');
+
+ // We have all libraries loaded after analysis.
+ driver.assertLoadedLibraryUriSet(
+ included: [
+ 'package:test/b.dart',
+ 'package:test/c.dart',
+ 'package:test/d.dart',
+ ],
+ );
+
+ // All libraries are valid.
+ expect(b_element.isValid, true);
+ expect(c_element.isValid, true);
+ expect(d_element.isValid, true);
+
+ // Change `a.dart`, remove `b.dart` that part it.
+ // Removes `c.dart` that imports `b.dart`.
+ // But `d.dart` is not affected.
+ driver.changeFile(a.path);
+ driver.assertLoadedLibraryUriSet(
+ excluded: [
+ 'package:test/b.dart',
+ 'package:test/c.dart',
+ ],
+ included: [
+ 'package:test/d.dart',
+ ],
+ );
+
+ // Only `d.dart` is still valid.
+ expect(b_element.isValid, false);
+ expect(c_element.isValid, false);
+ expect(d_element.isValid, true);
+ }
+
test_changeFile_selfConsistent() async {
var a = convertPath('/test/lib/a.dart');
var b = convertPath('/test/lib/b.dart');
@@ -987,7 +1109,7 @@
}
test_currentSession() async {
- var a = convertPath('/a.dart');
+ var a = convertPath('/test/lib/a.dart');
newFile(a, content: 'var V = 1;');
await driver.getResultValid(a);
@@ -1002,8 +1124,9 @@
var session2 = driver.currentSession;
expect(session2, isNotNull);
- // We get a new session.
- expect(session2, isNot(session1));
+ // We don't discard the session anymore.
+ // So, the session is always the same.
+ expect(session2, same(session1));
}
test_discoverAvailableFiles_packages() async {
@@ -3349,7 +3472,32 @@
return getFileSync2(path) as FileResult;
}
+ Set<String> get loadedLibraryUriSet {
+ var elementFactory = this.test.libraryContext2!.elementFactory;
+ var libraryReferences = elementFactory.rootReference.children;
+ return libraryReferences.map((e) => e.name).toSet();
+ }
+
+ void assertLoadedLibraryUriSet({
+ Iterable<String>? included,
+ Iterable<String>? excluded,
+ }) {
+ var uriSet = loadedLibraryUriSet;
+ if (included != null) {
+ expect(uriSet, containsAll(included));
+ }
+ if (excluded != null) {
+ for (var excludedUri in excluded) {
+ expect(uriSet, isNot(contains(excludedUri)));
+ }
+ }
+ }
+
Future<ResolvedUnitResult> getResultValid(String path) async {
return await getResult2(path) as ResolvedUnitResult;
}
+
+ Future<LibraryElementResult> getLibraryByUriValid(String uriStr) async {
+ return await getLibraryByUri2(uriStr) as LibraryElementResult;
+ }
}
diff --git a/tools/VERSION b/tools/VERSION
index 975750d..47236c6 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 15
PATCH 0
-PRERELEASE 150
+PRERELEASE 151
PRERELEASE_PATCH 0
\ No newline at end of file