Introduce function `parseFile2`.

This is similar to the existing `parseFile` function, but it is more
efficient, and it supports the same parameters `featureSet` and
`throwIfDiagnostics` as `parseString`.

Change-Id: I4b9ecc36a137cc1c99e671e379f00afa3942c9ba
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105542
Commit-Queue: Paul Berry <paulberry@google.com>
Auto-Submit: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/CHANGELOG.md b/pkg/analyzer/CHANGELOG.md
index f0d1b34a..adc4328 100644
--- a/pkg/analyzer/CHANGELOG.md
+++ b/pkg/analyzer/CHANGELOG.md
@@ -21,6 +21,9 @@
 * Changed the return type of `ClassTypeAlias.declaredElement` to `ClassElement`.
   There is no functional change; it has always returned an instance of
   `ClassElement`.
+* Deprecated `parseFile`.  Please use `parseFile2` instead--in addition to
+  supporting the same `featureSet` and `throwIfDiagnostics` parameters as
+  `parseString`, it is much more efficient than `parseFile`.
 
 ## 0.36.3
 * Deprecated `AstFactory.compilationUnit`.  In a future analyzer release, this
diff --git a/pkg/analyzer/lib/dart/analysis/utilities.dart b/pkg/analyzer/lib/dart/analysis/utilities.dart
index 7b96945..820ffa1 100644
--- a/pkg/analyzer/lib/dart/analysis/utilities.dart
+++ b/pkg/analyzer/lib/dart/analysis/utilities.dart
@@ -26,6 +26,9 @@
 /// Note that if more than one file is going to be parsed then this function is
 /// inefficient. Clients should instead use [AnalysisContextCollection] to
 /// create one or more contexts and use those contexts to parse the files.
+///
+/// Deprecated: please use [parseFile2] instead.
+@Deprecated('Use parseFile2')
 ParsedUnitResult parseFile(
     {@required String path, ResourceProvider resourceProvider}) {
   AnalysisContext context =
@@ -33,6 +36,42 @@
   return context.currentSession.getParsedUnit(path);
 }
 
+/// Return the result of parsing the file at the given [path].
+///
+/// If a [resourceProvider] is given, it will be used to access the file system.
+///
+/// [featureSet] determines what set of features will be assumed by the parser.
+/// This parameter is required because the analyzer does not yet have a
+/// performant way of computing the correct feature set for a single file to be
+/// parsed.  Callers that need the feature set to be strictly correct must
+/// create an [AnalysisContextCollection], query it to get an [AnalysisContext],
+/// query it to get an [AnalysisSession], and then call `getParsedUnit`.
+///
+/// Callers that don't need the feature set to be strictly correct can pass in
+/// `FeatureSet.fromEnableFlags([])` to enable the default set of features; this
+/// is much more performant than using an analysis session, because it doesn't
+/// require the analyzer to process the SDK.
+///
+/// If [throwIfDiagnostics] is `true` (the default), then if any diagnostics are
+/// produced because of syntactic errors in the [content] an `ArgumentError`
+/// will be thrown. If the parameter is `false`, then the caller can check the
+/// result to see whether there are any errors.
+ParseStringResult parseFile2(
+    {@required String path,
+    ResourceProvider resourceProvider,
+    @required FeatureSet featureSet,
+    bool throwIfDiagnostics: true}) {
+  if (featureSet == null) {
+    throw ArgumentError('A non-null feature set must be provided.');
+  }
+  resourceProvider ??= PhysicalResourceProvider.INSTANCE;
+  var content = (resourceProvider.getResource(path) as File).readAsStringSync();
+  return parseString(
+      content: content,
+      featureSet: featureSet,
+      throwIfDiagnostics: throwIfDiagnostics);
+}
+
 /// Returns the result of parsing the given [content] as a compilation unit.
 ///
 /// If a [featureSet] is provided, it will be the default set of features that
diff --git a/pkg/analyzer/test/dart/analysis/utilities_test.dart b/pkg/analyzer/test/dart/analysis/utilities_test.dart
index 1aebc1fe8..89c3dee 100644
--- a/pkg/analyzer/test/dart/analysis/utilities_test.dart
+++ b/pkg/analyzer/test/dart/analysis/utilities_test.dart
@@ -2,10 +2,14 @@
 // 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:io';
+
 import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/dart/analysis/utilities.dart';
+import 'package:analyzer/file_system/memory_file_system.dart';
 import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -17,6 +21,109 @@
 
 @reflectiveTest
 class UtilitiesTest with ResourceProviderMixin {
+  FeatureSet get defaultFeatureSet =>
+      FeatureSet.forTesting(sdkVersion: '2.2.2');
+
+  test_parseFile_default_resource_provider() {
+    String content = '''
+void main() => print('Hello, world!');
+    ''';
+    ParseStringResult result = _withTemporaryFile(content,
+        (path) => parseFile2(path: path, featureSet: defaultFeatureSet));
+    expect(result.content, content);
+    expect(result.errors, isEmpty);
+    expect(result.lineInfo, isNotNull);
+    expect(result.unit.toString(),
+        equals("void main() => print('Hello, world!');"));
+  }
+
+  test_parseFile_errors_noThrow() {
+    String content = '''
+void main() => print('Hello, world!')
+''';
+    ParseStringResult result = _withMemoryFile(
+        content,
+        (resourceProvider, path) => parseFile2(
+            path: path,
+            featureSet: defaultFeatureSet,
+            resourceProvider: resourceProvider,
+            throwIfDiagnostics: false));
+    expect(result.content, content);
+    expect(result.errors, hasLength(1));
+    expect(result.lineInfo, isNotNull);
+    expect(result.unit.toString(),
+        equals("void main() => print('Hello, world!');"));
+  }
+
+  test_parseFile_errors_throw() {
+    String content = '''
+void main() => print('Hello, world!')
+''';
+    expect(
+        () => _withMemoryFile(
+            content,
+            (resourceProvider, path) => parseFile2(
+                path: path,
+                featureSet: defaultFeatureSet,
+                resourceProvider: resourceProvider)),
+        throwsA(const TypeMatcher<ArgumentError>()));
+  }
+
+  test_parseFile_featureSet_nnbd_off() {
+    String content = '''
+int? f() => 1;
+''';
+    var featureSet = FeatureSet.forTesting(sdkVersion: '2.3.0');
+    expect(featureSet.isEnabled(Feature.non_nullable), isFalse);
+    ParseStringResult result = _withMemoryFile(
+        content,
+        (resourceProvider, path) => parseFile2(
+            path: path,
+            resourceProvider: resourceProvider,
+            throwIfDiagnostics: false,
+            featureSet: featureSet));
+    expect(result.content, content);
+    expect(result.errors, hasLength(1));
+    expect(result.lineInfo, isNotNull);
+    expect(result.unit.toString(), equals('int? f() => 1;'));
+  }
+
+  test_parseFile_featureSet_nnbd_on() {
+    String content = '''
+int? f() => 1;
+''';
+    var featureSet =
+        FeatureSet.forTesting(additionalFeatures: [Feature.non_nullable]);
+    ParseStringResult result = _withMemoryFile(
+        content,
+        (resourceProvider, path) => parseFile2(
+            path: path,
+            resourceProvider: resourceProvider,
+            throwIfDiagnostics: false,
+            featureSet: featureSet));
+    expect(result.content, content);
+    expect(result.errors, isEmpty);
+    expect(result.lineInfo, isNotNull);
+    expect(result.unit.toString(), equals('int? f() => 1;'));
+  }
+
+  test_parseFile_noErrors() {
+    String content = '''
+void main() => print('Hello, world!');
+''';
+    ParseStringResult result = _withMemoryFile(
+        content,
+        (resourceProvider, path) => parseFile2(
+            path: path,
+            featureSet: defaultFeatureSet,
+            resourceProvider: resourceProvider));
+    expect(result.content, content);
+    expect(result.errors, isEmpty);
+    expect(result.lineInfo, isNotNull);
+    expect(result.unit.toString(),
+        equals("void main() => print('Hello, world!');"));
+  }
+
   test_parseString_errors_noThrow() {
     String content = '''
 void main() => print('Hello, world!')
@@ -88,4 +195,24 @@
     expect(result.unit.toString(),
         equals("void main() => print('Hello, world!');"));
   }
+
+  T _withMemoryFile<T>(String content,
+      T callback(MemoryResourceProvider resourceProvider, String path)) {
+    var resourceProvider = MemoryResourceProvider();
+    var path =
+        resourceProvider.pathContext.fromUri(Uri.parse('file:///test.dart'));
+    resourceProvider.newFile(path, content);
+    return callback(resourceProvider, path);
+  }
+
+  T _withTemporaryFile<T>(String content, T callback(String path)) {
+    var tempDir = Directory.systemTemp.createTempSync();
+    try {
+      var file = File(p.join(tempDir.path, 'test.dart'));
+      file.writeAsStringSync(content);
+      return callback(file.path);
+    } finally {
+      tempDir.deleteSync(recursive: true);
+    }
+  }
 }