Add parseString() to replace the deprecated parseCompilationUnit().

Change-Id: Ib272f6a281b09a02ba037049e76bf08dd78dbbab
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105540
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analyzer/lib/dart/analysis/results.dart b/pkg/analyzer/lib/dart/analysis/results.dart
index 7f82c94..db9f9f8 100644
--- a/pkg/analyzer/lib/dart/analysis/results.dart
+++ b/pkg/analyzer/lib/dart/analysis/results.dart
@@ -105,6 +105,27 @@
   CompilationUnit get unit;
 }
 
+/// The result of parsing of a single file. The errors returned include only
+/// those discovered during scanning and parsing.
+///
+/// Similar to [ParsedUnitResult], but does not allow access to an analysis
+/// session.
+///
+/// Clients may not extend, implement or mix-in this class.
+abstract class ParseStringResult {
+  /// The content of the file that was scanned and parsed.
+  String get content;
+
+  /// The analysis errors that were computed during analysis.
+  List<AnalysisError> get errors;
+
+  /// Information about lines in the content.
+  LineInfo get lineInfo;
+
+  /// The parsed, unresolved compilation unit for the [content].
+  CompilationUnit get unit;
+}
+
 /// The result of building resolved AST(s) for the whole library.
 ///
 /// Clients may not extend, implement or mix-in this class.
diff --git a/pkg/analyzer/lib/dart/analysis/utilities.dart b/pkg/analyzer/lib/dart/analysis/utilities.dart
index 75ef87b..cd3210e 100644
--- a/pkg/analyzer/lib/dart/analysis/utilities.dart
+++ b/pkg/analyzer/lib/dart/analysis/utilities.dart
@@ -6,9 +6,17 @@
 
 import 'package:analyzer/dart/analysis/analysis_context.dart';
 import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
+import 'package:analyzer/dart/analysis/features.dart';
 import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/src/dart/analysis/results.dart';
+import 'package:analyzer/src/dart/scanner/reader.dart';
+import 'package:analyzer/src/dart/scanner/scanner.dart';
+import 'package:analyzer/src/generated/parser.dart';
+import 'package:analyzer/src/string_source.dart';
 import 'package:meta/meta.dart';
 
 /// Return the result of parsing the file at the given [path].
@@ -25,6 +33,36 @@
   return context.currentSession.getParsedUnit(path);
 }
 
+/// 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
+/// will be assumed by the parser.
+///
+/// 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 parseString(
+    {@required String content,
+    FeatureSet featureSet,
+    bool throwIfDiagnostics: true}) {
+  featureSet ??= FeatureSet.fromEnableFlags([]);
+  var source = StringSource(content, null);
+  var reader = CharSequenceReader(content);
+  var errorCollector = RecordingErrorListener();
+  var scanner = Scanner(source, reader, errorCollector)
+    ..configureFeatures(featureSet);
+  var token = scanner.tokenize();
+  var parser = Parser(source, errorCollector, featureSet: scanner.featureSet);
+  var unit = parser.parseCompilationUnit(token);
+  ParseStringResult result = ParseStringResultImpl(
+      content, LineInfo(scanner.lineStarts), unit, errorCollector.errors);
+  if (throwIfDiagnostics && result.errors.isNotEmpty) {
+    throw new ArgumentError('Content produced diagnostics when parsed');
+  }
+  return result;
+}
+
 /// Return the result of resolving the file at the given [path].
 ///
 /// If a [resourceProvider] is given, it will be used to access the file system.
diff --git a/pkg/analyzer/lib/src/dart/analysis/results.dart b/pkg/analyzer/lib/src/dart/analysis/results.dart
index b38336b..adfe2ea 100644
--- a/pkg/analyzer/lib/src/dart/analysis/results.dart
+++ b/pkg/analyzer/lib/src/dart/analysis/results.dart
@@ -163,6 +163,22 @@
   ResultState get state => ResultState.VALID;
 }
 
+class ParseStringResultImpl implements ParseStringResult {
+  @override
+  final String content;
+
+  @override
+  final List<AnalysisError> errors;
+
+  @override
+  final LineInfo lineInfo;
+
+  @override
+  final CompilationUnit unit;
+
+  ParseStringResultImpl(this.content, this.lineInfo, this.unit, this.errors);
+}
+
 class ResolvedLibraryResultImpl extends AnalysisResultImpl
     implements ResolvedLibraryResult {
   @override
diff --git a/pkg/analyzer/test/dart/analysis/test_all.dart b/pkg/analyzer/test/dart/analysis/test_all.dart
index fe1f2ee..44f950b 100644
--- a/pkg/analyzer/test/dart/analysis/test_all.dart
+++ b/pkg/analyzer/test/dart/analysis/test_all.dart
@@ -5,9 +5,11 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'declared_variables_test.dart' as declared_variables;
+import 'utilities_test.dart' as utilities;
 
 main() {
   defineReflectiveSuite(() {
     declared_variables.main();
+    utilities.main();
   }, name: 'analysis');
 }
diff --git a/pkg/analyzer/test/dart/analysis/utilities_test.dart b/pkg/analyzer/test/dart/analysis/utilities_test.dart
new file mode 100644
index 0000000..8240940
--- /dev/null
+++ b/pkg/analyzer/test/dart/analysis/utilities_test.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2019, 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 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/analysis/utilities.dart';
+import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(UtilitiesTest);
+  });
+}
+
+@reflectiveTest
+class UtilitiesTest with ResourceProviderMixin {
+  test_parseString_errors_noThrow() {
+    String content = '''
+void main() => print('Hello, world!')
+''';
+    ParseStringResult result =
+        parseString(content: content, 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_parseString_errors_throw() {
+    String content = '''
+void main() => print('Hello, world!')
+''';
+    expect(() => parseString(content: content),
+        throwsA(const TypeMatcher<ArgumentError>()));
+  }
+
+  test_parseString_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 = parseString(
+        content: content, 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_parseString_featureSet_nnbd_on() {
+    String content = '''
+int? f() => 1;
+''';
+    var featureSet =
+        FeatureSet.forTesting(additionalFeatures: [Feature.non_nullable]);
+    ParseStringResult result = parseString(
+        content: content, 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_parseString_noErrors() {
+    String content = '''
+void main() => print('Hello, world!');
+''';
+    ParseStringResult result = parseString(content: content);
+    expect(result.content, content);
+    expect(result.errors, isEmpty);
+    expect(result.lineInfo, isNotNull);
+    expect(result.unit.toString(),
+        equals("void main() => print('Hello, world!');"));
+  }
+}