mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
parent
374d433f1f
commit
10654fa955
15 changed files with 880 additions and 83 deletions
|
@ -1,9 +1,12 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
pub fn gen(v: &[&[u8]]) -> String {
|
use ring::digest::Context;
|
||||||
let mut ctx = ring::digest::Context::new(&ring::digest::SHA256);
|
use ring::digest::SHA256;
|
||||||
|
|
||||||
|
pub fn gen(v: &[impl AsRef<[u8]>]) -> String {
|
||||||
|
let mut ctx = Context::new(&SHA256);
|
||||||
for src in v {
|
for src in v {
|
||||||
ctx.update(src);
|
ctx.update(src.as_ref());
|
||||||
}
|
}
|
||||||
let digest = ctx.finish();
|
let digest = ctx.finish();
|
||||||
let out: Vec<String> = digest
|
let out: Vec<String> = digest
|
||||||
|
@ -13,3 +16,17 @@ pub fn gen(v: &[&[u8]]) -> String {
|
||||||
.collect();
|
.collect();
|
||||||
out.join("")
|
out.join("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gen() {
|
||||||
|
let actual = gen(&[b"hello world"]);
|
||||||
|
assert_eq!(
|
||||||
|
actual,
|
||||||
|
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ fn format_message(msg: &str, code: &u64) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum DiagnosticCategory {
|
pub enum DiagnosticCategory {
|
||||||
Warning,
|
Warning,
|
||||||
Error,
|
Error,
|
||||||
|
@ -172,7 +172,7 @@ impl From<i64> for DiagnosticCategory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DiagnosticMessageChain {
|
pub struct DiagnosticMessageChain {
|
||||||
message_text: String,
|
message_text: String,
|
||||||
|
@ -199,26 +199,26 @@ impl DiagnosticMessageChain {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
pub line: u64,
|
pub line: u64,
|
||||||
pub character: u64,
|
pub character: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Diagnostic {
|
pub struct Diagnostic {
|
||||||
category: DiagnosticCategory,
|
pub category: DiagnosticCategory,
|
||||||
code: u64,
|
pub code: u64,
|
||||||
start: Option<Position>,
|
pub start: Option<Position>,
|
||||||
end: Option<Position>,
|
pub end: Option<Position>,
|
||||||
message_text: Option<String>,
|
pub message_text: Option<String>,
|
||||||
message_chain: Option<DiagnosticMessageChain>,
|
pub message_chain: Option<DiagnosticMessageChain>,
|
||||||
source: Option<String>,
|
pub source: Option<String>,
|
||||||
source_line: Option<String>,
|
pub source_line: Option<String>,
|
||||||
file_name: Option<String>,
|
pub file_name: Option<String>,
|
||||||
related_information: Option<Vec<Diagnostic>>,
|
pub related_information: Option<Vec<Diagnostic>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Diagnostic {
|
impl Diagnostic {
|
||||||
|
@ -346,7 +346,7 @@ impl fmt::Display for Diagnostic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct Diagnostics(pub Vec<Diagnostic>);
|
pub struct Diagnostics(pub Vec<Diagnostic>);
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Diagnostics {
|
impl<'de> Deserialize<'de> for Diagnostics {
|
||||||
|
|
|
@ -57,7 +57,7 @@ fn compiler_snapshot() {
|
||||||
.execute(
|
.execute(
|
||||||
"<anon>",
|
"<anon>",
|
||||||
r#"
|
r#"
|
||||||
if (!(bootstrapCompilerRuntime)) {
|
if (!(startup)) {
|
||||||
throw Error("bad");
|
throw Error("bad");
|
||||||
}
|
}
|
||||||
console.log(`ts version: ${ts.version}`);
|
console.log(`ts version: ${ts.version}`);
|
||||||
|
|
|
@ -51,6 +51,7 @@ mod test_runner;
|
||||||
mod text_encoding;
|
mod text_encoding;
|
||||||
mod tokio_util;
|
mod tokio_util;
|
||||||
mod tsc;
|
mod tsc;
|
||||||
|
pub mod tsc2;
|
||||||
mod tsc_config;
|
mod tsc_config;
|
||||||
mod upgrade;
|
mod upgrade;
|
||||||
mod version;
|
mod version;
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::path::PathBuf;
|
||||||
// Update carefully!
|
// Update carefully!
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||||
pub enum MediaType {
|
pub enum MediaType {
|
||||||
JavaScript = 0,
|
JavaScript = 0,
|
||||||
JSX = 1,
|
JSX = 1,
|
||||||
|
@ -19,7 +19,9 @@ pub enum MediaType {
|
||||||
TSX = 4,
|
TSX = 4,
|
||||||
Json = 5,
|
Json = 5,
|
||||||
Wasm = 6,
|
Wasm = 6,
|
||||||
Unknown = 8,
|
TsBuildInfo = 7,
|
||||||
|
SourceMap = 8,
|
||||||
|
Unknown = 9,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for MediaType {
|
impl fmt::Display for MediaType {
|
||||||
|
@ -32,6 +34,8 @@ impl fmt::Display for MediaType {
|
||||||
MediaType::TSX => "TSX",
|
MediaType::TSX => "TSX",
|
||||||
MediaType::Json => "Json",
|
MediaType::Json => "Json",
|
||||||
MediaType::Wasm => "Wasm",
|
MediaType::Wasm => "Wasm",
|
||||||
|
MediaType::TsBuildInfo => "TsBuildInfo",
|
||||||
|
MediaType::SourceMap => "SourceMap",
|
||||||
MediaType::Unknown => "Unknown",
|
MediaType::Unknown => "Unknown",
|
||||||
};
|
};
|
||||||
write!(f, "{}", value)
|
write!(f, "{}", value)
|
||||||
|
@ -56,10 +60,22 @@ impl<'a> From<&'a String> for MediaType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for MediaType {
|
||||||
|
fn default() -> Self {
|
||||||
|
MediaType::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MediaType {
|
impl MediaType {
|
||||||
fn from_path(path: &Path) -> Self {
|
fn from_path(path: &Path) -> Self {
|
||||||
match path.extension() {
|
match path.extension() {
|
||||||
None => MediaType::Unknown,
|
None => match path.file_name() {
|
||||||
|
None => MediaType::Unknown,
|
||||||
|
Some(os_str) => match os_str.to_str() {
|
||||||
|
Some(".tsbuildinfo") => MediaType::TsBuildInfo,
|
||||||
|
_ => MediaType::Unknown,
|
||||||
|
},
|
||||||
|
},
|
||||||
Some(os_str) => match os_str.to_str() {
|
Some(os_str) => match os_str.to_str() {
|
||||||
Some("ts") => MediaType::TypeScript,
|
Some("ts") => MediaType::TypeScript,
|
||||||
Some("tsx") => MediaType::TSX,
|
Some("tsx") => MediaType::TSX,
|
||||||
|
@ -69,10 +85,42 @@ impl MediaType {
|
||||||
Some("cjs") => MediaType::JavaScript,
|
Some("cjs") => MediaType::JavaScript,
|
||||||
Some("json") => MediaType::Json,
|
Some("json") => MediaType::Json,
|
||||||
Some("wasm") => MediaType::Wasm,
|
Some("wasm") => MediaType::Wasm,
|
||||||
|
Some("tsbuildinfo") => MediaType::TsBuildInfo,
|
||||||
|
Some("map") => MediaType::SourceMap,
|
||||||
_ => MediaType::Unknown,
|
_ => MediaType::Unknown,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a MediaType to a `ts.Extension`.
|
||||||
|
///
|
||||||
|
/// *NOTE* This is defined in TypeScript as a string based enum. Changes to
|
||||||
|
/// that enum in TypeScript should be reflected here.
|
||||||
|
pub fn as_ts_extension(&self) -> String {
|
||||||
|
let ext = match self {
|
||||||
|
MediaType::JavaScript => ".js",
|
||||||
|
MediaType::JSX => ".jsx",
|
||||||
|
MediaType::TypeScript => ".ts",
|
||||||
|
MediaType::Dts => ".d.ts",
|
||||||
|
MediaType::TSX => ".tsx",
|
||||||
|
MediaType::Json => ".json",
|
||||||
|
// TypeScript doesn't have an "unknown", so we will treat WASM as JS for
|
||||||
|
// mapping purposes, though in reality, it is unlikely to ever be passed
|
||||||
|
// to the compiler.
|
||||||
|
MediaType::Wasm => ".js",
|
||||||
|
MediaType::TsBuildInfo => ".tsbuildinfo",
|
||||||
|
// TypeScript doesn't have an "source map", so we will treat SourceMap as
|
||||||
|
// JS for mapping purposes, though in reality, it is unlikely to ever be
|
||||||
|
// passed to the compiler.
|
||||||
|
MediaType::SourceMap => ".js",
|
||||||
|
// TypeScript doesn't have an "unknown", so we will treat WASM as JS for
|
||||||
|
// mapping purposes, though in reality, it is unlikely to ever be passed
|
||||||
|
// to the compiler.
|
||||||
|
MediaType::Unknown => ".js",
|
||||||
|
};
|
||||||
|
|
||||||
|
ext.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for MediaType {
|
impl Serialize for MediaType {
|
||||||
|
@ -88,7 +136,9 @@ impl Serialize for MediaType {
|
||||||
MediaType::TSX => 4 as i32,
|
MediaType::TSX => 4 as i32,
|
||||||
MediaType::Json => 5 as i32,
|
MediaType::Json => 5 as i32,
|
||||||
MediaType::Wasm => 6 as i32,
|
MediaType::Wasm => 6 as i32,
|
||||||
MediaType::Unknown => 8 as i32,
|
MediaType::TsBuildInfo => 7 as i32,
|
||||||
|
MediaType::SourceMap => 8 as i32,
|
||||||
|
MediaType::Unknown => 9 as i32,
|
||||||
};
|
};
|
||||||
Serialize::serialize(&value, serializer)
|
Serialize::serialize(&value, serializer)
|
||||||
}
|
}
|
||||||
|
@ -132,6 +182,14 @@ mod tests {
|
||||||
MediaType::from(Path::new("foo/bar.cjs")),
|
MediaType::from(Path::new("foo/bar.cjs")),
|
||||||
MediaType::JavaScript
|
MediaType::JavaScript
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
MediaType::from(Path::new("foo/.tsbuildinfo")),
|
||||||
|
MediaType::TsBuildInfo
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
MediaType::from(Path::new("foo/bar.js.map")),
|
||||||
|
MediaType::SourceMap
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
MediaType::from(Path::new("foo/bar.txt")),
|
MediaType::from(Path::new("foo/bar.txt")),
|
||||||
MediaType::Unknown
|
MediaType::Unknown
|
||||||
|
@ -148,7 +206,9 @@ mod tests {
|
||||||
assert_eq!(json!(MediaType::TSX), json!(4));
|
assert_eq!(json!(MediaType::TSX), json!(4));
|
||||||
assert_eq!(json!(MediaType::Json), json!(5));
|
assert_eq!(json!(MediaType::Json), json!(5));
|
||||||
assert_eq!(json!(MediaType::Wasm), json!(6));
|
assert_eq!(json!(MediaType::Wasm), json!(6));
|
||||||
assert_eq!(json!(MediaType::Unknown), json!(8));
|
assert_eq!(json!(MediaType::TsBuildInfo), json!(7));
|
||||||
|
assert_eq!(json!(MediaType::SourceMap), json!(8));
|
||||||
|
assert_eq!(json!(MediaType::Unknown), json!(9));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -160,6 +220,8 @@ mod tests {
|
||||||
assert_eq!(format!("{}", MediaType::TSX), "TSX");
|
assert_eq!(format!("{}", MediaType::TSX), "TSX");
|
||||||
assert_eq!(format!("{}", MediaType::Json), "Json");
|
assert_eq!(format!("{}", MediaType::Json), "Json");
|
||||||
assert_eq!(format!("{}", MediaType::Wasm), "Wasm");
|
assert_eq!(format!("{}", MediaType::Wasm), "Wasm");
|
||||||
|
assert_eq!(format!("{}", MediaType::TsBuildInfo), "TsBuildInfo");
|
||||||
|
assert_eq!(format!("{}", MediaType::SourceMap), "SourceMap");
|
||||||
assert_eq!(format!("{}", MediaType::Unknown), "Unknown");
|
assert_eq!(format!("{}", MediaType::Unknown), "Unknown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -465,7 +465,7 @@ impl ModuleGraphLoader {
|
||||||
filename: source_file.filename.to_str().unwrap().to_string(),
|
filename: source_file.filename.to_str().unwrap().to_string(),
|
||||||
version_hash: checksum::gen(&[
|
version_hash: checksum::gen(&[
|
||||||
&source_file.source_code.as_bytes(),
|
&source_file.source_code.as_bytes(),
|
||||||
version::DENO.as_bytes(),
|
&version::DENO.as_bytes(),
|
||||||
]),
|
]),
|
||||||
media_type: source_file.media_type,
|
media_type: source_file.media_type,
|
||||||
source_code: "".to_string(),
|
source_code: "".to_string(),
|
||||||
|
@ -481,7 +481,7 @@ impl ModuleGraphLoader {
|
||||||
let module_specifier = ModuleSpecifier::from(source_file.url.clone());
|
let module_specifier = ModuleSpecifier::from(source_file.url.clone());
|
||||||
let version_hash = checksum::gen(&[
|
let version_hash = checksum::gen(&[
|
||||||
&source_file.source_code.as_bytes(),
|
&source_file.source_code.as_bytes(),
|
||||||
version::DENO.as_bytes(),
|
&version::DENO.as_bytes(),
|
||||||
]);
|
]);
|
||||||
let source_code = source_file.source_code.clone();
|
let source_code = source_file.source_code.clone();
|
||||||
|
|
||||||
|
|
|
@ -373,8 +373,8 @@ impl Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct Stats(Vec<(String, u128)>);
|
pub struct Stats(pub Vec<(String, u128)>);
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Stats {
|
impl<'de> Deserialize<'de> for Stats {
|
||||||
fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
|
||||||
|
@ -572,6 +572,27 @@ impl Graph2 {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_media_type(
|
||||||
|
&self,
|
||||||
|
specifier: &ModuleSpecifier,
|
||||||
|
) -> Option<MediaType> {
|
||||||
|
if let Some(module) = self.modules.get(specifier) {
|
||||||
|
Some(module.media_type)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the source for a given module specifier. If the module is not part
|
||||||
|
/// of the graph, the result will be `None`.
|
||||||
|
pub fn get_source(&self, specifier: &ModuleSpecifier) -> Option<String> {
|
||||||
|
if let Some(module) = self.modules.get(specifier) {
|
||||||
|
Some(module.source.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Verify the subresource integrity of the graph based upon the optional
|
/// Verify the subresource integrity of the graph based upon the optional
|
||||||
/// lockfile, updating the lockfile with any missing resources. This will
|
/// lockfile, updating the lockfile with any missing resources. This will
|
||||||
/// error if any of the resources do not match their lock status.
|
/// error if any of the resources do not match their lock status.
|
||||||
|
@ -595,6 +616,56 @@ impl Graph2 {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given a string specifier and a referring module specifier, provide the
|
||||||
|
/// resulting module specifier and media type for the module that is part of
|
||||||
|
/// the graph.
|
||||||
|
pub fn resolve(
|
||||||
|
&self,
|
||||||
|
specifier: &str,
|
||||||
|
referrer: &ModuleSpecifier,
|
||||||
|
) -> Result<ModuleSpecifier, AnyError> {
|
||||||
|
if !self.modules.contains_key(referrer) {
|
||||||
|
return Err(MissingSpecifier(referrer.to_owned()).into());
|
||||||
|
}
|
||||||
|
let module = self.modules.get(referrer).unwrap();
|
||||||
|
if !module.dependencies.contains_key(specifier) {
|
||||||
|
return Err(
|
||||||
|
MissingDependency(referrer.to_owned(), specifier.to_owned()).into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let dependency = module.dependencies.get(specifier).unwrap();
|
||||||
|
// If there is a @deno-types pragma that impacts the dependency, then the
|
||||||
|
// maybe_type property will be set with that specifier, otherwise we use the
|
||||||
|
// specifier that point to the runtime code.
|
||||||
|
let resolved_specifier =
|
||||||
|
if let Some(type_specifier) = dependency.maybe_type.clone() {
|
||||||
|
type_specifier
|
||||||
|
} else if let Some(code_specifier) = dependency.maybe_code.clone() {
|
||||||
|
code_specifier
|
||||||
|
} else {
|
||||||
|
return Err(
|
||||||
|
MissingDependency(referrer.to_owned(), specifier.to_owned()).into(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
if !self.modules.contains_key(&resolved_specifier) {
|
||||||
|
return Err(
|
||||||
|
MissingDependency(referrer.to_owned(), resolved_specifier.to_string())
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let dep_module = self.modules.get(&resolved_specifier).unwrap();
|
||||||
|
// In the case that there is a X-TypeScript-Types or a triple-slash types,
|
||||||
|
// then the `maybe_types` specifier will be populated and we should use that
|
||||||
|
// instead.
|
||||||
|
let result = if let Some((_, types)) = dep_module.maybe_types.clone() {
|
||||||
|
types
|
||||||
|
} else {
|
||||||
|
resolved_specifier
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
/// Transpile (only transform) the graph, updating any emitted modules
|
/// Transpile (only transform) the graph, updating any emitted modules
|
||||||
/// with the specifier handler. The result contains any performance stats
|
/// with the specifier handler. The result contains any performance stats
|
||||||
/// from the compiler and optionally any user provided configuration compiler
|
/// from the compiler and optionally any user provided configuration compiler
|
||||||
|
@ -798,7 +869,7 @@ impl GraphBuilder2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use deno_core::futures::future;
|
use deno_core::futures::future;
|
||||||
|
|
1
cli/tests/tsc2/file_main.ts
Normal file
1
cli/tests/tsc2/file_main.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
console.log("hello deno");
|
3
cli/tests/tsc2/https_deno.land-x-a.ts
Normal file
3
cli/tests/tsc2/https_deno.land-x-a.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import * as b from "./b.ts";
|
||||||
|
|
||||||
|
console.log(b);
|
1
cli/tests/tsc2/https_deno.land-x-b.ts
Normal file
1
cli/tests/tsc2/https_deno.land-x-b.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const b = "b";
|
1
cli/tests/tsc2/https_deno.land-x-mod.ts
Normal file
1
cli/tests/tsc2/https_deno.land-x-mod.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
console.log("hello deno");
|
|
@ -417,7 +417,7 @@ impl TsCompiler {
|
||||||
{
|
{
|
||||||
let existing_hash = crate::checksum::gen(&[
|
let existing_hash = crate::checksum::gen(&[
|
||||||
&source_file.source_code.as_bytes(),
|
&source_file.source_code.as_bytes(),
|
||||||
version::DENO.as_bytes(),
|
&version::DENO.as_bytes(),
|
||||||
]);
|
]);
|
||||||
let expected_hash =
|
let expected_hash =
|
||||||
file_info["version"].as_str().unwrap().to_string();
|
file_info["version"].as_str().unwrap().to_string();
|
||||||
|
@ -988,7 +988,7 @@ fn execute_in_tsc(
|
||||||
}
|
}
|
||||||
|
|
||||||
let bootstrap_script = format!(
|
let bootstrap_script = format!(
|
||||||
"globalThis.bootstrapCompilerRuntime({{ debugFlag: {} }})",
|
"globalThis.startup({{ debugFlag: {}, legacy: true }})",
|
||||||
debug_flag
|
debug_flag
|
||||||
);
|
);
|
||||||
js_runtime.execute("<compiler>", &bootstrap_script)?;
|
js_runtime.execute("<compiler>", &bootstrap_script)?;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// that is created when Deno needs to compile TS/WASM to JS.
|
// that is created when Deno needs to compile TS/WASM to JS.
|
||||||
//
|
//
|
||||||
// It provides two functions that should be called by Rust:
|
// It provides two functions that should be called by Rust:
|
||||||
// - `bootstrapCompilerRuntime`
|
// - `startup`
|
||||||
// This functions must be called when creating isolate
|
// This functions must be called when creating isolate
|
||||||
// to properly setup runtime.
|
// to properly setup runtime.
|
||||||
// - `tsCompilerOnMessage`
|
// - `tsCompilerOnMessage`
|
||||||
|
@ -54,6 +54,9 @@ delete Object.prototype.__proto__;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @type {Map<string, ts.SourceFile>} */
|
||||||
|
const sourceFileCache = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../dts/typescript").DiagnosticRelatedInformation} diagnostic
|
* @param {import("../dts/typescript").DiagnosticRelatedInformation} diagnostic
|
||||||
*/
|
*/
|
||||||
|
@ -296,15 +299,15 @@ delete Object.prototype.__proto__;
|
||||||
debug(`host.fileExists("${fileName}")`);
|
debug(`host.fileExists("${fileName}")`);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
readFile(fileName) {
|
readFile(specifier) {
|
||||||
debug(`host.readFile("${fileName}")`);
|
debug(`host.readFile("${specifier}")`);
|
||||||
if (legacy) {
|
if (legacy) {
|
||||||
if (fileName == TS_BUILD_INFO) {
|
if (specifier == TS_BUILD_INFO) {
|
||||||
return legacyHostState.buildInfo;
|
return legacyHostState.buildInfo;
|
||||||
}
|
}
|
||||||
return unreachable();
|
return unreachable();
|
||||||
} else {
|
} else {
|
||||||
return core.jsonOpSync("op_read_file", { fileName }).data;
|
return core.jsonOpSync("op_load", { specifier }).data;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getSourceFile(
|
getSourceFile(
|
||||||
|
@ -338,6 +341,14 @@ delete Object.prototype.__proto__;
|
||||||
);
|
);
|
||||||
sourceFile.tsSourceFile.version = sourceFile.versionHash;
|
sourceFile.tsSourceFile.version = sourceFile.versionHash;
|
||||||
delete sourceFile.sourceCode;
|
delete sourceFile.sourceCode;
|
||||||
|
|
||||||
|
// This code is to support transition from the "legacy" compiler
|
||||||
|
// to the new one, by populating the new source file cache.
|
||||||
|
if (
|
||||||
|
!sourceFileCache.has(specifier) && specifier.startsWith(ASSETS)
|
||||||
|
) {
|
||||||
|
sourceFileCache.set(specifier, sourceFile.tsSourceFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return sourceFile.tsSourceFile;
|
return sourceFile.tsSourceFile;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -349,37 +360,26 @@ delete Object.prototype.__proto__;
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const sourceFile = sourceFileCache.get(specifier);
|
let sourceFile = sourceFileCache.get(specifier);
|
||||||
if (sourceFile) {
|
if (sourceFile) {
|
||||||
return sourceFile;
|
return sourceFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
/** @type {{ data: string; hash: string; }} */
|
||||||
/** @type {{ data: string; hash: string; }} */
|
const { data, hash } = core.jsonOpSync(
|
||||||
const { data, hash } = core.jsonOpSync(
|
"op_load",
|
||||||
"op_load_module",
|
{ specifier },
|
||||||
{ specifier },
|
);
|
||||||
);
|
assert(data, `"data" is unexpectedly null for "${specifier}".`);
|
||||||
const sourceFile = ts.createSourceFile(
|
sourceFile = ts.createSourceFile(
|
||||||
specifier,
|
specifier,
|
||||||
data,
|
data,
|
||||||
languageVersion,
|
languageVersion,
|
||||||
);
|
);
|
||||||
sourceFile.moduleName = specifier;
|
sourceFile.moduleName = specifier;
|
||||||
sourceFile.version = hash;
|
sourceFile.version = hash;
|
||||||
sourceFileCache.set(specifier, sourceFile);
|
sourceFileCache.set(specifier, sourceFile);
|
||||||
return sourceFile;
|
return sourceFile;
|
||||||
} catch (err) {
|
|
||||||
const message = err instanceof Error
|
|
||||||
? err.message
|
|
||||||
: JSON.stringify(err);
|
|
||||||
debug(` !! error: ${message}`);
|
|
||||||
if (onError) {
|
|
||||||
onError(message);
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getDefaultLibFileName() {
|
getDefaultLibFileName() {
|
||||||
|
@ -392,7 +392,7 @@ delete Object.prototype.__proto__;
|
||||||
return `${ASSETS}/lib.deno.worker.d.ts`;
|
return `${ASSETS}/lib.deno.worker.d.ts`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return `lib.esnext.d.ts`;
|
return `${ASSETS}/lib.esnext.d.ts`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getDefaultLibLocation() {
|
getDefaultLibLocation() {
|
||||||
|
@ -403,16 +403,14 @@ delete Object.prototype.__proto__;
|
||||||
if (legacy) {
|
if (legacy) {
|
||||||
legacyHostState.writeFile(fileName, data, sourceFiles);
|
legacyHostState.writeFile(fileName, data, sourceFiles);
|
||||||
} else {
|
} else {
|
||||||
let maybeModuleName;
|
let maybeSpecifiers;
|
||||||
if (sourceFiles) {
|
if (sourceFiles) {
|
||||||
assert(sourceFiles.length === 1, "unexpected number of source files");
|
maybeSpecifiers = sourceFiles.map((sf) => sf.moduleName);
|
||||||
const [sourceFile] = sourceFiles;
|
debug(` specifiers: ${maybeSpecifiers.join(", ")}`);
|
||||||
maybeModuleName = sourceFile.moduleName;
|
|
||||||
debug(` moduleName: ${maybeModuleName}`);
|
|
||||||
}
|
}
|
||||||
return core.jsonOpSync(
|
return core.jsonOpSync(
|
||||||
"op_write_file",
|
"op_emit",
|
||||||
{ maybeModuleName, fileName, data },
|
{ maybeSpecifiers, fileName, data },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -463,7 +461,7 @@ delete Object.prototype.__proto__;
|
||||||
return resolved;
|
return resolved;
|
||||||
} else {
|
} else {
|
||||||
/** @type {Array<[string, import("../dts/typescript").Extension]>} */
|
/** @type {Array<[string, import("../dts/typescript").Extension]>} */
|
||||||
const resolved = core.jsonOpSync("op_resolve_specifiers", {
|
const resolved = core.jsonOpSync("op_resolve", {
|
||||||
specifiers,
|
specifiers,
|
||||||
base,
|
base,
|
||||||
});
|
});
|
||||||
|
@ -737,6 +735,7 @@ delete Object.prototype.__proto__;
|
||||||
1208,
|
1208,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** @type {Array<{ key: string, value: number }>} */
|
||||||
const stats = [];
|
const stats = [];
|
||||||
let statsStart = 0;
|
let statsStart = 0;
|
||||||
|
|
||||||
|
@ -779,7 +778,6 @@ delete Object.prototype.__proto__;
|
||||||
}
|
}
|
||||||
|
|
||||||
function performanceEnd() {
|
function performanceEnd() {
|
||||||
// TODO(kitsonk) replace with performance.measure() when landed
|
|
||||||
const duration = new Date() - statsStart;
|
const duration = new Date() - statsStart;
|
||||||
stats.push({ key: "Compile time", value: duration });
|
stats.push({ key: "Compile time", value: duration });
|
||||||
return stats;
|
return stats;
|
||||||
|
@ -1328,18 +1326,73 @@ delete Object.prototype.__proto__;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasBootstrapped = false;
|
/**
|
||||||
|
* @typedef {object} Request
|
||||||
|
* @property {Record<string, any>} config
|
||||||
|
* @property {boolean} debug
|
||||||
|
* @property {string[]} rootNames
|
||||||
|
*/
|
||||||
|
|
||||||
function bootstrapCompilerRuntime({ debugFlag }) {
|
/** The API that is called by Rust when executing a request.
|
||||||
if (hasBootstrapped) {
|
* @param {Request} request
|
||||||
throw new Error("Worker runtime already bootstrapped");
|
*/
|
||||||
}
|
function exec({ config, debug: debugFlag, rootNames }) {
|
||||||
hasBootstrapped = true;
|
setLogDebug(debugFlag, "TS");
|
||||||
delete globalThis.__bootstrap;
|
performanceStart();
|
||||||
core.ops();
|
debug(">>> exec start", { rootNames });
|
||||||
setLogDebug(!!debugFlag, "TS");
|
debug(config);
|
||||||
|
|
||||||
|
const { options, errors: configFileParsingDiagnostics } = ts
|
||||||
|
.convertCompilerOptionsFromJson(config, "", "tsconfig.json");
|
||||||
|
const program = ts.createIncrementalProgram({
|
||||||
|
rootNames,
|
||||||
|
options,
|
||||||
|
host,
|
||||||
|
configFileParsingDiagnostics,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { diagnostics: emitDiagnostics } = program.emit();
|
||||||
|
|
||||||
|
const diagnostics = [
|
||||||
|
...program.getConfigFileParsingDiagnostics(),
|
||||||
|
...program.getSyntacticDiagnostics(),
|
||||||
|
...program.getOptionsDiagnostics(),
|
||||||
|
...program.getGlobalDiagnostics(),
|
||||||
|
...program.getSemanticDiagnostics(),
|
||||||
|
...emitDiagnostics,
|
||||||
|
].filter(({ code }) =>
|
||||||
|
!IGNORED_DIAGNOSTICS.includes(code) &&
|
||||||
|
!IGNORED_COMPILE_DIAGNOSTICS.includes(code)
|
||||||
|
);
|
||||||
|
performanceProgram({ program });
|
||||||
|
|
||||||
|
// TODO(@kitsonk) when legacy stats are removed, convert to just tuples
|
||||||
|
let stats = performanceEnd().map(({ key, value }) => [key, value]);
|
||||||
|
core.jsonOpSync("op_respond", {
|
||||||
|
diagnostics: fromTypeScriptDiagnostic(diagnostics),
|
||||||
|
stats,
|
||||||
|
});
|
||||||
|
debug("<<< exec stop");
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.bootstrapCompilerRuntime = bootstrapCompilerRuntime;
|
let hasStarted = false;
|
||||||
|
|
||||||
|
/** Startup the runtime environment, setting various flags.
|
||||||
|
* @param {{ debugFlag?: boolean; legacyFlag?: boolean; }} msg
|
||||||
|
*/
|
||||||
|
function startup({ debugFlag = false, legacyFlag = true }) {
|
||||||
|
if (hasStarted) {
|
||||||
|
throw new Error("The compiler runtime already started.");
|
||||||
|
}
|
||||||
|
hasStarted = true;
|
||||||
|
core.ops();
|
||||||
|
core.registerErrorClass("Error", Error);
|
||||||
|
setLogDebug(!!debugFlag, "TS");
|
||||||
|
legacy = legacyFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalThis.startup = startup;
|
||||||
|
globalThis.exec = exec;
|
||||||
|
// TODO(@kitsonk) remove when converted from legacy tsc
|
||||||
globalThis.tsCompilerOnMessage = tsCompilerOnMessage;
|
globalThis.tsCompilerOnMessage = tsCompilerOnMessage;
|
||||||
})(this);
|
})(this);
|
||||||
|
|
584
cli/tsc2.rs
Normal file
584
cli/tsc2.rs
Normal file
|
@ -0,0 +1,584 @@
|
||||||
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use crate::diagnostics::Diagnostics;
|
||||||
|
use crate::media_type::MediaType;
|
||||||
|
use crate::module_graph2::Graph2;
|
||||||
|
use crate::module_graph2::Stats;
|
||||||
|
use crate::tsc_config::TsConfig;
|
||||||
|
|
||||||
|
use deno_core::error::anyhow;
|
||||||
|
use deno_core::error::bail;
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use deno_core::error::Context;
|
||||||
|
use deno_core::json_op_sync;
|
||||||
|
use deno_core::serde_json;
|
||||||
|
use deno_core::serde_json::json;
|
||||||
|
use deno_core::serde_json::Value;
|
||||||
|
use deno_core::JsRuntime;
|
||||||
|
use deno_core::ModuleSpecifier;
|
||||||
|
use deno_core::OpFn;
|
||||||
|
use deno_core::RuntimeOptions;
|
||||||
|
use deno_core::Snapshot;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
||||||
|
pub struct EmittedFile {
|
||||||
|
pub data: String,
|
||||||
|
pub maybe_specifiers: Option<Vec<ModuleSpecifier>>,
|
||||||
|
pub media_type: MediaType,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A structure representing a request to be sent to the tsc runtime.
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Request {
|
||||||
|
/// The TypeScript compiler options which will be serialized and sent to
|
||||||
|
/// tsc.
|
||||||
|
pub config: TsConfig,
|
||||||
|
/// Indicates to the tsc runtime if debug logging should occur.
|
||||||
|
pub debug: bool,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub graph: Rc<Graph2>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub hash_data: Vec<Vec<u8>>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub maybe_tsbuildinfo: Option<String>,
|
||||||
|
/// A vector of strings that represent the root/entry point modules for the
|
||||||
|
/// program.
|
||||||
|
pub root_names: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct Response {
|
||||||
|
/// Any diagnostics that have been returned from the checker.
|
||||||
|
pub diagnostics: Diagnostics,
|
||||||
|
/// Any files that were emitted during the check.
|
||||||
|
pub emitted_files: Vec<EmittedFile>,
|
||||||
|
/// If there was any build info associated with the exec request.
|
||||||
|
pub maybe_tsbuildinfo: Option<String>,
|
||||||
|
/// Statistics from the check.
|
||||||
|
pub stats: Stats,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
hash_data: Vec<Vec<u8>>,
|
||||||
|
emitted_files: Vec<EmittedFile>,
|
||||||
|
graph: Rc<Graph2>,
|
||||||
|
maybe_tsbuildinfo: Option<String>,
|
||||||
|
maybe_response: Option<RespondArgs>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn new(
|
||||||
|
graph: Rc<Graph2>,
|
||||||
|
hash_data: Vec<Vec<u8>>,
|
||||||
|
maybe_tsbuildinfo: Option<String>,
|
||||||
|
) -> Self {
|
||||||
|
State {
|
||||||
|
hash_data,
|
||||||
|
emitted_files: Vec::new(),
|
||||||
|
graph,
|
||||||
|
maybe_tsbuildinfo,
|
||||||
|
maybe_response: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op<F>(op_fn: F) -> Box<OpFn>
|
||||||
|
where
|
||||||
|
F: Fn(&mut State, Value) -> Result<Value, AnyError> + 'static,
|
||||||
|
{
|
||||||
|
json_op_sync(move |s, args, _bufs| {
|
||||||
|
let state = s.borrow_mut::<State>();
|
||||||
|
op_fn(state, args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct CreateHashArgs {
|
||||||
|
/// The string data to be used to generate the hash. This will be mixed with
|
||||||
|
/// other state data in Deno to derive the final hash.
|
||||||
|
data: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_hash(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
||||||
|
let v: CreateHashArgs = serde_json::from_value(args)
|
||||||
|
.context("Invalid request from JavaScript for \"op_create_hash\".")?;
|
||||||
|
let mut data = vec![v.data.as_bytes().to_owned()];
|
||||||
|
data.extend_from_slice(&state.hash_data);
|
||||||
|
let hash = crate::checksum::gen(&data);
|
||||||
|
Ok(json!({ "hash": hash }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct EmitArgs {
|
||||||
|
/// The text data/contents of the file.
|
||||||
|
data: String,
|
||||||
|
/// The _internal_ filename for the file. This will be used to determine how
|
||||||
|
/// the file is cached and stored.
|
||||||
|
file_name: String,
|
||||||
|
/// A string representation of the specifier that was associated with a
|
||||||
|
/// module. This should be present on every module that represents a module
|
||||||
|
/// that was requested to be transformed.
|
||||||
|
maybe_specifiers: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
||||||
|
let v: EmitArgs = serde_json::from_value(args)
|
||||||
|
.context("Invalid request from JavaScript for \"op_emit\".")?;
|
||||||
|
match v.file_name.as_ref() {
|
||||||
|
"deno:///.tsbuildinfo" => state.maybe_tsbuildinfo = Some(v.data),
|
||||||
|
_ => state.emitted_files.push(EmittedFile {
|
||||||
|
data: v.data,
|
||||||
|
maybe_specifiers: if let Some(specifiers) = &v.maybe_specifiers {
|
||||||
|
let specifiers = specifiers
|
||||||
|
.iter()
|
||||||
|
.map(|s| ModuleSpecifier::resolve_url_or_path(s).unwrap())
|
||||||
|
.collect();
|
||||||
|
Some(specifiers)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
media_type: MediaType::from(&v.file_name),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(json!(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct LoadArgs {
|
||||||
|
/// The fully qualified specifier that should be loaded.
|
||||||
|
specifier: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
||||||
|
let v: LoadArgs = serde_json::from_value(args)
|
||||||
|
.context("Invalid request from JavaScript for \"op_load\".")?;
|
||||||
|
let specifier = ModuleSpecifier::resolve_url_or_path(&v.specifier)
|
||||||
|
.context("Error converting a string module specifier for \"op_load\".")?;
|
||||||
|
let mut hash: Option<String> = None;
|
||||||
|
let data = if &v.specifier == "deno:///.tsbuildinfo" {
|
||||||
|
state.maybe_tsbuildinfo.clone()
|
||||||
|
} else {
|
||||||
|
let maybe_source = state.graph.get_source(&specifier);
|
||||||
|
if let Some(source) = &maybe_source {
|
||||||
|
let mut data = vec![source.as_bytes().to_owned()];
|
||||||
|
data.extend_from_slice(&state.hash_data);
|
||||||
|
hash = Some(crate::checksum::gen(&data));
|
||||||
|
}
|
||||||
|
maybe_source
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(json!({ "data": data, "hash": hash }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ResolveArgs {
|
||||||
|
/// The base specifier that the supplied specifier strings should be resolved
|
||||||
|
/// relative to.
|
||||||
|
base: String,
|
||||||
|
/// A list of specifiers that should be resolved.
|
||||||
|
specifiers: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
||||||
|
let v: ResolveArgs = serde_json::from_value(args)
|
||||||
|
.context("Invalid request from JavaScript for \"op_resolve\".")?;
|
||||||
|
let mut resolved: Vec<(String, String)> = Vec::new();
|
||||||
|
let referrer = ModuleSpecifier::resolve_url_or_path(&v.base).context(
|
||||||
|
"Error converting a string module specifier for \"op_resolve\".",
|
||||||
|
)?;
|
||||||
|
for specifier in &v.specifiers {
|
||||||
|
if specifier.starts_with("asset:///") {
|
||||||
|
resolved.push((
|
||||||
|
specifier.clone(),
|
||||||
|
MediaType::from(specifier).as_ts_extension().to_string(),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
let resolved_specifier = state.graph.resolve(specifier, &referrer)?;
|
||||||
|
let media_type = if let Some(media_type) =
|
||||||
|
state.graph.get_media_type(&resolved_specifier)
|
||||||
|
{
|
||||||
|
media_type
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"Unable to resolve media type for specifier: \"{}\"",
|
||||||
|
resolved_specifier
|
||||||
|
)
|
||||||
|
};
|
||||||
|
resolved
|
||||||
|
.push((resolved_specifier.to_string(), media_type.as_ts_extension()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(json!(resolved))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Eq, PartialEq)]
|
||||||
|
pub struct RespondArgs {
|
||||||
|
pub diagnostics: Diagnostics,
|
||||||
|
pub stats: Stats,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn respond(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
||||||
|
let v: RespondArgs = serde_json::from_value(args)
|
||||||
|
.context("Error converting the result for \"op_respond\".")?;
|
||||||
|
state.maybe_response = Some(v);
|
||||||
|
Ok(json!(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a request on the supplied snapshot, returning a response which
|
||||||
|
/// contains information, like any emitted files, diagnostics, statistics and
|
||||||
|
/// optionally an updated TypeScript build info.
|
||||||
|
pub fn exec(
|
||||||
|
snapshot: Snapshot,
|
||||||
|
request: Request,
|
||||||
|
) -> Result<Response, AnyError> {
|
||||||
|
let mut runtime = JsRuntime::new(RuntimeOptions {
|
||||||
|
startup_snapshot: Some(snapshot),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
let op_state = runtime.op_state();
|
||||||
|
let mut op_state = op_state.borrow_mut();
|
||||||
|
op_state.put(State::new(
|
||||||
|
request.graph.clone(),
|
||||||
|
request.hash_data.clone(),
|
||||||
|
request.maybe_tsbuildinfo.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.register_op("op_create_hash", op(create_hash));
|
||||||
|
runtime.register_op("op_emit", op(emit));
|
||||||
|
runtime.register_op("op_load", op(load));
|
||||||
|
runtime.register_op("op_resolve", op(resolve));
|
||||||
|
runtime.register_op("op_respond", op(respond));
|
||||||
|
|
||||||
|
let startup_source = "globalThis.startup({ legacyFlag: false })";
|
||||||
|
let request_str =
|
||||||
|
serde_json::to_string(&request).context("Could not serialize request.")?;
|
||||||
|
let exec_source = format!("globalThis.exec({})", request_str);
|
||||||
|
|
||||||
|
runtime
|
||||||
|
.execute("[native code]", startup_source)
|
||||||
|
.context("Could not properly start the compiler runtime.")?;
|
||||||
|
runtime
|
||||||
|
.execute("[native_code]", &exec_source)
|
||||||
|
.context("Execute request failed.")?;
|
||||||
|
|
||||||
|
let op_state = runtime.op_state();
|
||||||
|
let mut op_state = op_state.borrow_mut();
|
||||||
|
let state = op_state.take::<State>();
|
||||||
|
|
||||||
|
if let Some(response) = state.maybe_response {
|
||||||
|
let diagnostics = response.diagnostics;
|
||||||
|
let emitted_files = state.emitted_files;
|
||||||
|
let maybe_tsbuildinfo = state.maybe_tsbuildinfo;
|
||||||
|
let stats = response.stats;
|
||||||
|
|
||||||
|
Ok(Response {
|
||||||
|
diagnostics,
|
||||||
|
emitted_files,
|
||||||
|
maybe_tsbuildinfo,
|
||||||
|
stats,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("The response for the exec request was not set."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::diagnostics::Diagnostic;
|
||||||
|
use crate::diagnostics::DiagnosticCategory;
|
||||||
|
use crate::js;
|
||||||
|
use crate::module_graph2::tests::MockSpecifierHandler;
|
||||||
|
use crate::module_graph2::GraphBuilder2;
|
||||||
|
use crate::tsc_config::TsConfig;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::env;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
async fn setup(
|
||||||
|
maybe_specifier: Option<ModuleSpecifier>,
|
||||||
|
maybe_hash_data: Option<Vec<Vec<u8>>>,
|
||||||
|
maybe_tsbuildinfo: Option<String>,
|
||||||
|
) -> State {
|
||||||
|
let specifier = maybe_specifier.unwrap_or_else(|| {
|
||||||
|
ModuleSpecifier::resolve_url_or_path("file:///main.ts").unwrap()
|
||||||
|
});
|
||||||
|
let hash_data = maybe_hash_data.unwrap_or_else(|| vec![b"".to_vec()]);
|
||||||
|
let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
|
||||||
|
let fixtures = c.join("tests/tsc2");
|
||||||
|
let handler = Rc::new(RefCell::new(MockSpecifierHandler {
|
||||||
|
fixtures,
|
||||||
|
..MockSpecifierHandler::default()
|
||||||
|
}));
|
||||||
|
let mut builder = GraphBuilder2::new(handler.clone(), None);
|
||||||
|
builder
|
||||||
|
.insert(&specifier)
|
||||||
|
.await
|
||||||
|
.expect("module not inserted");
|
||||||
|
let graph = Rc::new(builder.get_graph(&None).expect("could not get graph"));
|
||||||
|
State::new(graph, hash_data, maybe_tsbuildinfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_hash() {
|
||||||
|
let mut state = setup(None, Some(vec![b"something".to_vec()]), None).await;
|
||||||
|
let actual =
|
||||||
|
create_hash(&mut state, json!({ "data": "some sort of content" }))
|
||||||
|
.expect("could not invoke op");
|
||||||
|
assert_eq!(
|
||||||
|
actual,
|
||||||
|
json!({"hash": "ae92df8f104748768838916857a1623b6a3c593110131b0a00f81ad9dac16511"})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_emit() {
|
||||||
|
let mut state = setup(None, None, None).await;
|
||||||
|
let actual = emit(
|
||||||
|
&mut state,
|
||||||
|
json!({
|
||||||
|
"data": "some file content",
|
||||||
|
"fileName": "cache:///some/file.js",
|
||||||
|
"maybeSpecifiers": ["file:///some/file.ts"]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.expect("should have invoked op");
|
||||||
|
assert_eq!(actual, json!(true));
|
||||||
|
assert_eq!(state.emitted_files.len(), 1);
|
||||||
|
assert!(state.maybe_tsbuildinfo.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
state.emitted_files[0],
|
||||||
|
EmittedFile {
|
||||||
|
data: "some file content".to_string(),
|
||||||
|
maybe_specifiers: Some(vec![ModuleSpecifier::resolve_url_or_path(
|
||||||
|
"file:///some/file.ts"
|
||||||
|
)
|
||||||
|
.unwrap()]),
|
||||||
|
media_type: MediaType::JavaScript,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_emit_tsbuildinfo() {
|
||||||
|
let mut state = setup(None, None, None).await;
|
||||||
|
let actual = emit(
|
||||||
|
&mut state,
|
||||||
|
json!({
|
||||||
|
"data": "some file content",
|
||||||
|
"fileName": "deno:///.tsbuildinfo",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.expect("should have invoked op");
|
||||||
|
assert_eq!(actual, json!(true));
|
||||||
|
assert_eq!(state.emitted_files.len(), 0);
|
||||||
|
assert_eq!(
|
||||||
|
state.maybe_tsbuildinfo,
|
||||||
|
Some("some file content".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_load() {
|
||||||
|
let mut state = setup(
|
||||||
|
Some(
|
||||||
|
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/mod.ts")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
Some("some content".to_string()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let actual = load(
|
||||||
|
&mut state,
|
||||||
|
json!({ "specifier": "https://deno.land/x/mod.ts"}),
|
||||||
|
)
|
||||||
|
.expect("should have invoked op");
|
||||||
|
assert_eq!(
|
||||||
|
actual,
|
||||||
|
json!({
|
||||||
|
"data": "console.log(\"hello deno\");\n",
|
||||||
|
"hash": "149c777056afcc973d5fcbe11421b6d5ddc57b81786765302030d7fc893bf729"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_load_tsbuildinfo() {
|
||||||
|
let mut state = setup(
|
||||||
|
Some(
|
||||||
|
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/mod.ts")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
Some("some content".to_string()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let actual =
|
||||||
|
load(&mut state, json!({ "specifier": "deno:///.tsbuildinfo"}))
|
||||||
|
.expect("should have invoked op");
|
||||||
|
assert_eq!(
|
||||||
|
actual,
|
||||||
|
json!({
|
||||||
|
"data": "some content",
|
||||||
|
"hash": null
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_load_missing_specifier() {
|
||||||
|
let mut state = setup(None, None, None).await;
|
||||||
|
let actual = load(
|
||||||
|
&mut state,
|
||||||
|
json!({ "specifier": "https://deno.land/x/mod.ts"}),
|
||||||
|
)
|
||||||
|
.expect("should have invoked op");
|
||||||
|
assert_eq!(
|
||||||
|
actual,
|
||||||
|
json!({
|
||||||
|
"data": null,
|
||||||
|
"hash": null,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_resolve() {
|
||||||
|
let mut state = setup(
|
||||||
|
Some(
|
||||||
|
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/a.ts")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let actual = resolve(
|
||||||
|
&mut state,
|
||||||
|
json!({ "base": "https://deno.land/x/a.ts", "specifiers": [ "./b.ts" ]}),
|
||||||
|
)
|
||||||
|
.expect("should have invoked op");
|
||||||
|
assert_eq!(actual, json!([["https://deno.land/x/b.ts", ".ts"]]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_resolve_error() {
|
||||||
|
let mut state = setup(
|
||||||
|
Some(
|
||||||
|
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/a.ts")
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
resolve(
|
||||||
|
&mut state,
|
||||||
|
json!({ "base": "https://deno.land/x/a.ts", "specifiers": [ "./bad.ts" ]}),
|
||||||
|
).expect_err("should have errored");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_respond() {
|
||||||
|
let mut state = setup(None, None, None).await;
|
||||||
|
let actual = respond(
|
||||||
|
&mut state,
|
||||||
|
json!({
|
||||||
|
"diagnostics": [
|
||||||
|
{
|
||||||
|
"messageText": "Unknown compiler option 'invalid'.",
|
||||||
|
"category": 1,
|
||||||
|
"code": 5023
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stats": [["a", 12]]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.expect("should have invoked op");
|
||||||
|
assert_eq!(actual, json!(true));
|
||||||
|
assert_eq!(
|
||||||
|
state.maybe_response,
|
||||||
|
Some(RespondArgs {
|
||||||
|
diagnostics: Diagnostics(vec![Diagnostic {
|
||||||
|
category: DiagnosticCategory::Error,
|
||||||
|
code: 5023,
|
||||||
|
start: None,
|
||||||
|
end: None,
|
||||||
|
message_text: Some(
|
||||||
|
"Unknown compiler option \'invalid\'.".to_string()
|
||||||
|
),
|
||||||
|
message_chain: None,
|
||||||
|
source: None,
|
||||||
|
source_line: None,
|
||||||
|
file_name: None,
|
||||||
|
related_information: None,
|
||||||
|
}]),
|
||||||
|
stats: Stats(vec![("a".to_string(), 12)])
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_exec() {
|
||||||
|
let specifier =
|
||||||
|
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/a.ts").unwrap();
|
||||||
|
let hash_data = vec![b"something".to_vec()];
|
||||||
|
let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
|
||||||
|
let fixtures = c.join("tests/tsc2");
|
||||||
|
let handler = Rc::new(RefCell::new(MockSpecifierHandler {
|
||||||
|
fixtures,
|
||||||
|
..MockSpecifierHandler::default()
|
||||||
|
}));
|
||||||
|
let mut builder = GraphBuilder2::new(handler.clone(), None);
|
||||||
|
builder
|
||||||
|
.insert(&specifier)
|
||||||
|
.await
|
||||||
|
.expect("module not inserted");
|
||||||
|
let graph = Rc::new(builder.get_graph(&None).expect("could not get graph"));
|
||||||
|
let config = TsConfig::new(json!({
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": false,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"emitDecoratorMetadata": false,
|
||||||
|
"incremental": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"jsxFactory": "React.createElement",
|
||||||
|
"jsxFragmentFactory": "React.Fragment",
|
||||||
|
"lib": ["deno.window"],
|
||||||
|
"module": "esnext",
|
||||||
|
"noEmit": true,
|
||||||
|
"outDir": "deno:///",
|
||||||
|
"strict": true,
|
||||||
|
"target": "esnext",
|
||||||
|
"tsBuildInfoFile": "deno:///.tsbuildinfo",
|
||||||
|
}));
|
||||||
|
let request = Request {
|
||||||
|
config,
|
||||||
|
debug: false,
|
||||||
|
graph,
|
||||||
|
hash_data,
|
||||||
|
maybe_tsbuildinfo: None,
|
||||||
|
root_names: vec!["https://deno.land/x/a.ts".to_string()],
|
||||||
|
};
|
||||||
|
let actual = exec(js::compiler_isolate_init(), request)
|
||||||
|
.expect("exec should have not errored");
|
||||||
|
assert!(actual.diagnostics.0.is_empty());
|
||||||
|
assert!(actual.emitted_files.is_empty());
|
||||||
|
assert!(actual.maybe_tsbuildinfo.is_some());
|
||||||
|
assert_eq!(actual.stats.0.len(), 12);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
pub use anyhow::anyhow;
|
||||||
|
pub use anyhow::bail;
|
||||||
|
pub use anyhow::Context;
|
||||||
use rusty_v8 as v8;
|
use rusty_v8 as v8;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
Loading…
Reference in a new issue