ts.rs 22.1 KB
Newer Older
R
Ryan Dahl 已提交
1
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
2 3 4
use crate::compilers::CompiledModule;
use crate::compilers::CompiledModuleFuture;
use crate::deno_error::DenoError;
5
use crate::diagnostics::Diagnostic;
B
Bartek Iwańczuk 已提交
6
use crate::disk_cache::DiskCache;
7 8
use crate::file_fetcher::SourceFile;
use crate::file_fetcher::SourceFileFetcher;
A
Andy Hayden 已提交
9
use crate::msg;
10
use crate::msg::ErrorKind;
A
Andy Hayden 已提交
11
use crate::resources;
B
Bartek Iwańczuk 已提交
12
use crate::source_maps::SourceMapGetter;
13
use crate::startup_data;
14
use crate::state::*;
B
Bartek Iwańczuk 已提交
15
use crate::version;
16
use crate::worker::Worker;
17
use deno::Buf;
18
use deno::ErrBox;
19
use deno::ModuleSpecifier;
R
Ryan Dahl 已提交
20
use futures::Future;
21
use futures::Stream;
B
Bartek Iwańczuk 已提交
22 23 24 25
use ring;
use std::collections::HashSet;
use std::fmt::Write;
use std::fs;
26
use std::path::PathBuf;
K
Kitson Kelly 已提交
27
use std::str;
28
use std::sync::atomic::Ordering;
B
Bartek Iwańczuk 已提交
29 30
use std::sync::Mutex;
use url::Url;
R
Ryan Dahl 已提交
31

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
/// Struct which represents the state of the compiler
/// configuration where the first is canonical name for the configuration file,
/// second is a vector of the bytes of the contents of the configuration file,
/// third is bytes of the hash of contents.
#[derive(Clone)]
pub struct CompilerConfig {
  pub path: Option<PathBuf>,
  pub content: Option<Vec<u8>>,
  pub hash: Vec<u8>,
}

impl CompilerConfig {
  /// Take the passed flag and resolve the file name relative to the cwd.
  pub fn load(config_path: Option<String>) -> Result<Self, ErrBox> {
    let config_file = match &config_path {
      Some(config_file_name) => {
        debug!("Compiler config file: {}", config_file_name);
        let cwd = std::env::current_dir().unwrap();
        Some(cwd.join(config_file_name))
      }
      _ => None,
    };

    // Convert the PathBuf to a canonicalized string.  This is needed by the
    // compiler to properly deal with the configuration.
    let config_path = match &config_file {
      Some(config_file) => Some(config_file.canonicalize().unwrap().to_owned()),
      _ => None,
    };

    // Load the contents of the configuration file
    let config = match &config_file {
      Some(config_file) => {
        debug!("Attempt to load config: {}", config_file.to_str().unwrap());
        let config = fs::read(&config_file)?;
        Some(config)
      }
      _ => None,
    };

    let config_hash = match &config {
      Some(bytes) => bytes.clone(),
      _ => b"".to_vec(),
    };

    let ts_config = Self {
      path: config_path,
      content: config,
      hash: config_hash,
    };

    Ok(ts_config)
  }

  pub fn json(self: &Self) -> Result<serde_json::Value, ErrBox> {
    if self.content.is_none() {
      return Ok(serde_json::Value::Null);
    }

    let bytes = self.content.clone().unwrap();
    let json_string = std::str::from_utf8(&bytes)?;
    match serde_json::from_str(&json_string) {
      Ok(json_map) => Ok(json_map),
      Err(_) => Err(
        DenoError::new(
          ErrorKind::InvalidInput,
          "Compiler config is not a valid JSON".to_string(),
99 100
        )
        .into(),
101 102 103 104
      ),
    }
  }
}
B
Bartek Iwańczuk 已提交
105 106 107 108 109 110 111 112

/// Information associated with compiled file in cache.
/// Includes source code path and state hash.
/// version_hash is used to validate versions of the file
/// and could be used to remove stale file in cache.
pub struct CompiledFileMetadata {
  pub source_path: PathBuf,
  pub version_hash: String,
R
Ryan Dahl 已提交
113 114
}

B
Bartek Iwańczuk 已提交
115 116
static SOURCE_PATH: &'static str = "source_path";
static VERSION_HASH: &'static str = "version_hash";
117

B
Bartek Iwańczuk 已提交
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
impl CompiledFileMetadata {
  pub fn from_json_string(metadata_string: String) -> Option<Self> {
    // TODO: use serde for deserialization
    let maybe_metadata_json: serde_json::Result<serde_json::Value> =
      serde_json::from_str(&metadata_string);

    if let Ok(metadata_json) = maybe_metadata_json {
      let source_path = metadata_json[SOURCE_PATH].as_str().map(PathBuf::from);
      let version_hash = metadata_json[VERSION_HASH].as_str().map(String::from);

      if source_path.is_none() || version_hash.is_none() {
        return None;
      }

      return Some(CompiledFileMetadata {
        source_path: source_path.unwrap(),
        version_hash: version_hash.unwrap(),
      });
R
Ryan Dahl 已提交
136
    }
B
Bartek Iwańczuk 已提交
137 138

    None
R
Ryan Dahl 已提交
139 140
  }

B
Bartek Iwańczuk 已提交
141 142
  pub fn to_json_string(self: &Self) -> Result<String, serde_json::Error> {
    let mut value_map = serde_json::map::Map::new();
143

B
Bartek Iwańczuk 已提交
144 145 146 147 148
    value_map.insert(SOURCE_PATH.to_owned(), json!(&self.source_path));
    value_map.insert(VERSION_HASH.to_string(), json!(&self.version_hash));
    serde_json::to_string(&value_map)
  }
}
R
Ryan Dahl 已提交
149
/// Creates the JSON message send to compiler.ts's onmessage.
K
Kitson Kelly 已提交
150 151 152 153 154
fn req(
  root_names: Vec<String>,
  compiler_config: CompilerConfig,
  bundle: Option<String>,
) -> Buf {
155 156
  let j = match (compiler_config.path, compiler_config.content) {
    (Some(config_path), Some(config_data)) => json!({
157 158 159 160 161
      "rootNames": root_names,
      "bundle": bundle,
      "configPath": config_path,
      "config": str::from_utf8(&config_data).unwrap(),
    }),
162
    _ => json!({
163 164 165
      "rootNames": root_names,
      "bundle": bundle,
    }),
R
Ryan Dahl 已提交
166
  };
167

R
Ryan Dahl 已提交
168
  j.to_string().into_boxed_str().into_boxed_bytes()
R
Ryan Dahl 已提交
169 170
}

B
Bartek Iwańczuk 已提交
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
fn gen_hash(v: Vec<&[u8]>) -> String {
  let mut ctx = ring::digest::Context::new(&ring::digest::SHA1);
  for src in v.iter() {
    ctx.update(src);
  }
  let digest = ctx.finish();
  let mut out = String::new();
  // TODO There must be a better way to do this...
  for byte in digest.as_ref() {
    write!(&mut out, "{:02x}", byte).unwrap();
  }
  out
}

/// Emit a SHA1 hash based on source code, deno version and TS config.
/// Used to check if a recompilation for source code is needed.
pub fn source_code_version_hash(
  source_code: &[u8],
  version: &str,
  config_hash: &[u8],
) -> String {
  gen_hash(vec![source_code, version.as_bytes(), config_hash])
}

pub struct TsCompiler {
196
  pub file_fetcher: SourceFileFetcher,
B
Bartek Iwańczuk 已提交
197 198 199 200 201 202 203 204
  pub config: CompilerConfig,
  pub disk_cache: DiskCache,
  /// Set of all URLs that have been compiled. This prevents double
  /// compilation of module.
  pub compiled: Mutex<HashSet<Url>>,
  /// This setting is controlled by `--reload` flag. Unless the flag
  /// is provided disk cache is used.
  pub use_disk_cache: bool,
205 206
  /// This setting is controlled by `compilerOptions.checkJs`
  pub compile_js: bool,
B
Bartek Iwańczuk 已提交
207 208 209 210
}

impl TsCompiler {
  pub fn new(
211 212
    file_fetcher: SourceFileFetcher,
    disk_cache: DiskCache,
B
Bartek Iwańczuk 已提交
213 214
    use_disk_cache: bool,
    config_path: Option<String>,
215 216 217 218 219 220 221 222 223 224 225 226
  ) -> Result<Self, ErrBox> {
    let config = CompilerConfig::load(config_path)?;

    // If `checkJs` is set to true in `compilerOptions` then we're gonna be compiling
    // JavaScript files as well
    let config_json = config.json()?;
    let compile_js = match &config_json.get("compilerOptions") {
      Some(serde_json::Value::Object(m)) => match m.get("checkJs") {
        Some(serde_json::Value::Bool(bool_)) => *bool_,
        _ => false,
      },
      _ => false,
B
Bartek Iwańczuk 已提交
227 228
    };

229
    let compiler = Self {
230 231
      file_fetcher,
      disk_cache,
232
      config,
B
Bartek Iwańczuk 已提交
233 234
      compiled: Mutex::new(HashSet::new()),
      use_disk_cache,
235 236 237 238
      compile_js,
    };

    Ok(compiler)
B
Bartek Iwańczuk 已提交
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
  }

  /// Create a new V8 worker with snapshot of TS compiler and setup compiler's runtime.
  fn setup_worker(state: ThreadSafeState) -> Worker {
    // Count how many times we start the compiler worker.
    state.metrics.compiler_starts.fetch_add(1, Ordering::SeqCst);

    let mut worker = Worker::new(
      "TS".to_string(),
      startup_data::compiler_isolate_init(),
      // TODO(ry) Maybe we should use a separate state for the compiler.
      // as was done previously.
      state.clone(),
    );
    worker.execute("denoMain()").unwrap();
    worker.execute("workerMain()").unwrap();
    worker.execute("compilerMain()").unwrap();
    worker
  }

  pub fn bundle_async(
    self: &Self,
    state: ThreadSafeState,
    module_name: String,
    out_file: String,
  ) -> impl Future<Item = (), Error = ErrBox> {
    debug!(
      "Invoking the compiler to bundle. module_name: {}",
      module_name
    );

    let root_names = vec![module_name.clone()];
    let req_msg = req(root_names, self.config.clone(), Some(out_file));

    let worker = TsCompiler::setup_worker(state.clone());
    let resource = worker.state.resource.clone();
    let compiler_rid = resource.rid;
    let first_msg_fut =
      resources::post_message_to_worker(compiler_rid, req_msg)
        .then(move |_| worker)
        .then(move |result| {
          if let Err(err) = result {
            // TODO(ry) Need to forward the error instead of exiting.
            eprintln!("{}", err.to_string());
            std::process::exit(1);
          }
          debug!("Sent message to worker");
          let stream_future =
            resources::get_message_stream_from_worker(compiler_rid)
              .into_future();
          stream_future.map(|(f, _rest)| f).map_err(|(f, _rest)| f)
        });

    first_msg_fut.map_err(|_| panic!("not handled")).and_then(
      move |maybe_msg: Option<Buf>| {
        debug!("Received message from worker");

        if let Some(msg) = maybe_msg {
          let json_str = std::str::from_utf8(&msg).unwrap();
          debug!("Message: {}", json_str);
          if let Some(diagnostics) = Diagnostic::from_emit_result(json_str) {
            return Err(ErrBox::from(diagnostics));
          }
        }

        Ok(())
      },
    )
  }

  /// Mark given module URL as compiled to avoid multiple compilations of same module
  /// in single run.
  fn mark_compiled(&self, url: &Url) {
    let mut c = self.compiled.lock().unwrap();
    c.insert(url.clone());
  }

  /// Check if given module URL has already been compiled and can be fetched directly from disk.
  fn has_compiled(&self, url: &Url) -> bool {
    let c = self.compiled.lock().unwrap();
    c.contains(url)
  }

  /// Asynchronously compile module and all it's dependencies.
  ///
  /// This method compiled every module at most once.
  ///
  /// If `--reload` flag was provided then compiler will not on-disk cache and force recompilation.
  ///
  /// If compilation is required then new V8 worker is spawned with fresh TS compiler.
  pub fn compile_async(
    self: &Self,
    state: ThreadSafeState,
    source_file: &SourceFile,
333
  ) -> Box<CompiledModuleFuture> {
B
Bartek Iwańczuk 已提交
334
    if self.has_compiled(&source_file.url) {
335 336 337 338
      return match self.get_compiled_module(&source_file.url) {
        Ok(compiled) => Box::new(futures::future::ok(compiled)),
        Err(err) => Box::new(futures::future::err(err)),
      };
B
Bartek Iwańczuk 已提交
339 340 341 342 343 344 345 346 347 348 349
    }

    if self.use_disk_cache {
      // Try to load cached version:
      // 1. check if there's 'meta' file
      if let Some(metadata) = self.get_metadata(&source_file.url) {
        // 2. compare version hashes
        // TODO: it would probably be good idea to make it method implemented on SourceFile
        let version_hash_to_validate = source_code_version_hash(
          &source_file.source_code,
          version::DENO,
350
          &self.config.hash,
B
Bartek Iwańczuk 已提交
351 352 353 354 355
        );

        if metadata.version_hash == version_hash_to_validate {
          debug!("load_cache metadata version hash match");
          if let Ok(compiled_module) =
356
            self.get_compiled_module(&source_file.url)
B
Bartek Iwańczuk 已提交
357
          {
358 359
            self.mark_compiled(&source_file.url);
            return Box::new(futures::future::ok(compiled_module));
B
Bartek Iwańczuk 已提交
360
          }
K
Kitson Kelly 已提交
361 362
        }
      }
B
Bartek Iwańczuk 已提交
363
    }
K
Kitson Kelly 已提交
364

B
Bartek Iwańczuk 已提交
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
    let source_file_ = source_file.clone();

    debug!(">>>>> compile_sync START");
    let module_url = source_file.url.clone();

    debug!(
      "Running rust part of compile_sync, module specifier: {}",
      &source_file.url
    );

    let root_names = vec![module_url.to_string()];
    let req_msg = req(root_names, self.config.clone(), None);

    let worker = TsCompiler::setup_worker(state.clone());
    let compiling_job = state.progress.add("Compile", &module_url.to_string());
    let state_ = state.clone();

    let resource = worker.state.resource.clone();
    let compiler_rid = resource.rid;
    let first_msg_fut =
      resources::post_message_to_worker(compiler_rid, req_msg)
        .then(move |_| worker)
        .then(move |result| {
          if let Err(err) = result {
            // TODO(ry) Need to forward the error instead of exiting.
            eprintln!("{}", err.to_string());
            std::process::exit(1);
          }
          debug!("Sent message to worker");
          let stream_future =
            resources::get_message_stream_from_worker(compiler_rid)
              .into_future();
          stream_future.map(|(f, _rest)| f).map_err(|(f, _rest)| f)
        });

    let fut = first_msg_fut
      .map_err(|_| panic!("not handled"))
      .and_then(move |maybe_msg: Option<Buf>| {
        debug!("Received message from worker");

        if let Some(msg) = maybe_msg {
          let json_str = std::str::from_utf8(&msg).unwrap();
          debug!("Message: {}", json_str);
          if let Some(diagnostics) = Diagnostic::from_emit_result(json_str) {
            return Err(ErrBox::from(diagnostics));
          }
        }
K
Kitson Kelly 已提交
412

B
Bartek Iwańczuk 已提交
413
        Ok(())
414 415
      })
      .and_then(move |_| {
B
Bartek Iwańczuk 已提交
416 417 418 419
        // if we are this far it means compilation was successful and we can
        // load compiled filed from disk
        state_
          .ts_compiler
420
          .get_compiled_module(&source_file_.url)
B
Bartek Iwańczuk 已提交
421 422 423 424
          .map_err(|e| {
            // TODO: this situation shouldn't happen
            panic!("Expected to find compiled file: {}", e)
          })
425 426
      })
      .and_then(move |compiled_module| {
B
Bartek Iwańczuk 已提交
427 428 429
        // Explicit drop to keep reference alive until future completes.
        drop(compiling_job);

430
        Ok(compiled_module)
431 432
      })
      .then(move |r| {
B
Bartek Iwańczuk 已提交
433 434 435 436 437 438
        debug!(">>>>> compile_sync END");
        // TODO(ry) do this in worker's destructor.
        // resource.close();
        r
      });

439
    Box::new(fut)
B
Bartek Iwańczuk 已提交
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
  }

  /// Get associated `CompiledFileMetadata` for given module if it exists.
  pub fn get_metadata(self: &Self, url: &Url) -> Option<CompiledFileMetadata> {
    // Try to load cached version:
    // 1. check if there's 'meta' file
    let cache_key = self
      .disk_cache
      .get_cache_filename_with_extension(url, "meta");
    if let Ok(metadata_bytes) = self.disk_cache.get(&cache_key) {
      if let Ok(metadata) = std::str::from_utf8(&metadata_bytes) {
        if let Some(read_metadata) =
          CompiledFileMetadata::from_json_string(metadata.to_string())
        {
          return Some(read_metadata);
455 456
        }
      }
B
Bartek Iwańczuk 已提交
457 458 459 460
    }

    None
  }
R
Ryan Dahl 已提交
461

462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
  pub fn get_compiled_module(
    self: &Self,
    module_url: &Url,
  ) -> Result<CompiledModule, ErrBox> {
    let compiled_source_file = self.get_compiled_source_file(module_url)?;

    let compiled_module = CompiledModule {
      code: str::from_utf8(&compiled_source_file.source_code)
        .unwrap()
        .to_string(),
      name: module_url.to_string(),
    };

    Ok(compiled_module)
  }

B
Bartek Iwańczuk 已提交
478 479 480 481 482
  /// Return compiled JS file for given TS module.
  // TODO: ideally we shouldn't construct SourceFile by hand, but it should be delegated to
  // SourceFileFetcher
  pub fn get_compiled_source_file(
    self: &Self,
483
    module_url: &Url,
B
Bartek Iwańczuk 已提交
484 485 486
  ) -> Result<SourceFile, ErrBox> {
    let cache_key = self
      .disk_cache
487
      .get_cache_filename_with_extension(&module_url, "js");
B
Bartek Iwańczuk 已提交
488 489 490 491 492
    let compiled_code = self.disk_cache.get(&cache_key)?;
    let compiled_code_filename = self.disk_cache.location.join(cache_key);
    debug!("compiled filename: {:?}", compiled_code_filename);

    let compiled_module = SourceFile {
493
      url: module_url.clone(),
B
Bartek Iwańczuk 已提交
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
      filename: compiled_code_filename,
      media_type: msg::MediaType::JavaScript,
      source_code: compiled_code,
    };

    Ok(compiled_module)
  }

  /// Save compiled JS file for given TS module to on-disk cache.
  ///
  /// Along compiled file a special metadata file is saved as well containing
  /// hash that can be validated to avoid unnecessary recompilation.
  fn cache_compiled_file(
    self: &Self,
    module_specifier: &ModuleSpecifier,
    contents: &str,
  ) -> std::io::Result<()> {
    let js_key = self
      .disk_cache
      .get_cache_filename_with_extension(module_specifier.as_url(), "js");
    self
      .disk_cache
      .set(&js_key, contents.as_bytes())
      .and_then(|_| {
        self.mark_compiled(module_specifier.as_url());

        let source_file = self
521
          .file_fetcher
B
Bartek Iwańczuk 已提交
522 523 524 525 526 527
          .fetch_source_file(&module_specifier)
          .expect("Source file not found");

        let version_hash = source_code_version_hash(
          &source_file.source_code,
          version::DENO,
528
          &self.config.hash,
B
Bartek Iwańczuk 已提交
529 530 531 532 533 534 535 536 537 538 539 540 541
        );

        let compiled_file_metadata = CompiledFileMetadata {
          source_path: source_file.filename.to_owned(),
          version_hash,
        };
        let meta_key = self
          .disk_cache
          .get_cache_filename_with_extension(module_specifier.as_url(), "meta");
        self.disk_cache.set(
          &meta_key,
          compiled_file_metadata.to_json_string()?.as_bytes(),
        )
542
      })
B
Bartek Iwańczuk 已提交
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
  }

  /// Return associated source map file for given TS module.
  // TODO: ideally we shouldn't construct SourceFile by hand, but it should be delegated to
  // SourceFileFetcher
  pub fn get_source_map_file(
    self: &Self,
    module_specifier: &ModuleSpecifier,
  ) -> Result<SourceFile, ErrBox> {
    let cache_key = self
      .disk_cache
      .get_cache_filename_with_extension(module_specifier.as_url(), "js.map");
    let source_code = self.disk_cache.get(&cache_key)?;
    let source_map_filename = self.disk_cache.location.join(cache_key);
    debug!("source map filename: {:?}", source_map_filename);

    let source_map_file = SourceFile {
      url: module_specifier.as_url().to_owned(),
      filename: source_map_filename,
      media_type: msg::MediaType::JavaScript,
      source_code,
    };

    Ok(source_map_file)
  }

  /// Save source map file for given TS module to on-disk cache.
  fn cache_source_map(
    self: &Self,
    module_specifier: &ModuleSpecifier,
    contents: &str,
  ) -> std::io::Result<()> {
    let source_map_key = self
      .disk_cache
      .get_cache_filename_with_extension(module_specifier.as_url(), "js.map");
    self.disk_cache.set(&source_map_key, contents.as_bytes())
  }

  /// This method is called by TS compiler via an "op".
  pub fn cache_compiler_output(
    self: &Self,
    module_specifier: &ModuleSpecifier,
    extension: &str,
    contents: &str,
  ) -> std::io::Result<()> {
    match extension {
      ".map" => self.cache_source_map(module_specifier, contents),
      ".js" => self.cache_compiled_file(module_specifier, contents),
      _ => unreachable!(),
    }
  }
A
andy finch 已提交
594 595
}

B
Bartek Iwańczuk 已提交
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
impl SourceMapGetter for TsCompiler {
  fn get_source_map(&self, script_name: &str) -> Option<Vec<u8>> {
    self
      .try_to_resolve_and_get_source_map(script_name)
      .and_then(|out| Some(out.source_code))
  }

  fn get_source_line(&self, script_name: &str, line: usize) -> Option<String> {
    self
      .try_resolve_and_get_source_file(script_name)
      .and_then(|out| {
        str::from_utf8(&out.source_code).ok().and_then(|v| {
          let lines: Vec<&str> = v.lines().collect();
          assert!(lines.len() > line);
          Some(lines[line].to_string())
        })
      })
  }
}

// `SourceMapGetter` related methods
impl TsCompiler {
  fn try_to_resolve(self: &Self, script_name: &str) -> Option<ModuleSpecifier> {
    // if `script_name` can't be resolved to ModuleSpecifier it's probably internal
    // script (like `gen/cli/bundle/compiler.js`) so we won't be
    // able to get source for it anyway
    ModuleSpecifier::resolve_url(script_name).ok()
  }

  fn try_resolve_and_get_source_file(
    &self,
    script_name: &str,
  ) -> Option<SourceFile> {
    if let Some(module_specifier) = self.try_to_resolve(script_name) {
630
      return match self.file_fetcher.fetch_source_file(&module_specifier) {
B
Bartek Iwańczuk 已提交
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
        Ok(out) => Some(out),
        Err(_) => None,
      };
    }

    None
  }

  fn try_to_resolve_and_get_source_map(
    &self,
    script_name: &str,
  ) -> Option<SourceFile> {
    if let Some(module_specifier) = self.try_to_resolve(script_name) {
      return match self.get_source_map_file(&module_specifier) {
        Ok(out) => Some(out),
        Err(_) => None,
      };
    }

    None
  }
R
Ryan Dahl 已提交
652 653 654 655 656
}

#[cfg(test)]
mod tests {
  use super::*;
B
Bartek Iwańczuk 已提交
657 658 659 660 661 662 663 664 665
  use crate::tokio_util;
  use deno::ModuleSpecifier;
  use std::path::PathBuf;

  impl TsCompiler {
    fn compile_sync(
      self: &Self,
      state: ThreadSafeState,
      source_file: &SourceFile,
666
    ) -> Result<CompiledModule, ErrBox> {
B
Bartek Iwańczuk 已提交
667 668 669
      tokio_util::block_on(self.compile_async(state, source_file))
    }
  }
R
Ryan Dahl 已提交
670 671 672

  #[test]
  fn test_compile_sync() {
A
andy finch 已提交
673
    tokio_util::init(|| {
B
Bartek Iwańczuk 已提交
674 675 676
      let specifier =
        ModuleSpecifier::resolve_url_or_path("./tests/002_hello.ts").unwrap();

677
      let out = SourceFile {
B
Bartek Iwańczuk 已提交
678
        url: specifier.as_url().clone(),
679
        filename: PathBuf::from("/tests/002_hello.ts"),
A
andy finch 已提交
680
        media_type: msg::MediaType::TypeScript,
681
        source_code: include_bytes!("../../tests/002_hello.ts").to_vec(),
A
andy finch 已提交
682 683
      };

B
Bartek Iwańczuk 已提交
684 685 686 687
      let mock_state = ThreadSafeState::mock(vec![
        String::from("./deno"),
        String::from("hello.js"),
      ]);
688
      let compiled = mock_state
B
Bartek Iwańczuk 已提交
689 690 691
        .ts_compiler
        .compile_sync(mock_state.clone(), &out)
        .unwrap();
692 693 694 695
      assert!(compiled
        .code
        .as_bytes()
        .starts_with("console.log(\"Hello World\");".as_bytes()));
A
andy finch 已提交
696
    })
697 698
  }

K
Kitson Kelly 已提交
699 700 701
  #[test]
  fn test_bundle_async() {
    let specifier = "./tests/002_hello.ts";
702
    use deno::ModuleSpecifier;
703
    let module_name = ModuleSpecifier::resolve_url_or_path(specifier)
K
Kitson Kelly 已提交
704 705 706 707 708 709 710 711
      .unwrap()
      .to_string();

    let state = ThreadSafeState::mock(vec![
      String::from("./deno"),
      String::from("./tests/002_hello.ts"),
      String::from("$deno$/bundle.js"),
    ]);
B
Bartek Iwańczuk 已提交
712 713 714 715 716
    let out = state.ts_compiler.bundle_async(
      state.clone(),
      module_name,
      String::from("$deno$/bundle.js"),
    );
K
Kitson Kelly 已提交
717
    assert!(tokio_util::block_on(out).is_ok());
K
Kitson Kelly 已提交
718
  }
B
Bartek Iwańczuk 已提交
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741

  #[test]
  fn test_source_code_version_hash() {
    assert_eq!(
      "08574f9cdeb94fd3fb9cdc7a20d086daeeb42bca",
      source_code_version_hash(b"1+2", "0.4.0", b"{}")
    );
    // Different source_code should result in different hash.
    assert_eq!(
      "d8abe2ead44c3ff8650a2855bf1b18e559addd06",
      source_code_version_hash(b"1", "0.4.0", b"{}")
    );
    // Different version should result in different hash.
    assert_eq!(
      "d6feffc5024d765d22c94977b4fe5975b59d6367",
      source_code_version_hash(b"1", "0.1.0", b"{}")
    );
    // Different config should result in different hash.
    assert_eq!(
      "3b35db249b26a27decd68686f073a58266b2aec2",
      source_code_version_hash(b"1", "0.4.0", b"{\"compilerOptions\": {}}")
    );
  }
R
Ryan Dahl 已提交
742
}