diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2020-07-19 19:49:44 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-19 19:49:44 +0200 |
commit | fa61956f03491101b6ef64423ea2f1f73af26a73 (patch) | |
tree | c3800702071ca78aa4dd71bdd0a59a9bbe460bdd /deno_typescript | |
parent | 53adde866dd399aa2509d14508642fce37afb8f5 (diff) |
Port internal TS code to JS (#6793)
Co-authored-by: Ryan Dahl <ry@tinyclouds.org>
Diffstat (limited to 'deno_typescript')
-rw-r--r-- | deno_typescript/compiler_main.js | 386 | ||||
-rw-r--r-- | deno_typescript/lib.rs | 244 | ||||
-rw-r--r-- | deno_typescript/ops.rs | 163 |
3 files changed, 2 insertions, 791 deletions
diff --git a/deno_typescript/compiler_main.js b/deno_typescript/compiler_main.js deleted file mode 100644 index a42f96860..000000000 --- a/deno_typescript/compiler_main.js +++ /dev/null @@ -1,386 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -// Because we're bootstrapping the TypeScript compiler without dependencies on -// Node, this is written in JavaScript, but leverages JSDoc that can be -// understood by the TypeScript language service, so it allows type safety -// checking in VSCode. - -"use strict"; - -const ASSETS = "$asset$"; - -/** - * @param {string} configText - * @param {Array<string>} rootNames - */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function main(configText, rootNames) { - ops = Deno.core.ops(); - println(`>>> ts version ${ts.version}`); - println(`>>> rootNames ${rootNames}`); - - const host = new Host(); - - assert(rootNames.length === 1); - // If root file is external file, ie. URL with "file://" - // then create an internal name - in case of bundling - // cli runtime this is always true. - const rootFile = rootNames[0]; - const result = externalSpecifierRegEx.exec(rootFile); - let rootSpecifier = rootFile; - if (result) { - const [, specifier] = result; - const internalSpecifier = `$deno$${specifier}`; - moduleMap.set(internalSpecifier, rootFile); - rootSpecifier = internalSpecifier; - } - const { options, diagnostics } = configure(configText); - handleDiagnostics(host, diagnostics); - - println(`>>> TS config: ${JSON.stringify(options)}`); - - const program = ts.createProgram([rootSpecifier], options, host); - - handleDiagnostics( - host, - ts.getPreEmitDiagnostics(program).filter(({ code }) => { - // TS1063: An export assignment cannot be used in a namespace. - if (code === 1063) return false; - // TS2691: An import path cannot end with a '.ts' extension. Consider - // importing 'bad-module' instead. - if (code === 2691) return false; - // TS5009: Cannot find the common subdirectory path for the input files. - if (code === 5009) return false; - return true; - }), - ); - - const emitResult = program.emit(); - handleDiagnostics(host, emitResult.diagnostics); - - dispatch( - "op_set_emit_result", - Object.assign(emitResult, { tsVersion: ts.version }), - ); -} - -/** - * @param {...string} s - */ -function println(...s) { - Deno.core.print(s.join(" ") + "\n"); -} - -/** - * @returns {never} - */ -function unreachable() { - throw Error("unreachable"); -} - -/** - * @param {unknown} cond - * @returns {asserts cond} - */ -function assert(cond) { - if (!cond) { - throw Error("assert"); - } -} - -/** - * @param {Uint8Array | null} ui8 - */ -function decodeAscii(ui8) { - let out = ""; - if (!ui8) { - return out; - } - for (let i = 0; i < ui8.length; i++) { - out += String.fromCharCode(ui8[i]); - } - return out; -} - -/** - * @param {string} str - */ -function encode(str) { - const charCodes = str.split("").map((c) => c.charCodeAt(0)); - const ui8 = new Uint8Array(charCodes); - return ui8; -} - -/** **Warning!** Op ids must be acquired from Rust using `Deno.core.ops()` - * before dispatching any action. - * @type {Record<string, number>} - */ -let ops; - -/** - * @type {Map<string, string>} - */ -const moduleMap = new Map(); - -const externalSpecifierRegEx = /^file:\/{3}\S+\/js(\/\S+\.ts)$/; - -/** - * This is a minimal implementation of a compiler host to be able to allow the - * creation of runtime bundles. Some of the methods are implemented in a way - * to just appease the TypeScript compiler, not to necessarily be a general - * purpose implementation. - * - * @implements {ts.CompilerHost} - */ -class Host { - /** - * @param {string} _fileName - */ - fileExists(_fileName) { - return true; - } - - /** - * @param {string} _fileName - */ - readFile(_fileName) { - unreachable(); - } - - useCaseSensitiveFileNames() { - return false; - } - - /** - * @param {ts.CompilerOptions} _options - */ - getDefaultLibFileName(_options) { - return "lib.esnext.d.ts"; - } - - getDefaultLibLocation() { - return ASSETS; - } - - getCurrentDirectory() { - return "."; - } - - /** - * @param {string} fileName - * @param {ts.ScriptTarget} languageVersion - * @param {(message: string) => void} _onError - * @param {boolean} shouldCreateNewSourceFile - */ - getSourceFile( - fileName, - languageVersion, - _onError, - shouldCreateNewSourceFile, - ) { - assert(!shouldCreateNewSourceFile); // We haven't yet encountered this. - - // This hacks around the fact that TypeScript tries to magically guess the - // d.ts filename. - if (fileName.startsWith("$typeRoots$")) { - assert(fileName.startsWith("$typeRoots$/")); - assert(fileName.endsWith("/index.d.ts")); - fileName = fileName - .replace("$typeRoots$/", "") - .replace("/index.d.ts", ""); - } - - // This looks up any modules that have been mapped to internal names - const moduleUrl = moduleMap.has(fileName) - ? moduleMap.get(fileName) - : fileName; - - const { sourceCode } = dispatch("op_load_module", { - moduleUrl, - languageVersion, - shouldCreateNewSourceFile, - }); - - const sourceFile = ts.createSourceFile( - fileName, - sourceCode, - languageVersion, - ); - sourceFile.moduleName = fileName; - return sourceFile; - } - - /** - * @param {string} fileName - * @param {string} data - * @param {boolean} _writeByteOrderMark - * @param {((message: string) => void)?} _onError - * @param {ReadonlyArray<ts.SourceFile>?} sourceFiles - */ - writeFile( - fileName, - data, - _writeByteOrderMark, - _onError = null, - sourceFiles = null, - ) { - if (sourceFiles == null) { - return; - } - const moduleName = sourceFiles[sourceFiles.length - 1].moduleName; - return dispatch("op_write_file", { fileName, moduleName, data }); - } - - /** - * @param {string} _fileName - * @param {ts.Path} _path - * @param {ts.ScriptTarget} _languageVersion - * @param {*} _onError - * @param {boolean} _shouldCreateNewSourceFile - */ - getSourceFileByPath( - _fileName, - _path, - _languageVersion, - _onError, - _shouldCreateNewSourceFile, - ) { - unreachable(); - } - - /** - * @param {string} fileName - */ - getCanonicalFileName(fileName) { - return fileName; - } - - getNewLine() { - return "\n"; - } - - /** - * @param {string[]} moduleNames - * @param {string} containingFile - * @return {Array<ts.ResolvedModule | undefined>} - */ - resolveModuleNames(moduleNames, containingFile) { - // If the containing file is an internal specifier, map it back to the - // external specifier - containingFile = moduleMap.has(containingFile) - ? moduleMap.get(containingFile) - : containingFile; - /** @type {string[]} */ - const resolvedNames = dispatch("op_resolve_module_names", { - moduleNames, - containingFile, - }); - /** @type {ts.ResolvedModule[]} */ - const r = resolvedNames.map((resolvedFileName) => { - const extension = getExtension(resolvedFileName); - if (!moduleMap.has(resolvedFileName)) { - // If we match the external specifier regex, we will then create an internal - // specifier and then use that when creating the source file - const result = externalSpecifierRegEx.exec(resolvedFileName); - if (result) { - const [, specifier] = result; - const internalSpecifier = `$deno$${specifier}`; - moduleMap.set(internalSpecifier, resolvedFileName); - resolvedFileName = internalSpecifier; - } - } - return { resolvedFileName, extension }; - }); - return r; - } -} - -/** - * @param {string} configurationText - */ -function configure(configurationText) { - const { config, error } = ts.parseConfigFileTextToJson( - "tsconfig.json", - configurationText, - ); - if (error) { - return { options: {}, diagnostics: [error] }; - } - const { options, errors } = ts.convertCompilerOptionsFromJson( - config.compilerOptions, - "", - ); - return { - options, - diagnostics: errors.length ? errors : undefined, - }; -} - -/** - * @param {string} opName - * @param {Record<string,any>} obj - */ -function dispatch(opName, obj) { - const opId = ops[opName]; - - if (!opId) { - throw new Error(`Unknown op: ${opName}`); - } - - const s = JSON.stringify(obj); - const msg = encode(s); - const resUi8 = Deno.core.dispatch(opId, msg); - const resStr = decodeAscii(resUi8); - const res = JSON.parse(resStr); - if (!res["ok"]) { - throw Error(`${opName} failed ${res["err"]}. Args: ${JSON.stringify(obj)}`); - } - return res["ok"]; -} - -/** - * @param {number} code - */ -function exit(code) { - dispatch("op_exit2", { code }); - return unreachable(); -} - -// Maximum number of diagnostics to display. -const MAX_ERRORS = 5; - -/** - * @param {ts.CompilerHost} host - * @param {ReadonlyArray<ts.Diagnostic> | undefined} diagnostics - */ -function handleDiagnostics(host, diagnostics) { - if (diagnostics && diagnostics.length) { - let rest = 0; - if (diagnostics.length > MAX_ERRORS) { - rest = diagnostics.length - MAX_ERRORS; - diagnostics = diagnostics.slice(0, MAX_ERRORS); - } - const msg = ts.formatDiagnosticsWithColorAndContext(diagnostics, host); - println(msg); - if (rest) { - println(`And ${rest} other errors.`); - } - exit(1); - } -} - -/** Returns the TypeScript Extension enum for a given media type. - * @param {string} fileName - * @returns {ts.Extension} - */ -function getExtension(fileName) { - if (fileName.endsWith(".d.ts")) { - return ts.Extension.Dts; - } else if (fileName.endsWith(".ts")) { - return ts.Extension.Ts; - } else if (fileName.endsWith(".js")) { - return ts.Extension.Js; - } else { - throw TypeError(`Cannot resolve extension for ${fileName}`); - } -} diff --git a/deno_typescript/lib.rs b/deno_typescript/lib.rs index f64959e0d..f01993464 100644 --- a/deno_typescript/lib.rs +++ b/deno_typescript/lib.rs @@ -1,33 +1,17 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -#![deny(warnings)] - extern crate deno_core; extern crate serde; extern crate serde_json; -mod ops; -use deno_core::js_check; pub use deno_core::v8_set_flags; -use deno_core::CoreIsolate; use deno_core::CoreIsolateState; -use deno_core::ErrBox; -use deno_core::ModuleSpecifier; use deno_core::Op; use deno_core::OpDispatcher; -use deno_core::StartupData; use deno_core::ZeroCopyBuf; -pub use ops::EmitResult; -use ops::WrittenFile; use std::collections::HashMap; -use std::fs; -use std::path::Path; use std::path::PathBuf; -use std::sync::Arc; -use std::sync::Mutex; -static TYPESCRIPT_CODE: &str = include_str!("typescript/lib/typescript.js"); -static COMPILER_CODE: &str = include_str!("compiler_main.js"); -static SYSTEM_LOADER: &str = include_str!("system_loader.js"); +pub static TYPESCRIPT_CODE: &str = include_str!("typescript/lib/typescript.js"); pub fn ts_version() -> String { let data = include_str!("typescript/package.json"); @@ -35,221 +19,7 @@ pub fn ts_version() -> String { pkg["version"].as_str().unwrap().to_string() } -type ExternCrateModules = HashMap<String, String>; - -#[derive(Debug)] -pub struct TSState { - bundle: bool, - exit_code: i32, - emit_result: Option<EmitResult>, - /// A list of files emitted by typescript. WrittenFile is tuple of the form - /// (url, corresponding_module, source_code) - written_files: Vec<WrittenFile>, - extern_crate_modules: ExternCrateModules, -} - -fn compiler_op<D>( - ts_state: Arc<Mutex<TSState>>, - dispatcher: D, -) -> impl OpDispatcher -where - D: Fn(&mut TSState, &[u8]) -> Op, -{ - move |_state: &mut CoreIsolateState, - zero_copy_bufs: &mut [ZeroCopyBuf]| - -> Op { - assert_eq!(zero_copy_bufs.len(), 1, "Invalid number of arguments"); - let mut s = ts_state.lock().unwrap(); - dispatcher(&mut s, &zero_copy_bufs[0]) - } -} - -pub struct TSIsolate { - isolate: CoreIsolate, - state: Arc<Mutex<TSState>>, -} - -impl TSIsolate { - fn new( - bundle: bool, - maybe_extern_crate_modules: Option<ExternCrateModules>, - ) -> TSIsolate { - let mut isolate = CoreIsolate::new(StartupData::None, false); - js_check(isolate.execute("assets/typescript.js", TYPESCRIPT_CODE)); - js_check(isolate.execute("compiler_main.js", COMPILER_CODE)); - - let extern_crate_modules = maybe_extern_crate_modules.unwrap_or_default(); - - let state = Arc::new(Mutex::new(TSState { - bundle, - exit_code: 0, - emit_result: None, - written_files: Vec::new(), - extern_crate_modules, - })); - - isolate.register_op( - "op_load_module", - compiler_op(state.clone(), ops::json_op(ops::op_load_module)), - ); - isolate.register_op( - "op_exit2", - compiler_op(state.clone(), ops::json_op(ops::op_exit2)), - ); - isolate.register_op( - "op_write_file", - compiler_op(state.clone(), ops::json_op(ops::op_write_file)), - ); - isolate.register_op( - "op_resolve_module_names", - compiler_op(state.clone(), ops::json_op(ops::op_resolve_module_names)), - ); - isolate.register_op( - "op_set_emit_result", - compiler_op(state.clone(), ops::json_op(ops::op_set_emit_result)), - ); - - TSIsolate { isolate, state } - } - - // TODO(ry) Instead of Result<Arc<Mutex<TSState>>, ErrBox>, return something - // like Result<TSState, ErrBox>. I think it would be nicer if this function - // consumes TSIsolate. - /// Compiles each module to ESM. Doesn't write any files to disk. - /// Passes all output via state. - fn compile( - mut self, - config_json: &serde_json::Value, - root_names: Vec<String>, - ) -> Result<Arc<Mutex<TSState>>, ErrBox> { - let root_names_json = serde_json::json!(root_names).to_string(); - let source = - &format!("main({:?}, {})", config_json.to_string(), root_names_json); - self.isolate.execute("<anon>", source)?; - Ok(self.state) - } -} - -/// Compile provided roots into a single JS bundle. -/// -/// This function writes compiled bundle to disk at provided path. -/// -/// Source map file and type declaration file are emitted -/// alongside the bundle. -/// -/// To instantiate bundle use returned `module_name`. -pub fn compile_bundle( - bundle_filename: &Path, - root_names: Vec<PathBuf>, - extern_crate_modules: Option<ExternCrateModules>, -) -> Result<String, ErrBox> { - let ts_isolate = TSIsolate::new(true, extern_crate_modules); - - let config_json = serde_json::json!({ - "compilerOptions": { - "declaration": true, - // In order to help ensure there are no type directed emits in the code - // which interferes with transpiling only, the setting - // `"importsNotUsedAsValues"` set to `"error"` will help ensure that items - // that are written as `import type` are caught and are treated as errors. - "importsNotUsedAsValues": "error", - // Emit the source alongside the sourcemaps within a single file; - // requires --inlineSourceMap or --sourceMap to be set. - // "inlineSources": true, - "lib": ["esnext"], - "listEmittedFiles": true, - "listFiles": true, - "module": "system", - "outFile": bundle_filename, - "removeComments": true, - "sourceMap": true, - "strict": true, - "target": "esnext", - "typeRoots" : ["$typeRoots$"], - }, - }); - - let root_names_str: Vec<String> = root_names - .iter() - .map(|p| { - if !p.exists() { - panic!("File not found {}", p.display()); - } - - let module_specifier = - ModuleSpecifier::resolve_url_or_path(&p.to_string_lossy()).unwrap(); - module_specifier.as_str().to_string() - }) - .collect(); - - // TODO lift js_check to caller? - let locked_state = js_check(ts_isolate.compile(&config_json, root_names_str)); - let state = locked_state.lock().unwrap(); - // Assuming that TypeScript has emitted the main file last. - let main = state.written_files.last().unwrap(); - let module_name = main.module_name.clone(); - Ok(module_name) -} - -#[allow(dead_code)] -fn print_source_code(code: &str) { - let mut i = 1; - for line in code.lines() { - println!("{:3} {}", i, line); - i += 1; - } -} - -/// Create a V8 snapshot. -pub fn mksnapshot_bundle( - isolate: &mut CoreIsolate, - snapshot_filename: &Path, - bundle_filename: &Path, - main_module_name: &str, -) -> Result<(), ErrBox> { - js_check(isolate.execute("system_loader.js", SYSTEM_LOADER)); - let source_code_vec = std::fs::read(bundle_filename).unwrap(); - let bundle_source_code = std::str::from_utf8(&source_code_vec).unwrap(); - js_check( - isolate.execute(&bundle_filename.to_string_lossy(), bundle_source_code), - ); - let script = &format!("__instantiate(\"{}\", false);", main_module_name); - js_check(isolate.execute("anon", script)); - write_snapshot(isolate, snapshot_filename)?; - Ok(()) -} - -/// Create a V8 snapshot. This differs from mksnapshot_bundle in that is also -/// runs typescript.js -pub fn mksnapshot_bundle_ts( - isolate: &mut CoreIsolate, - snapshot_filename: &Path, - bundle_filename: &Path, - main_module_name: &str, -) -> Result<(), ErrBox> { - js_check(isolate.execute("typescript.js", TYPESCRIPT_CODE)); - mksnapshot_bundle( - isolate, - snapshot_filename, - bundle_filename, - main_module_name, - ) -} - -fn write_snapshot( - runtime_isolate: &mut CoreIsolate, - snapshot_filename: &Path, -) -> Result<(), ErrBox> { - println!("Creating snapshot..."); - let snapshot = runtime_isolate.snapshot(); - let snapshot_slice: &[u8] = &*snapshot; - println!("Snapshot size: {}", snapshot_slice.len()); - fs::write(&snapshot_filename, snapshot_slice)?; - println!("Snapshot written to: {} ", snapshot_filename.display()); - Ok(()) -} - -pub fn get_asset(name: &str) -> Option<&'static str> { +fn get_asset(name: &str) -> Option<&'static str> { macro_rules! inc { ($e:expr) => { Some(include_str!(concat!("typescript/lib/", $e))) @@ -324,16 +94,6 @@ pub fn get_asset(name: &str) -> Option<&'static str> { } } -/// Sets the --trace-serializer V8 flag for debugging snapshots. -pub fn trace_serializer() { - let dummy = "foo".to_string(); - let r = deno_core::v8_set_flags(vec![ - dummy.clone(), - "--trace-serializer".to_string(), - ]); - assert_eq!(r, vec![dummy]); -} - /// Warning: Returns a non-JSON op dispatcher. Must be manually attached to /// CoreIsolate. pub fn op_fetch_asset<S: ::std::hash::BuildHasher>( diff --git a/deno_typescript/ops.rs b/deno_typescript/ops.rs deleted file mode 100644 index f5904af1a..000000000 --- a/deno_typescript/ops.rs +++ /dev/null @@ -1,163 +0,0 @@ -use crate::TSState; -use deno_core::ErrBox; -use deno_core::ModuleSpecifier; -use deno_core::Op; -use serde::Deserialize; -use serde_json::json; -use serde_json::Value; - -#[derive(Clone, Debug)] -pub struct WrittenFile { - pub url: String, - pub module_name: String, - pub source_code: String, -} - -type Dispatcher = fn(state: &mut TSState, args: Value) -> Result<Value, ErrBox>; - -pub fn json_op(d: Dispatcher) -> impl Fn(&mut TSState, &[u8]) -> Op { - move |state: &mut TSState, control: &[u8]| { - let result = serde_json::from_slice(control) - .map_err(ErrBox::from) - .and_then(move |args| d(state, args)); - - let response = match result { - Ok(v) => json!({ "ok": v }), - Err(err) => json!({ "err": err.to_string() }), - }; - - let x = serde_json::to_string(&response).unwrap(); - let vec = x.into_bytes(); - Op::Sync(vec.into_boxed_slice()) - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct LoadModule { - module_url: String, - language_version: Option<i32>, - should_create_new_source_file: bool, -} - -pub fn op_load_module(s: &mut TSState, v: Value) -> Result<Value, ErrBox> { - let v: LoadModule = serde_json::from_value(v)?; - let (module_name, source_code) = if v.module_url.starts_with("$asset$/") { - let asset = v.module_url.replace("$asset$/", ""); - - let source_code = match crate::get_asset(&asset) { - Some(code) => code.to_string(), - None => { - return Err( - std::io::Error::new(std::io::ErrorKind::NotFound, "Asset not found") - .into(), - ); - } - }; - - (asset, source_code) - } else { - assert!(!v.module_url.starts_with("$assets$"), "you meant $asset$"); - let module_specifier = ModuleSpecifier::resolve_url_or_path(&v.module_url)?; - let module_url = module_specifier.as_url(); - match module_url.scheme() { - "file" => { - let path = module_url.to_file_path().unwrap(); - println!("cargo:rerun-if-changed={}", path.display()); - ( - module_specifier.as_str().to_string(), - std::fs::read_to_string(&path)?, - ) - } - "crate" => { - let crate_name = module_url.host_str().unwrap(); - // TODO(afinch7) turn failures here into real error messages. - let path_prefix = s.extern_crate_modules.get(crate_name).unwrap(); - let path = - std::path::Path::new(path_prefix).join(&module_url.path()[1..]); - ( - module_specifier.as_str().to_string(), - std::fs::read_to_string(&path)?, - ) - } - _ => unimplemented!(), - } - }; - Ok(json!({ - "moduleName": module_name, - "sourceCode": source_code, - })) -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct WriteFile { - file_name: String, - data: String, - module_name: String, -} - -pub fn op_write_file(s: &mut TSState, v: Value) -> Result<Value, ErrBox> { - let v: WriteFile = serde_json::from_value(v)?; - let module_specifier = ModuleSpecifier::resolve_url_or_path(&v.file_name)?; - if s.bundle { - std::fs::write(&v.file_name, &v.data)?; - } - s.written_files.push(WrittenFile { - url: module_specifier.as_str().to_string(), - module_name: v.module_name, - source_code: v.data, - }); - Ok(json!(true)) -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct ResolveModuleNames { - module_names: Vec<String>, - containing_file: String, -} - -pub fn op_resolve_module_names( - _s: &mut TSState, - v: Value, -) -> Result<Value, ErrBox> { - let v: ResolveModuleNames = serde_json::from_value(v).unwrap(); - let mut resolved = Vec::<String>::new(); - let referrer = ModuleSpecifier::resolve_url_or_path(&v.containing_file)?; - for specifier in v.module_names { - if specifier.starts_with("$asset$/") { - resolved.push(specifier.clone()); - } else { - let ms = ModuleSpecifier::resolve_import(&specifier, referrer.as_str())?; - resolved.push(ms.as_str().to_string()); - } - } - Ok(json!(resolved)) -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct Exit { - code: i32, -} - -pub fn op_exit2(s: &mut TSState, v: Value) -> Result<Value, ErrBox> { - let v: Exit = serde_json::from_value(v)?; - s.exit_code = v.code; - std::process::exit(v.code) -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EmitResult { - pub emit_skipped: bool, - pub diagnostics: Vec<String>, - pub emitted_files: Vec<String>, -} - -pub fn op_set_emit_result(s: &mut TSState, v: Value) -> Result<Value, ErrBox> { - let v: EmitResult = serde_json::from_value(v)?; - s.emit_result = Some(v); - Ok(json!(true)) -} |