Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for loading tsconfig.json #2089

Merged
merged 3 commits into from
Apr 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cli/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ ts_sources = [
"../js/buffer.ts",
"../js/build.ts",
"../js/chmod.ts",
"../js/console_table.ts",
"../js/colors.ts",
"../js/compiler.ts",
"../js/console.ts",
"../js/console_table.ts",
"../js/copy_file.ts",
"../js/core.ts",
"../js/custom_event.ts",
Expand Down
25 changes: 25 additions & 0 deletions cli/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,23 @@ fn req(specifier: &str, referrer: &str, cmd_id: u32) -> Buf {
.into_boxed_bytes()
}

/// Returns an optional tuple which represents the state of the compiler
/// configuration where the first is canonical name for the configuration file
/// and a vector of the bytes of the contents of the configuration file.
pub fn get_compiler_config(
kitsonk marked this conversation as resolved.
Show resolved Hide resolved
parent_state: &ThreadSafeState,
_compiler_type: &str,
) -> Option<(String, Vec<u8>)> {
// The compiler type is being passed to make it easier to implement custom
// compilers in the future.
match (&parent_state.config_path, &parent_state.config) {
(Some(config_path), Some(config)) => {
Some((config_path.to_string(), config.to_vec()))
}
_ => None,
}
}

pub fn compile_async(
parent_state: ThreadSafeState,
specifier: &str,
Expand Down Expand Up @@ -306,4 +323,12 @@ mod tests {

assert_eq!(parse_cmd_id(res_json), cmd_id);
}

#[test]
fn test_get_compiler_config_no_flag() {
let compiler_type = "typescript";
let state = ThreadSafeState::mock();
let out = get_compiler_config(&state, compiler_type);
assert_eq!(out, None);
}
}
83 changes: 67 additions & 16 deletions cli/deno_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,18 @@ pub struct DenoDir {
// This splits to http and https deps
pub deps_http: PathBuf,
pub deps_https: PathBuf,
/// The active configuration file contents (or empty array) which applies to
/// source code cached by `DenoDir`.
pub config: Vec<u8>,
kitsonk marked this conversation as resolved.
Show resolved Hide resolved
}

impl DenoDir {
// Must be called before using any function from this module.
// https://github.com/denoland/deno/blob/golang/deno_dir.go#L99-L111
pub fn new(custom_root: Option<PathBuf>) -> std::io::Result<Self> {
pub fn new(
custom_root: Option<PathBuf>,
state_config: &Option<Vec<u8>>,
kitsonk marked this conversation as resolved.
Show resolved Hide resolved
) -> std::io::Result<Self> {
// Only setup once.
let home_dir = dirs::home_dir().expect("Could not get home directory.");
let fallback = home_dir.join(".deno");
Expand All @@ -73,12 +79,22 @@ impl DenoDir {
let deps_http = deps.join("http");
let deps_https = deps.join("https");

// Internally within DenoDir, we use the config as part of the hash to
// determine if a file has been transpiled with the same configuration, but
// we have borrowed the `State` configuration, which we want to either clone
// or create an empty `Vec` which we will use in our hash function.
let config = match state_config {
Some(config) => config.clone(),
_ => b"".to_vec(),
};

let deno_dir = Self {
root,
gen,
deps,
deps_http,
deps_https,
config,
};

// TODO Lazily create these directories.
Expand All @@ -102,7 +118,8 @@ impl DenoDir {
filename: &str,
source_code: &[u8],
) -> (PathBuf, PathBuf) {
let cache_key = source_code_hash(filename, source_code, version::DENO);
let cache_key =
source_code_hash(filename, source_code, version::DENO, &self.config);
(
self.gen.join(cache_key.to_string() + ".js"),
self.gen.join(cache_key.to_string() + ".js.map"),
Expand Down Expand Up @@ -156,6 +173,11 @@ impl DenoDir {

let gen = self.gen.clone();

// If we don't clone the config, we then end up creating an implied lifetime
// which gets returned in the future, so we clone here so as to not leak the
// move below when the future is resolving.
let config = self.config.clone();

Either::B(
get_source_code_async(
self,
Expand Down Expand Up @@ -191,8 +213,12 @@ impl DenoDir {
return Ok(out);
}

let cache_key =
source_code_hash(&out.filename, &out.source_code, version::DENO);
let cache_key = source_code_hash(
&out.filename,
&out.source_code,
version::DENO,
&config,
);
let (output_code_filename, output_source_map_filename) = (
gen.join(cache_key.to_string() + ".js"),
gen.join(cache_key.to_string() + ".js.map"),
Expand Down Expand Up @@ -468,15 +494,19 @@ fn load_cache2(
Ok((read_output_code, read_source_map))
}

/// Generate an SHA1 hash for source code, to be used to determine if a cached
/// version of the code is valid or invalid.
fn source_code_hash(
filename: &str,
source_code: &[u8],
version: &str,
config: &[u8],
) -> String {
let mut ctx = ring::digest::Context::new(&ring::digest::SHA1);
ctx.update(version.as_bytes());
ctx.update(filename.as_bytes());
ctx.update(source_code);
ctx.update(config);
let digest = ctx.finish();
let mut out = String::new();
// TODO There must be a better way to do this...
Expand Down Expand Up @@ -860,8 +890,9 @@ mod tests {

fn test_setup() -> (TempDir, DenoDir) {
let temp_dir = TempDir::new().expect("tempdir fail");
let deno_dir =
DenoDir::new(Some(temp_dir.path().to_path_buf())).expect("setup fail");
let config = Some(b"{}".to_vec());
let deno_dir = DenoDir::new(Some(temp_dir.path().to_path_buf()), &config)
.expect("setup fail");
(temp_dir, deno_dir)
}

Expand Down Expand Up @@ -904,7 +935,8 @@ mod tests {
let (temp_dir, deno_dir) = test_setup();
let filename = "hello.js";
let source_code = b"1+2";
let hash = source_code_hash(filename, source_code, version::DENO);
let config = b"{}";
let hash = source_code_hash(filename, source_code, version::DENO, config);
assert_eq!(
(
temp_dir.path().join(format!("gen/{}.js", hash)),
Expand All @@ -914,6 +946,24 @@ mod tests {
);
}

#[test]
fn test_cache_path_config() {
// We are changing the compiler config from the "mock" and so we expect the
// resolved files coming back to not match the calculated hash.
let (temp_dir, deno_dir) = test_setup();
let filename = "hello.js";
let source_code = b"1+2";
let config = b"{\"compilerOptions\":{}}";
let hash = source_code_hash(filename, source_code, version::DENO, config);
assert_ne!(
(
temp_dir.path().join(format!("gen/{}.js", hash)),
temp_dir.path().join(format!("gen/{}.js.map", hash))
),
deno_dir.cache_path(filename, source_code)
);
}

#[test]
fn test_code_cache() {
let (_temp_dir, deno_dir) = test_setup();
Expand All @@ -922,7 +972,8 @@ mod tests {
let source_code = b"1+2";
let output_code = b"1+2 // output code";
let source_map = b"{}";
let hash = source_code_hash(filename, source_code, version::DENO);
let config = b"{}";
let hash = source_code_hash(filename, source_code, version::DENO, config);
let (cache_path, source_map_path) =
deno_dir.cache_path(filename, source_code);
assert!(cache_path.ends_with(format!("gen/{}.js", hash)));
Expand All @@ -949,23 +1000,23 @@ mod tests {
#[test]
fn test_source_code_hash() {
assert_eq!(
"7e44de2ed9e0065da09d835b76b8d70be503d276",
source_code_hash("hello.ts", b"1+2", "0.2.11")
"830c8b63ba3194cf2108a3054c176b2bf53aee45",
source_code_hash("hello.ts", b"1+2", "0.2.11", b"{}")
);
// Different source_code should result in different hash.
assert_eq!(
"57033366cf9db1ef93deca258cdbcd9ef5f4bde1",
source_code_hash("hello.ts", b"1", "0.2.11")
"fb06127e9b2e169bea9c697fa73386ae7c901e8b",
source_code_hash("hello.ts", b"1", "0.2.11", b"{}")
);
// Different filename should result in different hash.
assert_eq!(
"19657f90b5b0540f87679e2fb362e7bd62b644b0",
source_code_hash("hi.ts", b"1+2", "0.2.11")
"3a17b6a493ff744b6a455071935f4bdcd2b72ec7",
source_code_hash("hi.ts", b"1+2", "0.2.11", b"{}")
);
// Different version should result in different hash.
assert_eq!(
"e2b4b7162975a02bf2770f16836eb21d5bcb8be1",
source_code_hash("hi.ts", b"1+2", "0.2.0")
"d6b2cfdc39dae9bd3ad5b493ee1544eb22e7475f",
source_code_hash("hi.ts", b"1+2", "0.2.0", b"{}")
);
}

Expand Down
24 changes: 24 additions & 0 deletions cli/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ pub struct DenoFlags {
pub log_debug: bool,
pub version: bool,
pub reload: bool,
/// When the `--config`/`-c` flag is used to pass the name, this will be set
/// the path passed on the command line, otherwise `None`.
pub config_path: Option<String>,
pub allow_read: bool,
pub allow_write: bool,
pub allow_net: bool,
Expand Down Expand Up @@ -79,6 +82,13 @@ pub fn create_cli_app<'a, 'b>() -> App<'a, 'b> {
.short("r")
.long("reload")
.help("Reload source code cache (recompile TypeScript)"),
).arg(
Arg::with_name("config")
.short("c")
.long("config")
.value_name("FILE")
.help("Load compiler configuration file")
.takes_value(true),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just feed for thought here: If we ever find a need to introduce deno specific config, we'll have trouble with appropriate flag name, how about --ts-config for TS compiler configuration?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal opinion, if we ever need a custom config file for Deno, we are probably doing it wrong. Even if we allowed setting of flags via a configuration file, I would hope we would expand the syntax of the TypeScript format, where "compilerOptions" would still be provided to the compiler and the rest parsed by Deno. In my opinion, Deno and TypeScript should always be synergistic instead of some sort of "adjunct".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deno and TypeScript should always be synergistic instead of some sort of "adjunct".

Fits the Philosphy. Thumb up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kitsonk thanks for response, I generally agree with your opinion - now looking at #1739 it makes a lot of sense to make it single --config that other compilers can use.

).arg(
Arg::with_name("v8-options")
.long("v8-options")
Expand Down Expand Up @@ -146,6 +156,7 @@ pub fn parse_flags(matches: ArgMatches) -> DenoFlags {
if matches.is_present("reload") {
flags.reload = true;
}
flags.config_path = matches.value_of("config").map(ToOwned::to_owned);
if matches.is_present("allow-read") {
flags.allow_read = true;
}
Expand Down Expand Up @@ -353,4 +364,17 @@ mod tests {
}
)
}

#[test]
fn test_set_flags_11() {
let flags =
flags_from_vec(svec!["deno", "-c", "tsconfig.json", "script.ts"]);
assert_eq!(
flags,
DenoFlags {
config_path: Some("tsconfig.json".to_owned()),
..DenoFlags::default()
}
)
}
}
11 changes: 11 additions & 0 deletions cli/msg.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ union Any {
Chdir,
Chmod,
Close,
CompilerConfig,
CompilerConfigRes,
CopyFile,
Cwd,
CwdRes,
Expand Down Expand Up @@ -174,6 +176,15 @@ table StartRes {
no_color: bool;
}

table CompilerConfig {
compiler_type: string;
}

table CompilerConfigRes {
path: string;
data: [ubyte];
}

table FormatError {
error: string;
}
Expand Down
38 changes: 38 additions & 0 deletions cli/ops.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use atty;
use crate::ansi;
use crate::compiler::get_compiler_config;
use crate::errors;
use crate::errors::{DenoError, DenoResult, ErrorKind};
use crate::fs as deno_fs;
Expand Down Expand Up @@ -146,6 +147,8 @@ pub fn dispatch_all(

pub fn op_selector_compiler(inner_type: msg::Any) -> Option<OpCreator> {
match inner_type {
msg::Any::CompilerConfig => Some(op_compiler_config),
msg::Any::Cwd => Some(op_cwd),
msg::Any::FetchModuleMetaData => Some(op_fetch_module_meta_data),
msg::Any::WorkerGetMessage => Some(op_worker_get_message),
msg::Any::WorkerPostMessage => Some(op_worker_post_message),
Expand Down Expand Up @@ -443,6 +446,41 @@ fn op_fetch_module_meta_data(
}()))
}

/// Retrieve any relevant compiler configuration.
fn op_compiler_config(
state: &ThreadSafeState,
base: &msg::Base<'_>,
data: deno_buf,
) -> Box<OpWithError> {
assert_eq!(data.len(), 0);
let inner = base.inner_as_compiler_config().unwrap();
let cmd_id = base.cmd_id();
let compiler_type = inner.compiler_type().unwrap();

Box::new(futures::future::result(|| -> OpResult {
let builder = &mut FlatBufferBuilder::new();
let (path, out) = match get_compiler_config(state, compiler_type) {
Some(val) => val,
_ => ("".to_owned(), "".as_bytes().to_owned()),
};
let data_off = builder.create_vector(&out);
let msg_args = msg::CompilerConfigResArgs {
path: Some(builder.create_string(&path)),
data: Some(data_off),
};
let inner = msg::CompilerConfigRes::create(builder, &msg_args);
Ok(serialize_response(
cmd_id,
builder,
msg::BaseArgs {
inner: Some(inner.as_union_value()),
inner_type: msg::Any::CompilerConfigRes,
..Default::default()
},
))
}()))
}

fn op_chdir(
_state: &ThreadSafeState,
base: &msg::Base<'_>,
Expand Down
Loading