[frontend_server] Add --canary flag

Closes: https://github.com/dart-lang/sdk/issues/52774
Change-Id: Ie42a803c5b63a41c37984a790ab0406c8939872d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/311149
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Anna Gringauze <annagrin@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Mark Zhou <markzipan@google.com>
diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart
index 7fd2c6d..ac27309 100644
--- a/pkg/frontend_server/lib/frontend_server.dart
+++ b/pkg/frontend_server/lib/frontend_server.dart
@@ -203,6 +203,8 @@
   ..addOption('dartdevc-module-format',
       help: 'The module format to use on for the dartdevc compiler',
       defaultsTo: 'amd')
+  ..addFlag('dartdevc-canary',
+      help: 'Enable canary features in dartdevc compiler', defaultsTo: false)
   ..addFlag('flutter-widget-cache',
       help: 'Enable the widget cache to track changes to Widget subtypes',
       defaultsTo: false)
@@ -371,15 +373,17 @@
 }
 
 class FrontendCompiler implements CompilerInterface {
-  FrontendCompiler(StringSink? outputStream,
-      {BinaryPrinterFactory? printerFactory,
-      this.transformer,
-      this.unsafePackageSerialization,
-      this.incrementalSerialization = true,
-      this.useDebuggerModuleNames = false,
-      this.emitDebugMetadata = false,
-      this.emitDebugSymbols = false})
-      : _outputStream = outputStream ?? stdout,
+  FrontendCompiler(
+    StringSink? outputStream, {
+    BinaryPrinterFactory? printerFactory,
+    this.transformer,
+    this.unsafePackageSerialization,
+    this.incrementalSerialization = true,
+    this.useDebuggerModuleNames = false,
+    this.emitDebugMetadata = false,
+    this.emitDebugSymbols = false,
+    this.canaryFeatures = false,
+  })  : _outputStream = outputStream ?? stdout,
         printerFactory = printerFactory ?? BinaryPrinterFactory();
 
   /// Fields with initializers
@@ -393,6 +397,7 @@
   final StringSink _outputStream;
   BinaryPrinterFactory printerFactory;
   bool useDebuggerModuleNames;
+  bool canaryFeatures;
 
   /// Initialized in [compile].
   late List<Uri> _additionalSources;
@@ -750,6 +755,7 @@
       emitDebugMetadata: emitDebugMetadata,
       moduleFormat: moduleFormat,
       soundNullSafety: soundNullSafety,
+      canaryFeatures: canaryFeatures,
     );
     if (fullComponent) {
       await bundler.initialize(component, _mainSource, packageConfig);
diff --git a/pkg/frontend_server/lib/src/javascript_bundle.dart b/pkg/frontend_server/lib/src/javascript_bundle.dart
index 6e49f07..6f883b5 100644
--- a/pkg/frontend_server/lib/src/javascript_bundle.dart
+++ b/pkg/frontend_server/lib/src/javascript_bundle.dart
@@ -35,6 +35,7 @@
     this.emitDebugMetadata = false,
     this.emitDebugSymbols = false,
     this.soundNullSafety = false,
+    this.canaryFeatures = false,
     String? moduleFormat,
   }) : _moduleFormat = parseModuleFormat(moduleFormat ?? 'amd');
 
@@ -43,6 +44,7 @@
   final bool emitDebugSymbols;
   final ModuleFormat _moduleFormat;
   final bool soundNullSafety;
+  final bool canaryFeatures;
   final FileSystem? _fileSystem;
   final Set<Library> _loadedLibraries;
   final Map<Uri, Component> _uriToComponent = <Uri, Component>{};
@@ -208,6 +210,7 @@
           emitDebugSymbols: emitDebugSymbols,
           moduleName: moduleName,
           soundNullSafety: soundNullSafety,
+          canaryFeatures: canaryFeatures,
         ),
         _importToSummary,
         _summaryToModule,
diff --git a/pkg/frontend_server/lib/starter.dart b/pkg/frontend_server/lib/starter.dart
index b8cd41c..7727912 100644
--- a/pkg/frontend_server/lib/starter.dart
+++ b/pkg/frontend_server/lib/starter.dart
@@ -87,13 +87,16 @@
     return 0;
   }
 
-  compiler ??= FrontendCompiler(output,
-      printerFactory: binaryPrinterFactory,
-      unsafePackageSerialization: options["unsafe-package-serialization"],
-      incrementalSerialization: options["incremental-serialization"],
-      useDebuggerModuleNames: options['debugger-module-names'],
-      emitDebugMetadata: options['experimental-emit-debug-metadata'],
-      emitDebugSymbols: options['emit-debug-symbols']);
+  compiler ??= FrontendCompiler(
+    output,
+    printerFactory: binaryPrinterFactory,
+    unsafePackageSerialization: options["unsafe-package-serialization"],
+    incrementalSerialization: options["incremental-serialization"],
+    useDebuggerModuleNames: options['debugger-module-names'],
+    emitDebugMetadata: options['experimental-emit-debug-metadata'],
+    emitDebugSymbols: options['emit-debug-symbols'],
+    canaryFeatures: options['dartdevc-canary'],
+  );
 
   if (options.rest.isNotEmpty) {
     return await compiler.compile(options.rest[0], options,
diff --git a/pkg/frontend_server/test/frontend_server_test.dart b/pkg/frontend_server/test/frontend_server_test.dart
index 5f454be..4d9c146 100644
--- a/pkg/frontend_server/test/frontend_server_test.dart
+++ b/pkg/frontend_server/test/frontend_server_test.dart
@@ -1901,6 +1901,53 @@
       ];
 
       expect(await starter(args), 0);
+
+      expect(dillFile.existsSync(), true);
+    });
+
+    test('compile to JavaScript with canary features enabled', () async {
+      var file = File('${tempDir.path}/foo.dart')..createSync();
+      file.writeAsStringSync("main() {\n}\n");
+      var packageConfig = File('${tempDir.path}/.dart_tool/package_config.json')
+        ..createSync(recursive: true)
+        ..writeAsStringSync('''
+  {
+    "configVersion": 2,
+    "packages": [
+      {
+        "name": "hello",
+        "rootUri": "../",
+        "packageUri": "./"
+      }
+    ]
+  }
+  ''');
+      var dillFile = File('${tempDir.path}/app.dill');
+      var sourcesFile = File('${tempDir.path}/app.dill.sources');
+
+      expect(dillFile.existsSync(), false);
+      expect(sourcesFile.existsSync(), false);
+
+      final List<String> args = <String>[
+        '--sdk-root=${sdkRoot.toFilePath()}',
+        '--incremental',
+        '--platform=${ddcPlatformKernel.path}',
+        '--output-dill=${dillFile.path}',
+        '--packages=${packageConfig.path}',
+        '--target=dartdevc',
+        '--dartdevc-canary',
+        file.path,
+      ];
+
+      expect(await starter(args), 0);
+
+      expect(dillFile.existsSync(), true);
+      expect(sourcesFile.existsSync(), true);
+      var ddcFlags = utf8
+          .decode(sourcesFile.readAsBytesSync())
+          .split('\n')
+          .singleWhere((l) => l.startsWith('// Flags: '));
+      expect(ddcFlags, contains('canary'));
     });
 
     test('compile to JavaScript with package scheme', () async {
@@ -2441,7 +2488,9 @@
       expect(await result, 0);
       expect(count, 1);
       frontendServer.close();
-    }, timeout: Timeout.none);
+    },
+        timeout: Timeout.none,
+        skip: 'https://github.com/dart-lang/sdk/issues/52775');
 
     test('compile to JavaScript, all modules with sound null safety', () async {
       var file = File('${tempDir.path}/foo.dart')..createSync();
@@ -2867,8 +2916,8 @@
         '--output-dill=${dillFile.path}',
         '--output-incremental-dill=${incrementalDillFile.path}'
       ];
-      File dart2js = File.fromUri(
-          Platform.script.resolve("../../../pkg/compiler/lib/src/dart2js.dart"));
+      File dart2js = File.fromUri(Platform.script
+          .resolve("../../../pkg/compiler/lib/src/dart2js.dart"));
       expect(dart2js.existsSync(), equals(true));
       File dart2jsOtherFile = File.fromUri(Platform.script
           .resolve("../../../pkg/compiler/lib/src/compiler.dart"));
@@ -2925,7 +2974,8 @@
             component =
                 loadComponentFromBinary(platformKernel.toFilePath(), component);
             expect(component.mainMethod, isNotNull);
-            verifyComponent(target, VerificationStage.afterModularTransformations, component);
+            verifyComponent(target,
+                VerificationStage.afterModularTransformations, component);
 
             count += 1;
 
@@ -2950,7 +3000,8 @@
             component =
                 loadComponentFromBinary(platformKernel.toFilePath(), component);
             expect(component.mainMethod, isNotNull);
-            verifyComponent(target, VerificationStage.afterModularTransformations, component);
+            verifyComponent(target,
+                VerificationStage.afterModularTransformations, component);
 
             count += 1;