Version 2.17.0-162.0.dev

Merge commit 'ec829affeaf40df7ed4613904cb324acdd11f3e9' into 'dev'
diff --git a/pkg/analyzer/lib/src/summary2/ast_text_printer.dart b/pkg/analyzer/lib/src/summary2/ast_text_printer.dart
index aff9c4e..7f45994 100644
--- a/pkg/analyzer/lib/src/summary2/ast_text_printer.dart
+++ b/pkg/analyzer/lib/src/summary2/ast_text_printer.dart
@@ -242,6 +242,12 @@
   }
 
   @override
+  void visitConstructorSelector(ConstructorSelector node) {
+    _token(node.period);
+    node.name.accept(this);
+  }
+
+  @override
   void visitContinueStatement(ContinueStatement node) {
     _token(node.continueKeyword);
     node.label?.accept(this);
@@ -296,9 +302,17 @@
   }
 
   @override
+  void visitEnumConstantArguments(EnumConstantArguments node) {
+    node.typeArguments?.accept(this);
+    node.constructorSelector?.accept(this);
+    node.argumentList.accept(this);
+  }
+
+  @override
   void visitEnumConstantDeclaration(EnumConstantDeclaration node) {
     _declaration(node);
     node.name.accept(this);
+    node.arguments?.accept(this);
   }
 
   @override
@@ -306,8 +320,13 @@
     _compilationUnitMember(node);
     _token(node.enumKeyword);
     node.name.accept(this);
+    node.typeParameters?.accept(this);
+    node.withClause?.accept(this);
+    node.implementsClause?.accept(this);
     _token(node.leftBracket);
-    _nodeList(node.constants, node.rightBracket);
+    _nodeList(node.constants, node.semicolon ?? node.rightBracket);
+    _token(node.semicolon);
+    node.members.accept(this);
     _token(node.rightBracket);
   }
 
@@ -877,6 +896,18 @@
   }
 
   @override
+  void visitSuperFormalParameter(SuperFormalParameter node) {
+    _normalFormalParameter(node);
+    _token(node.keyword);
+    node.type?.accept(this);
+    _token(node.superKeyword);
+    _token(node.period);
+    node.identifier.accept(this);
+    node.typeParameters?.accept(this);
+    node.parameters?.accept(this);
+  }
+
+  @override
   void visitSwitchCase(SwitchCase node) {
     _nodeList(node.labels);
     _token(node.keyword);
diff --git a/pkg/analyzer/test/generated/error_parser_test.dart b/pkg/analyzer/test/generated/error_parser_test.dart
index 7073755..139bb79 100644
--- a/pkg/analyzer/test/generated/error_parser_test.dart
+++ b/pkg/analyzer/test/generated/error_parser_test.dart
@@ -11,6 +11,7 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import '../util/ast_type_matchers.dart';
+import '../util/feature_sets.dart';
 import 'parser_test_base.dart';
 
 main() {
@@ -2431,7 +2432,7 @@
   }
 
   void test_positionalAfterNamedArgument() {
-    createParser('(x: 1, 2)');
+    createParser('(x: 1, 2)', featureSet: FeatureSets.language_2_16);
     ArgumentList list = parser.parseArgumentList();
     expectNotNullIfNoErrors(list);
     listener.assertErrors(
diff --git a/pkg/analyzer/test/util/feature_sets.dart b/pkg/analyzer/test/util/feature_sets.dart
index 34ff0eb..85a4cd9 100644
--- a/pkg/analyzer/test/util/feature_sets.dart
+++ b/pkg/analyzer/test/util/feature_sets.dart
@@ -27,6 +27,11 @@
     flags: [],
   );
 
+  static final FeatureSet language_2_16 = FeatureSet.fromEnableFlags2(
+    sdkLanguageVersion: Version.parse('2.16.0'),
+    flags: [],
+  );
+
   static final FeatureSet latest = FeatureSet.latestLanguageVersion();
 
   static final FeatureSet latestWithExperiments = FeatureSet.fromEnableFlags2(
diff --git a/pkg/dart2js_info/README.md b/pkg/dart2js_info/README.md
index 5f05010..0f59cc2 100644
--- a/pkg/dart2js_info/README.md
+++ b/pkg/dart2js_info/README.md
@@ -51,8 +51,7 @@
 
 ## Format
 
-There are several formats of info files. Dart2js today produces a JSON format,
-but very soon will switch to produce a binary format by default.
+Dart2js info files are produced in either a binary or JSON format.
 
 ## Info API
 
@@ -502,7 +501,7 @@
 `myMethodName`, you will save at least that 13.97%, and possibly some more from
 the reachable size, but how much of that we are not certain.
 
-### Coverage tools
+### Coverage Server Analysis
 
 Coverage information requires a bit more setup and work to get them running. The
 steps are as follows:
@@ -539,6 +538,42 @@
 $ dart2js_info coverage_analysis main.dart.info.data main.dart.coverage.json
 ```
 
+### Runtime Code Analysis
+
+Runtime code analysis requires both an info file and a runtime data file. 
+
+The info file is emitted by compiling a dart2js app with `--dump-info`:
+
+```console
+$ dart2js --dump-info main.dart
+```
+
+Enable the collection of runtime data by compiling a dart2js app with an
+experimental flag:
+
+```console
+$ dart2js --experimental-track-allocations main.dart
+```
+
+After using your app (manually or via integration tests), dump the top-level
+window object below to a text file:
+
+```javascript
+JSON.stringify($__dart_deferred_initializers__.allocations)
+```
+
+Finally run this tool:
+
+```console
+$ dart2js_info runtime_coverage main.dart.info.data main.runtime.data.txt
+```
+
+And with the following to view package-level information:
+
+```console
+$ dart2js_info runtime_coverage --show-packages main.dart.info.data main.runtime.data.txt
+```
+
 ## Code location, features and bugs
 
 This package is developed in [github][repo].  Please file feature requests and
diff --git a/pkg/dart2js_info/bin/src/runtime_coverage_analysis.dart b/pkg/dart2js_info/bin/src/runtime_coverage_analysis.dart
index 577aee6..c2fa5f6 100644
--- a/pkg/dart2js_info/bin/src/runtime_coverage_analysis.dart
+++ b/pkg/dart2js_info/bin/src/runtime_coverage_analysis.dart
@@ -21,7 +21,8 @@
 ///     This can be combined with the --dump-info step above.
 ///
 ///   * Load your app, exercise your code, then extract the runtime code
-///     coverage JSON blob by querying `$__dart_deferred_initializers__.allocations` in the page.
+///     coverage JSON blob by querying
+///     `$__dart_deferred_initializers__.allocations` in the page.
 ///
 ///   * Finally, run this tool.
 library compiler.tool.runtime_coverage_analysis;
@@ -45,7 +46,10 @@
   @override
   final String description = "Analyze runtime coverage data";
 
-  RuntimeCoverageAnalysisCommand();
+  RuntimeCoverageAnalysisCommand() {
+    argParser.addFlag('show-packages',
+        defaultsTo: false, help: "Show coverage details at the package level.");
+  }
 
   @override
   void run() async {
@@ -53,16 +57,23 @@
     if (args.length < 2) {
       usageException('Missing arguments, expected: info.data coverage.json');
     }
-    await _runtimeCoverageAnalysis(args[0], args[1]);
+    var showPackages = argResults['show-packages'];
+    if (showPackages) {
+      await _reportWithPackages(args[0], args[1]);
+    } else {
+      await _report(args[0], args[1]);
+    }
   }
 }
 
-Future<void> _runtimeCoverageAnalysis(infoFile, coverageFile) async {
+Future<void> _report(
+  String infoFile,
+  String coverageFile,
+) async {
   var info = await infoFromFile(infoFile);
   var coverageRaw = jsonDecode(File(coverageFile).readAsStringSync());
-  var coverage = <String, bool>{};
-  coverageRaw
-      .forEach((k, v) => coverage[k] = coverage[k] ?? false || v as bool);
+  // The value associated with each coverage item isn't used for now.
+  var coverage = coverageRaw.keys.toSet();
 
   int totalProgramSize = info.program.size;
   int totalLibSize = info.libraries.fold(0, (n, lib) => n + lib.size);
@@ -74,9 +85,8 @@
   void tallyCode(Info i) {
     totalCode += i.size;
     var name = qualifiedName(i);
-    var used = coverage[name];
-
-    if (used != null) {
+    var used = coverage.contains(name);
+    if (used) {
       usedCode += i.size;
     } else {
       unused.add(i);
@@ -102,8 +112,6 @@
   print('');
   var unusedTotal = totalCode - usedCode;
   _section('Runtime Coverage Breakdown', size: unusedTotal);
-
-  // TODO(markzipan): support grouping results by package/library.
   for (int i = 0; i < unused.length; i++) {
     var item = unused.removeFirst();
     var percent = (item.size * 100 / unusedTotal).toStringAsFixed(2);
@@ -111,6 +119,95 @@
   }
 }
 
+Future<void> _reportWithPackages(
+  String infoFile,
+  String coverageFile,
+) async {
+  var info = await infoFromFile(infoFile);
+  var coverageRaw = jsonDecode(File(coverageFile).readAsStringSync());
+  // The value associated with each coverage item isn't used for now.
+  var coverage = coverageRaw.keys.toSet();
+
+  int totalProgramSize = info.program.size;
+  int totalLibSize = info.libraries.fold(0, (n, lib) => n + lib.size);
+
+  int totalCode = 0;
+  int usedCode = 0;
+  var packageData = <String, PackageInfo>{};
+  var unused = PriorityQueue<Info>((a, b) => b.size.compareTo(a.size));
+
+  void tallyCode(Info i) {
+    totalCode += i.size;
+    var name = qualifiedName(i);
+    var used = coverage.contains(name);
+
+    var package = packageNameOrScheme(i);
+    packageData.putIfAbsent(package, () => PackageInfo());
+    packageData[package].add(i, used: used);
+
+    if (used) {
+      usedCode += i.size;
+    } else {
+      unused.add(i);
+    }
+  }
+
+  info.classes.forEach(tallyCode);
+  info.closures.forEach(tallyCode);
+
+  _section('Runtime Coverage Summary');
+  _showHeader('', 'bytes', '%');
+  _show('Program size', totalProgramSize, totalProgramSize);
+  _show('Libraries (excluding statics)', totalLibSize, totalProgramSize);
+  _show('Code (classes + closures)', totalCode, totalProgramSize);
+  _show('Used', usedCode, totalProgramSize);
+
+  print('');
+  _showHeader('', 'count', '%');
+  var total = info.classes.length + info.closures.length;
+  _show('Classes + closures', total, total);
+  _show('Used', total - unused.length, total);
+
+  print('');
+  var unusedTotal = totalCode - usedCode;
+  _section('Runtime Coverage Breakdown (packages)', size: unusedTotal);
+  for (var entry in packageData.entries.sortedBy((e) => -e.value.unusedSize)) {
+    var packageLabel = entry.key;
+    var packageInfo = entry.value;
+
+    print(' $packageLabel (${packageInfo.unusedSize} bytes unused)');
+
+    var packageRatioString = (packageInfo.usedRatio * 100).toStringAsFixed(2);
+    _leftPadded(
+        '  proportion of package used:',
+        '${packageInfo.usedSize}/${packageInfo.totalSize} '
+            '($packageRatioString%)');
+
+    var codeRatioString =
+        (packageInfo.unusedSize / totalCode * 100).toStringAsFixed(2);
+    _leftPadded('  proportion of unused code to all code:',
+        '${packageInfo.unusedSize}/$totalCode ($codeRatioString%)');
+
+    var unusedCodeRatioString =
+        (packageInfo.unusedSize / unusedTotal * 100).toStringAsFixed(2);
+    _leftPadded('  proportion of unused code to all unused code:',
+        '${packageInfo.unusedSize}/$unusedTotal ($unusedCodeRatioString%)');
+
+    _leftPadded('  package breakdown: ', '');
+    for (var item in packageInfo.elements.toList()) {
+      var percent =
+          (item.size * 100 / packageInfo.totalSize).toStringAsFixed(2);
+      var name = qualifiedName(item);
+      var used = coverage.contains(name);
+      var usedNotch = used ? '+' : '-';
+      _leftPadded('    $usedNotch ${qualifiedName(item)}:',
+          '${item.size} bytes ($percent% of package)');
+    }
+
+    print('');
+  }
+}
+
 void _section(String title, {int size}) {
   if (size == null) {
     print(title);
@@ -128,3 +225,28 @@
   var percent = (size * 100 / total).toStringAsFixed(2);
   print(' ${pad(msg, 30, right: true)} ${pad(size, 8)} ${pad(percent, 6)}%');
 }
+
+_leftPadded(String msg1, String msg2) {
+  print(' ${pad(msg1, 50, right: true)} $msg2');
+}
+
+class PackageInfo {
+  final elements = PriorityQueue<Info>((a, b) => b.size.compareTo(a.size));
+  num totalSize = 0;
+  num usedSize = 0;
+  num unusedSize = 0;
+  num usedRatio = 0.0;
+
+  PackageInfo();
+
+  void add(Info i, {bool used = true}) {
+    totalSize += i.size;
+    if (used) {
+      usedSize += i.size;
+    } else {
+      unusedSize += i.size;
+    }
+    elements.add(i);
+    usedRatio = usedSize / totalSize;
+  }
+}
diff --git a/pkg/dart2js_info/lib/src/util.dart b/pkg/dart2js_info/lib/src/util.dart
index 874202c..5478ec8 100644
--- a/pkg/dart2js_info/lib/src/util.dart
+++ b/pkg/dart2js_info/lib/src/util.dart
@@ -123,6 +123,23 @@
   return null;
 }
 
+/// Provides the package name associated with [info], the URI scheme if
+/// available, or null otherwise.
+String packageNameOrScheme(Info info) {
+  while (info.parent != null) {
+    info = info.parent;
+  }
+  if (info is LibraryInfo) {
+    if (info.uri.isScheme('package')) {
+      return '${info.uri}'.split('/').first;
+    }
+    if (info.uri.hasScheme) {
+      return info.uri.scheme;
+    }
+  }
+  return null;
+}
+
 /// Produce a string containing [value] padded with white space up to [n] chars.
 pad(value, n, {bool right = false}) {
   var s = '$value';
diff --git a/tools/VERSION b/tools/VERSION
index d69f4db..ae3ec1b 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 17
 PATCH 0
-PRERELEASE 161
+PRERELEASE 162
 PRERELEASE_PATCH 0
\ No newline at end of file