[flutter_tools] ensure generated entrypoint matches test and web entrypoint language version (#59291)

Ensure that the language version of the test/web generated entrypoint matches the language version of the test file to run, or the overall package language version if no annotation is provided.
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index 8e50148..6670ef0 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -544,7 +544,7 @@
 
 Future<void> _runFrameworkTests() async {
   final bq.BigqueryApi bigqueryApi = await _getBigqueryApi();
-  final List<String> nullSafetyOptions = <String>['--enable-experiment=non-nullable', '--no-sound-null-safety'];
+  final List<String> nullSafetyOptions = <String>['--enable-experiment=non-nullable'];
   final List<String> trackWidgetCreationAlternatives = <String>['--track-widget-creation', '--no-track-widget-creation'];
 
   Future<void> runWidgets() async {
diff --git a/dev/integration_tests/non_nullable/test/test_test.dart b/dev/integration_tests/non_nullable/test/test_test.dart
index 1a89396..a6f7411 100644
--- a/dev/integration_tests/non_nullable/test/test_test.dart
+++ b/dev/integration_tests/non_nullable/test/test_test.dart
@@ -2,11 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// @dart=2.9
+// @dart=2.8
 import 'package:flutter_test/flutter_test.dart';
 
-String? x;
-
 void main() {
   testWidgets('trivial', (WidgetTester tester) async {
     expect(true, true);
diff --git a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
index a28fb74..48f83a3 100644
--- a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
@@ -23,6 +23,7 @@
 import '../build_info.dart';
 import '../cache.dart';
 import '../convert.dart';
+import '../dart/language_version.dart';
 import '../dart/pub.dart';
 import '../devfs.dart';
 import '../device.dart';
@@ -581,6 +582,10 @@
       }
 
       final String entrypoint = <String>[
+        determineLanguageVersion(
+          globals.fs.file(mainUri),
+          packageConfig[flutterProject.manifest.appName],
+        ),
         '// Flutter web bootstrap script for $importedEntrypoint.',
         '',
         "import 'dart:ui' as ui;",
diff --git a/packages/flutter_tools/lib/src/dart/language_version.dart b/packages/flutter_tools/lib/src/dart/language_version.dart
new file mode 100644
index 0000000..b3f57a6
--- /dev/null
+++ b/packages/flutter_tools/lib/src/dart/language_version.dart
@@ -0,0 +1,68 @@
+// Copyright 2014 The Flutter Authors. 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:file/file.dart';
+import 'package:package_config/package_config.dart';
+
+final RegExp _languageVersion = RegExp(r'\/\/\s*@dart');
+final RegExp _declarationEnd = RegExp('(import)|(library)|(part)');
+const String _blockCommentStart = '/*';
+const String _blockCommentEnd = '*/';
+
+/// Attempts to read the language version of a dart [file], returning
+/// the entire comment.
+///
+/// If this is not present, falls back to the language version defined in
+/// [package]. If [package] is not provided and there is no
+/// language version header, returns `null`. This does not specifically check
+/// for language declarations other than library, part, or import.
+///
+/// The specification for the language version tag is defined at:
+/// https://github.com/dart-lang/language/blob/master/accepted/future-releases/language-versioning/feature-specification.md#individual-library-language-version-override
+String determineLanguageVersion(File file, Package package) {
+  int blockCommentDepth = 0;
+  for (final String line in file.readAsLinesSync()) {
+    final String trimmedLine = line.trim();
+    if (trimmedLine.isEmpty) {
+      continue;
+    }
+    // Check for the start or end of a block comment. Within a block
+    // comment, all language version declarations are ignored. Block
+    // comments can be nested, and the start or end may occur on
+    // the same line. This does not handle the case of invalid
+    // block comment combinations like `*/ /*` since that will cause
+    // a compilation error anyway.
+    bool sawBlockComment = false;
+    final int startMatches = _blockCommentStart.allMatches(trimmedLine).length;
+    final int endMatches = _blockCommentEnd.allMatches(trimmedLine).length;
+    if (startMatches > 0) {
+      blockCommentDepth += startMatches;
+      sawBlockComment = true;
+    }
+    if (endMatches > 0) {
+      blockCommentDepth -= endMatches;
+      sawBlockComment = true;
+    }
+    if (blockCommentDepth != 0 || sawBlockComment) {
+      continue;
+    }
+    // Check for a match with the language version.
+    final Match match = _languageVersion.matchAsPrefix(trimmedLine);
+    if (match != null) {
+      return trimmedLine;
+    }
+
+    // Check for a declaration which ends the search for a language
+    // version.
+    if (_declarationEnd.matchAsPrefix(trimmedLine) != null) {
+      break;
+    }
+  }
+
+  // If the language version cannot be found, use the package version.
+  if (package != null) {
+    return '// @dart = ${package.languageVersion}';
+  }
+  return null;
+}
diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart
index 9cb4d41..40d471e 100644
--- a/packages/flutter_tools/lib/src/test/flutter_platform.dart
+++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart
@@ -5,14 +5,14 @@
 import 'dart:async';
 
 import 'package:meta/meta.dart';
+import 'package:package_config/package_config.dart';
 import 'package:stream_channel/stream_channel.dart';
-import 'package:vm_service/vm_service.dart' as vm_service;
-
 import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/environment.dart'; // ignore: implementation_imports
+import 'package:test_core/src/runner/plugin/platform_helpers.dart'; // ignore: implementation_imports
 import 'package:test_core/src/runner/runner_suite.dart'; // ignore: implementation_imports
 import 'package:test_core/src/runner/suite.dart'; // ignore: implementation_imports
-import 'package:test_core/src/runner/plugin/platform_helpers.dart'; // ignore: implementation_imports
-import 'package:test_core/src/runner/environment.dart'; // ignore: implementation_imports
+import 'package:vm_service/vm_service.dart' as vm_service;
 
 import '../base/common.dart';
 import '../base/file_system.dart';
@@ -20,6 +20,7 @@
 import '../build_info.dart';
 import '../compile.dart';
 import '../convert.dart';
+import '../dart/language_version.dart';
 import '../dart/package_map.dart';
 import '../globals.dart' as globals;
 import '../project.dart';
@@ -151,7 +152,7 @@
   @required InternetAddress host,
   File testConfigFile,
   bool updateGoldens = false,
-  bool nullSafety = false,
+  String languageVersionHeader = '',
 }) {
   assert(testUrl != null);
   assert(host != null);
@@ -164,6 +165,7 @@
 
   final StringBuffer buffer = StringBuffer();
   buffer.write('''
+$languageVersionHeader
 import 'dart:async';
 import 'dart:convert';  // ignore: dart_convert_import
 import 'dart:io';  // ignore: dart_io_import
@@ -181,17 +183,11 @@
 import '${Uri.file(testConfigFile.path)}' as test_config;
 ''');
   }
-  // This type is sensitive to the non-nullable experiment.
-  final String beforeLoadTypedef = nullSafety
-    ? 'Future<dynamic> Function()?'
-    : 'Future<dynamic> Function()';
   buffer.write('''
 
 /// Returns a serialized test suite.
-StreamChannel<dynamic> serializeSuite(Function getMain(),
-    {bool hidePrints = true, $beforeLoadTypedef beforeLoad}) {
-  return RemoteListener.start(getMain,
-      hidePrints: hidePrints, beforeLoad: beforeLoad);
+StreamChannel<dynamic> serializeSuite(Function getMain()) {
+  return RemoteListener.start(getMain);
 }
 
 /// Capture any top-level errors (mostly lazy syntax errors, since other are
@@ -402,11 +398,17 @@
   @visibleForTesting
   Future<HttpServer> bind(InternetAddress host, int port) => HttpServer.bind(host, port);
 
+  PackageConfig _packageConfig;
+
   Future<_AsyncError> _startTest(
     String testPath,
     StreamChannel<dynamic> controller,
     int ourTestCount,
   ) async {
+    _packageConfig ??= await loadPackageConfigWithLogging(
+      globals.fs.file(globalPackagesPath),
+      logger: globals.logger,
+    );
     globals.printTrace('test $ourTestCount: starting test $testPath');
 
     _AsyncError outOfBandError; // error that we couldn't send to the harness that we need to send via our future
@@ -748,15 +750,20 @@
     Uri testUrl,
   }) {
     assert(testUrl.scheme == 'file');
+    final File file = globals.fs.file(testUrl);
     return generateTestBootstrap(
       testUrl: testUrl,
       testConfigFile: findTestConfigFile(globals.fs.file(testUrl)),
       host: host,
       updateGoldens: updateGoldens,
-      nullSafety: extraFrontEndOptions?.contains('--enable-experiment=non-nullable') ?? false,
+      languageVersionHeader: determineLanguageVersion(
+        file,
+        _packageConfig[flutterProject?.manifest?.appName],
+      ),
     );
   }
 
+
   File _cachedFontConfig;
 
   @override
diff --git a/packages/flutter_tools/test/general.shard/dart/language_version_test.dart b/packages/flutter_tools/test/general.shard/dart/language_version_test.dart
new file mode 100644
index 0000000..2bfc87d
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/dart/language_version_test.dart
@@ -0,0 +1,228 @@
+// Copyright 2014 The Flutter Authors. 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:file/memory.dart';
+import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/dart/language_version.dart';
+import 'package:package_config/package_config.dart';
+
+import '../../src/common.dart';
+
+void main() {
+  testWithoutContext('detects language version in comment', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+// @dart = 2.9
+''');
+
+    expect(determineLanguageVersion(file, null), '// @dart = 2.9');
+  });
+
+  testWithoutContext('detects technically invalid language version', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+// @dart
+''');
+
+    expect(determineLanguageVersion(file, null), '// @dart');
+  });
+
+  testWithoutContext('detects language version with leading whitespace', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+    // @dart = 2.9
+''');
+
+    expect(determineLanguageVersion(file, null), '// @dart = 2.9');
+  });
+
+  testWithoutContext('detects language version with tabs', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+//\t@dart = 2.9
+''');
+
+    expect(determineLanguageVersion(file, null), '//\t@dart = 2.9');
+  });
+
+  testWithoutContext('detects language version with tons of whitespace', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+//        @dart       = 23
+''');
+
+    expect(determineLanguageVersion(file, null), '//        @dart       = 23');
+  });
+
+  testWithoutContext('does not detect language version in dartdoc', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+/// @dart = 2.9
+''');
+
+    expect(determineLanguageVersion(file, null), null);
+  });
+
+  testWithoutContext('does not detect language version in block comment', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+/*
+// @dart = 2.9
+*/
+''');
+
+    expect(determineLanguageVersion(file, null), null);
+  });
+
+  testWithoutContext('does not detect language version in nested block comment', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+/*
+/*
+// @dart = 2.9
+*/
+*/
+''');
+
+    expect(determineLanguageVersion(file, null), null);
+  });
+
+  testWithoutContext('detects language version after nested block comment', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+/* /*
+*/
+*/
+// @dart = 2.9
+''');
+
+    expect(determineLanguageVersion(file, null), '// @dart = 2.9');
+  });
+
+  testWithoutContext('does not crash with unbalanced opening block comments', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+/*
+/*
+*/
+// @dart = 2.9
+''');
+
+    expect(determineLanguageVersion(file, null), null);
+  });
+
+  testWithoutContext('does not crash with unbalanced closing block comments', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+/*
+*/
+*/
+// @dart = 2.9
+''');
+
+    expect(determineLanguageVersion(file, null), null);
+  });
+
+  testWithoutContext('does not detect language version in single line block comment', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+/* // @dart = 2.9 */
+''');
+
+    expect(determineLanguageVersion(file, null), null);
+  });
+
+  testWithoutContext('does not detect language version after import declaration', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+import 'dart:ui' as ui;
+
+// @dart = 2.9
+''');
+
+    expect(determineLanguageVersion(file, null), null);
+  });
+
+  testWithoutContext('does not detect language version after part declaration', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+part of 'foo.dart';
+
+// @dart = 2.9
+''');
+
+    expect(determineLanguageVersion(file, null), null);
+  });
+
+  testWithoutContext('does not detect language version after library declaration', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+
+library funstuff;
+
+// @dart = 2.9
+''');
+
+    expect(determineLanguageVersion(file, null), null);
+  });
+
+  testWithoutContext('looks up language version from package if not found in file', () {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final File file = fileSystem.file('example.dart')
+      ..writeAsStringSync('''
+// Some license
+''');
+    final Package package = Package(
+      'foo',
+      Uri.parse('file://foo/'),
+      languageVersion: LanguageVersion(2, 7),
+    );
+
+    expect(determineLanguageVersion(file, package), '// @dart = 2.7');
+  });
+}