// 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 libUris = {}; Component component; Future main(List 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 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 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 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 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 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> 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 tokenize(SourceLibraryBuilder library, {bool suppressLexicalErrors: false}) async { Token result = await super .tokenize(library, suppressLexicalErrors: suppressLexicalErrors); cache[library.fileUri] = result; return result; } }