bulk fix change mapping

Striving to do the "simplest thing that could possibly work" this may just be a conversation starter.


Change-Id: Id3deab6fd1541febbc090c696352563e6b5ce932
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/172460
Commit-Queue: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
index e739602..2e797f0 100644
--- a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
@@ -71,6 +71,7 @@
 import 'package:analyzer/src/error/codes.dart';
 import 'package:analyzer/src/generated/engine.dart' show AnalysisEngine;
 import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/generated/utilities_general.dart';
 import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
 import 'package:analyzer_plugin/utilities/change_builder/conflicting_edit_exception.dart';
 
@@ -351,11 +352,13 @@
   /// diagnostics.
   ChangeBuilder builder;
 
+  /// A map associating libraries to fixes with change counts.
+  final ChangeMap changeMap = ChangeMap();
+
   /// Initialize a newly created processor to create fixes for diagnostics in
   /// libraries in the [workspace].
-  BulkFixProcessor(this.instrumentationService, this.workspace) {
-    builder = ChangeBuilder(workspace: workspace);
-  }
+  BulkFixProcessor(this.instrumentationService, this.workspace)
+      : builder = ChangeBuilder(workspace: workspace);
 
   /// Return a change builder that has been used to create fixes for the
   /// diagnostics in the libraries in the given [contexts].
@@ -431,19 +434,38 @@
       }
     }
 
+    int computeChangeHash() {
+      var hash = 0;
+      var edits = builder.sourceChange.edits;
+      for (var i = 0; i < edits.length; ++i) {
+        hash = JenkinsSmiHash.combine(hash, edits[i].hashCode);
+      }
+      return JenkinsSmiHash.finish(hash);
+    }
+
+    Future<void> generate(CorrectionProducer producer, String code) async {
+      var oldHash = computeChangeHash();
+      await compute(producer);
+      var newHash = computeChangeHash();
+      if (newHash != oldHash) {
+        changeMap.add(result.path, code);
+      }
+    }
+
     var errorCode = diagnostic.errorCode;
     try {
+      var codeName = errorCode.name;
       if (errorCode is LintCode) {
-        var generators = lintProducerMap[errorCode.name];
+        var generators = lintProducerMap[codeName];
         if (generators != null) {
           for (var generator in generators) {
-            await compute(generator());
+            await generate(generator(), codeName);
           }
         }
       } else {
         var generator = nonLintProducerMap[errorCode];
         if (generator != null) {
-          await compute(generator());
+          await generate(generator(), codeName);
         }
         var multiGenerators = nonLintMultiProducerMap[errorCode];
         if (multiGenerators != null) {
@@ -451,7 +473,7 @@
             var multiProducer = multiGenerator();
             multiProducer.configure(context);
             for (var producer in multiProducer.producers) {
-              await compute(producer);
+              await generate(producer, codeName);
             }
           }
         }
@@ -464,3 +486,15 @@
     }
   }
 }
+
+/// Maps changes to library paths.
+class ChangeMap {
+  /// Map of paths to maps of codes to counts.
+  final Map<String, Map<String, int>> libraryMap = {};
+
+  /// Add an entry for the given [code] in the given [libraryPath].
+  void add(String libraryPath, String code) {
+    var changes = libraryMap.putIfAbsent(libraryPath, () => {});
+    changes.update(code, (value) => value + 1, ifAbsent: () => 1);
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/bulk/bulk_fix_processor.dart b/pkg/analysis_server/test/src/services/correction/fix/bulk/bulk_fix_processor.dart
index 3e9e14c..1f45cc4 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/bulk/bulk_fix_processor.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/bulk/bulk_fix_processor.dart
@@ -36,7 +36,7 @@
   }
 
   Future<void> assertHasFix(String expected) async {
-    change = await _computeFixes();
+    change = await _computeSourceChange();
 
     // apply to "file"
     var fileEdits = change.edits;
@@ -48,11 +48,22 @@
   }
 
   Future<void> assertNoFix() async {
-    change = await _computeFixes();
+    change = await _computeSourceChange();
     var fileEdits = change.edits;
     expect(fileEdits, isEmpty);
   }
 
+  /// Computes fixes for the specified [testUnit].
+  Future<BulkFixProcessor> computeFixes() async {
+    var tracker = DeclarationsTracker(MemoryByteStore(), resourceProvider);
+    var analysisContext = contextFor(testFile);
+    tracker.addContext(analysisContext);
+    var processor =
+        BulkFixProcessor(InstrumentationService.NULL_SERVICE, workspace);
+    await processor.fixErrors([analysisContext]);
+    return processor;
+  }
+
   @override
   void setUp() {
     super.setUp();
@@ -60,15 +71,10 @@
     _createAnalysisOptionsFile();
   }
 
-  /// Computes fixes for the given [error] in [testUnit].
-  Future<SourceChange> _computeFixes() async {
-    var tracker = DeclarationsTracker(MemoryByteStore(), resourceProvider);
-    var analysisContext = contextFor(testFile);
-    tracker.addContext(analysisContext);
-    var changeBuilder =
-        await BulkFixProcessor(InstrumentationService.NULL_SERVICE, workspace)
-            .fixErrors([analysisContext]);
-    return changeBuilder.sourceChange;
+  /// Returns the source change for computed fixes in the specified [testUnit].
+  Future<SourceChange> _computeSourceChange() async {
+    var processor = await computeFixes();
+    return processor.builder.sourceChange;
   }
 
   /// Create the analysis options file needed in order to correctly analyze the
diff --git a/pkg/analysis_server/test/src/services/correction/fix/bulk/bulk_fix_processor_test.dart b/pkg/analysis_server/test/src/services/correction/fix/bulk/bulk_fix_processor_test.dart
new file mode 100644
index 0000000..8ac6ee0
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/bulk/bulk_fix_processor_test.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2020, 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/src/services/linter/lint_names.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'bulk_fix_processor.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ChangeMapTest);
+  });
+}
+
+@reflectiveTest
+class ChangeMapTest extends BulkFixProcessorTest {
+  Future<void> test_changeMap() async {
+    createAnalysisOptionsFile(experiments: experiments, lints: [
+      LintNames.annotate_overrides,
+      LintNames.unnecessary_new,
+    ]);
+
+    await resolveTestCode('''
+class A { }
+
+var a = new A();
+var aa = new A();
+''');
+
+    var processor = await computeFixes();
+    var changeMap = processor.changeMap;
+    var errors = changeMap.libraryMap[testFile];
+    expect(errors, hasLength(1));
+    expect(errors[LintNames.unnecessary_new], 2);
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart
index 3b98bfd..14012da 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart
@@ -9,6 +9,7 @@
 import 'add_diagnostic_property_reference_test.dart'
     as add_diagnostic_property_reference;
 import 'add_override_test.dart' as add_override;
+import 'bulk_fix_processor_test.dart' as bulk_fix_processor;
 import 'convert_documentation_into_line_test.dart'
     as convert_documentation_into_line;
 import 'convert_map_from_iterable_to_for_literal_test.dart'
@@ -70,6 +71,7 @@
     add_const.main();
     add_diagnostic_property_reference.main();
     add_override.main();
+    bulk_fix_processor.main();
     convert_documentation_into_line.main();
     convert_map_from_iterable_to_for_literal.main();
     convert_to_contains.main();