mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
eff9c288a4
Change-Id: I69681f299698458583bde63c853ead63a4cd3641 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/234665 Reviewed-by: Samuel Rawlins <srawlins@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
359 lines
12 KiB
Dart
359 lines
12 KiB
Dart
// Copyright (c) 2019, 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.
|
|
|
|
// This is a hacked-together client of the NNBD migration API, intended for
|
|
// early testing of the migration process. It runs a small hardcoded set of
|
|
// packages through the migration engine and outputs statistics about the
|
|
// result of migration, as well as categories (and counts) of exceptions that
|
|
// occurred.
|
|
|
|
import 'dart:io';
|
|
|
|
import 'package:analyzer/dart/analysis/results.dart';
|
|
import 'package:analyzer/dart/ast/ast.dart';
|
|
import 'package:analyzer/diagnostic/diagnostic.dart';
|
|
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
|
|
import 'package:analyzer/src/generated/source.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
import 'package:args/args.dart';
|
|
import 'package:nnbd_migration/nnbd_migration.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
import 'src/package.dart';
|
|
|
|
void main(List<String> args) async {
|
|
ArgResults parsedArgs = parseArguments(args)!;
|
|
|
|
Sdk sdk = Sdk(parsedArgs['sdk'] as String);
|
|
|
|
warnOnNoAssertions();
|
|
|
|
Playground playground =
|
|
Playground(defaultPlaygroundPath, parsedArgs['clean'] as bool);
|
|
|
|
List<Package> packages = [
|
|
for (String package in parsedArgs['packages'] as Iterable<String>)
|
|
SdkPackage(package),
|
|
for (String package in parsedArgs['manual_packages'] as Iterable<String>)
|
|
ManualPackage(package),
|
|
];
|
|
|
|
var packageNames = parsedArgs['git_packages'] as Iterable<String>;
|
|
await Future.wait(packageNames.map((n) async => packages.add(
|
|
await GitPackage.gitPackageFactory(
|
|
n, playground, parsedArgs['update'] as bool?))));
|
|
|
|
String? categoryOfInterest =
|
|
parsedArgs.rest.isEmpty ? null : parsedArgs.rest.single;
|
|
|
|
var listener = _Listener(categoryOfInterest,
|
|
printExceptionNodeOnly: parsedArgs['exception_node_only'] as bool?);
|
|
assert(listener.numExceptions == 0);
|
|
var overallStartTime = DateTime.now();
|
|
for (var package in packages) {
|
|
print('Migrating $package');
|
|
var startTime = DateTime.now();
|
|
listener.currentPackage = package.name;
|
|
var contextCollection = AnalysisContextCollectionImpl(
|
|
includedPaths: package.migrationPaths as List<String>,
|
|
sdkPath: sdk.sdkPath);
|
|
|
|
var files = <String>{};
|
|
var previousExceptionCount = listener.numExceptions;
|
|
for (var context in contextCollection.contexts) {
|
|
var localFiles =
|
|
context.contextRoot.analyzedFiles().where((s) => s.endsWith('.dart'));
|
|
files.addAll(localFiles);
|
|
var session = context.currentSession;
|
|
var migration = NullabilityMigration(listener, permissive: true);
|
|
for (var file in localFiles) {
|
|
var resolvedUnit =
|
|
await session.getResolvedUnit(file) as ResolvedUnitResult;
|
|
if (!resolvedUnit.errors.any((e) => e.severity == Severity.error)) {
|
|
migration.prepareInput(resolvedUnit);
|
|
} else {
|
|
print(' Skipping $file; it has errors.');
|
|
}
|
|
}
|
|
for (var file in localFiles) {
|
|
var resolvedUnit =
|
|
await session.getResolvedUnit(file) as ResolvedUnitResult;
|
|
if (!resolvedUnit.errors.any((e) => e.severity == Severity.error)) {
|
|
migration.processInput(resolvedUnit);
|
|
}
|
|
}
|
|
for (var file in localFiles) {
|
|
var resolvedUnit =
|
|
await session.getResolvedUnit(file) as ResolvedUnitResult;
|
|
if (!resolvedUnit.errors.any((e) => e.severity == Severity.error)) {
|
|
migration.finalizeInput(resolvedUnit);
|
|
}
|
|
}
|
|
migration.finish();
|
|
}
|
|
|
|
var endTime = DateTime.now();
|
|
print(' Migrated $package in ${endTime.difference(startTime).inSeconds} '
|
|
'seconds');
|
|
print(' ${files.length} files found');
|
|
var exceptionCount = listener.numExceptions - previousExceptionCount;
|
|
print(' $exceptionCount exceptions in this package');
|
|
}
|
|
|
|
var overallDuration = DateTime.now().difference(overallStartTime);
|
|
print('${packages.length} packages migrated in ${overallDuration.inSeconds} '
|
|
'seconds');
|
|
print('${listener.numTypesMadeNullable} types made nullable');
|
|
print('${listener.numNullChecksAdded} null checks added');
|
|
print('${listener.numVariablesMarkedLate} variables marked late');
|
|
print('${listener.numInsertedCasts} casts inserted');
|
|
print('${listener.numInsertedParenthesis} parenthesis groupings inserted');
|
|
print('${listener.numMetaImportsAdded} meta imports added');
|
|
print('${listener.numRequiredAnnotationsAdded} required annotations added');
|
|
print('${listener.numDeadCodeSegmentsFound} dead code segments found');
|
|
print('and ${listener.numOtherEdits} other edits not categorized');
|
|
print('${listener.numExceptions} exceptions in '
|
|
'${listener.groupedExceptions.length} categories');
|
|
|
|
var sortedExceptions = [
|
|
for (var entry in listener.groupedExceptions.entries)
|
|
ExceptionCategory(entry.key, entry.value)
|
|
]..sort((category1, category2) => category2.count.compareTo(category1.count));
|
|
var exceptionalPackages =
|
|
sortedExceptions.expand((category) => category.packageNames).toSet();
|
|
print('Packages with exceptions: $exceptionalPackages');
|
|
print('Exception categories:');
|
|
for (var category in sortedExceptions) {
|
|
print(' $category');
|
|
}
|
|
|
|
if (categoryOfInterest == null) {
|
|
print('\n(Note: to show stack traces & nodes for a particular failure,'
|
|
' rerun with a search string as an argument.)');
|
|
}
|
|
}
|
|
|
|
ArgResults? parseArguments(List<String> args) {
|
|
ArgParser argParser = ArgParser();
|
|
ArgResults? parsedArgs;
|
|
|
|
argParser.addFlag('clean',
|
|
abbr: 'c',
|
|
defaultsTo: false,
|
|
help: 'Recursively delete the playground directory before beginning.');
|
|
|
|
argParser.addFlag('help', abbr: 'h', help: 'Display options');
|
|
|
|
argParser.addFlag('exception_node_only',
|
|
defaultsTo: false,
|
|
negatable: true,
|
|
help: 'Only print the exception node instead of the full stack trace.');
|
|
|
|
argParser.addFlag('update',
|
|
abbr: 'u',
|
|
defaultsTo: false,
|
|
negatable: true,
|
|
help: 'Auto-update fetched packages in the playground.');
|
|
|
|
argParser.addOption('sdk',
|
|
abbr: 's',
|
|
defaultsTo: path.dirname(path.dirname(Platform.resolvedExecutable)),
|
|
help: 'Select the root of the SDK to analyze against for this run '
|
|
'(compiled with --nnbd). For example: ../../xcodebuild/DebugX64NNBD/dart-sdk');
|
|
|
|
argParser.addMultiOption(
|
|
'git_packages',
|
|
abbr: 'g',
|
|
defaultsTo: [],
|
|
help: 'Shallow-clone the given git repositories into a playground area,'
|
|
' run pub get on them, and migrate them.',
|
|
);
|
|
|
|
argParser.addMultiOption(
|
|
'manual_packages',
|
|
abbr: 'm',
|
|
defaultsTo: [],
|
|
help: 'Run migration against packages in these directories. Does not '
|
|
'run pub get, any git commands, or any other preparation.',
|
|
);
|
|
|
|
argParser.addMultiOption(
|
|
'packages',
|
|
abbr: 'p',
|
|
defaultsTo: [],
|
|
help: 'The list of SDK packages to run the migration against.',
|
|
);
|
|
|
|
try {
|
|
parsedArgs = argParser.parse(args);
|
|
} on ArgParserException {
|
|
stderr.writeln(argParser.usage);
|
|
exit(1);
|
|
}
|
|
if (parsedArgs['help'] as bool) {
|
|
print(argParser.usage);
|
|
exit(0);
|
|
}
|
|
|
|
if (parsedArgs.rest.length > 1) {
|
|
throw 'invalid args. Specify *one* argument to get exceptions of interest.';
|
|
}
|
|
return parsedArgs;
|
|
}
|
|
|
|
void printWarning(String warn) {
|
|
stderr.writeln('''
|
|
!!!
|
|
!!! Warning! $warn
|
|
!!!
|
|
''');
|
|
}
|
|
|
|
void warnOnNoAssertions() {
|
|
try {
|
|
assert(false);
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
|
|
printWarning("You didn't --enable-asserts!");
|
|
}
|
|
|
|
class ExceptionCategory {
|
|
final String topOfStack;
|
|
final List<MapEntry<String?, int>> exceptionCountPerPackage;
|
|
|
|
ExceptionCategory(this.topOfStack, Map<String?, int> exceptions)
|
|
: exceptionCountPerPackage = exceptions.entries.toList()
|
|
..sort((e1, e2) => e2.value.compareTo(e1.value));
|
|
|
|
int get count => exceptionCountPerPackage.length;
|
|
|
|
List<String?> get packageNames =>
|
|
[for (var entry in exceptionCountPerPackage) entry.key];
|
|
|
|
Iterable<String> get packageNamesAndCounts =>
|
|
exceptionCountPerPackage.map((entry) => '${entry.key} x${entry.value}');
|
|
|
|
String toString() => '$topOfStack (${packageNamesAndCounts.join(', ')})';
|
|
}
|
|
|
|
class _Listener implements NullabilityMigrationListener {
|
|
/// Set this to `true` to cause just the exception nodes to be printed when
|
|
/// `_Listener.categoryOfInterest` is non-null. Set this to `false` to cause
|
|
/// the full stack trace to be printed.
|
|
final bool? printExceptionNodeOnly;
|
|
|
|
/// Set this to a non-null value to cause any exception to be printed in full
|
|
/// if its category contains the string.
|
|
final String? categoryOfInterest;
|
|
|
|
/// Exception mapped to a map of packages & exception counts.
|
|
final groupedExceptions = <String, Map<String?, int>>{};
|
|
|
|
int numExceptions = 0;
|
|
|
|
int numTypesMadeNullable = 0;
|
|
|
|
int numVariablesMarkedLate = 0;
|
|
|
|
int numInsertedCasts = 0;
|
|
|
|
int numInsertedParenthesis = 0;
|
|
|
|
int numNullChecksAdded = 0;
|
|
|
|
int numMetaImportsAdded = 0;
|
|
|
|
int numRequiredAnnotationsAdded = 0;
|
|
|
|
int numDeadCodeSegmentsFound = 0;
|
|
|
|
int numOtherEdits = 0;
|
|
|
|
String? currentPackage;
|
|
|
|
_Listener(this.categoryOfInterest, {this.printExceptionNodeOnly = false});
|
|
|
|
@override
|
|
void addEdit(Source source, SourceEdit edit) {
|
|
if (edit.replacement == '') {
|
|
return;
|
|
}
|
|
|
|
if (edit.replacement.contains('!')) {
|
|
++numNullChecksAdded;
|
|
}
|
|
|
|
if (edit.replacement.contains('(')) {
|
|
++numInsertedParenthesis;
|
|
}
|
|
|
|
if (edit.replacement == '?' && edit.length == 0) {
|
|
++numTypesMadeNullable;
|
|
} else if (edit.replacement == "import 'package:meta/meta.dart';\n" &&
|
|
edit.length == 0) {
|
|
++numMetaImportsAdded;
|
|
} else if (edit.replacement == 'required ' && edit.length == 0) {
|
|
++numRequiredAnnotationsAdded;
|
|
} else if (edit.replacement == 'late ' && edit.length == 0) {
|
|
++numVariablesMarkedLate;
|
|
} else if (edit.replacement.startsWith(' as ') && edit.length == 0) {
|
|
++numInsertedCasts;
|
|
} else if ((edit.replacement == '/* ' ||
|
|
edit.replacement == ' /*' ||
|
|
edit.replacement == '; /*') &&
|
|
edit.length == 0) {
|
|
++numDeadCodeSegmentsFound;
|
|
} else if ((edit.replacement == '*/ ' ||
|
|
edit.replacement == ' */' ||
|
|
edit.replacement == ')' ||
|
|
edit.replacement == '!' ||
|
|
edit.replacement == '(') &&
|
|
edit.length == 0) {
|
|
} else {
|
|
numOtherEdits++;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void addSuggestion(String descriptions, Location location) {}
|
|
|
|
@override
|
|
void reportException(
|
|
Source? source, AstNode? node, Object exception, StackTrace stackTrace) {
|
|
var category = _classifyStackTrace(stackTrace.toString().split('\n'));
|
|
String detail = '''
|
|
In file $source
|
|
While processing $node
|
|
Exception $exception
|
|
$stackTrace
|
|
''';
|
|
if (categoryOfInterest != null && category.contains(categoryOfInterest!)) {
|
|
if (printExceptionNodeOnly!) {
|
|
print('$node');
|
|
} else {
|
|
print(detail);
|
|
}
|
|
}
|
|
(groupedExceptions[category] ??= <String?, int>{})
|
|
.update(currentPackage, (value) => ++value, ifAbsent: () => 1);
|
|
++numExceptions;
|
|
}
|
|
|
|
String _classifyStackTrace(List<String> stackTrace) {
|
|
for (var entry in stackTrace) {
|
|
if (entry.contains('EdgeBuilder._unimplemented')) continue;
|
|
if (entry.contains('_AssertionError._doThrowNew')) continue;
|
|
if (entry.contains('_AssertionError._throwNew')) continue;
|
|
if (entry.contains('NodeBuilder._unimplemented')) continue;
|
|
if (entry.contains('Object.noSuchMethod')) continue;
|
|
if (entry.contains('List.[] (dart:core-patch/growable_array.dart')) {
|
|
continue;
|
|
}
|
|
return entry;
|
|
}
|
|
return '???';
|
|
}
|
|
}
|