[dart2js] Add a couple scripts to aid our migration.
These are simple scripts to count progress and find migration candidates.
Change-Id: I872d85891001349dadbcf1d67e64ab5aa993d2a5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/250146
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
Reviewed-by: Mayank Patke <fishythefish@google.com>
diff --git a/pkg/compiler/pubspec.yaml b/pkg/compiler/pubspec.yaml
index ff1ae62..d25829f 100644
--- a/pkg/compiler/pubspec.yaml
+++ b/pkg/compiler/pubspec.yaml
@@ -34,3 +34,4 @@
modular_test: any
sourcemap_testing: any
testing: any
+ vm: any
diff --git a/pkg/compiler/tool/null_safety/candidates.dart b/pkg/compiler/tool/null_safety/candidates.dart
new file mode 100644
index 0000000..6dc4dde
--- /dev/null
+++ b/pkg/compiler/tool/null_safety/candidates.dart
@@ -0,0 +1,152 @@
+// Copyright (c) 2022, 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.
+
+/// Script to identify good opportunities for null safety migration.
+///
+/// This script sorts libraries based on a "migratable" order. We compute
+/// this order by counting how many of a library's dependencies have been
+/// migrated.
+
+import 'dart:io';
+
+import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity;
+import 'package:_fe_analyzer_shared/src/parser/parser.dart';
+import 'package:_fe_analyzer_shared/src/scanner/io.dart'
+ show readBytesFromFileSync;
+import 'package:_fe_analyzer_shared/src/scanner/scanner.dart';
+import 'package:front_end/src/api_prototype/front_end.dart';
+import 'package:front_end/src/api_prototype/language_version.dart';
+import 'package:front_end/src/api_prototype/terminal_color_support.dart'
+ show printDiagnosticMessage;
+import 'package:front_end/src/base/processed_options.dart';
+import 'package:front_end/src/fasta/compiler_context.dart';
+import 'package:front_end/src/fasta/source/diet_parser.dart';
+import 'package:front_end/src/fasta/source/directive_listener.dart';
+import 'package:front_end/src/fasta/uri_translator.dart' show UriTranslator;
+import 'package:kernel/target/targets.dart' show TargetFlags;
+import 'package:vm/target/vm.dart' show VmTarget;
+
+void main(List<String> args) async {
+ var prefix = args.isEmpty ? 'pkg/compiler/' : args.first;
+ var files = <Uri, List<int>>{};
+ var isLegacy = <Uri>{};
+ var isNullSafe = <Uri>{};
+
+ var entryUri = Uri.parse('package:compiler/src/dart2js.dart');
+ var options = CompilerOptions()
+ ..sdkRoot = Uri.base.resolve("sdk/")
+ ..onDiagnostic = _onDiagnosticMessageHandler
+ ..compileSdk = true
+ ..packagesFileUri = Uri.base.resolve('.dart_tool/package_config.json')
+ ..target = VmTarget(TargetFlags());
+ var pOptions = ProcessedOptions(options: options);
+ var uriResolver = await pOptions.getUriTranslator();
+ var context = CompilerContext(pOptions);
+ await context.runInContext((_) async {
+ collectSources(uriResolver, entryUri, files);
+ });
+
+ for (var file in files.keys) {
+ if (await uriUsesLegacyLanguageVersion(file, options)) {
+ isLegacy.add(file);
+ } else {
+ isNullSafe.add(file);
+ }
+ }
+
+ var fileSummary = <Uri, FileData>{};
+ for (var file in files.keys) {
+ if (!file.path.contains(prefix)) continue;
+ var directives = extractDirectiveUris(files[file]!)
+ .map(file.resolve)
+ .where((uri) => uri.path.contains('pkg/compiler/'));
+ var migrated = directives.where(isNullSafe.contains).length;
+ var total = directives.length;
+ fileSummary[file] = FileData(isNullSafe.contains(file), total, migrated);
+ }
+
+ var keys = fileSummary.keys.toList();
+ keys.sort((a, b) {
+ var fa = fileSummary[a]!;
+ var fb = fileSummary[b]!;
+ if (fa.isNullSafe && !fb.isNullSafe) return -1;
+ if (fb.isNullSafe && !fa.isNullSafe) return 1;
+ if (fa.totalDependencies == 0 && fb.totalDependencies != 0) return -1;
+ if (fb.totalDependencies == 0 && fa.totalDependencies != 0) return 1;
+ if (fa.ratio != fb.ratio) return fb.ratio.compareTo(fa.ratio);
+ return fb.migratedDependencies.compareTo(fb.migratedDependencies);
+ });
+
+ for (var file in keys) {
+ var data = fileSummary[file]!;
+ String status;
+ String text = shorten(file);
+ if (data.isNullSafe) {
+ status = '\x1b[33mmigrated ---\x1b[0m | $text';
+ } else if (data.totalDependencies == 0) {
+ status = '\x1b[32mready ---\x1b[0m | $text';
+ } else if (data.ratio == 1.0) {
+ status = '\x1b[32mready 100%\x1b[0m | $text';
+ } else {
+ var perc = (data.ratio * 100).toStringAsFixed(0).padLeft(3);
+ status = '\x1b[31mwait $perc%\x1b[0m'
+ ' | $text [${data.migratedDependencies} / ${data.totalDependencies}]';
+ }
+ print(status);
+ }
+}
+
+class FileData {
+ final bool isNullSafe;
+ final int totalDependencies;
+ final int migratedDependencies;
+
+ double get ratio => migratedDependencies / totalDependencies;
+ FileData(this.isNullSafe, this.totalDependencies, this.migratedDependencies);
+}
+
+void _onDiagnosticMessageHandler(DiagnosticMessage m) {
+ if (m.severity == Severity.internalProblem || m.severity == Severity.error) {
+ printDiagnosticMessage(m, stderr.writeln);
+ exitCode = 1;
+ }
+}
+
+/// Add to [files] all sources reachable from [start].
+void collectSources(
+ UriTranslator uriResolver, Uri start, Map<Uri, List<int>> files) {
+ void helper(Uri uri) {
+ if (uri.scheme == 'dart') return;
+ uri = uriResolver.translate(uri) ?? uri;
+ if (!uri.path.contains('pkg/compiler/')) return;
+ if (files.containsKey(uri)) return;
+ var contents = readBytesFromFileSync(uri);
+ files[uri] = contents;
+ for (var directiveUri in extractDirectiveUris(contents)) {
+ helper(uri.resolve(directiveUri));
+ }
+ }
+
+ helper(start);
+}
+
+/// Parse [contents] as a Dart program and return the URIs that appear in its
+/// import, export, and part directives.
+Set<String> extractDirectiveUris(List<int> contents) {
+ var listener = new DirectiveListener();
+ new TopLevelParser(listener,
+ useImplicitCreationExpression: useImplicitCreationExpressionInCfe)
+ .parseUnit(scan(contents).tokens);
+ // Note: this purposely ignores part files (listener.parts).
+ return new Set<String>()
+ ..addAll(listener.imports.map((directive) => directive.uri!))
+ ..addAll(listener.exports.map((directive) => directive.uri!));
+}
+
+String shorten(Uri uri) {
+ if (uri.scheme != 'file') return uri.toString();
+ final prefix = Uri.base.path;
+ if (uri.path.startsWith(prefix)) return uri.path.substring(prefix.length);
+ return uri.toString();
+}
diff --git a/pkg/compiler/tool/null_safety/tally.dart b/pkg/compiler/tool/null_safety/tally.dart
new file mode 100644
index 0000000..33a2d1a
--- /dev/null
+++ b/pkg/compiler/tool/null_safety/tally.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2022, 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.
+
+/// Script to count progress of the pkg/compiler/lib/ migration
+
+import 'dart:io';
+
+void main(List<String> args) {
+ var path = args.isEmpty ? 'pkg/compiler/lib/' : args.first;
+ var dart2jsDir = Directory.fromUri(Uri.base.resolve(path));
+ var entries = <FileData>[];
+ for (var file in dart2jsDir.listSync(recursive: true)) {
+ if (file is File && file.uri.path.endsWith('.dart')) {
+ entries.add(FileData(file));
+ }
+ }
+
+ var tally = Tally();
+ for (var e in entries) {
+ tally.totalFiles++;
+ tally.totalBytes += e.sizeBytes;
+ tally.totalLOC += e.sizeLOC;
+ if (e.nullSafe) {
+ tally.migratedFiles++;
+ tally.migratedBytes += e.sizeBytes;
+ tally.migratedLOC += e.sizeLOC;
+ }
+ }
+
+ print(tally.formatString());
+ //print(tally.csvRow());
+}
+
+/// Details about each file in the package to properly count migration progress.
+class FileData {
+ final Uri path;
+ final int sizeBytes;
+ final int sizeLOC;
+ final bool nullSafe;
+
+ FileData._(this.path, this.sizeBytes, this.sizeLOC, this.nullSafe);
+
+ factory FileData(File file) {
+ var contents = file.readAsStringSync();
+ var length = contents.length;
+ var sizeLOC = '\n'.allMatches(contents).length;
+ var nullSafe = !contents.contains("// @dart = 2.10");
+ return FileData._(file.uri, length, sizeLOC, nullSafe);
+ }
+}
+
+/// Cumulative information about the status of the null safety migration.
+class Tally {
+ int totalFiles = 0;
+ int migratedFiles = 0;
+ int totalBytes = 0;
+ int migratedBytes = 0;
+ int totalLOC = 0;
+ int migratedLOC = 0;
+
+ /// Emit a readable table representation of the null safety progress.
+ String formatString() {
+ String _pad(String text, int width) {
+ return (' ' * (10 - text.length)) + text;
+ }
+
+ String _row(String name, int a, int b) {
+ var padA = _pad('$a', 10);
+ var padB = _pad('$b', 10);
+ var padC = _pad((a * 100 / b).toStringAsFixed(2), 10);
+ return '${_pad(name, 8)} $padA $padB $padC%';
+ }
+
+ return '${_pad("", 10)} ${_pad("migrated", 10)} ${_pad("total", 10)} ${_pad("%", 10)}\n'
+ '${_row('Files', migratedFiles, totalFiles)}\n'
+ '${_row('Lines', migratedLOC, totalLOC)}\n'
+ '${_row('Bytes', migratedBytes, totalBytes)}';
+ }
+
+ /// Emit a csv representation of the null safety progress, useful to track
+ /// data over time.
+ String csvRow() => [
+ totalFiles,
+ migratedFiles,
+ totalBytes,
+ migratedBytes,
+ totalLOC,
+ migratedLOC,
+ ].join(',');
+}