Add "convert to int literal" to dartfix

This updates the analysis server edit.dartfix request
to find and convert double literals to int literals.
Under the covers it uses the prefer_int_literal lint
and gracefully degrades if the lint is not found
(e.g. third_party/linter needs to be rolled).

Change-Id: I0236af9c19667e543541fef18836bfeeba8c67d7
Reviewed-on: https://dart-review.googlesource.com/c/81302
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
diff --git a/pkg/analysis_server/lib/src/edit/edit_dartfix.dart b/pkg/analysis_server/lib/src/edit/edit_dartfix.dart
index 8821d75..3a8e344 100644
--- a/pkg/analysis_server/lib/src/edit/edit_dartfix.dart
+++ b/pkg/analysis_server/lib/src/edit/edit_dartfix.dart
@@ -2,14 +2,13 @@
 // 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:analysis_server/plugin/edit/assist/assist_core.dart';
 import 'package:analysis_server/plugin/edit/assist/assist_dart.dart';
 import 'package:analysis_server/plugin/edit/fix/fix_core.dart';
 import 'package:analysis_server/protocol/protocol.dart';
 import 'package:analysis_server/protocol/protocol_generated.dart';
 import 'package:analysis_server/src/analysis_server.dart';
-import 'package:analysis_server/src/services/correction/assist.dart';
-import 'package:analysis_server/src/services/correction/assist_internal.dart';
+import 'package:analysis_server/src/edit/fix/prefer_int_literals_fix.dart';
+import 'package:analysis_server/src/edit/fix/prefer_mixin_fix.dart';
 import 'package:analysis_server/src/services/correction/fix.dart';
 import 'package:analysis_server/src/services/correction/fix_internal.dart';
 import 'package:analyzer/analyzer.dart';
@@ -78,30 +77,36 @@
     }
 
     // Get the desired lints
-    final LintRule preferMixin = Registry.ruleRegistry['prefer_mixin'];
-    if (preferMixin == null) {
-      return new Response.serverError(
-          request, 'Missing prefer_mixin lint', null);
-    }
+    final lintRules = Registry.ruleRegistry;
+
+    final preferMixin = lintRules['prefer_mixin'];
     final preferMixinFix = new PreferMixinFix(this);
     preferMixin.reporter = preferMixinFix;
 
+    final preferIntLiterals = lintRules['prefer_int_literals'];
+    final preferIntLiteralsFix = new PreferIntLiteralsFix(this);
+    preferIntLiterals?.reporter = preferIntLiteralsFix;
+
     // Setup
     final linters = <Linter>[
       preferMixin,
+      preferIntLiterals,
     ];
     final fixes = <LinterFix>[
       preferMixinFix,
+      preferIntLiteralsFix,
     ];
     final visitors = <AstVisitor>[];
     final registry = new NodeLintRegistry(false);
     for (Linter linter in linters) {
-      final visitor = linter.getVisitor();
-      if (visitor != null) {
-        visitors.add(visitor);
-      }
-      if (linter is NodeLintRule) {
-        (linter as NodeLintRule).registerNodeProcessors(registry);
+      if (linter != null) {
+        final visitor = linter.getVisitor();
+        if (visitor != null) {
+          visitors.add(visitor);
+        }
+        if (linter is NodeLintRule) {
+          (linter as NodeLintRule).registerNodeProcessors(registry);
+        }
       }
     }
     final AstVisitor astVisitor = visitors.isNotEmpty
@@ -149,23 +154,36 @@
         }
         Source source = result.sourceFactory.forUri2(result.uri);
         for (Linter linter in linters) {
-          linter.reporter.source = source;
+          if (linter != null) {
+            linter.reporter.source = source;
+          }
         }
         if (astVisitor != null) {
           unit.accept(astVisitor);
         }
         unit.accept(linterVisitor);
+        for (LinterFix fix in fixes) {
+          await fix.applyLocalFixes(result);
+        }
       }
     }
 
     // Cleanup
     for (Linter linter in linters) {
-      linter.reporter = null;
+      if (linter != null) {
+        linter.reporter.source = null;
+        linter.reporter = null;
+      }
     }
 
     // Apply distributed fixes
+    if (preferIntLiterals == null) {
+      // TODO(danrubel): Remove this once linter rolled into sdk/third_party.
+      addRecommendation('*** Convert double literal not available'
+          ' because prefer_int_literal not found. May need to roll linter');
+    }
     for (LinterFix fix in fixes) {
-      await fix.applyFix();
+      await fix.applyRemainingFixes();
     }
 
     return new EditDartfixResult(descriptionOfFixes, otherRecommendations,
@@ -279,7 +297,11 @@
 
   LinterFix(this.dartFix);
 
-  Future<void> applyFix();
+  /// Apply fixes for the current compilation unit.
+  Future<void> applyLocalFixes(AnalysisResult result);
+
+  /// Apply any fixes remaining after analysis is complete.
+  Future<void> applyRemainingFixes();
 
   @override
   void reportError(AnalysisError error) {
@@ -328,57 +350,3 @@
     // ignored
   }
 }
-
-class PreferMixinFix extends LinterFix {
-  final classesToConvert = new Set<Element>();
-
-  PreferMixinFix(EditDartFix dartFix) : super(dartFix);
-
-  @override
-  Future<void> applyFix() async {
-    for (Element elem in classesToConvert) {
-      await convertClassToMixin(elem);
-    }
-  }
-
-  Future<void> convertClassToMixin(Element elem) async {
-    AnalysisResult result =
-        await dartFix.server.getAnalysisResult(elem.source?.fullName);
-
-    for (CompilationUnitMember declaration in result.unit.declarations) {
-      if (declaration is ClassOrMixinDeclaration &&
-          declaration.name.name == elem.name) {
-        AssistProcessor processor = new AssistProcessor(
-            new EditDartFixAssistContext(
-                dartFix, elem.source, result.unit, declaration.name));
-        List<Assist> assists = await processor
-            .computeAssist(DartAssistKind.CONVERT_CLASS_TO_MIXIN);
-        final location = dartFix.locationDescription(result, elem.nameOffset);
-        if (assists.isNotEmpty) {
-          for (Assist assist in assists) {
-            dartFix.addFix(
-                'Convert ${elem.displayName} to a mixin in $location',
-                assist.change);
-          }
-        } else {
-          // TODO(danrubel): If assists is empty, then determine why
-          // assist could not be performed and report that in the description.
-          dartFix.addRecommendation(
-              'Could not convert ${elem.displayName} to a mixin'
-              ' because the class contains a constructor in $location');
-        }
-      }
-    }
-  }
-
-  @override
-  void reportErrorForNode(ErrorCode errorCode, AstNode node,
-      [List<Object> arguments]) {
-    TypeName type = node;
-    Element element = type.name.staticElement;
-    String filePath = element.source?.fullName;
-    if (filePath != null && dartFix.isIncluded(filePath)) {
-      classesToConvert.add(element);
-    }
-  }
-}
diff --git a/pkg/analysis_server/lib/src/edit/fix/prefer_int_literals_fix.dart b/pkg/analysis_server/lib/src/edit/fix/prefer_int_literals_fix.dart
new file mode 100644
index 0000000..81758f9
--- /dev/null
+++ b/pkg/analysis_server/lib/src/edit/fix/prefer_int_literals_fix.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2018, 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:analysis_server/plugin/edit/assist/assist_core.dart';
+import 'package:analysis_server/src/edit/edit_dartfix.dart';
+import 'package:analysis_server/src/services/correction/assist.dart';
+import 'package:analysis_server/src/services/correction/assist_internal.dart';
+import 'package:analyzer/analyzer.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/src/dart/analysis/driver.dart';
+
+class PreferIntLiteralsFix extends LinterFix {
+  final literalsToConvert = <DoubleLiteral>[];
+
+  PreferIntLiteralsFix(EditDartFix dartFix) : super(dartFix);
+
+  @override
+  Future<void> applyLocalFixes(AnalysisResult result) async {
+    while (literalsToConvert.isNotEmpty) {
+      DoubleLiteral literal = literalsToConvert.removeLast();
+      AssistProcessor processor = new AssistProcessor(
+          new EditDartFixAssistContext(dartFix, source, result.unit, literal));
+      List<Assist> assists =
+          await processor.computeAssist(DartAssistKind.CONVERT_TO_INT_LITERAL);
+      final location = dartFix.locationDescription(result, literal.offset);
+      if (assists.isNotEmpty) {
+        for (Assist assist in assists) {
+          dartFix.addFix(
+              'Replace a double literal with an int literal in $location',
+              assist.change);
+        }
+      } else {
+        // TODO(danrubel): If assists is empty, then determine why
+        // assist could not be performed and report that in the description.
+        dartFix.addRecommendation('Could not replace'
+            ' a double literal with an int literal in $location');
+      }
+    }
+  }
+
+  @override
+  Future<void> applyRemainingFixes() {
+    // All fixes applied in [applyLocalFixes]
+    return null;
+  }
+
+  @override
+  void reportErrorForNode(ErrorCode errorCode, AstNode node,
+      [List<Object> arguments]) {
+    String filePath = source.fullName;
+    if (filePath != null && dartFix.isIncluded(filePath)) {
+      literalsToConvert.add(node);
+    }
+  }
+}
diff --git a/pkg/analysis_server/lib/src/edit/fix/prefer_mixin_fix.dart b/pkg/analysis_server/lib/src/edit/fix/prefer_mixin_fix.dart
new file mode 100644
index 0000000..5e2aad1
--- /dev/null
+++ b/pkg/analysis_server/lib/src/edit/fix/prefer_mixin_fix.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2018, 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:analysis_server/plugin/edit/assist/assist_core.dart';
+import 'package:analysis_server/src/edit/edit_dartfix.dart';
+import 'package:analysis_server/src/services/correction/assist.dart';
+import 'package:analysis_server/src/services/correction/assist_internal.dart';
+import 'package:analyzer/analyzer.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/analysis/driver.dart';
+
+class PreferMixinFix extends LinterFix {
+  final classesToConvert = new Set<Element>();
+
+  PreferMixinFix(EditDartFix dartFix) : super(dartFix);
+
+  @override
+  Future<void> applyLocalFixes(AnalysisResult result) {
+    // All fixes applied in [applyRemainingFixes]
+    return null;
+  }
+
+  @override
+  Future<void> applyRemainingFixes() async {
+    for (Element elem in classesToConvert) {
+      await convertClassToMixin(elem);
+    }
+  }
+
+  Future<void> convertClassToMixin(Element elem) async {
+    AnalysisResult result =
+        await dartFix.server.getAnalysisResult(elem.source?.fullName);
+
+    for (CompilationUnitMember declaration in result.unit.declarations) {
+      if (declaration is ClassOrMixinDeclaration &&
+          declaration.name.name == elem.name) {
+        AssistProcessor processor = new AssistProcessor(
+            new EditDartFixAssistContext(
+                dartFix, elem.source, result.unit, declaration.name));
+        List<Assist> assists = await processor
+            .computeAssist(DartAssistKind.CONVERT_CLASS_TO_MIXIN);
+        final location = dartFix.locationDescription(result, elem.nameOffset);
+        if (assists.isNotEmpty) {
+          for (Assist assist in assists) {
+            dartFix.addFix(
+                'Convert ${elem.displayName} to a mixin in $location',
+                assist.change);
+          }
+        } else {
+          // TODO(danrubel): If assists is empty, then determine why
+          // assist could not be performed and report that in the description.
+          dartFix.addRecommendation(
+              'Could not convert ${elem.displayName} to a mixin'
+              ' because the class contains a constructor in $location');
+        }
+      }
+    }
+  }
+
+  @override
+  void reportErrorForNode(ErrorCode errorCode, AstNode node,
+      [List<Object> arguments]) {
+    TypeName type = node;
+    Element element = type.name.staticElement;
+    String filePath = element.source?.fullName;
+    if (filePath != null && dartFix.isIncluded(filePath)) {
+      classesToConvert.add(element);
+    }
+  }
+}