Don't read referenced files in AnalysisDriver until necessary.

Bug: https://github.com/dart-lang/sdk/issues/43744
Change-Id: I06d58b2a11fdf2db9bd6800f350ac25b8aab3fb4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/168382
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/CHANGELOG.md b/pkg/analyzer/CHANGELOG.md
index 924f983..13a6e34 100644
--- a/pkg/analyzer/CHANGELOG.md
+++ b/pkg/analyzer/CHANGELOG.md
@@ -1,5 +1,7 @@
 ## 0.40.5-dev
 * Deprecated `GenericTypeAliasElement`. Use `FunctionTypeAliasElement`.
+* Read imports, exports, and parts on demand in `AnalysisDriver`.
+  Specifically, `parseFileSync` will not read any referenced files.
 
 ## 0.40.4
 * Deprecated `IndexExpression.auxiliaryElements` and
diff --git a/pkg/analyzer/lib/src/dart/analysis/file_state.dart b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
index 2524193..00d6c6f 100644
--- a/pkg/analyzer/lib/src/dart/analysis/file_state.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/file_state.dart
@@ -192,23 +192,54 @@
 
   /// Return the set of all directly referenced files - imported, exported or
   /// parted.
-  Set<FileState> get directReferencedFiles => _directReferencedFiles;
+  Set<FileState> get directReferencedFiles {
+    return _directReferencedFiles ??= <FileState>{
+      ...importedFiles,
+      ...exportedFiles,
+      ...partedFiles,
+    };
+  }
 
   /// Return the set of all directly referenced libraries - imported or
   /// exported.
-  Set<FileState> get directReferencedLibraries => _directReferencedLibraries;
+  Set<FileState> get directReferencedLibraries {
+    return _directReferencedLibraries ??= <FileState>{
+      ...importedFiles,
+      ...exportedFiles,
+    };
+  }
 
   /// Return `true` if the file exists.
   bool get exists => _exists;
 
   /// The list of files this file exports.
-  List<FileState> get exportedFiles => _exportedFiles;
+  List<FileState> get exportedFiles {
+    if (_exportedFiles == null) {
+      _exportedFiles = <FileState>[];
+      for (var directive in _unlinked2.exports) {
+        var uri = _selectRelativeUri(directive);
+        var file = _fileForRelativeUri(uri);
+        _exportedFiles.add(file);
+      }
+    }
+    return _exportedFiles;
+  }
 
   @override
   int get hashCode => uri.hashCode;
 
   /// The list of files this file imports.
-  List<FileState> get importedFiles => _importedFiles;
+  List<FileState> get importedFiles {
+    if (_importedFiles == null) {
+      _importedFiles = <FileState>[];
+      for (var directive in _unlinked2.imports) {
+        var uri = _selectRelativeUri(directive);
+        var file = _fileForRelativeUri(uri);
+        _importedFiles.add(file);
+      }
+    }
+    return _importedFiles;
+  }
 
   LibraryCycle get internal_libraryCycle => _libraryCycle;
 
@@ -237,6 +268,7 @@
   /// of. Return `null` if a library is not known, for example because we have
   /// not processed a library file yet.
   FileState get library {
+    _fsState.readPartsForLibraries();
     List<FileState> libraries = _fsState._partToLibraries[this];
     if (libraries == null || libraries.isEmpty) {
       return null;
@@ -264,13 +296,27 @@
 
   /// The list of files files that this library consists of, i.e. this library
   /// file itself and its [partedFiles].
-  List<FileState> get libraryFiles => _libraryFiles;
+  List<FileState> get libraryFiles {
+    return _libraryFiles ??= [this, ...partedFiles];
+  }
 
   /// Return information about line in the file.
   LineInfo get lineInfo => _lineInfo;
 
   /// The list of files this library file references as parts.
-  List<FileState> get partedFiles => _partedFiles;
+  List<FileState> get partedFiles {
+    if (_partedFiles == null) {
+      _partedFiles = <FileState>[];
+      for (var uri in _unlinked2.parts) {
+        var file = _fileForRelativeUri(uri);
+        _partedFiles.add(file);
+        _fsState._partToLibraries
+            .putIfAbsent(file, () => <FileState>[])
+            .add(this);
+      }
+    }
+    return _partedFiles;
+  }
 
   /// The external names referenced by the file.
   Set<String> get referencedNames {
@@ -287,7 +333,7 @@
 
     void appendReferenced(FileState file) {
       if (transitiveFiles.add(file)) {
-        file._directReferencedFiles?.forEach(appendReferenced);
+        file.directReferencedFiles.forEach(appendReferenced);
       }
     }
 
@@ -436,39 +482,16 @@
       }
     }
 
-    // Build the graph.
-    _importedFiles = <FileState>[];
-    _exportedFiles = <FileState>[];
-    _partedFiles = <FileState>[];
-    for (var directive in _unlinked2.imports) {
-      var uri = _selectRelativeUri(directive);
-      var file = _fileForRelativeUri(uri);
-      _importedFiles.add(file);
-    }
-    for (var directive in _unlinked2.exports) {
-      var uri = _selectRelativeUri(directive);
-      var file = _fileForRelativeUri(uri);
-      _exportedFiles.add(file);
-    }
-    for (var uri in _unlinked2.parts) {
-      var file = _fileForRelativeUri(uri);
-      _partedFiles.add(file);
-      _fsState._partToLibraries
-          .putIfAbsent(file, () => <FileState>[])
-          .add(this);
-    }
-    _libraryFiles = [this, ..._partedFiles];
+    // Read imports/exports on demand.
+    _importedFiles = null;
+    _exportedFiles = null;
+    _directReferencedFiles = null;
+    _directReferencedLibraries = null;
 
-    // Compute referenced files.
-    _directReferencedFiles = <FileState>{
-      ..._importedFiles,
-      ..._exportedFiles,
-      ..._partedFiles,
-    };
-    _directReferencedLibraries = <FileState>{
-      ..._importedFiles,
-      ..._exportedFiles,
-    };
+    // Read parts on demand.
+    _fsState._librariesWithoutPartsRead.add(this);
+    _partedFiles = null;
+    _libraryFiles = null;
 
     // Update mapping from subtyped names to files.
     for (var name in _driverUnlinkedUnit.subtypedNames) {
@@ -748,6 +771,11 @@
   /// Mapping from a path to the corresponding canonical [FileState].
   final Map<String, FileState> _pathToCanonicalFile = {};
 
+  /// We don't read parts until requested, but if we need to know the
+  /// library for a file, we need to read parts of every file to know
+  /// which libraries reference this part.
+  final List<FileState> _librariesWithoutPartsRead = [];
+
   /// Mapping from a part to the libraries it is a part of.
   final Map<FileState, List<FileState>> _partToLibraries = {};
 
@@ -950,6 +978,19 @@
     _fileContentCache.remove(path);
   }
 
+  void readPartsForLibraries() {
+    // Make a copy, because reading new files will update it.
+    var libraryToProcess = _librariesWithoutPartsRead.toList();
+
+    // We will process these files, so clear it now.
+    // It will be filled with new files during the loop below.
+    _librariesWithoutPartsRead.clear();
+
+    for (var library in libraryToProcess) {
+      library.partedFiles;
+    }
+  }
+
   /// Remove the file with the given [path].
   void removeFile(String path) {
     markFileForReading(path);
diff --git a/pkg/analyzer/test/src/dart/analysis/driver_test.dart b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
index 7d59c48..16cf839 100644
--- a/pkg/analyzer/test/src/dart/analysis/driver_test.dart
+++ b/pkg/analyzer/test/src/dart/analysis/driver_test.dart
@@ -2349,6 +2349,49 @@
     }
   }
 
+  test_parseFileSync_doesNotReadImportedFiles() async {
+    var a = convertPath('/test/lib/a.dart');
+    var b = convertPath('/test/lib/b.dart');
+
+    newFile(a, content: '');
+    newFile(b, content: r'''
+import 'a.dart';
+''');
+
+    expect(driver.fsState.knownFilePaths, isEmpty);
+
+    // Don't read `a.dart` when parse.
+    driver.parseFileSync(b);
+    expect(driver.fsState.knownFilePaths, unorderedEquals([b]));
+
+    // Still don't read `a.dart` when parse the second time.
+    driver.parseFileSync(b);
+    expect(driver.fsState.knownFilePaths, unorderedEquals([b]));
+  }
+
+  test_parseFileSync_doesNotReadPartedFiles() async {
+    var a = convertPath('/test/lib/a.dart');
+    var b = convertPath('/test/lib/b.dart');
+
+    newFile(a, content: r'''
+part of my;
+''');
+    newFile(b, content: r'''
+library my;
+part 'a.dart';
+''');
+
+    expect(driver.fsState.knownFilePaths, isEmpty);
+
+    // Don't read `a.dart` when parse.
+    driver.parseFileSync(b);
+    expect(driver.fsState.knownFilePaths, unorderedEquals([b]));
+
+    // Still don't read `a.dart` when parse the second time.
+    driver.parseFileSync(b);
+    expect(driver.fsState.knownFilePaths, unorderedEquals([b]));
+  }
+
   test_parseFileSync_languageVersion() async {
     var path = convertPath('/test/lib/test.dart');