[dartdev] fix --code support

See: https://github.com/dart-lang/sdk/issues/47219

I'll follow up with tests for error conditions once diagnostic codes are validated by the protocol (see: https://dart-review.googlesource.com/c/sdk/+/255541).

Change-Id: I3318364e952522522608e86bdfdd231839685689
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255544
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/dartdev/lib/src/analysis_server.dart b/pkg/dartdev/lib/src/analysis_server.dart
index cc9c182..f1d511d 100644
--- a/pkg/dartdev/lib/src/analysis_server.dart
+++ b/pkg/dartdev/lib/src/analysis_server.dart
@@ -194,10 +194,11 @@
   }
 
   Future<EditBulkFixesResult> requestBulkFixes(
-      String filePath, bool inTestMode) {
+      String filePath, bool inTestMode, List<String> codes) {
     return _sendCommand('edit.bulkFixes', params: <String, dynamic>{
       'included': [path.canonicalize(filePath)],
-      'inTestMode': inTestMode
+      'inTestMode': inTestMode,
+      if (codes.isNotEmpty) 'codes': codes,
     }).then((result) {
       return EditBulkFixesResult.fromJson(
           ResponseDecoder(null), 'result', result);
diff --git a/pkg/dartdev/lib/src/commands/fix.dart b/pkg/dartdev/lib/src/commands/fix.dart
index f9ba17b..62e6cf5 100644
--- a/pkg/dartdev/lib/src/commands/fix.dart
+++ b/pkg/dartdev/lib/src/commands/fix.dart
@@ -42,6 +42,11 @@
       negatable: false,
       help: 'Apply the proposed changes.',
     );
+    argParser.addMultiOption(
+      'code',
+      help: 'Apply fixes for one (or more) diagnostic codes.',
+      valueHelp: 'code1,code2,...',
+    );
     argParser.addFlag(
       'compare-to-golden',
       defaultsTo: false,
@@ -73,6 +78,7 @@
       printUsage();
       return 0;
     }
+    var codes = args['code'];
 
     var target = _getTarget(args.rest);
     if (!target.existsSync()) {
@@ -121,7 +127,7 @@
       List<SourceFileEdit> edits;
       var pass = 0;
       do {
-        var fixes = await server.requestBulkFixes(fixPath, inTestMode);
+        var fixes = await server.requestBulkFixes(fixPath, inTestMode, codes);
         _mergeDetails(detailsMap, fixes.details);
         edits = fixes.edits;
         _applyEdits(server, edits);
diff --git a/pkg/dartdev/test/commands/fix_test.dart b/pkg/dartdev/test/commands/fix_test.dart
index 8f33a56..09c9cbc 100644
--- a/pkg/dartdev/test/commands/fix_test.dart
+++ b/pkg/dartdev/test/commands/fix_test.dart
@@ -174,6 +174,35 @@
           ]));
     });
 
+    test('--dry-run --code (single)', () async {
+      p = project(
+        mainSrc: '''
+var x = "";
+class A {
+  A a() => new A();
+}
+''',
+        analysisOptions: '''
+linter:
+  rules:
+    - prefer_single_quotes
+    - unnecessary_new
+''',
+      );
+      var result = await runFix(
+          ['--dry-run', '--code', 'prefer_single_quotes', '.'],
+          workingDir: p!.dirPath);
+      expect(result.exitCode, 0);
+      expect(result.stderr, isEmpty);
+      expect(
+          result.stdout,
+          stringContainsInOrder([
+            '1 proposed fix in 1 file.',
+            'lib${Platform.pathSeparator}main.dart',
+            '  prefer_single_quotes $bullet 1 fix',
+          ]));
+    });
+
     test('--apply lib/main.dart', () async {
       p = project(
         mainSrc: '''
@@ -199,6 +228,103 @@
           ]));
     });
 
+    test('--apply --code (single)', () async {
+      p = project(
+        mainSrc: '''
+var x = "";
+class A {
+  A a() => new A();
+}
+''',
+        analysisOptions: '''
+linter:
+  rules:
+    - prefer_single_quotes
+    - unnecessary_new
+''',
+      );
+      var result = await runFix(
+          ['--apply', '--code', 'prefer_single_quotes', '.'],
+          workingDir: p!.dirPath);
+      expect(result.exitCode, 0);
+      expect(result.stderr, isEmpty);
+      expect(
+          result.stdout,
+          stringContainsInOrder([
+            'Applying fixes...',
+            'lib${Platform.pathSeparator}main.dart',
+            '  prefer_single_quotes $bullet 1 fix',
+            '1 fix made in 1 file.',
+          ]));
+    });
+
+    test('--apply --code (multiple)', () async {
+      p = project(
+        mainSrc: '''
+var x = "";
+class A {
+  A a() => new A();
+}
+''',
+        analysisOptions: '''
+linter:
+  rules:
+    - prefer_single_quotes
+    - unnecessary_new
+''',
+      );
+      var result = await runFix([
+        '--apply',
+        '--code',
+        'prefer_single_quotes',
+        '--code',
+        'unnecessary_new',
+        '.'
+      ], workingDir: p!.dirPath);
+      expect(result.exitCode, 0);
+      expect(result.stderr, isEmpty);
+      expect(
+          result.stdout,
+          stringContainsInOrder([
+            'Applying fixes...',
+            'lib${Platform.pathSeparator}main.dart',
+            '  prefer_single_quotes $bullet 1 fix',
+            '  unnecessary_new $bullet 1 fix',
+            '2 fixes made in 1 file.',
+          ]));
+    });
+
+    test('--apply --code (multiple: comma-delimited)', () async {
+      p = project(
+        mainSrc: '''
+var x = "";
+class A {
+  A a() => new A();
+}
+''',
+        analysisOptions: '''
+linter:
+  rules:
+    - prefer_single_quotes
+    - unnecessary_new
+''',
+      );
+      var result = await runFix(
+          ['--apply', '--code=prefer_single_quotes,unnecessary_new', '.'],
+          workingDir: p!.dirPath);
+      expect(result.exitCode, 0);
+      expect(result.stderr, isEmpty);
+      expect(
+          result.stdout,
+          stringContainsInOrder([
+            'Applying fixes...',
+            'lib${Platform.pathSeparator}main.dart',
+            '  prefer_single_quotes $bullet 1 fix',
+            '  unnecessary_new $bullet 1 fix',
+            '2 fixes made in 1 file.',
+          ]));
+    });
+
     test('--apply (.)', () async {
       p = project(
         mainSrc: '''