// Copyright 2013 The Flutter Authors. 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.8 library flutter_frontend_server; import 'dart:async'; import 'dart:io' hide FileSystemEntity; import 'package:args/args.dart'; import 'package:frontend_server/frontend_server.dart' as frontend show FrontendCompiler, CompilerInterface, listenAndCompile, argParser, usage, ProgramTransformer; import 'package:kernel/ast.dart'; import 'package:path/path.dart' as path; import 'package:vm/incremental_compiler.dart'; /// Wrapper around [FrontendCompiler] that adds [widgetCreatorTracker] kernel /// transformation to the compilation. class _FlutterFrontendCompiler implements frontend.CompilerInterface { final frontend.CompilerInterface _compiler; _FlutterFrontendCompiler(StringSink output, {bool unsafePackageSerialization, bool useDebuggerModuleNames, bool emitDebugMetadata, frontend.ProgramTransformer transformer}) : _compiler = frontend.FrontendCompiler(output, transformer: transformer, useDebuggerModuleNames: useDebuggerModuleNames, emitDebugMetadata: emitDebugMetadata, unsafePackageSerialization: unsafePackageSerialization); @override Future compile(String filename, ArgResults options, {IncrementalCompiler generator}) async { return _compiler.compile(filename, options, generator: generator); } @override Future recompileDelta({String entryPoint}) async { return _compiler.recompileDelta(entryPoint: entryPoint); } @override void acceptLastDelta() { _compiler.acceptLastDelta(); } @override Future rejectLastDelta() async { return _compiler.rejectLastDelta(); } @override void invalidate(Uri uri) { _compiler.invalidate(uri); } @override Future compileExpression( String expression, List definitions, List typeDefinitions, String libraryUri, String klass, bool isStatic) { return _compiler.compileExpression( expression, definitions, typeDefinitions, libraryUri, klass, isStatic); } @override Future compileExpressionToJs( String libraryUri, int line, int column, Map jsModules, Map jsFrameValues, String moduleName, String expression) { return _compiler.compileExpressionToJs(libraryUri, line, column, jsModules, jsFrameValues, moduleName, expression); } @override void reportError(String msg) { _compiler.reportError(msg); } @override void resetIncrementalCompiler() { _compiler.resetIncrementalCompiler(); } } /// Entry point for this module, that creates `_FrontendCompiler` instance and /// processes user input. /// `compiler` is an optional parameter so it can be replaced with mocked /// version for testing. Future starter( List args, { frontend.CompilerInterface compiler, Stream> input, StringSink output, frontend.ProgramTransformer transformer, }) async { ArgResults options; frontend.argParser.addMultiOption( 'delete-tostring-package-uri', help: 'Replaces implementations of `toString` with `super.toString()` for ' 'specified package', valueHelp: 'dart:ui', defaultsTo: const [], ); try { options = frontend.argParser.parse(args); } catch (error) { print('ERROR: $error\n'); print(frontend.usage); return 1; } final Set deleteToStringPackageUris = (options['delete-tostring-package-uri'] as List).toSet(); if (options['train'] as bool) { if (!options.rest.isNotEmpty) { throw Exception('Must specify input.dart'); } final String input = options.rest[0]; final String sdkRoot = options['sdk-root'] as String; final Directory temp = Directory.systemTemp.createTempSync('train_frontend_server'); try { for (int i = 0; i < 3; i++) { final String outputTrainingDill = path.join(temp.path, 'app.dill'); options = frontend.argParser.parse([ '--incremental', '--sdk-root=$sdkRoot', '--output-dill=$outputTrainingDill', '--target=flutter', '--track-widget-creation', '--enable-asserts', ]); compiler ??= _FlutterFrontendCompiler( output, transformer: ToStringTransformer(null, deleteToStringPackageUris), ); await compiler.compile(input, options); compiler.acceptLastDelta(); await compiler.recompileDelta(); compiler.acceptLastDelta(); compiler.resetIncrementalCompiler(); await compiler.recompileDelta(); compiler.acceptLastDelta(); await compiler.recompileDelta(); compiler.acceptLastDelta(); } return 0; } finally { temp.deleteSync(recursive: true); } } compiler ??= _FlutterFrontendCompiler(output, transformer: ToStringTransformer(transformer, deleteToStringPackageUris), useDebuggerModuleNames: options['debugger-module-names'] as bool, emitDebugMetadata: options['experimental-emit-debug-metadata'] as bool, unsafePackageSerialization: options['unsafe-package-serialization'] as bool); if (options.rest.isNotEmpty) { return await compiler.compile(options.rest[0], options) ? 0 : 254; } final Completer completer = Completer(); frontend.listenAndCompile(compiler, input ?? stdin, options, completer); return completer.future; } // Transformer/visitor for toString // If we add any more of these, they really should go into a separate library. /// A [RecursiveVisitor] that replaces [Object.toString] overrides with /// `super.toString()`. class ToStringVisitor extends RecursiveVisitor { /// The [packageUris] must not be null. ToStringVisitor(this._packageUris) : assert(_packageUris != null); /// A set of package URIs to apply this transformer to, e.g. 'dart:ui' and /// 'package:flutter/foundation.dart'. final Set _packageUris; /// Turn 'dart:ui' into 'dart:ui', or /// 'package:flutter/src/semantics_event.dart' into 'package:flutter'. String _importUriToPackage(Uri importUri) => '${importUri.scheme}:${importUri.pathSegments.first}'; bool _isInTargetPackage(Procedure node) { return _packageUris.contains(_importUriToPackage(node.enclosingLibrary.importUri)); } bool _hasKeepAnnotation(Procedure node) { for (ConstantExpression expression in node.annotations.whereType()) { if (expression.constant is! InstanceConstant) { continue; } final InstanceConstant constant = expression.constant as InstanceConstant; if (constant.classNode.name == '_KeepToString' && constant.classNode.enclosingLibrary.importUri.toString() == 'dart:ui') { return true; } } return false; } @override void visitProcedure(Procedure node) { if ( node.name.text == 'toString' && node.enclosingClass != null && node.enclosingLibrary != null && !node.isStatic && !node.isAbstract && !node.enclosingClass.isEnum && _isInTargetPackage(node) && !_hasKeepAnnotation(node) ) { node.function.body.replaceWith( ReturnStatement( SuperMethodInvocation( node.name, Arguments([]), ), ), ); } } @override void defaultMember(Member node) {} } /// Replaces [Object.toString] overrides with calls to super for the specified /// [packageUris]. class ToStringTransformer extends frontend.ProgramTransformer { /// The [packageUris] parameter must not be null, but may be empty. ToStringTransformer(this._child, this._packageUris) : assert(_packageUris != null); final frontend.ProgramTransformer _child; /// A set of package URIs to apply this transformer to, e.g. 'dart:ui' and /// 'package:flutter/foundation.dart'. final Set _packageUris; @override void transform(Component component) { assert(_child is! ToStringTransformer); if (_packageUris.isNotEmpty) { component.visitChildren(ToStringVisitor(_packageUris)); } _child?.transform(component); } }