Generate syntax for plugin registration that works both with and without null safety. (#109480) (#109906)

Co-authored-by: Jackson Gardner <eyebrowsoffire@gmail.com>
diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart
index 8e7962e..488da9b 100644
--- a/packages/flutter_tools/lib/src/flutter_plugins.dart
+++ b/packages/flutter_tools/lib/src/flutter_plugins.dart
@@ -567,6 +567,7 @@
 // Generated file. Do not edit.
 //
 
+// @dart = 2.13
 // ignore_for_file: type=lint
 
 {{#methodChannelPlugins}}
diff --git a/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart b/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart
index 314c69b..b79701b 100644
--- a/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart
+++ b/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart
@@ -44,8 +44,8 @@
   testUsingContext('generated plugin registrant passes analysis', () async {
     await _createProject(projectDir, <String>[]);
     // We need a dependency so the plugin registrant is not completely empty.
-    await _addDependency(projectDir, 'shared_preferences',
-        version: '^2.0.0');
+    await _editPubspecFile(projectDir, _addDependencyEditor('shared_preferences',
+        version: '^2.0.0'));
     // The plugin registrant is created on build...
     await _buildWebProject(projectDir);
 
@@ -62,6 +62,7 @@
 
     // Ensure the contents match what we expect for a non-empty plugin registrant.
     final String contents = registrant.readAsStringSync();
+    expect(contents, contains('// @dart = 2.13'));
     expect(contents, contains("import 'package:shared_preferences_web/shared_preferences_web.dart';"));
     expect(contents, contains('void registerPlugins([final Registrar? pluginRegistrar]) {'));
     expect(contents, contains('SharedPreferencesPlugin.registerWith(registrar);'));
@@ -77,6 +78,54 @@
         ),
   });
 
+  testUsingContext('generated plugin registrant passes analysis without null safety', () async {
+    await _createProject(projectDir, <String>[]);
+    // We need a dependency so the plugin registrant is not completely empty.
+    await _editPubspecFile(projectDir,
+      _composeEditors(<PubspecEditor>[
+        _addDependencyEditor('shared_preferences', version: '^2.0.0'),
+
+        // This turns null safety off
+        _setDartSDKVersionEditor('>=2.11.0 <3.0.0'),
+      ]));
+
+    // The generated main.dart file has a bunch of stuff that is invalid without null safety, so
+    // replace it with a no-op dummy main file. We aren't testing it in this scenario anyway.
+    await _replaceMainFile(projectDir, 'void main() {}');
+
+    // The plugin registrant is created on build...
+    await _buildWebProject(projectDir);
+
+    // Find the web_plugin_registrant, now that it lives outside "lib":
+    final Directory buildDir = projectDir
+        .childDirectory('.dart_tool/flutter_build')
+        .listSync()
+        .firstWhere((FileSystemEntity entity) => entity is Directory) as Directory;
+
+    // Ensure the file exists, and passes analysis.
+    final File registrant = buildDir.childFile('web_plugin_registrant.dart');
+    expect(registrant, exists);
+    await _analyzeEntity(registrant);
+
+    // Ensure the contents match what we expect for a non-empty plugin registrant.
+    final String contents = registrant.readAsStringSync();
+    expect(contents, contains('// @dart = 2.13'));
+    expect(contents, contains("import 'package:shared_preferences_web/shared_preferences_web.dart';"));
+    expect(contents, contains('void registerPlugins([final Registrar? pluginRegistrar]) {'));
+    expect(contents, contains('SharedPreferencesPlugin.registerWith(registrar);'));
+    expect(contents, contains('registrar.registerMessageHandler();'));
+  }, overrides: <Type, Generator>{
+    Pub: () => Pub(
+          fileSystem: globals.fs,
+          logger: globals.logger,
+          processManager: globals.processManager,
+          usage: globals.flutterUsage,
+          botDetector: globals.botDetector,
+          platform: globals.platform,
+        ),
+  });
+
+
   testUsingContext('(no-op) generated plugin registrant passes analysis', () async {
     await _createProject(projectDir, <String>[]);
     // No dependencies on web plugins this time!
@@ -110,8 +159,8 @@
   // See: https://github.com/dart-lang/dart-services/pull/874
   testUsingContext('generated plugin registrant for dartpad is created on pub get', () async {
     await _createProject(projectDir, <String>[]);
-    await _addDependency(projectDir, 'shared_preferences',
-        version: '^2.0.0');
+    await _editPubspecFile(projectDir,
+      _addDependencyEditor('shared_preferences', version: '^2.0.0'));
     // The plugin registrant for dartpad is created on flutter pub get.
     await _doFlutterPubGet(projectDir);
 
@@ -154,10 +203,12 @@
     // With the above lint rule added, we want to ensure that the `generated_plugin_registrant.dart`
     // file does not fail analysis (this is a regression test - an ignore was
     // added to cover this case).
-    await _addDependency(
+    await _editPubspecFile(
       projectDir,
-      'test_web_plugin_with_a_purposefully_extremely_long_package_name',
-      path: '../test_plugin',
+      _addDependencyEditor(
+        'test_web_plugin_with_a_purposefully_extremely_long_package_name',
+        path: '../test_plugin',
+      )
     );
     // The plugin registrant is only created after a build...
     await _buildWebProject(projectDir);
@@ -255,32 +306,76 @@
   ]);
 }
 
-Future<void> _addDependency(
-  Directory projectDir,
-  String package, {
-  String? version,
-  String? path,
-}) async {
-  assert(version != null || path != null,
-      'Need to define a source for the package.');
-  assert(version == null || path == null,
-      'Cannot only load a package from path or from Pub, not both.');
+typedef PubspecEditor = void Function(List<String> pubSpecContents);
 
+Future<void> _editPubspecFile(
+  Directory projectDir,
+  PubspecEditor editor,
+) async {
   final File pubspecYaml = projectDir.childFile('pubspec.yaml');
   expect(pubspecYaml, exists);
 
   final List<String> lines = await pubspecYaml.readAsLines();
-  for (int i = 0; i < lines.length; i++) {
-    final String line = lines[i];
-    if (line.startsWith('dependencies:')) {
-      lines.insert(
-          i + 1,
-          '  $package: ${version ?? '\n'
-              '    path: $path'}');
-      break;
+  editor(lines);
+  await pubspecYaml.writeAsString(lines.join('\n'));
+}
+
+Future<void> _replaceMainFile(Directory projectDir, String fileContents) async {
+  final File mainFile = projectDir.childDirectory('lib').childFile('main.dart');
+  await mainFile.writeAsString(fileContents);
+}
+
+PubspecEditor _addDependencyEditor(String packageToAdd, {String? version, String? path}) {
+  assert(version != null || path != null,
+      'Need to define a source for the package.');
+  assert(version == null || path == null,
+      'Cannot only load a package from path or from Pub, not both.');
+  void editor(List<String> lines) {
+    for (int i = 0; i < lines.length; i++) {
+      final String line = lines[i];
+      if (line.startsWith('dependencies:')) {
+        lines.insert(
+            i + 1,
+            '  $packageToAdd: ${version ?? '\n'
+                '   path: $path'}');
+        break;
+      }
     }
   }
-  await pubspecYaml.writeAsString(lines.join('\n'));
+  return editor;
+}
+
+PubspecEditor _setDartSDKVersionEditor(String version) {
+  void editor(List<String> lines) {
+    for (int i = 0; i < lines.length; i++) {
+      final String line = lines[i];
+      if (line.startsWith('environment:')) {
+        for (i++; i < lines.length; i++) {
+          final String innerLine = lines[i];
+          final String sdkLine = "  sdk: '$version'";
+          if(innerLine.isNotEmpty && !innerLine.startsWith('  ')) {
+            lines.insert(i, sdkLine);
+            break;
+          }
+          if(innerLine.startsWith('  sdk:')) {
+            lines[i] = sdkLine;
+            break;
+          }
+        }
+        break;
+      }
+    }
+  }
+  return editor;
+}
+
+PubspecEditor _composeEditors(Iterable<PubspecEditor> editors) {
+  void composedEditor(List<String> lines) {
+    for (final PubspecEditor editor in editors) {
+      editor(lines);
+    }
+  }
+  return composedEditor;
 }
 
 Future<void> _addAnalysisOptions(