Added checks to gather method data

Change-Id: I809e05ea3554ae9768d3f9cb25c824bbe36ca2bb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/203600
Commit-Queue: Gabriel Castro <gabrielmcastro@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
diff --git a/pkg/compiler/tool/kernel_visitor/dart_html_metrics_visitor.dart b/pkg/compiler/tool/kernel_visitor/dart_html_metrics_visitor.dart
index 353d7d1..13cc373 100644
--- a/pkg/compiler/tool/kernel_visitor/dart_html_metrics_visitor.dart
+++ b/pkg/compiler/tool/kernel_visitor/dart_html_metrics_visitor.dart
@@ -1,11 +1,12 @@
 // Copyright (c) 2021, 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 "dart:convert";
 import "dart:io";
 import "package:kernel/kernel.dart";
 import "package:kernel/ast.dart";
 
-main(List<String> args) {
+main(List<String> args) async {
   // Ensure right args are passed.
   if (args.length < 1) {
     print("usage: ${Platform.script} a.dill");
@@ -19,8 +20,8 @@
   // Visit component.
   component.accept(visitor);
 
-  // Print compiled data.
-  print(visitor.classInfo);
+  // Save data to file.
+  visitor.saveDataToFile("dart2html_metrics.json");
 }
 
 /// Visits classes in libraries specified by `libraryFilter`
@@ -55,12 +56,13 @@
 
   @override
   void visitProcedure(Procedure node) {
-    // If this method invokes super, track for class.
-    if (node.containsSuperCalls) {
-      classInfo[currentClass]
-          .methods
-          .add(ClassMetricsMethod(node.name.text, true));
-    }
+    classInfo[currentClass].methods.add(ClassMetricsMethod(
+        node.name.text,
+        node.containsSuperCalls,
+        node.isInstanceMember,
+        node.isExternal,
+        node.isAbstract,
+        node.kind.toString()));
   }
 
   @override
@@ -71,6 +73,11 @@
       currentClass = node.name;
       var metrics = ClassMetrics();
 
+      // Check if class contains native members.
+      if (node.annotations.any(_isNativeMarkerAnnotation)) {
+        metrics.containsNativeMember = true;
+      }
+
       // Check if Mixed.
       if (node.superclass?.isAnonymousMixin ?? false) {
         metrics.mixed = true;
@@ -83,18 +90,26 @@
         metrics.parent = unmangledParent;
       }
 
+      // Check for implemented classes.
+      if (node.implementedTypes.length > 0) {
+        var implementedTypes =
+            node.implementedTypes.map((type) => type.className.asClass.name);
+        metrics.implementedTypes = implementedTypes.toList();
+      }
+
       classInfo[currentClass] = metrics;
+
       super.visitClass(node);
     }
   }
 
   // Returns List of parsed mixins from superclass name.
   List<String> _filterMixins(String superWithMixins) {
-    var start = superWithMixins.indexOf('with') + 4;
+    var start = superWithMixins.indexOf("with") + 4;
     var mixins = superWithMixins.substring(start);
-    mixins = mixins.replaceAll(' ', '');
+    mixins = mixins.replaceAll(" ", "");
 
-    return mixins.split(',');
+    return mixins.split(",");
   }
 
   // Recursively searches superclasses, filtering anonymous mixins,
@@ -107,31 +122,76 @@
     return node.name;
   }
 
-  // Passes through the aggregated data and does post processing,
-  // adding classes that inherit.
+  // Returns true if a class Annotation is Native.
+  bool _isNativeMarkerAnnotation(Expression annotation) {
+    if (annotation is ConstructorInvocation) {
+      var type = annotation.constructedType;
+      if (type.classNode.name == "Native") {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // Passes through the aggregated data and processes,
+  // adding child classes and overridden methods from parent.
   void _processData() {
     classInfo.keys.forEach((className) {
       var parentName = classInfo[className].parent;
-
       if (classInfo[parentName] != null) {
         classInfo[parentName].inheritedBy.add(className);
+
+        var notOverridden = <String>[];
+        var parentMethods = classInfo[parentName].methods.map((m) => m.name);
+        var classMethods = classInfo[className].methods.map((m) => m.name);
+
+        parentMethods.forEach((method) =>
+            {if (!classMethods.contains(method)) notOverridden.add(method)});
+
+        // Update Method Info.
+        classInfo[className].notOverriddenMethods = notOverridden;
       }
     });
   }
+
+  // Saves the data to file.
+  void saveDataToFile(String filename) {
+    var formatted = jsonFormat(classInfo);
+
+    File(filename).writeAsStringSync(formatted);
+  }
+
+  // Converts the passed Map to a pretty print JSON string.
+  String jsonFormat(Map<String, ClassMetrics> info) {
+    JsonEncoder encoder = new JsonEncoder.withIndent("  ");
+    return encoder.convert(info);
+  }
 }
 
 /// Tracks info compiled for a class.
 class ClassMetrics {
   List<ClassMetricsMethod> methods;
   List<String> mixins;
+  List<String> implementedTypes;
+  List<String> notOverriddenMethods;
   List<String> inheritedBy;
   String parent;
   bool mixed;
+  bool containsNativeMember;
 
   ClassMetrics(
-      {this.parent, this.mixed = false, mixins, methods, inheritedBy}) {
-    this.mixins = mixins ?? [];
+      {this.mixed = false,
+      this.containsNativeMember = false,
+      this.parent,
+      methods,
+      mixins,
+      notOverridden,
+      implementedTypes,
+      inheritedBy}) {
     this.methods = methods ?? [];
+    this.mixins = mixins ?? [];
+    this.notOverriddenMethods = notOverridden ?? [];
+    this.implementedTypes = implementedTypes ?? [];
     this.inheritedBy = inheritedBy ?? [];
   }
 
@@ -151,6 +211,9 @@
       "mixins": mixins,
       "parent": parent,
       "inheritedBy": inheritedBy,
+      "containsNativeMember": containsNativeMember,
+      "notOverriddenMethods": notOverriddenMethods,
+      "implementedTypes": implementedTypes
     };
   }
 }
@@ -158,11 +221,27 @@
 /// Tracks info related to a specific method.
 class ClassMetricsMethod {
   String name;
+  String methodKind;
   bool invokesSuper;
+  bool isInstanceMember;
+  bool isExternal;
+  bool isAbstract;
 
-  ClassMetricsMethod(this.name, [this.invokesSuper = false]);
+  ClassMetricsMethod(this.name,
+      [this.invokesSuper = false,
+      this.isInstanceMember = false,
+      this.isExternal = false,
+      this.isAbstract = false,
+      this.methodKind = ""]);
 
   Map<String, dynamic> toJson() {
-    return {"name": name, "invokesSuper": invokesSuper};
+    return {
+      "name": name,
+      "invokesSuper": invokesSuper,
+      "isInstanceMember": isInstanceMember,
+      "isExternal": isExternal,
+      "isAbstract": isAbstract,
+      "methodKind": methodKind
+    };
   }
 }
diff --git a/pkg/compiler/tool/kernel_visitor/test/info_visitor_test.dart b/pkg/compiler/tool/kernel_visitor/test/info_visitor_test.dart
index 9b20b89..1e24648 100644
--- a/pkg/compiler/tool/kernel_visitor/test/info_visitor_test.dart
+++ b/pkg/compiler/tool/kernel_visitor/test/info_visitor_test.dart
@@ -84,4 +84,13 @@
     Expect.equals(visitor.classInfo["F"].mixed, true);
     Expect.deepEquals(visitor.classInfo["F"].mixins, ["Mix1", "Mix2"]);
   });
+
+  test("Class E implements A", () {
+    Expect.equals(visitor.classInfo["E"].implementedTypes.contains("A"), true);
+  });
+
+  test("Class G extends A but fails to override getValue()", () {
+    Expect.equals(
+        visitor.classInfo["G"].notOverriddenMethods.contains("getValue"), true);
+  });
 }
diff --git a/pkg/compiler/tool/kernel_visitor/test/test_classes.dart b/pkg/compiler/tool/kernel_visitor/test/test_classes.dart
index 226aa2d..2b02ef8 100644
--- a/pkg/compiler/tool/kernel_visitor/test/test_classes.dart
+++ b/pkg/compiler/tool/kernel_visitor/test/test_classes.dart
@@ -43,3 +43,18 @@
 class F extends B with Mix1, Mix2 {
   F();
 }
+
+// Test class with interface
+class E implements A {
+  E();
+
+  @override
+  getValue() {
+    return "E Value";
+  }
+}
+
+// Test class with unoverriden superclass method
+class G extends A {
+  G();
+}