// 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 'package:dev_compiler/dev_compiler.dart'; import 'package:dev_compiler/src/command/command.dart'; import 'package:dev_compiler/src/js_ast/js_ast.dart'; import 'package:front_end/src/api_unstable/ddc.dart' as fe; import 'package:kernel/target/targets.dart'; /// Result compiling using [compileFromMemory]. /// /// This is meant for testing and therefore include not only the resulting /// [Program] various artifacts of the compilation. class MemoryCompilerResult { final fe.DdcResult ddcResult; final ProgramCompiler compiler; final Program program; final List errors; MemoryCompilerResult( this.ddcResult, this.compiler, this.program, this.errors, ); } /// Result of compiling using [componentFromMemory]. /// /// This is meant for use in tests and performs the front end compilation to /// components without calling DDC. class MemoryComponentResult { final fe.DdcResult ddcResult; final List errors; fe.InitializedCompilerState? initialCompilerState; MemoryComponentResult(this.ddcResult, this.errors, this.initialCompilerState); } /// Uri used as the base uri for files provided in memory through the /// [MemoryFileSystem]. Uri memoryDirectory = Uri.parse('memory://'); /// Compiles [entryPoint] to a kernel `Component` using the [memoryFiles] as /// sources. /// /// [memoryFiles] maps relative paths to their source text. [entryPoint] must /// be absolute, using [baseUri] (defaulted to [memoryDirectory]) to refer to a /// file from [memoryFiles]. Future componentFromMemory( Map memoryFiles, Uri entryPoint, { Map explicitExperimentalFlags = const {}, Uri? baseUri, }) async { baseUri ??= memoryDirectory; var errors = []; void diagnosticMessageHandler(fe.CfeDiagnosticMessage message) { if (message.severity == fe.CfeSeverity.error) { errors.add(message); } fe.printDiagnosticMessage(message, print); } var memoryFileSystem = fe.MemoryFileSystem(baseUri); for (var entry in memoryFiles.entries) { memoryFileSystem .entityForUri(baseUri.resolve(entry.key)) .writeAsStringSync(entry.value); } var compilerState = fe.initializeCompiler( null, false, sourcePathToUri(getSdkPath()), sourcePathToUri(defaultSdkSummaryPath), null, sourcePathToUri(defaultLibrarySpecPath), [], DevCompilerTarget(TargetFlags(trackCreationLocations: false)), fileSystem: fe.HybridFileSystem(memoryFileSystem), environmentDefines: {}, explicitExperimentalFlags: explicitExperimentalFlags, ); var result = await fe.compile(compilerState, [ entryPoint, ], diagnosticMessageHandler); if (result == null) { throw 'Memory compilation failed'; } return MemoryComponentResult(result, errors, compilerState); } /// Compiles [entryPoint] to a kernel `Component` using the [memoryFiles] as /// sources. Uses the incremental compiler. /// /// [memoryFiles] maps relative paths to their source text. [entryPoint] must /// be absolute, using [baseUri] (defaulted to [memoryDirectory]) to refer to a /// file from [memoryFiles]. Future incrementalComponentFromMemory( Map memoryFiles, Uri entryPoint, { Map explicitExperimentalFlags = const {}, Uri? baseUri, fe.InitializedCompilerState? initialCompilerState, Uri? packageConfigUri, }) async { baseUri ??= memoryDirectory; var errors = []; void diagnosticMessageHandler(fe.CfeDiagnosticMessage message) { if (message.severity == fe.CfeSeverity.error) { errors.add(message); } fe.printDiagnosticMessage(message, print); } var memoryFileSystem = fe.MemoryFileSystem(baseUri); for (var entry in memoryFiles.entries) { memoryFileSystem .entityForUri(baseUri.resolve(entry.key)) .writeAsStringSync(entry.value); } var inputDigests = { sourcePathToUri(defaultSdkSummaryPath): const [0], }; var compilerState = await fe.initializeIncrementalCompiler( initialCompilerState, {}, [], false, sourcePathToUri(getSdkPath()), sourcePathToUri(defaultSdkSummaryPath), packageConfigUri, sourcePathToUri(defaultLibrarySpecPath), [], inputDigests, DevCompilerTarget(TargetFlags(trackCreationLocations: false)), fileSystem: fe.HybridFileSystem(memoryFileSystem), environmentDefines: {}, explicitExperimentalFlags: explicitExperimentalFlags, ); var incrementalCompiler = compilerState.incrementalCompiler!; compilerState.options.onDiagnostic = diagnosticMessageHandler; var incrementalCompilerResult = await incrementalCompiler.computeDelta( entryPoints: [entryPoint], fullComponent: false, ); var cachedSdkInput = compilerState.workerInputCache![sourcePathToUri(defaultSdkSummaryPath)]; var result = fe.DdcResult( incrementalCompilerResult.component, cachedSdkInput?.component, [], incrementalCompilerResult.classHierarchy, incrementalCompilerResult.neededDillLibraries, ); return MemoryComponentResult(result, errors, compilerState); } /// Compiles [entryPoint] to JavaScript using the [memoryFiles] as sources. /// /// [memoryFiles] maps relative paths to their source text. [entryPoint] must /// be absolute, using [memoryDirectory] as a base uri to refer to a file from /// [memoryFiles]. Future compileFromMemory( Map memoryFiles, Uri entryPoint, { Map? explicitExperimentalFlags, Uri? baseUri, }) async { baseUri ??= memoryDirectory; var MemoryComponentResult(ddcResult: result, :errors) = await componentFromMemory(memoryFiles, entryPoint, baseUri: baseUri); var options = Options(moduleName: 'test'); var compiler = // TODO(nshahan): Do we need to support [importToSummary] and // [summaryToModule]. ProgramCompiler(result.component, result.classHierarchy, options, {}, {}); var jsModule = compiler.emitModule(result.compiledLibraries); return MemoryCompilerResult(result, compiler, jsModule, errors); }