ee0c0bcd57
This is in preparation to migrate package:kernel to null safety. For the visitor interfaces to support non-nullable return types, the implementations must avoid using `null` as return value in its base case. TEST=Refactoring Change-Id: Ie8fa5d41b99850d9e4abb59634c72920c64128d9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/183691 Reviewed-by: Jens Johansen <jensj@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com>
326 lines
11 KiB
Dart
326 lines
11 KiB
Dart
// 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.
|
|
|
|
// @dart = 2.9
|
|
|
|
import 'dart:convert' show utf8;
|
|
import 'dart:io'
|
|
show Directory, File, FileSystemEntity, exitCode, stdin, stdout;
|
|
|
|
import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity;
|
|
import 'package:_fe_analyzer_shared/src/scanner/token.dart'
|
|
show CommentToken, Token;
|
|
import 'package:front_end/src/api_prototype/compiler_options.dart' as api;
|
|
import 'package:front_end/src/api_prototype/file_system.dart' as api;
|
|
import 'package:front_end/src/api_unstable/ddc.dart'
|
|
show CompilerContext, IncrementalCompiler, ProcessedOptions, Severity;
|
|
import 'package:front_end/src/base/processed_options.dart'
|
|
show ProcessedOptions;
|
|
import 'package:front_end/src/compute_platform_binaries_location.dart'
|
|
show computePlatformBinariesLocation;
|
|
import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext;
|
|
import 'package:front_end/src/fasta/dill/dill_target.dart' show DillTarget;
|
|
import 'package:front_end/src/fasta/incremental_compiler.dart'
|
|
show IncrementalCompiler, IncrementalKernelTarget;
|
|
import 'package:front_end/src/fasta/kernel/kernel_target.dart'
|
|
show KernelTarget;
|
|
import 'package:front_end/src/fasta/source/source_library_builder.dart'
|
|
show SourceLibraryBuilder;
|
|
import 'package:front_end/src/fasta/source/source_loader.dart'
|
|
show SourceLoader;
|
|
import 'package:front_end/src/fasta/uri_translator.dart' show UriTranslator;
|
|
import 'package:kernel/kernel.dart';
|
|
import 'package:kernel/target/targets.dart' show TargetFlags;
|
|
import "package:vm/target/vm.dart" show VmTarget;
|
|
|
|
import "utils/io_utils.dart";
|
|
|
|
final Uri repoDir = computeRepoDirUri();
|
|
|
|
Set<Uri> libUris = {};
|
|
|
|
Component component;
|
|
|
|
Future<void> main(List<String> args) async {
|
|
api.CompilerOptions compilerOptions = getOptions();
|
|
|
|
Uri dotPackagesUri = repoDir.resolve(".packages");
|
|
if (!new File.fromUri(dotPackagesUri).existsSync()) {
|
|
throw "Couldn't find .packages";
|
|
}
|
|
compilerOptions.packagesFileUri = dotPackagesUri;
|
|
|
|
ProcessedOptions options = new ProcessedOptions(options: compilerOptions);
|
|
|
|
libUris.add(repoDir.resolve("pkg/_fe_analyzer_shared/lib/src/parser"));
|
|
libUris.add(repoDir.resolve("pkg/_fe_analyzer_shared/lib/src/scanner"));
|
|
for (Uri uri in libUris) {
|
|
List<FileSystemEntity> entities =
|
|
new Directory.fromUri(uri).listSync(recursive: true);
|
|
for (FileSystemEntity entity in entities) {
|
|
if (entity is File && entity.path.endsWith(".dart")) {
|
|
options.inputs.add(entity.uri);
|
|
}
|
|
}
|
|
}
|
|
CompilerContext context = new CompilerContext(options);
|
|
IncrementalCompiler incrementalCompiler =
|
|
new TestIncrementalCompiler(context);
|
|
component = await incrementalCompiler.computeDelta();
|
|
|
|
for (Library library in component.libraries) {
|
|
if (library.importUri.scheme == "dart") continue;
|
|
// This isn't perfect because of parts, but (for now) it'll do.
|
|
for (Uri uri in libUris) {
|
|
if (library.fileUri.toString().startsWith(uri.toString())) {
|
|
library.accept(new InvocationVisitor());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (args.isNotEmpty && args[0] == "--interactive") {
|
|
List<Uri> editsPerformed = [];
|
|
for (Uri uri in edits.keys) {
|
|
print("\n\n\n");
|
|
if (edits[uri] != null && edits[uri].isNotEmpty) {
|
|
String update;
|
|
while (update != "y" &&
|
|
update != "yes" &&
|
|
update != "n" &&
|
|
update != "no") {
|
|
print("Do you want to update $uri? (y/n)");
|
|
update = stdin.readLineSync();
|
|
}
|
|
if (update != "y" && update != "yes") continue;
|
|
|
|
List<Edit> theseEdits = edits[uri];
|
|
theseEdits.sort((a, b) => a.offset - b.offset);
|
|
String content = utf8.decode(component.uriToSource[uri].source,
|
|
allowMalformed: true);
|
|
StringBuffer sb = new StringBuffer();
|
|
int latest = 0;
|
|
for (Edit edit in theseEdits) {
|
|
sb.write(content.substring(latest, edit.offset));
|
|
sb.write(edit.insertData);
|
|
latest = edit.offset;
|
|
}
|
|
sb.write(content.substring(latest, content.length));
|
|
new File.fromUri(uri).writeAsStringSync(sb.toString());
|
|
editsPerformed.add(uri);
|
|
}
|
|
}
|
|
if (editsPerformed.isNotEmpty) {
|
|
print("\n\nYou should now probably run something like\n\n");
|
|
stdout.write(r"tools/sdks/dart-sdk/bin/dartfmt -w");
|
|
for (Uri uri in editsPerformed) {
|
|
File f = new File.fromUri(uri);
|
|
Directory relative = new Directory.fromUri(Uri.base.resolve("."));
|
|
if (!f.path.startsWith(relative.path)) {
|
|
throw "${f.path} vs ${relative.path}";
|
|
}
|
|
String relativePath = f.path.substring(relative.path.length);
|
|
stdout.write(" ${relativePath}");
|
|
}
|
|
stdout.write("\n\n");
|
|
}
|
|
}
|
|
|
|
if (edits.isNotEmpty) {
|
|
exitCode = 1;
|
|
}
|
|
|
|
int totalSuggestedEdits = edits.values
|
|
.fold(0, (previousValue, element) => previousValue + element.length);
|
|
print("Done. Suggested ${totalSuggestedEdits} edits "
|
|
"in ${edits.length} files.");
|
|
}
|
|
|
|
int errorCount = 0;
|
|
|
|
api.CompilerOptions getOptions() {
|
|
// Compile sdk because when this is run from a lint it uses the checked-in sdk
|
|
// and we might not have a suitable compiled platform.dill file.
|
|
Uri sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
|
|
api.CompilerOptions options = new api.CompilerOptions()
|
|
..sdkRoot = sdkRoot
|
|
..compileSdk = true
|
|
..target = new VmTarget(new TargetFlags())
|
|
..librariesSpecificationUri = repoDir.resolve("sdk/lib/libraries.json")
|
|
..omitPlatform = true
|
|
..onDiagnostic = (api.DiagnosticMessage message) {
|
|
if (message.severity == Severity.error) {
|
|
print(message.plainTextFormatted.join('\n'));
|
|
errorCount++;
|
|
}
|
|
}
|
|
..environmentDefines = const {};
|
|
return options;
|
|
}
|
|
|
|
class InvocationVisitor extends RecursiveVisitor {
|
|
void visitProcedure(Procedure node) {
|
|
if (node.isNoSuchMethodForwarder) return;
|
|
super.visitProcedure(node);
|
|
}
|
|
|
|
void visitMethodInvocation(MethodInvocation node) {
|
|
super.visitMethodInvocation(node);
|
|
if (node.interfaceTargetReference?.node != null) {
|
|
note(node.interfaceTargetReference?.node, node.arguments, node);
|
|
}
|
|
}
|
|
|
|
void visitSuperMethodInvocation(SuperMethodInvocation node) {
|
|
super.visitSuperMethodInvocation(node);
|
|
note(node.interfaceTargetReference.node, node.arguments, node);
|
|
}
|
|
|
|
void visitStaticInvocation(StaticInvocation node) {
|
|
super.visitStaticInvocation(node);
|
|
note(node.targetReference.node, node.arguments, node);
|
|
}
|
|
|
|
void visitConstructorInvocation(ConstructorInvocation node) {
|
|
super.visitConstructorInvocation(node);
|
|
note(node.targetReference.node, node.arguments, node);
|
|
}
|
|
|
|
void note(
|
|
NamedNode node, Arguments arguments, InvocationExpression invocation) {
|
|
List<VariableDeclaration> positionalParameters;
|
|
if (node is Procedure) {
|
|
positionalParameters = node.function.positionalParameters;
|
|
} else if (node is Constructor) {
|
|
positionalParameters = node.function.positionalParameters;
|
|
} else {
|
|
throw "Unexpected node: ${node.runtimeType}";
|
|
}
|
|
|
|
for (int i = 0; i < arguments.positional.length; i++) {
|
|
bool wantComment = false;
|
|
if (arguments.positional[i] is NullLiteral ||
|
|
arguments.positional[i] is BoolLiteral ||
|
|
arguments.positional[i] is IntLiteral) {
|
|
wantComment = true;
|
|
} else if (arguments.positional[i] is MapLiteral) {
|
|
MapLiteral literal = arguments.positional[i];
|
|
if (literal.entries.isEmpty) wantComment = true;
|
|
} else if (arguments.positional[i] is ListLiteral) {
|
|
ListLiteral literal = arguments.positional[i];
|
|
if (literal.expressions.isEmpty) wantComment = true;
|
|
} else if (arguments.positional[i] is MethodInvocation) {
|
|
MethodInvocation methodInvocation = arguments.positional[i];
|
|
if (methodInvocation.receiver is NullLiteral ||
|
|
methodInvocation.receiver is IntLiteral ||
|
|
methodInvocation.receiver is BoolLiteral) {
|
|
wantComment = true;
|
|
}
|
|
}
|
|
if (wantComment) {
|
|
check(arguments.positional[i], i, positionalParameters[i], node,
|
|
"/* ${positionalParameters[i].name} = */");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Map<Uri, Token> cache = {};
|
|
|
|
void check(
|
|
Expression argumentExpression,
|
|
int parameterNumber,
|
|
VariableDeclaration parameter,
|
|
NamedNode targetNode,
|
|
String expectedComment) {
|
|
if (targetNode is Procedure && targetNode.kind == ProcedureKind.Operator) {
|
|
// Operator calls doesn't look like 'regular' method calls.
|
|
return;
|
|
}
|
|
if (argumentExpression.fileOffset == -1) return;
|
|
Location location = argumentExpression.location;
|
|
Token token = cache[location.file];
|
|
while (token.offset != argumentExpression.fileOffset) {
|
|
token = token.next;
|
|
if (token.isEof) {
|
|
throw "Couldn't find token for $argumentExpression "
|
|
"(${argumentExpression.fileOffset}).";
|
|
}
|
|
}
|
|
bool foundComment = false;
|
|
CommentToken commentToken = token.precedingComments;
|
|
while (commentToken != null) {
|
|
if (commentToken.lexeme == expectedComment) {
|
|
// Exact match.
|
|
foundComment = true;
|
|
break;
|
|
}
|
|
if (commentToken.lexeme.replaceAll(" ", "") ==
|
|
expectedComment.replaceAll(" ", "")) {
|
|
// Close enough.
|
|
foundComment = true;
|
|
break;
|
|
}
|
|
commentToken = commentToken.next;
|
|
}
|
|
if (foundComment) {
|
|
return;
|
|
}
|
|
Location calculatedLocation =
|
|
component.getLocation(location.file, token.offset);
|
|
print("Please add comment $expectedComment at "
|
|
"${token.offset} => "
|
|
"${calculatedLocation}");
|
|
edits[location.file] ??= [];
|
|
edits[location.file].add(new Edit(token.offset, expectedComment));
|
|
}
|
|
|
|
Map<Uri, List<Edit>> edits = {};
|
|
|
|
class Edit {
|
|
final int offset;
|
|
final String insertData;
|
|
Edit(this.offset, this.insertData);
|
|
}
|
|
|
|
class TestIncrementalCompiler extends IncrementalCompiler {
|
|
TestIncrementalCompiler(CompilerContext context) : super(context);
|
|
|
|
IncrementalKernelTarget createIncrementalKernelTarget(
|
|
api.FileSystem fileSystem,
|
|
bool includeComments,
|
|
DillTarget dillTarget,
|
|
UriTranslator uriTranslator) {
|
|
return new TestIncrementalKernelTarget(
|
|
fileSystem, /* includeComments = */ true, dillTarget, uriTranslator);
|
|
}
|
|
}
|
|
|
|
class TestIncrementalKernelTarget extends IncrementalKernelTarget {
|
|
TestIncrementalKernelTarget(api.FileSystem fileSystem, bool includeComments,
|
|
DillTarget dillTarget, UriTranslator uriTranslator)
|
|
: super(fileSystem, includeComments, dillTarget, uriTranslator);
|
|
|
|
SourceLoader createLoader() =>
|
|
new TestSourceLoader(fileSystem, includeComments, this);
|
|
|
|
void runBuildTransformations() {
|
|
// Don't do any transformations!
|
|
}
|
|
}
|
|
|
|
class TestSourceLoader extends SourceLoader {
|
|
TestSourceLoader(
|
|
api.FileSystem fileSystem, bool includeComments, KernelTarget target)
|
|
: super(fileSystem, includeComments, target);
|
|
|
|
Future<Token> tokenize(SourceLibraryBuilder library,
|
|
{bool suppressLexicalErrors: false}) async {
|
|
Token result = await super
|
|
.tokenize(library, suppressLexicalErrors: suppressLexicalErrors);
|
|
cache[library.fileUri] = result;
|
|
return result;
|
|
}
|
|
}
|