diff options
Diffstat (limited to 'deno_typescript')
| -rw-r--r-- | deno_typescript/Cargo.toml | 24 | ||||
| -rw-r--r-- | deno_typescript/README.md | 8 | ||||
| -rw-r--r-- | deno_typescript/amd_runtime.js | 54 | ||||
| -rw-r--r-- | deno_typescript/compiler_main.js | 375 | ||||
| -rw-r--r-- | deno_typescript/lib.deno_core.d.ts | 66 | ||||
| -rw-r--r-- | deno_typescript/lib.rs | 299 | ||||
| -rw-r--r-- | deno_typescript/ops.rs | 137 | ||||
| m--------- | deno_typescript/typescript | 0 |
8 files changed, 963 insertions, 0 deletions
diff --git a/deno_typescript/Cargo.toml b/deno_typescript/Cargo.toml new file mode 100644 index 000000000..584572fb4 --- /dev/null +++ b/deno_typescript/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "deno_typescript" +version = "0.20.0" +license = "MIT" +description = "To compile TypeScript to a snapshot during build.rs" +repository = "https://github.com/denoland/deno" +authors = ["the Deno authors"] +edition = "2018" + +exclude = [ + "typescript/tests/*", + "typescript/src/*", + "typescript/scripts/*", + "typescript/doc/*", + "typescript/lib/*/*.json", +] + +[lib] +path = "lib.rs" + +[dependencies] +deno = { path = "../core", version = "0.20.0" } +serde_json = "1.0.40" +serde = { version = "1.0.100", features = ["derive"] } diff --git a/deno_typescript/README.md b/deno_typescript/README.md new file mode 100644 index 000000000..d51e88e72 --- /dev/null +++ b/deno_typescript/README.md @@ -0,0 +1,8 @@ +This crate provides utilities to compile typescript, bundle it up, and create a +V8 snapshot, all during build. This allows users to startup fast. + +The cli_snapshots crate, neighboring this one uses deno_typescript at build +time. + +This crate does not depend on Node, Python, nor any other external dependencies +besides those listed as such in Cargo.toml. diff --git a/deno_typescript/amd_runtime.js b/deno_typescript/amd_runtime.js new file mode 100644 index 000000000..1c4f0007a --- /dev/null +++ b/deno_typescript/amd_runtime.js @@ -0,0 +1,54 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +// A very very basic AMD preamble to support the output of TypeScript outFile +// bundles. + +/** + * @type {(name: string) => any} + */ +let require; + +/** + * @type {(name: string, deps: ReadonlyArray<string>, factory: (...deps: any[]) => void) => void} + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +let define; + +(function() { + /** + * @type {Map<string, { name: string, exports: any }>} + */ + const modules = new Map(); + + /** + * @param {string} name + */ + function createOrLoadModule(name) { + let m = modules.get(name); + if (!m) { + m = { name, exports: {} }; + modules.set(name, m); + } + return m; + } + + require = name => { + return createOrLoadModule(name).exports; + }; + + define = (name, deps, factory) => { + const currentModule = createOrLoadModule(name); + const localExports = currentModule.exports; + const args = deps.map(dep => { + if (dep === "require") { + return require; + } else if (dep === "exports") { + return localExports; + } else { + const depModule = createOrLoadModule(dep); + return depModule.exports; + } + }); + factory(...args); + }; +})(); diff --git a/deno_typescript/compiler_main.js b/deno_typescript/compiler_main.js new file mode 100644 index 000000000..fa155f12d --- /dev/null +++ b/deno_typescript/compiler_main.js @@ -0,0 +1,375 @@ +// Copyright 2018-2019 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. + +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 > 0); + + const { options, diagnostics } = configure(configText); + handleDiagnostics(host, diagnostics); + + println(`>>> TS config: ${JSON.stringify(options)}`); + + const program = ts.createProgram(rootNames, 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( + "setEmitResult", + 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 + */ +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(); + return undefined; + } + + useCaseSensitiveFileNames() { + return false; + } + + /** + * @param {ts.CompilerOptions} _options + */ + getDefaultLibFileName(_options) { + return "lib.deno_core.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 + if (moduleMap.has(fileName)) { + fileName = moduleMap.get(fileName); + } + + const { sourceCode, moduleName } = dispatch("readFile", { + fileName, + languageVersion, + shouldCreateNewSourceFile + }); + + // If we match the external specifier regex, we will then create an internal + // specifier and then use that when creating the source file + let internalModuleName = moduleName; + const result = externalSpecifierRegEx.exec(moduleName); + if (result) { + const [, specifier] = result; + const internalSpecifier = `$deno$${specifier}`; + moduleMap.set(internalSpecifier, moduleName); + internalModuleName = internalSpecifier; + } + + const sourceFile = ts.createSourceFile( + internalModuleName, + sourceCode, + languageVersion + ); + sourceFile.moduleName = internalModuleName; + 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("writeFile", { 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(); + return undefined; + } + + /** + * @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("resolveModuleNames", { + moduleNames, + containingFile + }); + /** @type {ts.ResolvedModule[]} */ + const r = resolvedNames.map(resolvedFileName => { + const extension = getExtension(resolvedFileName); + 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("exit", { 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.deno_core.d.ts b/deno_typescript/lib.deno_core.d.ts new file mode 100644 index 000000000..c3a20eb08 --- /dev/null +++ b/deno_typescript/lib.deno_core.d.ts @@ -0,0 +1,66 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +// This file contains APIs that are introduced into the global namespace by +// Deno core. These are not intended to be used directly by runtime users of +// Deno and therefore do not flow through to the runtime type library. + +declare interface MessageCallback { + (opId: number, msg: Uint8Array): void; +} + +interface EvalErrorInfo { + // Is the object thrown a native Error? + isNativeError: boolean; + // Was the error happened during compilation? + isCompileError: boolean; + // The actual thrown entity + // (might be an Error or anything else thrown by the user) + // If isNativeError is true, this is an Error + // eslint-disable-next-line @typescript-eslint/no-explicit-any + thrown: any; +} + +declare interface DenoCore { + print(s: string, isErr?: boolean); + dispatch( + opId: number, + control: Uint8Array, + zeroCopy?: ArrayBufferView | null + ): Uint8Array | null; + setAsyncHandler(cb: MessageCallback): void; + sharedQueue: { + head(): number; + numRecords(): number; + size(): number; + push(buf: Uint8Array): boolean; + reset(): void; + shift(): Uint8Array | null; + }; + + ops(): Record<string, number>; + + recv(cb: MessageCallback): void; + + send( + opId: number, + control: null | ArrayBufferView, + data?: ArrayBufferView + ): null | Uint8Array; + + shared: SharedArrayBuffer; + + /** Evaluate provided code in the current context. + * It differs from eval(...) in that it does not create a new context. + * Returns an array: [output, errInfo]. + * If an error occurs, `output` becomes null and `errInfo` is non-null. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + evalContext(code: string): [any, EvalErrorInfo | null]; + + errorToJSON: (e: Error) => string; +} + +declare interface DenoInterface { + core: DenoCore; +} +declare let Deno: DenoInterface; diff --git a/deno_typescript/lib.rs b/deno_typescript/lib.rs new file mode 100644 index 000000000..d130ee4c6 --- /dev/null +++ b/deno_typescript/lib.rs @@ -0,0 +1,299 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +extern crate deno; +extern crate serde; +extern crate serde_json; + +mod ops; +use deno::js_check; +pub use deno::v8_set_flags; +use deno::CoreOp; +use deno::ErrBox; +use deno::Isolate; +use deno::ModuleSpecifier; +use deno::PinnedBuf; +use deno::StartupData; +pub use ops::EmitResult; +use ops::WrittenFile; +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 AMD_RUNTIME_CODE: &str = include_str!("amd_runtime.js"); + +pub fn ts_version() -> String { + let data = include_str!("typescript/package.json"); + let pkg: serde_json::Value = serde_json::from_str(data).unwrap(); + pkg["version"].as_str().unwrap().to_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>, +} + +impl TSState { + fn main_module_name(&self) -> String { + // Assuming that TypeScript has emitted the main file last. + self.written_files.last().unwrap().module_name.clone() + } +} + +fn compiler_op<D>( + ts_state: Arc<Mutex<TSState>>, + dispatcher: D, +) -> impl Fn(&[u8], Option<PinnedBuf>) -> CoreOp +where + D: Fn(&mut TSState, &[u8]) -> CoreOp, +{ + move |control: &[u8], zero_copy_buf: Option<PinnedBuf>| -> CoreOp { + assert!(zero_copy_buf.is_none()); // zero_copy_buf unused in compiler. + let mut s = ts_state.lock().unwrap(); + dispatcher(&mut s, control) + } +} + +pub struct TSIsolate { + isolate: Isolate, + state: Arc<Mutex<TSState>>, +} + +impl TSIsolate { + fn new(bundle: bool) -> TSIsolate { + let mut isolate = Isolate::new(StartupData::None, false); + js_check(isolate.execute("assets/typescript.js", TYPESCRIPT_CODE)); + js_check(isolate.execute("compiler_main.js", COMPILER_CODE)); + + let state = Arc::new(Mutex::new(TSState { + bundle, + exit_code: 0, + emit_result: None, + written_files: Vec::new(), + })); + + isolate.register_op( + "readFile", + compiler_op(state.clone(), ops::json_op(ops::read_file)), + ); + isolate + .register_op("exit", compiler_op(state.clone(), ops::json_op(ops::exit))); + isolate.register_op( + "writeFile", + compiler_op(state.clone(), ops::json_op(ops::write_file)), + ); + isolate.register_op( + "resolveModuleNames", + compiler_op(state.clone(), ops::json_op(ops::resolve_module_names)), + ); + isolate.register_op( + "setEmitResult", + compiler_op(state.clone(), ops::json_op(ops::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.clone()) + } +} + +pub fn compile_bundle( + bundle: &Path, + root_names: Vec<PathBuf>, +) -> Result<Arc<Mutex<TSState>>, ErrBox> { + let ts_isolate = TSIsolate::new(true); + + let config_json = serde_json::json!({ + "compilerOptions": { + "declaration": true, + "lib": ["esnext"], + "module": "amd", + "target": "esnext", + "listFiles": true, + "listEmittedFiles": true, + // "types" : ["typescript.d.ts"], + "typeRoots" : ["$typeRoots$"], + // Emit the source alongside the sourcemaps within a single file; + // requires --inlineSourceMap or --sourceMap to be set. + // "inlineSources": true, + "sourceMap": true, + "outFile": bundle, + }, + }); + + let mut 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(); + root_names_str.push("$asset$/lib.deno_core.d.ts".to_string()); + + // TODO lift js_check to caller? + let state = js_check(ts_isolate.compile(&config_json, root_names_str)); + + Ok(state) +} + +#[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( + bundle: &Path, + state: Arc<Mutex<TSState>>, +) -> Result<(), ErrBox> { + let mut runtime_isolate = Isolate::new(StartupData::None, true); + let source_code_vec = std::fs::read(bundle)?; + let source_code = std::str::from_utf8(&source_code_vec)?; + + js_check(runtime_isolate.execute("amd_runtime.js", AMD_RUNTIME_CODE)); + js_check(runtime_isolate.execute(&bundle.to_string_lossy(), &source_code)); + + let main = state.lock().unwrap().main_module_name(); + js_check(runtime_isolate.execute("anon", &format!("require('{}')", main))); + + write_snapshot(runtime_isolate, bundle)?; + + Ok(()) +} + +/// Create a V8 snapshot. This differs from mksnapshot_bundle in that is also +/// runs typescript.js +pub fn mksnapshot_bundle_ts( + bundle: &Path, + state: Arc<Mutex<TSState>>, +) -> Result<(), ErrBox> { + let mut runtime_isolate = Isolate::new(StartupData::None, true); + let source_code_vec = std::fs::read(bundle)?; + let source_code = std::str::from_utf8(&source_code_vec)?; + + js_check(runtime_isolate.execute("amd_runtime.js", AMD_RUNTIME_CODE)); + js_check(runtime_isolate.execute("typescript.js", TYPESCRIPT_CODE)); + js_check(runtime_isolate.execute(&bundle.to_string_lossy(), &source_code)); + + let main = state.lock().unwrap().main_module_name(); + js_check(runtime_isolate.execute("anon", &format!("require('{}')", main))); + + write_snapshot(runtime_isolate, bundle)?; + + Ok(()) +} + +fn write_snapshot( + runtime_isolate: Isolate, + bundle: &Path, +) -> Result<(), ErrBox> { + println!("creating snapshot..."); + let snapshot = runtime_isolate.snapshot()?; + let snapshot_slice = + unsafe { std::slice::from_raw_parts(snapshot.data_ptr, snapshot.data_len) }; + println!("snapshot bytes {}", snapshot_slice.len()); + + let snapshot_path = bundle.with_extension("bin"); + + fs::write(&snapshot_path, snapshot_slice)?; + println!("snapshot path {} ", snapshot_path.display()); + Ok(()) +} + +/// Same as get_asset() but returns NotFound intead of None. +pub fn get_asset2(name: &str) -> Result<&'static str, ErrBox> { + match get_asset(name) { + Some(a) => Ok(a), + None => Err( + std::io::Error::new(std::io::ErrorKind::NotFound, "Asset not found") + .into(), + ), + } +} + +pub fn get_asset(name: &str) -> Option<&'static str> { + macro_rules! inc { + ($e:expr) => { + Some(include_str!(concat!("typescript/lib/", $e))) + }; + } + match name { + "lib.deno_core.d.ts" => Some(include_str!("lib.deno_core.d.ts")), + "typescript.d.ts" => inc!("typescript.d.ts"), + "lib.esnext.d.ts" => inc!("lib.esnext.d.ts"), + "lib.es2019.d.ts" => inc!("lib.es2019.d.ts"), + "lib.es2018.d.ts" => inc!("lib.es2018.d.ts"), + "lib.es2017.d.ts" => inc!("lib.es2017.d.ts"), + "lib.es2016.d.ts" => inc!("lib.es2016.d.ts"), + "lib.es5.d.ts" => inc!("lib.es5.d.ts"), + "lib.es2015.d.ts" => inc!("lib.es2015.d.ts"), + "lib.es2015.core.d.ts" => inc!("lib.es2015.core.d.ts"), + "lib.es2015.collection.d.ts" => inc!("lib.es2015.collection.d.ts"), + "lib.es2015.generator.d.ts" => inc!("lib.es2015.generator.d.ts"), + "lib.es2015.iterable.d.ts" => inc!("lib.es2015.iterable.d.ts"), + "lib.es2015.promise.d.ts" => inc!("lib.es2015.promise.d.ts"), + "lib.es2015.symbol.d.ts" => inc!("lib.es2015.symbol.d.ts"), + "lib.es2015.proxy.d.ts" => inc!("lib.es2015.proxy.d.ts"), + "lib.es2015.symbol.wellknown.d.ts" => { + inc!("lib.es2015.symbol.wellknown.d.ts") + } + "lib.es2015.reflect.d.ts" => inc!("lib.es2015.reflect.d.ts"), + "lib.es2016.array.include.d.ts" => inc!("lib.es2016.array.include.d.ts"), + "lib.es2017.object.d.ts" => inc!("lib.es2017.object.d.ts"), + "lib.es2017.sharedmemory.d.ts" => inc!("lib.es2017.sharedmemory.d.ts"), + "lib.es2017.string.d.ts" => inc!("lib.es2017.string.d.ts"), + "lib.es2017.intl.d.ts" => inc!("lib.es2017.intl.d.ts"), + "lib.es2017.typedarrays.d.ts" => inc!("lib.es2017.typedarrays.d.ts"), + "lib.es2018.asyncgenerator.d.ts" => inc!("lib.es2018.asyncgenerator.d.ts"), + "lib.es2018.asynciterable.d.ts" => inc!("lib.es2018.asynciterable.d.ts"), + "lib.es2018.promise.d.ts" => inc!("lib.es2018.promise.d.ts"), + "lib.es2018.regexp.d.ts" => inc!("lib.es2018.regexp.d.ts"), + "lib.es2018.intl.d.ts" => inc!("lib.es2018.intl.d.ts"), + "lib.es2019.array.d.ts" => inc!("lib.es2019.array.d.ts"), + "lib.es2019.object.d.ts" => inc!("lib.es2019.object.d.ts"), + "lib.es2019.string.d.ts" => inc!("lib.es2019.string.d.ts"), + "lib.es2019.symbol.d.ts" => inc!("lib.es2019.symbol.d.ts"), + "lib.esnext.bigint.d.ts" => inc!("lib.esnext.bigint.d.ts"), + "lib.esnext.intl.d.ts" => inc!("lib.esnext.intl.d.ts"), + _ => None, + } +} + +/// Sets the --trace-serializer V8 flag for debugging snapshots. +pub fn trace_serializer() { + let dummy = "foo".to_string(); + let r = + deno::v8_set_flags(vec![dummy.clone(), "--trace-serializer".to_string()]); + assert_eq!(r, vec![dummy]); +} diff --git a/deno_typescript/ops.rs b/deno_typescript/ops.rs new file mode 100644 index 000000000..7e7d24c72 --- /dev/null +++ b/deno_typescript/ops.rs @@ -0,0 +1,137 @@ +use crate::TSState; +use deno::CoreOp; +use deno::ErrBox; +use deno::ModuleSpecifier; +use deno::Op; +use serde::Deserialize; +use serde_json::json; +use serde_json::Value; + +#[derive(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]) -> CoreOp { + 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 ReadFile { + file_name: String, + language_version: Option<i32>, + should_create_new_source_file: bool, +} + +pub fn read_file(_s: &mut TSState, v: Value) -> Result<Value, ErrBox> { + let v: ReadFile = serde_json::from_value(v)?; + let (module_name, source_code) = if v.file_name.starts_with("$asset$/") { + let asset = v.file_name.replace("$asset$/", ""); + let source_code = crate::get_asset2(&asset)?.to_string(); + (asset, source_code) + } else { + assert!(!v.file_name.starts_with("$assets$"), "you meant $asset$"); + let module_specifier = ModuleSpecifier::resolve_url_or_path(&v.file_name)?; + let path = module_specifier.as_url().to_file_path().unwrap(); + println!("cargo:rerun-if-changed={}", path.display()); + ( + module_specifier.as_str().to_string(), + std::fs::read_to_string(&path)?, + ) + }; + 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 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 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 exit(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 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)) +} diff --git a/deno_typescript/typescript b/deno_typescript/typescript new file mode 160000 +Subproject 26655db1dd04d93217002ac87f950aba812c53e |
