| // Copyright (c) 2023, 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. |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io' show File; |
| |
| import 'package:dev_compiler/src/command/command.dart'; |
| import 'package:dev_compiler/src/command/options.dart' show Options; |
| import 'package:dev_compiler/src/compiler/module_builder.dart'; |
| import 'package:dev_compiler/src/kernel/compiler.dart' show ProgramCompiler; |
| import 'package:dev_compiler/src/kernel/compiler_new.dart' |
| show LibraryBundleCompiler; |
| import 'package:dev_compiler/src/kernel/expression_compiler.dart' |
| show ExpressionCompiler; |
| import 'package:dev_compiler/src/kernel/module_metadata.dart'; |
| import 'package:front_end/src/api_unstable/ddc.dart' as fe; |
| import 'package:kernel/ast.dart' show Component, Library; |
| import 'package:path/path.dart' as p; |
| import 'package:source_maps/source_maps.dart' as source_maps; |
| |
| import '../shared_test_options.dart'; |
| |
| class TestCompilationResult { |
| final String? result; |
| final bool isSuccess; |
| |
| TestCompilationResult(this.result, this.isSuccess); |
| } |
| |
| class TestExpressionCompiler { |
| final SetupCompilerOptions setup; |
| final Component component; |
| final ExpressionCompiler compiler; |
| final ModuleMetadata? metadata; |
| final source_maps.SingleMapping sourceMap; |
| |
| TestExpressionCompiler._( |
| this.setup, this.component, this.compiler, this.metadata, this.sourceMap); |
| |
| static Future<TestExpressionCompiler> init(SetupCompilerOptions setup, |
| {required Uri input, |
| required Uri output, |
| Uri? packages, |
| Map<String, bool> experiments = const {}}) async { |
| setup.diagnosticMessages.clear(); |
| setup.errors.clear(); |
| // Initialize the incremental compiler and module component. |
| // TODO: extend this for multi-module compilations by storing separate |
| // compilers/components/names per module. |
| setup.options.packagesFileUri = packages; |
| setup.options.explicitExperimentalFlags.addAll(fe.parseExperimentalFlags( |
| experiments, |
| onError: (message) => throw Exception(message))); |
| var frontend = DevelopmentIncrementalCompiler(setup.options, input); |
| var compilerResult = await frontend.computeDelta(); |
| var component = compilerResult.component; |
| component.computeCanonicalNames(); |
| // Initialize DDC. |
| var moduleName = p.basenameWithoutExtension(output.toFilePath()); |
| |
| var classHierarchy = compilerResult.classHierarchy!; |
| var compilerOptions = Options( |
| replCompile: true, |
| moduleName: moduleName, |
| experiments: experiments, |
| soundNullSafety: setup.soundNullSafety, |
| emitDebugMetadata: true, |
| canaryFeatures: setup.canaryFeatures, |
| enableAsserts: setup.enableAsserts, |
| moduleFormats: [setup.moduleFormat], |
| ); |
| var coreTypes = compilerResult.coreTypes; |
| |
| final importToSummary = Map<Library, Component>.identity(); |
| final summaryToModule = Map<Component, String>.identity(); |
| for (var lib in component.libraries) { |
| importToSummary[lib] = component; |
| } |
| summaryToModule[component] = moduleName; |
| |
| var kernel2jsCompiler = compilerOptions.emitLibraryBundle |
| ? LibraryBundleCompiler(component, classHierarchy, compilerOptions, |
| importToSummary, summaryToModule, coreTypes: coreTypes) |
| : ProgramCompiler(component, classHierarchy, compilerOptions, |
| importToSummary, summaryToModule, |
| coreTypes: coreTypes); |
| var module = kernel2jsCompiler.emitModule(component); |
| |
| var moduleFormat = compilerOptions.emitLibraryBundle |
| ? ModuleFormat.ddcLibraryBundle |
| : setup.moduleFormat; |
| |
| // Perform a full compile, writing the compiled JS + sourcemap. |
| var code = jsProgramToCode( |
| module, |
| moduleFormat, |
| inlineSourceMap: compilerOptions.inlineSourceMap, |
| buildSourceMap: compilerOptions.sourceMap, |
| emitDebugMetadata: compilerOptions.emitDebugMetadata, |
| emitDebugSymbols: compilerOptions.emitDebugSymbols, |
| jsUrl: '$output', |
| mapUrl: '$output.map', |
| compiler: kernel2jsCompiler, |
| component: component, |
| ); |
| var codeBytes = utf8.encode(code.code); |
| var sourceMapBytes = utf8.encode(json.encode(code.sourceMap)); |
| |
| File(output.toFilePath()).writeAsBytesSync(codeBytes); |
| File('${output.toFilePath()}.map').writeAsBytesSync(sourceMapBytes); |
| |
| // Save the expression compiler for future compilation. |
| var compiler = ExpressionCompiler( |
| setup.options, |
| moduleFormat, |
| setup.errors, |
| frontend, |
| kernel2jsCompiler, |
| component, |
| ); |
| |
| if (setup.errors.isNotEmpty) { |
| throw Exception('Compilation failed with: ${setup.errors}'); |
| } |
| setup.diagnosticMessages.clear(); |
| setup.errors.clear(); |
| |
| var sourceMap = source_maps.SingleMapping.fromJson(code.sourceMap!); |
| return TestExpressionCompiler._( |
| setup, component, compiler, code.metadata, sourceMap); |
| } |
| |
| // Line and column are 1-based. |
| Future<TestCompilationResult> compileExpression( |
| {required Uri libraryUri, |
| Uri? scriptUri, |
| required int line, |
| required int column, |
| required Map<String, String> scope, |
| required String expression}) async { |
| // clear previous errors |
| setup.errors.clear(); |
| |
| // At this time, no metadata is stored for the Dart SDK. This is one of the |
| // reasons debugging and expression evaluation support is so limited within |
| // the SDK. When requesting evaluation of library level expressions, we can |
| // ignore the lack of metadata and simply use the URI directly. |
| String importUri; |
| if (libraryUri.isScheme('dart')) { |
| importUri = libraryUri.toString(); |
| } else { |
| var libraryMetadata = metadataForLibraryUri(libraryUri); |
| importUri = libraryMetadata.importUri; |
| } |
| var jsExpression = await compiler.compileExpressionToJs( |
| importUri, scriptUri?.toString(), line, column, scope, expression); |
| if (setup.errors.isNotEmpty) { |
| jsExpression = setup.errors.toString().replaceAll( |
| RegExp( |
| r'org-dartlang-debug:synthetic_debug_expression:[0-9]*:[0-9]*:'), |
| ''); |
| |
| return TestCompilationResult(jsExpression, false); |
| } |
| |
| return TestCompilationResult(jsExpression, true); |
| } |
| |
| LibraryMetadata metadataForLibraryUri(Uri libraryUri) => |
| metadata!.libraries.entries |
| .firstWhere((entry) => entry.value.fileUri == '$libraryUri') |
| .value; |
| } |