0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-01 12:16:11 -05:00

perf: use emit from swc instead of tsc (#15118)

This commit is contained in:
David Sherret 2022-07-12 18:58:39 -04:00 committed by GitHub
parent 7610764980
commit 0c87dd1e98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 664 additions and 525 deletions

215
cli/cache/check.rs vendored Normal file
View file

@ -0,0 +1,215 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use std::path::Path;
use deno_ast::ModuleSpecifier;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_runtime::deno_webstorage::rusqlite::params;
use deno_runtime::deno_webstorage::rusqlite::Connection;
use super::common::run_sqlite_pragma;
/// The cache used to tell whether type checking should occur again.
///
/// This simply stores a hash of the inputs of each successful type check
/// and only clears them out when changing CLI versions.
pub struct TypeCheckCache {
conn: Connection,
}
impl TypeCheckCache {
pub fn new(db_file_path: &Path) -> Result<Self, AnyError> {
let conn = Connection::open(db_file_path).with_context(|| {
format!(
concat!(
"Error opening type checking cache at {} -- ",
"Perhaps it's corrupt. Maybe try deleting it."
),
db_file_path.display()
)
})?;
Self::from_connection(conn, crate::version::deno())
}
fn from_connection(
conn: Connection,
cli_version: String,
) -> Result<Self, AnyError> {
run_sqlite_pragma(&conn)?;
create_tables(&conn, cli_version)?;
Ok(Self { conn })
}
pub fn has_check_hash(&self, hash: u64) -> bool {
match self.hash_check_hash_result(hash) {
Ok(val) => val,
Err(err) => {
if cfg!(debug_assertions) {
panic!("Error retrieving hash: {}", err);
} else {
log::debug!("Error retrieving hash: {}", err);
// fail silently when not debugging
false
}
}
}
}
fn hash_check_hash_result(&self, hash: u64) -> Result<bool, AnyError> {
let query = "SELECT * FROM checkcache WHERE check_hash=?1 LIMIT 1";
let mut stmt = self.conn.prepare_cached(query)?;
Ok(stmt.exists(params![hash.to_string()])?)
}
pub fn add_check_hash(&self, check_hash: u64) {
if let Err(err) = self.add_check_hash_result(check_hash) {
if cfg!(debug_assertions) {
panic!("Error saving check hash: {}", err);
} else {
log::debug!("Error saving check hash: {}", err);
}
}
}
fn add_check_hash_result(&self, check_hash: u64) -> Result<(), AnyError> {
let sql = "
INSERT OR REPLACE INTO
checkcache (check_hash)
VALUES
(?1)";
let mut stmt = self.conn.prepare_cached(sql)?;
stmt.execute(params![&check_hash.to_string(),])?;
Ok(())
}
pub fn get_tsbuildinfo(&self, specifier: &ModuleSpecifier) -> Option<String> {
let mut stmt = self
.conn
.prepare_cached("SELECT text FROM tsbuildinfo WHERE specifier=?1 LIMIT 1")
.ok()?;
let mut rows = stmt.query(params![specifier.to_string()]).ok()?;
let row = rows.next().ok().flatten()?;
row.get(0).ok()
}
pub fn set_tsbuildinfo(&self, specifier: &ModuleSpecifier, text: &str) {
if let Err(err) = self.set_tsbuildinfo_result(specifier, text) {
// should never error here, but if it ever does don't fail
if cfg!(debug_assertions) {
panic!("Error saving tsbuildinfo: {}", err);
} else {
log::debug!("Error saving tsbuildinfo: {}", err);
}
}
}
fn set_tsbuildinfo_result(
&self,
specifier: &ModuleSpecifier,
text: &str,
) -> Result<(), AnyError> {
let mut stmt = self.conn.prepare_cached(
"INSERT OR REPLACE INTO tsbuildinfo (specifier, text) VALUES (?1, ?2)",
)?;
stmt.execute(params![specifier.to_string(), text])?;
Ok(())
}
}
fn create_tables(
conn: &Connection,
cli_version: String,
) -> Result<(), AnyError> {
// INT doesn't store up to u64, so use TEXT
conn.execute(
"CREATE TABLE IF NOT EXISTS checkcache (
check_hash TEXT PRIMARY KEY
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS tsbuildinfo (
specifier TEXT PRIMARY KEY,
text TEXT NOT NULL
)",
[],
)?;
conn.execute(
"CREATE TABLE IF NOT EXISTS info (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
)",
[],
)?;
// delete the cache when the CLI version changes
let data_cli_version: Option<String> = conn
.query_row(
"SELECT value FROM info WHERE key='CLI_VERSION' LIMIT 1",
[],
|row| row.get(0),
)
.ok();
if data_cli_version != Some(cli_version.to_string()) {
conn.execute("DELETE FROM checkcache", params![])?;
conn.execute("DELETE FROM tsbuildinfo", params![])?;
let mut stmt = conn
.prepare("INSERT OR REPLACE INTO info (key, value) VALUES (?1, ?2)")?;
stmt.execute(params!["CLI_VERSION", &cli_version])?;
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn check_cache_general_use() {
let conn = Connection::open_in_memory().unwrap();
let cache =
TypeCheckCache::from_connection(conn, "1.0.0".to_string()).unwrap();
assert!(!cache.has_check_hash(1));
cache.add_check_hash(1);
assert!(cache.has_check_hash(1));
assert!(!cache.has_check_hash(2));
let specifier1 = ModuleSpecifier::parse("file:///test.json").unwrap();
assert_eq!(cache.get_tsbuildinfo(&specifier1), None);
cache.set_tsbuildinfo(&specifier1, "test");
assert_eq!(cache.get_tsbuildinfo(&specifier1), Some("test".to_string()));
// try changing the cli version (should clear)
let conn = cache.conn;
let cache =
TypeCheckCache::from_connection(conn, "2.0.0".to_string()).unwrap();
assert!(!cache.has_check_hash(1));
cache.add_check_hash(1);
assert!(cache.has_check_hash(1));
assert_eq!(cache.get_tsbuildinfo(&specifier1), None);
cache.set_tsbuildinfo(&specifier1, "test");
assert_eq!(cache.get_tsbuildinfo(&specifier1), Some("test".to_string()));
// recreating the cache should not remove the data because the CLI version and state hash is the same
let conn = cache.conn;
let cache =
TypeCheckCache::from_connection(conn, "2.0.0".to_string()).unwrap();
assert!(cache.has_check_hash(1));
assert!(!cache.has_check_hash(2));
assert_eq!(cache.get_tsbuildinfo(&specifier1), Some("test".to_string()));
// adding when already exists should not cause issue
cache.add_check_hash(1);
assert!(cache.has_check_hash(1));
cache.set_tsbuildinfo(&specifier1, "other");
assert_eq!(
cache.get_tsbuildinfo(&specifier1),
Some("other".to_string())
);
}
}

31
cli/cache/common.rs vendored Normal file
View file

@ -0,0 +1,31 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use deno_core::error::AnyError;
use deno_runtime::deno_webstorage::rusqlite::Connection;
/// Very fast non-cryptographically secure hash.
pub fn fast_insecure_hash(bytes: &[u8]) -> u64 {
use std::hash::Hasher;
use twox_hash::XxHash64;
let mut hasher = XxHash64::default();
hasher.write(bytes);
hasher.finish()
}
/// Runs the common sqlite pragma.
pub fn run_sqlite_pragma(conn: &Connection) -> Result<(), AnyError> {
// Enable write-ahead-logging and tweak some other stuff
let initial_pragmas = "
-- enable write-ahead-logging mode
PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
PRAGMA temp_store=memory;
PRAGMA page_size=4096;
PRAGMA mmap_size=6000000;
PRAGMA optimize;
";
conn.execute_batch(initial_pragmas)?;
Ok(())
}

View file

@ -1,14 +1,17 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use crate::cache::CacheType;
use crate::cache::Cacher;
use crate::cache::EmitMetadata;
use crate::fs_util; use crate::fs_util;
use crate::http_cache::url_to_filename; use crate::http_cache::url_to_filename;
use super::CacheType;
use super::Cacher;
use super::EmitMetadata;
use deno_ast::ModuleSpecifier; use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::url::{Host, Url}; use deno_core::url::Host;
use deno_core::url::Url;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs; use std::fs;
use std::io; use std::io;
@ -184,7 +187,6 @@ impl Cacher for DiskCache {
let extension = match cache_type { let extension = match cache_type {
CacheType::Emit => "js", CacheType::Emit => "js",
CacheType::SourceMap => "js.map", CacheType::SourceMap => "js.map",
CacheType::TypeScriptBuildInfo => "buildinfo",
CacheType::Version => { CacheType::Version => {
return self.get_emit_metadata(specifier).map(|d| d.version_hash) return self.get_emit_metadata(specifier).map(|d| d.version_hash)
} }
@ -206,7 +208,6 @@ impl Cacher for DiskCache {
let extension = match cache_type { let extension = match cache_type {
CacheType::Emit => "js", CacheType::Emit => "js",
CacheType::SourceMap => "js.map", CacheType::SourceMap => "js.map",
CacheType::TypeScriptBuildInfo => "buildinfo",
CacheType::Version => { CacheType::Version => {
let data = if let Some(mut data) = self.get_emit_metadata(specifier) { let data = if let Some(mut data) = self.get_emit_metadata(specifier) {
data.version_hash = value; data.version_hash = value;

71
cli/cache/emit.rs vendored Normal file
View file

@ -0,0 +1,71 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError;
use super::CacheType;
use super::Cacher;
/// Emit cache for a single file.
#[derive(Debug, Clone, PartialEq)]
pub struct SpecifierEmitCacheData {
pub source_hash: String,
pub text: String,
pub map: Option<String>,
}
pub trait EmitCache {
/// Gets the emit data from the cache.
fn get_emit_data(
&self,
specifier: &ModuleSpecifier,
) -> Option<SpecifierEmitCacheData>;
/// Sets the emit data in the cache.
fn set_emit_data(
&self,
specifier: ModuleSpecifier,
data: SpecifierEmitCacheData,
) -> Result<(), AnyError>;
/// Gets the stored hash of the source of the provider specifier
/// to tell if the emit is out of sync with the source.
/// TODO(13302): this is actually not reliable and should be removed
/// once switching to an sqlite db
fn get_source_hash(&self, specifier: &ModuleSpecifier) -> Option<String>;
/// Gets the emitted JavaScript of the TypeScript source.
/// TODO(13302): remove this once switching to an sqlite db
fn get_emit_text(&self, specifier: &ModuleSpecifier) -> Option<String>;
}
impl<T: Cacher> EmitCache for T {
fn get_emit_data(
&self,
specifier: &ModuleSpecifier,
) -> Option<SpecifierEmitCacheData> {
Some(SpecifierEmitCacheData {
source_hash: self.get_source_hash(specifier)?,
text: self.get_emit_text(specifier)?,
map: self.get(CacheType::SourceMap, specifier),
})
}
fn get_source_hash(&self, specifier: &ModuleSpecifier) -> Option<String> {
self.get(CacheType::Version, specifier)
}
fn get_emit_text(&self, specifier: &ModuleSpecifier) -> Option<String> {
self.get(CacheType::Emit, specifier)
}
fn set_emit_data(
&self,
specifier: ModuleSpecifier,
data: SpecifierEmitCacheData,
) -> Result<(), AnyError> {
self.set(CacheType::Version, &specifier, data.source_hash)?;
self.set(CacheType::Emit, &specifier, data.text)?;
if let Some(map) = data.map {
self.set(CacheType::SourceMap, &specifier, map)?;
}
Ok(())
}
}

View file

@ -1,3 +1,5 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@ -10,6 +12,9 @@ use deno_runtime::deno_webstorage::rusqlite::Connection;
use serde::Serialize; use serde::Serialize;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use super::common::fast_insecure_hash;
use super::common::run_sqlite_pragma;
/// Cache used to skip formatting/linting a file again when we /// Cache used to skip formatting/linting a file again when we
/// know it is already formatted or has no lint diagnostics. /// know it is already formatted or has no lint diagnostics.
pub struct IncrementalCache(Option<IncrementalCacheInner>); pub struct IncrementalCache(Option<IncrementalCacheInner>);
@ -165,7 +170,7 @@ impl SqlIncrementalCache {
state_hash: u64, state_hash: u64,
cli_version: String, cli_version: String,
) -> Result<Self, AnyError> { ) -> Result<Self, AnyError> {
run_pragma(&conn)?; run_sqlite_pragma(&conn)?;
create_tables(&conn, cli_version)?; create_tables(&conn, cli_version)?;
Ok(Self { conn, state_hash }) Ok(Self { conn, state_hash })
@ -229,22 +234,6 @@ impl SqlIncrementalCache {
} }
} }
fn run_pragma(conn: &Connection) -> Result<(), AnyError> {
// Enable write-ahead-logging and tweak some other stuff
let initial_pragmas = "
-- enable write-ahead-logging mode
PRAGMA journal_mode=WAL;
PRAGMA synchronous=NORMAL;
PRAGMA temp_store=memory;
PRAGMA page_size=4096;
PRAGMA mmap_size=6000000;
PRAGMA optimize;
";
conn.execute_batch(initial_pragmas)?;
Ok(())
}
fn create_tables( fn create_tables(
conn: &Connection, conn: &Connection,
cli_version: String, cli_version: String,
@ -284,16 +273,6 @@ fn create_tables(
Ok(()) Ok(())
} }
/// Very fast non-cryptographically secure hash.
fn fast_insecure_hash(bytes: &[u8]) -> u64 {
use std::hash::Hasher;
use twox_hash::XxHash64;
let mut hasher = XxHash64::default();
hasher.write(bytes);
hasher.finish()
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::path::PathBuf; use std::path::PathBuf;

View file

@ -1,6 +1,5 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use crate::disk_cache::DiskCache;
use crate::errors::get_error_class_name; use crate::errors::get_error_class_name;
use crate::file_fetcher::FileFetcher; use crate::file_fetcher::FileFetcher;
@ -16,6 +15,18 @@ use deno_graph::source::Loader;
use deno_runtime::permissions::Permissions; use deno_runtime::permissions::Permissions;
use std::sync::Arc; use std::sync::Arc;
mod check;
mod common;
mod disk_cache;
mod emit;
mod incremental;
pub use check::TypeCheckCache;
pub use disk_cache::DiskCache;
pub use emit::EmitCache;
pub use emit::SpecifierEmitCacheData;
pub use incremental::IncrementalCache;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub struct EmitMetadata { pub struct EmitMetadata {
pub version_hash: String, pub version_hash: String,
@ -24,7 +35,6 @@ pub struct EmitMetadata {
pub enum CacheType { pub enum CacheType {
Emit, Emit,
SourceMap, SourceMap,
TypeScriptBuildInfo,
Version, Version,
} }

View file

@ -1,6 +1,7 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use crate::disk_cache::DiskCache; use crate::cache::DiskCache;
use std::path::PathBuf; use std::path::PathBuf;
/// `DenoDir` serves as coordinator for multiple `DiskCache`s containing them /// `DenoDir` serves as coordinator for multiple `DiskCache`s containing them
@ -56,6 +57,12 @@ impl DenoDir {
// bump this version name to invalidate the entire cache // bump this version name to invalidate the entire cache
self.root.join("lint_incremental_cache_v1") self.root.join("lint_incremental_cache_v1")
} }
/// Path for the incremental cache used for linting.
pub fn type_checking_cache_db_file_path(&self) -> PathBuf {
// bump this version name to invalidate the entire cache
self.root.join("check_cache_v1")
}
} }
/// To avoid the poorly managed dirs crate /// To avoid the poorly managed dirs crate

View file

@ -9,8 +9,9 @@ use crate::args::ConfigFile;
use crate::args::EmitConfigOptions; use crate::args::EmitConfigOptions;
use crate::args::TsConfig; use crate::args::TsConfig;
use crate::args::TypeCheckMode; use crate::args::TypeCheckMode;
use crate::cache::CacheType; use crate::cache::EmitCache;
use crate::cache::Cacher; use crate::cache::SpecifierEmitCacheData;
use crate::cache::TypeCheckCache;
use crate::colors; use crate::colors;
use crate::diagnostics::Diagnostics; use crate::diagnostics::Diagnostics;
use crate::graph_util::GraphData; use crate::graph_util::GraphData;
@ -35,97 +36,12 @@ use deno_graph::ModuleGraph;
use deno_graph::ModuleGraphError; use deno_graph::ModuleGraphError;
use deno_graph::ModuleKind; use deno_graph::ModuleKind;
use deno_graph::ResolutionError; use deno_graph::ResolutionError;
use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt; use std::fmt;
use std::result; use std::result;
use std::sync::Arc; use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
/// Emit cache for a single file.
#[derive(Debug, Clone, PartialEq)]
pub struct SpecifierEmitCacheData {
pub source_hash: String,
pub text: String,
pub map: Option<String>,
}
pub trait EmitCache {
/// Gets the emit data from the cache.
fn get_emit_data(
&self,
specifier: &ModuleSpecifier,
) -> Option<SpecifierEmitCacheData>;
/// Gets the stored hash of the source of the provider specifier
/// to tell if the emit is out of sync with the source.
/// TODO(13302): this is actually not reliable and should be removed
/// once switching to an sqlite db
fn get_source_hash(&self, specifier: &ModuleSpecifier) -> Option<String>;
/// Gets the emitted JavaScript of the TypeScript source.
/// TODO(13302): remove this once switching to an sqlite db
fn get_emit_text(&self, specifier: &ModuleSpecifier) -> Option<String>;
/// Sets the emit data in the cache.
fn set_emit_data(
&self,
specifier: ModuleSpecifier,
data: SpecifierEmitCacheData,
) -> Result<(), AnyError>;
/// Gets the .tsbuildinfo file from the cache.
fn get_tsbuildinfo(&self, specifier: &ModuleSpecifier) -> Option<String>;
/// Sets the .tsbuildinfo file in the cache.
fn set_tsbuildinfo(
&self,
specifier: ModuleSpecifier,
text: String,
) -> Result<(), AnyError>;
}
impl<T: Cacher> EmitCache for T {
fn get_emit_data(
&self,
specifier: &ModuleSpecifier,
) -> Option<SpecifierEmitCacheData> {
Some(SpecifierEmitCacheData {
source_hash: self.get_source_hash(specifier)?,
text: self.get_emit_text(specifier)?,
map: self.get(CacheType::SourceMap, specifier),
})
}
fn get_source_hash(&self, specifier: &ModuleSpecifier) -> Option<String> {
self.get(CacheType::Version, specifier)
}
fn get_emit_text(&self, specifier: &ModuleSpecifier) -> Option<String> {
self.get(CacheType::Emit, specifier)
}
fn set_emit_data(
&self,
specifier: ModuleSpecifier,
data: SpecifierEmitCacheData,
) -> Result<(), AnyError> {
self.set(CacheType::Version, &specifier, data.source_hash)?;
self.set(CacheType::Emit, &specifier, data.text)?;
if let Some(map) = data.map {
self.set(CacheType::SourceMap, &specifier, map)?;
}
Ok(())
}
fn get_tsbuildinfo(&self, specifier: &ModuleSpecifier) -> Option<String> {
self.get(CacheType::TypeScriptBuildInfo, specifier)
}
fn set_tsbuildinfo(
&self,
specifier: ModuleSpecifier,
text: String,
) -> Result<(), AnyError> {
self.set(CacheType::TypeScriptBuildInfo, &specifier, text)
}
}
/// A structure representing stats from an emit operation for a graph. /// A structure representing stats from an emit operation for a graph.
#[derive(Clone, Debug, Default, Eq, PartialEq)] #[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Stats(pub Vec<(String, u32)>); pub struct Stats(pub Vec<(String, u32)>);
@ -236,13 +152,17 @@ pub fn get_ts_config_for_emit(
let mut ts_config = TsConfig::new(json!({ let mut ts_config = TsConfig::new(json!({
"allowJs": true, "allowJs": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"checkJs": false,
"experimentalDecorators": true, "experimentalDecorators": true,
"incremental": true, "incremental": true,
"jsx": "react", "jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
"isolatedModules": true, "isolatedModules": true,
"lib": lib, "lib": lib,
"module": "esnext", "module": "esnext",
"resolveJsonModule": true, "resolveJsonModule": true,
"sourceMap": false,
"strict": true, "strict": true,
"target": "esnext", "target": "esnext",
"tsBuildInfoFile": "deno:///.tsbuildinfo", "tsBuildInfoFile": "deno:///.tsbuildinfo",
@ -378,10 +298,6 @@ pub struct CheckOptions {
pub type_check_mode: TypeCheckMode, pub type_check_mode: TypeCheckMode,
/// Set the debug flag on the TypeScript type checker. /// Set the debug flag on the TypeScript type checker.
pub debug: bool, pub debug: bool,
/// If true, any files emitted will be cached, even if there are diagnostics
/// produced. If false, if there are diagnostics, caching emitted files will
/// be skipped.
pub emit_with_diagnostics: bool,
/// The module specifier to the configuration file, passed to tsc so that /// The module specifier to the configuration file, passed to tsc so that
/// configuration related diagnostics are properly formed. /// configuration related diagnostics are properly formed.
pub maybe_config_specifier: Option<ModuleSpecifier>, pub maybe_config_specifier: Option<ModuleSpecifier>,
@ -389,46 +305,43 @@ pub struct CheckOptions {
pub ts_config: TsConfig, pub ts_config: TsConfig,
/// If true, `Check <specifier>` will be written to stdout for each root. /// If true, `Check <specifier>` will be written to stdout for each root.
pub log_checks: bool, pub log_checks: bool,
/// If true, valid existing emits and `.tsbuildinfo` files will be ignored. /// If true, valid `.tsbuildinfo` files will be ignored and type checking
/// will always occur.
pub reload: bool, pub reload: bool,
pub reload_exclusions: HashSet<ModuleSpecifier>,
} }
/// The result of a check or emit of a module graph. Note that the actual /// The result of a check of a module graph.
/// emitted sources are stored in the cache and are not returned in the result.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct CheckEmitResult { pub struct CheckResult {
pub diagnostics: Diagnostics, pub diagnostics: Diagnostics,
pub stats: Stats, pub stats: Stats,
} }
/// Given a set of roots and graph data, type check the module graph and /// Given a set of roots and graph data, type check the module graph.
/// optionally emit modules, updating the cache as appropriate. Emitting is
/// determined by the `ts_config` supplied in the options, and if emitting, the
/// files are stored in the cache.
/// ///
/// It is expected that it is determined if a check and/or emit is validated /// It is expected that it is determined if a check and/or emit is validated
/// before the function is called. /// before the function is called.
pub fn check_and_maybe_emit( pub fn check(
roots: &[(ModuleSpecifier, ModuleKind)], roots: &[(ModuleSpecifier, ModuleKind)],
graph_data: Arc<RwLock<GraphData>>, graph_data: Arc<RwLock<GraphData>>,
cache: &dyn EmitCache, cache: &TypeCheckCache,
options: CheckOptions, options: CheckOptions,
) -> Result<CheckEmitResult, AnyError> { ) -> Result<CheckResult, AnyError> {
let check_js = options.ts_config.get_check_js(); let check_js = options.ts_config.get_check_js();
let segment_graph_data = { let segment_graph_data = {
let graph_data = graph_data.read(); let graph_data = graph_data.read();
graph_data.graph_segment(roots).unwrap() graph_data.graph_segment(roots).unwrap()
}; };
if valid_emit( let check_hash = match get_check_hash(&segment_graph_data, &options) {
&segment_graph_data, CheckHashResult::NoFiles => return Ok(Default::default()),
cache, CheckHashResult::Hash(hash) => hash,
&options.ts_config, };
options.reload,
&options.reload_exclusions, // do not type check if we know this is type checked
) { if !options.reload && cache.has_check_hash(check_hash) {
return Ok(Default::default()); return Ok(Default::default());
} }
let root_names = get_tsc_roots(roots, &segment_graph_data, check_js); let root_names = get_tsc_roots(roots, &segment_graph_data, check_js);
if options.log_checks { if options.log_checks {
for (root, _) in roots { for (root, _) in roots {
@ -454,12 +367,11 @@ pub fn check_and_maybe_emit(
options.ts_config.as_bytes(), options.ts_config.as_bytes(),
version::deno().as_bytes().to_owned(), version::deno().as_bytes().to_owned(),
]; ];
let config_bytes = options.ts_config.as_bytes();
let response = tsc::exec(tsc::Request { let response = tsc::exec(tsc::Request {
config: options.ts_config, config: options.ts_config,
debug: options.debug, debug: options.debug,
graph_data: graph_data.clone(), graph_data,
hash_data, hash_data,
maybe_config_specifier: options.maybe_config_specifier, maybe_config_specifier: options.maybe_config_specifier,
maybe_tsbuildinfo, maybe_tsbuildinfo,
@ -478,105 +390,15 @@ pub fn check_and_maybe_emit(
response.diagnostics response.diagnostics
}; };
// sometimes we want to emit when there are diagnostics, and sometimes we if let Some(tsbuildinfo) = response.maybe_tsbuildinfo {
// don't. tsc will always return an emit if there are diagnostics cache.set_tsbuildinfo(&roots[0].0, &tsbuildinfo);
if (diagnostics.is_empty() || options.emit_with_diagnostics)
&& !response.emitted_files.is_empty()
{
if let Some(info) = &response.maybe_tsbuildinfo {
// while we retrieve the build info for just the first module, it can be
// used for all the roots in the graph, so we will cache it for all roots
for (root, _) in roots {
cache.set_tsbuildinfo(root.clone(), info.to_string())?;
}
}
struct SpecifierEmitData {
pub version_hash: String,
pub text: Option<String>,
pub map: Option<String>,
}
impl SpecifierEmitData {
fn into_cache_data(self) -> Option<SpecifierEmitCacheData> {
self.text.map(|text| SpecifierEmitCacheData {
source_hash: self.version_hash,
text,
map: self.map,
})
}
}
// combine the emitted files into groups based on their specifier and media type
let mut emit_data_items: HashMap<ModuleSpecifier, SpecifierEmitData> =
HashMap::with_capacity(response.emitted_files.len());
for emit in response.emitted_files.into_iter() {
if let Some(specifiers) = emit.maybe_specifiers {
assert!(specifiers.len() == 1);
// The emitted specifier might not be the file specifier we want, so we
// resolve it via the graph.
let graph_data = graph_data.read();
let specifier = graph_data.follow_redirect(&specifiers[0]);
let (source_bytes, media_type, ts_check) =
match graph_data.get(&specifier) {
Some(ModuleEntry::Module {
code,
media_type,
ts_check,
..
}) => (code.as_bytes(), *media_type, *ts_check),
_ => {
log::debug!("skipping emit for {}", specifier);
continue;
}
};
// Sometimes if `tsc` sees a CommonJS file or a JSON module, it will
// _helpfully_ output it, which we don't really want to do unless
// someone has enabled check_js.
if matches!(media_type, MediaType::Json)
|| (!check_js
&& !ts_check
&& matches!(
media_type,
MediaType::JavaScript | MediaType::Cjs | MediaType::Mjs
))
{
log::debug!("skipping emit for {}", specifier);
continue;
}
let mut emit_data_item = emit_data_items
.entry(specifier.clone())
.or_insert_with(|| SpecifierEmitData {
version_hash: get_version(source_bytes, &config_bytes),
text: None,
map: None,
});
match emit.media_type {
MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => {
emit_data_item.text = Some(emit.data);
}
MediaType::SourceMap => {
emit_data_item.map = Some(emit.data);
}
_ => unreachable!(
"unexpected media_type {} {}",
emit.media_type, specifier
),
}
}
}
// now insert these items into the cache
for (specifier, data) in emit_data_items.into_iter() {
if let Some(cache_data) = data.into_cache_data() {
cache.set_emit_data(specifier, cache_data)?;
}
}
} }
Ok(CheckEmitResult { if diagnostics.is_empty() {
cache.add_check_hash(check_hash);
}
Ok(CheckResult {
diagnostics, diagnostics,
stats: response.stats, stats: response.stats,
}) })
@ -590,12 +412,12 @@ pub struct EmitOptions {
/// Given a module graph, emit any appropriate modules and cache them. /// Given a module graph, emit any appropriate modules and cache them.
// TODO(nayeemrmn): This would ideally take `GraphData` like // TODO(nayeemrmn): This would ideally take `GraphData` like
// `check_and_maybe_emit()`, but the AST isn't stored in that. Cleanup. // `check()`, but the AST isn't stored in that. Cleanup.
pub fn emit( pub fn emit(
graph: &ModuleGraph, graph: &ModuleGraph,
cache: &dyn EmitCache, cache: &dyn EmitCache,
options: EmitOptions, options: EmitOptions,
) -> Result<CheckEmitResult, AnyError> { ) -> Result<CheckResult, AnyError> {
let start = Instant::now(); let start = Instant::now();
let config_bytes = options.ts_config.as_bytes(); let config_bytes = options.ts_config.as_bytes();
let include_js = options.ts_config.get_check_js(); let include_js = options.ts_config.get_check_js();
@ -623,7 +445,7 @@ pub fn emit(
let transpiled_source = module let transpiled_source = module
.maybe_parsed_source .maybe_parsed_source
.as_ref() .as_ref()
.map(|ps| ps.transpile(&emit_options)) .map(|source| source.transpile(&emit_options))
.unwrap()?; .unwrap()?;
emit_count += 1; emit_count += 1;
cache.set_emit_data( cache.set_emit_data(
@ -642,26 +464,41 @@ pub fn emit(
("Total time".to_string(), start.elapsed().as_millis() as u32), ("Total time".to_string(), start.elapsed().as_millis() as u32),
]); ]);
Ok(CheckEmitResult { Ok(CheckResult {
diagnostics: Diagnostics::default(), diagnostics: Diagnostics::default(),
stats, stats,
}) })
} }
/// Check a module graph to determine if the graph contains anything that enum CheckHashResult {
/// is required to be emitted to be valid. It determines what modules in the Hash(u64),
/// graph are emittable and for those that are emittable, if there is currently NoFiles,
/// a valid emit in the cache. }
fn valid_emit(
/// Gets a hash of the inputs for type checking. This can then
/// be used to tell
fn get_check_hash(
graph_data: &GraphData, graph_data: &GraphData,
cache: &dyn EmitCache, options: &CheckOptions,
ts_config: &TsConfig, ) -> CheckHashResult {
reload: bool, // twox hash is insecure, but fast so it works for our purposes
reload_exclusions: &HashSet<ModuleSpecifier>, use std::hash::Hasher;
) -> bool { use twox_hash::XxHash64;
let config_bytes = ts_config.as_bytes();
let check_js = ts_config.get_check_js(); let mut hasher = XxHash64::default();
for (specifier, module_entry) in graph_data.entries() { hasher.write_u8(match options.type_check_mode {
TypeCheckMode::All => 0,
TypeCheckMode::Local => 1,
TypeCheckMode::None => 2,
});
hasher.write(&options.ts_config.as_bytes());
let check_js = options.ts_config.get_check_js();
let mut sorted_entries = graph_data.entries().collect::<Vec<_>>();
sorted_entries.sort_by_key(|(s, _)| s.as_str()); // make it deterministic
let mut has_file = false;
let mut has_file_to_type_check = false;
for (specifier, module_entry) in sorted_entries {
if let ModuleEntry::Module { if let ModuleEntry::Module {
code, code,
media_type, media_type,
@ -669,13 +506,26 @@ fn valid_emit(
.. ..
} = module_entry } = module_entry
{ {
if *ts_check {
has_file_to_type_check = true;
}
match media_type { match media_type {
MediaType::TypeScript MediaType::TypeScript
| MediaType::Dts
| MediaType::Dmts
| MediaType::Dcts
| MediaType::Mts | MediaType::Mts
| MediaType::Cts | MediaType::Cts
| MediaType::Tsx | MediaType::Tsx => {
| MediaType::Jsx => {} has_file = true;
MediaType::JavaScript | MediaType::Mjs | MediaType::Cjs => { has_file_to_type_check = true;
}
MediaType::JavaScript
| MediaType::Mjs
| MediaType::Cjs
| MediaType::Jsx => {
has_file = true;
if !check_js && !ts_check { if !check_js && !ts_check {
continue; continue;
} }
@ -683,25 +533,20 @@ fn valid_emit(
MediaType::Json MediaType::Json
| MediaType::TsBuildInfo | MediaType::TsBuildInfo
| MediaType::SourceMap | MediaType::SourceMap
| MediaType::Dts
| MediaType::Dmts
| MediaType::Dcts
| MediaType::Wasm | MediaType::Wasm
| MediaType::Unknown => continue, | MediaType::Unknown => continue,
} }
if reload && !reload_exclusions.contains(specifier) { hasher.write(specifier.as_str().as_bytes());
return false; hasher.write(code.as_bytes());
}
if let Some(source_hash) = cache.get_source_hash(specifier) {
if source_hash != get_version(code.as_bytes(), &config_bytes) {
return false;
}
} else {
return false;
}
} }
} }
true
if !has_file || !check_js && !has_file_to_type_check {
// no files to type check
CheckHashResult::NoFiles
} else {
CheckHashResult::Hash(hasher.finish())
}
} }
/// An adapter struct to make a deno_graph::ModuleGraphError display as expected /// An adapter struct to make a deno_graph::ModuleGraphError display as expected

View file

@ -162,8 +162,10 @@ impl GraphData {
} }
} }
pub fn entries(&self) -> HashMap<&ModuleSpecifier, &ModuleEntry> { pub fn entries(
self.modules.iter().collect() &self,
) -> impl Iterator<Item = (&ModuleSpecifier, &ModuleEntry)> {
self.modules.iter()
} }
/// Walk dependencies from `roots` and return every encountered specifier. /// Walk dependencies from `roots` and return every encountered specifier.

View file

@ -9,7 +9,6 @@ mod compat;
mod deno_dir; mod deno_dir;
mod diagnostics; mod diagnostics;
mod diff; mod diff;
mod disk_cache;
mod display; mod display;
mod emit; mod emit;
mod errors; mod errors;
@ -59,6 +58,7 @@ use crate::args::TypeCheckMode;
use crate::args::UninstallFlags; use crate::args::UninstallFlags;
use crate::args::UpgradeFlags; use crate::args::UpgradeFlags;
use crate::args::VendorFlags; use crate::args::VendorFlags;
use crate::cache::TypeCheckCache;
use crate::emit::TsConfigType; use crate::emit::TsConfigType;
use crate::file_fetcher::File; use crate::file_fetcher::File;
use crate::file_watcher::ResolutionResult; use crate::file_watcher::ResolutionResult;
@ -661,19 +661,20 @@ async fn create_graph_and_maybe_check(
eprintln!("{}", ignored_options); eprintln!("{}", ignored_options);
} }
let maybe_config_specifier = ps.options.maybe_config_file_specifier(); let maybe_config_specifier = ps.options.maybe_config_file_specifier();
let check_result = emit::check_and_maybe_emit( // todo: don't use anything on failure
let cache =
TypeCheckCache::new(&ps.dir.type_checking_cache_db_file_path())?;
let check_result = emit::check(
&graph.roots, &graph.roots,
Arc::new(RwLock::new(graph.as_ref().into())), Arc::new(RwLock::new(graph.as_ref().into())),
&ps.dir.gen_cache, &cache,
emit::CheckOptions { emit::CheckOptions {
type_check_mode: ps.options.type_check_mode(), type_check_mode: ps.options.type_check_mode(),
debug, debug,
emit_with_diagnostics: false,
maybe_config_specifier, maybe_config_specifier,
ts_config: ts_config_result.ts_config, ts_config: ts_config_result.ts_config,
log_checks: true, log_checks: true,
reload: ps.options.reload_flag(), reload: ps.options.reload_flag(),
reload_exclusions: Default::default(),
}, },
)?; )?;
debug!("{}", check_result.stats); debug!("{}", check_result.stats);

View file

@ -5,11 +5,12 @@ use crate::args::DenoSubcommand;
use crate::args::Flags; use crate::args::Flags;
use crate::args::TypeCheckMode; use crate::args::TypeCheckMode;
use crate::cache; use crate::cache;
use crate::cache::EmitCache;
use crate::cache::TypeCheckCache;
use crate::compat; use crate::compat;
use crate::compat::NodeEsmResolver; use crate::compat::NodeEsmResolver;
use crate::deno_dir; use crate::deno_dir;
use crate::emit; use crate::emit;
use crate::emit::EmitCache;
use crate::emit::TsConfigType; use crate::emit::TsConfigType;
use crate::emit::TsTypeLib; use crate::emit::TsTypeLib;
use crate::file_fetcher::FileFetcher; use crate::file_fetcher::FileFetcher;
@ -394,7 +395,7 @@ impl ProcState {
// should be skipped. // should be skipped.
let reload_exclusions: HashSet<ModuleSpecifier> = { let reload_exclusions: HashSet<ModuleSpecifier> = {
let graph_data = self.graph_data.read(); let graph_data = self.graph_data.read();
graph_data.entries().into_keys().cloned().collect() graph_data.entries().map(|(s, _)| s).cloned().collect()
}; };
{ {
@ -426,36 +427,45 @@ impl ProcState {
log::warn!("{}", ignored_options); log::warn!("{}", ignored_options);
} }
if self.options.type_check_mode() == TypeCheckMode::None { // start type checking if necessary
let options = emit::EmitOptions { let type_checking_task =
ts_config: ts_config_result.ts_config, if self.options.type_check_mode() != TypeCheckMode::None {
reload: self.options.reload_flag(), let maybe_config_specifier = self.options.maybe_config_file_specifier();
reload_exclusions, let roots = roots.clone();
let options = emit::CheckOptions {
type_check_mode: self.options.type_check_mode(),
debug: self.options.log_level() == Some(log::Level::Debug),
maybe_config_specifier,
ts_config: ts_config_result.ts_config.clone(),
log_checks: true,
reload: self.options.reload_flag()
&& !roots.iter().all(|r| reload_exclusions.contains(&r.0)),
};
// todo(THIS PR): don't use a cache on failure
let check_cache =
TypeCheckCache::new(&self.dir.type_checking_cache_db_file_path())?;
let graph_data = self.graph_data.clone();
Some(tokio::task::spawn_blocking(move || {
emit::check(&roots, graph_data, &check_cache, options)
}))
} else {
None
}; };
let emit_result = emit::emit(&graph, &self.dir.gen_cache, options)?;
log::debug!("{}", emit_result.stats); let options = emit::EmitOptions {
} else { ts_config: ts_config_result.ts_config,
let maybe_config_specifier = self.options.maybe_config_file_specifier(); reload: self.options.reload_flag(),
let options = emit::CheckOptions { reload_exclusions,
type_check_mode: self.options.type_check_mode(), };
debug: self.options.log_level() == Some(log::Level::Debug), let emit_result = emit::emit(&graph, &self.dir.gen_cache, options)?;
emit_with_diagnostics: false, log::debug!("{}", emit_result.stats);
maybe_config_specifier,
ts_config: ts_config_result.ts_config, if let Some(type_checking_task) = type_checking_task {
log_checks: true, let type_check_result = type_checking_task.await??;
reload: self.options.reload_flag(), if !type_check_result.diagnostics.is_empty() {
reload_exclusions, return Err(anyhow!(type_check_result.diagnostics));
};
let emit_result = emit::check_and_maybe_emit(
&roots,
self.graph_data.clone(),
&self.dir.gen_cache,
options,
)?;
if !emit_result.diagnostics.is_empty() {
return Err(anyhow!(emit_result.diagnostics));
} }
log::debug!("{}", emit_result.stats); log::debug!("{}", type_check_result.stats);
} }
if self.options.type_check_mode() != TypeCheckMode::None { if self.options.type_check_mode() != TypeCheckMode::None {

View file

@ -1,7 +1,11 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
use std::process::Stdio;
use crate::itest; use crate::itest;
use test_util as util;
itest!(_095_check_with_bare_import { itest!(_095_check_with_bare_import {
args: "check 095_cache_with_bare_import.ts", args: "check 095_cache_with_bare_import.ts",
output: "095_cache_with_bare_import.ts.out", output: "095_cache_with_bare_import.ts.out",
@ -43,3 +47,62 @@ itest!(declaration_header_file_with_no_exports {
args: "check --quiet declaration_header_file_with_no_exports.ts", args: "check --quiet declaration_header_file_with_no_exports.ts",
output_str: Some(""), output_str: Some(""),
}); });
#[test]
fn cache_switching_config_then_no_config() {
let deno_dir = util::new_deno_dir();
assert!(does_type_checking(&deno_dir, true));
assert!(does_type_checking(&deno_dir, false));
// should now not do type checking even when it changes
// configs because it previously did
assert!(!does_type_checking(&deno_dir, true));
assert!(!does_type_checking(&deno_dir, false));
fn does_type_checking(deno_dir: &util::TempDir, with_config: bool) -> bool {
let mut cmd = util::deno_cmd_with_deno_dir(deno_dir);
cmd
.current_dir(util::testdata_path())
.stderr(Stdio::piped())
.arg("check")
.arg("check/cache_config_on_off/main.ts");
if with_config {
cmd
.arg("--config")
.arg("check/cache_config_on_off/deno.json");
}
let output = cmd.spawn().unwrap().wait_with_output().unwrap();
assert!(output.status.success());
let stderr = std::str::from_utf8(&output.stderr).unwrap();
stderr.contains("Check")
}
}
#[test]
fn reload_flag() {
// should do type checking whenever someone specifies --reload
let deno_dir = util::new_deno_dir();
assert!(does_type_checking(&deno_dir, false));
assert!(!does_type_checking(&deno_dir, false));
assert!(does_type_checking(&deno_dir, true));
assert!(does_type_checking(&deno_dir, true));
assert!(!does_type_checking(&deno_dir, false));
fn does_type_checking(deno_dir: &util::TempDir, reload: bool) -> bool {
let mut cmd = util::deno_cmd_with_deno_dir(deno_dir);
cmd
.current_dir(util::testdata_path())
.stderr(Stdio::piped())
.arg("check")
.arg("check/cache_config_on_off/main.ts");
if reload {
cmd.arg("--reload");
}
let output = cmd.spawn().unwrap().wait_with_output().unwrap();
assert!(output.status.success());
let stderr = std::str::from_utf8(&output.stderr).unwrap();
stderr.contains("Check")
}
}

View file

@ -158,12 +158,6 @@ fn cache_test() {
.expect("Failed to spawn script"); .expect("Failed to spawn script");
assert!(output.status.success()); assert!(output.status.success());
let out = std::str::from_utf8(&output.stderr).unwrap();
// Check if file and dependencies are written successfully
assert!(out.contains("host.writeFile(\"deno://subdir/print_hello.js\")"));
assert!(out.contains("host.writeFile(\"deno://subdir/mod2.js\")"));
assert!(out.contains("host.writeFile(\"deno://006_url_imports.js\")"));
let prg = util::deno_exe_path(); let prg = util::deno_exe_path();
let output = Command::new(&prg) let output = Command::new(&prg)
.env("DENO_DIR", deno_dir.path()) .env("DENO_DIR", deno_dir.path())
@ -369,46 +363,6 @@ fn ts_no_recheck_on_redirect() {
assert!(std::str::from_utf8(&output.stderr).unwrap().is_empty()); assert!(std::str::from_utf8(&output.stderr).unwrap().is_empty());
} }
#[test]
fn ts_reload() {
let hello_ts = util::testdata_path().join("002_hello.ts");
assert!(hello_ts.is_file());
let deno_dir = TempDir::new();
let mut initial = util::deno_cmd_with_deno_dir(&deno_dir)
.current_dir(util::testdata_path())
.arg("cache")
.arg("--check=all")
.arg(&hello_ts)
.spawn()
.expect("failed to spawn script");
let status_initial =
initial.wait().expect("failed to wait for child process");
assert!(status_initial.success());
let output = util::deno_cmd_with_deno_dir(&deno_dir)
.current_dir(util::testdata_path())
.arg("cache")
.arg("--check=all")
.arg("--reload")
.arg("-L")
.arg("debug")
.arg(&hello_ts)
.output()
.expect("failed to spawn script");
// check the output of the the bundle program.
let output_path = hello_ts.canonicalize().unwrap();
assert!(
dbg!(std::str::from_utf8(&output.stderr).unwrap().trim()).contains(
&format!(
"host.getSourceFile(\"{}\", Latest)",
url::Url::from_file_path(&output_path).unwrap().as_str()
)
)
);
}
#[test] #[test]
fn timeout_clear() { fn timeout_clear() {
// https://github.com/denoland/deno/issues/7599 // https://github.com/denoland/deno/issues/7599

View file

@ -2,8 +2,10 @@
use deno_core::url; use deno_core::url;
use std::process::Command; use std::process::Command;
use std::process::Stdio;
use test_util as util; use test_util as util;
use test_util::TempDir; use test_util::TempDir;
use util::assert_contains;
itest!(stdout_write_all { itest!(stdout_write_all {
args: "run --quiet stdout_write_all.ts", args: "run --quiet stdout_write_all.ts",
@ -268,7 +270,7 @@ fn webstorage_location_shares_origin() {
.arg("--location") .arg("--location")
.arg("https://example.com/a.ts") .arg("https://example.com/a.ts")
.arg("webstorage/fixture.ts") .arg("webstorage/fixture.ts")
.stdout(std::process::Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -283,7 +285,7 @@ fn webstorage_location_shares_origin() {
.arg("--location") .arg("--location")
.arg("https://example.com/b.ts") .arg("https://example.com/b.ts")
.arg("webstorage/logger.ts") .arg("webstorage/logger.ts")
.stdout(std::process::Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -305,7 +307,7 @@ fn webstorage_config_file() {
.arg("--config") .arg("--config")
.arg("webstorage/config_a.jsonc") .arg("webstorage/config_a.jsonc")
.arg("webstorage/fixture.ts") .arg("webstorage/fixture.ts")
.stdout(std::process::Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -320,7 +322,7 @@ fn webstorage_config_file() {
.arg("--config") .arg("--config")
.arg("webstorage/config_b.jsonc") .arg("webstorage/config_b.jsonc")
.arg("webstorage/logger.ts") .arg("webstorage/logger.ts")
.stdout(std::process::Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -335,7 +337,7 @@ fn webstorage_config_file() {
.arg("--config") .arg("--config")
.arg("webstorage/config_a.jsonc") .arg("webstorage/config_a.jsonc")
.arg("webstorage/logger.ts") .arg("webstorage/logger.ts")
.stdout(std::process::Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -359,7 +361,7 @@ fn webstorage_location_precedes_config() {
.arg("--config") .arg("--config")
.arg("webstorage/config_a.jsonc") .arg("webstorage/config_a.jsonc")
.arg("webstorage/fixture.ts") .arg("webstorage/fixture.ts")
.stdout(std::process::Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -376,7 +378,7 @@ fn webstorage_location_precedes_config() {
.arg("--config") .arg("--config")
.arg("webstorage/config_b.jsonc") .arg("webstorage/config_b.jsonc")
.arg("webstorage/logger.ts") .arg("webstorage/logger.ts")
.stdout(std::process::Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -396,7 +398,7 @@ fn webstorage_main_module() {
.current_dir(util::testdata_path()) .current_dir(util::testdata_path())
.arg("run") .arg("run")
.arg("webstorage/fixture.ts") .arg("webstorage/fixture.ts")
.stdout(std::process::Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -409,7 +411,7 @@ fn webstorage_main_module() {
.current_dir(util::testdata_path()) .current_dir(util::testdata_path())
.arg("run") .arg("run")
.arg("webstorage/logger.ts") .arg("webstorage/logger.ts")
.stdout(std::process::Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -422,7 +424,7 @@ fn webstorage_main_module() {
.current_dir(util::testdata_path()) .current_dir(util::testdata_path())
.arg("run") .arg("run")
.arg("webstorage/fixture.ts") .arg("webstorage/fixture.ts")
.stdout(std::process::Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -1632,8 +1634,8 @@ fn no_validate_asm() {
.current_dir(util::testdata_path()) .current_dir(util::testdata_path())
.arg("run") .arg("run")
.arg("no_validate_asm.js") .arg("no_validate_asm.js")
.stderr(std::process::Stdio::piped()) .stderr(Stdio::piped())
.stdout(std::process::Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -1650,7 +1652,7 @@ fn exec_path() {
.arg("run") .arg("run")
.arg("--allow-read") .arg("--allow-read")
.arg("exec_path.ts") .arg("exec_path.ts")
.stdout(std::process::Stdio::piped()) .stdout(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -1776,7 +1778,7 @@ fn rust_log() {
.current_dir(util::testdata_path()) .current_dir(util::testdata_path())
.arg("run") .arg("run")
.arg("001_hello.js") .arg("001_hello.js")
.stderr(std::process::Stdio::piped()) .stderr(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -1790,7 +1792,7 @@ fn rust_log() {
.arg("run") .arg("run")
.arg("001_hello.js") .arg("001_hello.js")
.env("RUST_LOG", "debug") .env("RUST_LOG", "debug")
.stderr(std::process::Stdio::piped()) .stderr(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -1810,7 +1812,7 @@ fn dont_cache_on_check_fail() {
.arg("--check=all") .arg("--check=all")
.arg("--reload") .arg("--reload")
.arg("error_003_typescript.ts") .arg("error_003_typescript.ts")
.stderr(std::process::Stdio::piped()) .stderr(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -1824,7 +1826,7 @@ fn dont_cache_on_check_fail() {
.arg("run") .arg("run")
.arg("--check=all") .arg("--check=all")
.arg("error_003_typescript.ts") .arg("error_003_typescript.ts")
.stderr(std::process::Stdio::piped()) .stderr(Stdio::piped())
.spawn() .spawn()
.unwrap() .unwrap()
.wait_with_output() .wait_with_output()
@ -2374,8 +2376,8 @@ fn issue12740() {
.current_dir(util::testdata_path()) .current_dir(util::testdata_path())
.arg("run") .arg("run")
.arg(&mod1_path) .arg(&mod1_path)
.stderr(std::process::Stdio::null()) .stderr(Stdio::null())
.stdout(std::process::Stdio::null()) .stdout(Stdio::null())
.spawn() .spawn()
.unwrap() .unwrap()
.wait() .wait()
@ -2387,8 +2389,8 @@ fn issue12740() {
.current_dir(util::testdata_path()) .current_dir(util::testdata_path())
.arg("run") .arg("run")
.arg(&mod1_path) .arg(&mod1_path)
.stderr(std::process::Stdio::null()) .stderr(Stdio::null())
.stdout(std::process::Stdio::null()) .stdout(Stdio::null())
.spawn() .spawn()
.unwrap() .unwrap()
.wait() .wait()
@ -2411,8 +2413,8 @@ fn issue12807() {
.arg("run") .arg("run")
.arg("--check") .arg("--check")
.arg(&mod1_path) .arg(&mod1_path)
.stderr(std::process::Stdio::null()) .stderr(Stdio::null())
.stdout(std::process::Stdio::null()) .stdout(Stdio::null())
.spawn() .spawn()
.unwrap() .unwrap()
.wait() .wait()
@ -2425,8 +2427,8 @@ fn issue12807() {
.arg("run") .arg("run")
.arg("--check") .arg("--check")
.arg(&mod1_path) .arg(&mod1_path)
.stderr(std::process::Stdio::null()) .stderr(Stdio::null())
.stdout(std::process::Stdio::null()) .stdout(Stdio::null())
.spawn() .spawn()
.unwrap() .unwrap()
.wait() .wait()
@ -2663,6 +2665,36 @@ itest!(js_root_with_ts_check {
exit_code: 1, exit_code: 1,
}); });
#[test]
fn check_local_then_remote() {
let _http_guard = util::http_server();
let deno_dir = util::new_deno_dir();
let output = util::deno_cmd_with_deno_dir(&deno_dir)
.current_dir(util::testdata_path())
.arg("run")
.arg("--check")
.arg("run/remote_type_error/main.ts")
.spawn()
.unwrap()
.wait_with_output()
.unwrap();
assert!(output.status.success());
let output = util::deno_cmd_with_deno_dir(&deno_dir)
.current_dir(util::testdata_path())
.arg("run")
.arg("--check=all")
.arg("run/remote_type_error/main.ts")
.env("NO_COLOR", "1")
.stderr(Stdio::piped())
.spawn()
.unwrap()
.wait_with_output()
.unwrap();
assert!(!output.status.success());
let stderr = std::str::from_utf8(&output.stderr).unwrap();
assert_contains!(stderr, "Type 'string' is not assignable to type 'number'.");
}
itest!(no_prompt_flag { itest!(no_prompt_flag {
args: "run --quiet --unstable --no-prompt no_prompt.ts", args: "run --quiet --unstable --no-prompt no_prompt.ts",
output_str: Some(""), output_str: Some(""),

View file

@ -0,0 +1,5 @@
{
"compilerOptions": {
"strict": false
}
}

View file

@ -0,0 +1 @@
console.log(5);

View file

@ -11,7 +11,7 @@ BRH:0
DA:1,1 DA:1,1
DA:2,2 DA:2,2
DA:3,2 DA:3,2
DA:4,2 DA:4,0
DA:5,0 DA:5,0
DA:6,0 DA:6,0
DA:7,2 DA:7,2
@ -22,6 +22,6 @@ DA:12,0
DA:13,0 DA:13,0
DA:14,0 DA:14,0
DA:15,0 DA:15,0
LH:5 LH:4
LF:14 LF:14
end_of_record end_of_record

View file

@ -1,4 +1,5 @@
cover [WILDCARD]/coverage/branch.ts ... 35.714% (5/14) cover [WILDCARD]/coverage/branch.ts ... 28.571% (4/14)
4 | } else {
5 | return false; 5 | return false;
6 | } 6 | }
-----|----- -----|-----

View file

@ -11,44 +11,62 @@ FNF:4
FNH:2 FNH:2
BRF:0 BRF:0
BRH:0 BRH:0
DA:13,1
DA:14,1
DA:15,1
DA:16,1
DA:17,2 DA:17,2
DA:18,2 DA:18,2
DA:19,2 DA:19,2
DA:20,2 DA:20,2
DA:21,2
DA:22,2 DA:22,2
DA:23,2 DA:23,2
DA:24,2 DA:24,2
DA:25,2 DA:25,2
DA:26,2 DA:26,2
DA:27,2 DA:27,2
DA:29,1
DA:30,1
DA:31,1
DA:32,1 DA:32,1
DA:33,1 DA:33,1
DA:34,1 DA:34,1
DA:35,1 DA:35,1
DA:36,1
DA:37,2 DA:37,2
DA:38,2 DA:38,2
DA:39,2 DA:39,2
DA:40,2 DA:40,2
DA:41,2 DA:41,2
DA:42,2 DA:42,2
DA:44,1
DA:45,1
DA:46,0 DA:46,0
DA:47,0 DA:47,0
DA:48,0 DA:48,0
DA:49,0 DA:49,0
DA:50,0
DA:51,0 DA:51,0
DA:52,0 DA:52,0
DA:53,0 DA:53,0
DA:54,0 DA:54,0
DA:55,0 DA:55,0
DA:56,0 DA:56,0
DA:58,1
DA:59,1
DA:60,1 DA:60,1
DA:62,1
DA:63,1
DA:64,0 DA:64,0
DA:65,0 DA:65,0
DA:66,0 DA:66,0
DA:67,0 DA:67,0
DA:68,0 DA:68,0
DA:70,1
DA:71,0 DA:71,0
DA:73,1
DA:74,1 DA:74,1
LH:22 LH:39
LF:38 LF:56
end_of_record end_of_record

View file

@ -1,9 +1,9 @@
cover [WILDCARD]/coverage/complex.ts ... 57.895% (22/38) cover [WILDCARD]/coverage/complex.ts ... 69.643% (39/56)
46 | export function unused( 46 | export function unused(
47 | foo: string, 47 | foo: string,
48 | bar: string, 48 | bar: string,
49 | baz: string, 49 | baz: string,
-----|----- 50 | ): Complex {
51 | return complex( 51 | return complex(
52 | foo, 52 | foo,
53 | bar, 53 | bar,

View file

@ -0,0 +1,3 @@
import { doAction } from "http://localhost:4545/run/remote_type_error/remote.ts";
doAction();

View file

@ -0,0 +1,5 @@
export function doAction() {
// this is an intentional type error
const val: number = "test";
console.log(val);
}

View file

@ -2,6 +2,7 @@
use crate::args::CoverageFlags; use crate::args::CoverageFlags;
use crate::args::Flags; use crate::args::Flags;
use crate::cache::EmitCache;
use crate::colors; use crate::colors;
use crate::fs_util::collect_files; use crate::fs_util::collect_files;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
@ -676,16 +677,9 @@ pub async fn cover_files(
| MediaType::Mts | MediaType::Mts
| MediaType::Cts | MediaType::Cts
| MediaType::Tsx => { | MediaType::Tsx => {
let emit_path = ps match ps.dir.gen_cache.get_emit_text(&file.specifier) {
.dir Some(source) => source,
.gen_cache None => {
.get_cache_filename_with_extension(&file.specifier, "js")
.unwrap_or_else(|| {
unreachable!("Unable to get cache filename: {}", &file.specifier)
});
match ps.dir.gen_cache.get(&emit_path) {
Ok(b) => String::from_utf8(b).unwrap(),
Err(_) => {
return Err(anyhow!( return Err(anyhow!(
"Missing transpiled source code for: \"{}\". "Missing transpiled source code for: \"{}\".
Before generating coverage report, run `deno test --coverage` to ensure consistent state.", Before generating coverage report, run `deno test --coverage` to ensure consistent state.",

View file

@ -39,7 +39,7 @@ use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
use super::incremental_cache::IncrementalCache; use crate::cache::IncrementalCache;
/// Format JavaScript/TypeScript files. /// Format JavaScript/TypeScript files.
pub async fn format( pub async fn format(

View file

@ -41,7 +41,7 @@ use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use super::incremental_cache::IncrementalCache; use crate::cache::IncrementalCache;
static STDIN_FILE_NAME: &str = "_stdin.ts"; static STDIN_FILE_NAME: &str = "_stdin.ts";

View file

@ -4,7 +4,6 @@ pub mod bench;
pub mod coverage; pub mod coverage;
pub mod doc; pub mod doc;
pub mod fmt; pub mod fmt;
pub mod incremental_cache;
pub mod installer; pub mod installer;
pub mod lint; pub mod lint;
pub mod repl; pub mod repl;

View file

@ -252,8 +252,6 @@ pub struct Request {
pub struct Response { pub struct Response {
/// Any diagnostics that have been returned from the checker. /// Any diagnostics that have been returned from the checker.
pub diagnostics: Diagnostics, 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. /// If there was any build info associated with the exec request.
pub maybe_tsbuildinfo: Option<String>, pub maybe_tsbuildinfo: Option<String>,
/// Statistics from the check. /// Statistics from the check.
@ -263,7 +261,6 @@ pub struct Response {
#[derive(Debug)] #[derive(Debug)]
struct State { struct State {
hash_data: Vec<Vec<u8>>, hash_data: Vec<Vec<u8>>,
emitted_files: Vec<EmittedFile>,
graph_data: Arc<RwLock<GraphData>>, graph_data: Arc<RwLock<GraphData>>,
maybe_config_specifier: Option<ModuleSpecifier>, maybe_config_specifier: Option<ModuleSpecifier>,
maybe_tsbuildinfo: Option<String>, maybe_tsbuildinfo: Option<String>,
@ -283,7 +280,6 @@ impl State {
) -> Self { ) -> Self {
State { State {
hash_data, hash_data,
emitted_files: Default::default(),
graph_data, graph_data,
maybe_config_specifier, maybe_config_specifier,
maybe_tsbuildinfo, maybe_tsbuildinfo,
@ -337,10 +333,6 @@ struct EmitArgs {
/// The _internal_ filename for the file. This will be used to determine how /// The _internal_ filename for the file. This will be used to determine how
/// the file is cached and stored. /// the file is cached and stored.
file_name: String, 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>>,
} }
#[op] #[op]
@ -349,43 +341,9 @@ fn op_emit(state: &mut OpState, args: EmitArgs) -> bool {
match args.file_name.as_ref() { match args.file_name.as_ref() {
"deno:///.tsbuildinfo" => state.maybe_tsbuildinfo = Some(args.data), "deno:///.tsbuildinfo" => state.maybe_tsbuildinfo = Some(args.data),
_ => { _ => {
let media_type = MediaType::from(&args.file_name); if cfg!(debug_assertions) {
let media_type = if matches!( panic!("Unhandled emit write: {}", args.file_name);
media_type, }
MediaType::JavaScript
| MediaType::Mjs
| MediaType::Cjs
| MediaType::Dts
| MediaType::Dmts
| MediaType::Dcts
| MediaType::SourceMap
| MediaType::TsBuildInfo
) {
media_type
} else {
MediaType::JavaScript
};
state.emitted_files.push(EmittedFile {
data: args.data,
maybe_specifiers: if let Some(specifiers) = &args.maybe_specifiers {
let specifiers = specifiers
.iter()
.map(|s| {
if let Some(data_specifier) = state.remapped_specifiers.get(s) {
data_specifier.clone()
} else if let Some(remapped_specifier) = state.root_map.get(s) {
remapped_specifier.clone()
} else {
normalize_specifier(s).unwrap()
}
})
.collect();
Some(specifiers)
} else {
None
},
media_type,
})
} }
} }
@ -703,13 +661,11 @@ pub fn exec(request: Request) -> Result<Response, AnyError> {
if let Some(response) = state.maybe_response { if let Some(response) = state.maybe_response {
let diagnostics = response.diagnostics; let diagnostics = response.diagnostics;
let emitted_files = state.emitted_files;
let maybe_tsbuildinfo = state.maybe_tsbuildinfo; let maybe_tsbuildinfo = state.maybe_tsbuildinfo;
let stats = response.stats; let stats = response.stats;
Ok(Response { Ok(Response {
diagnostics, diagnostics,
emitted_files,
maybe_tsbuildinfo, maybe_tsbuildinfo,
stats, stats,
}) })
@ -907,64 +863,6 @@ mod tests {
} }
} }
#[tokio::test]
async fn test_emit() {
let mut state = setup(None, None, None).await;
let actual = op_emit::call(
&mut state,
EmitArgs {
data: "some file content".to_string(),
file_name: "cache:///some/file.js".to_string(),
maybe_specifiers: Some(vec!["file:///some/file.ts".to_string()]),
},
);
assert!(actual);
let state = state.borrow::<State>();
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![resolve_url_or_path(
"file:///some/file.ts"
)
.unwrap()]),
media_type: MediaType::JavaScript,
}
);
}
#[tokio::test]
async fn test_emit_strange_specifier() {
let mut state = setup(None, None, None).await;
let actual = op_emit::call(
&mut state,
EmitArgs {
data: "some file content".to_string(),
file_name: "deno:///some.file.ts?q=.json".to_string(),
maybe_specifiers: Some(
vec!["file:///some/file.ts?q=.json".to_string()],
),
},
);
assert!(actual);
let state = state.borrow::<State>();
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![resolve_url_or_path(
"file:///some/file.ts?q=.json"
)
.unwrap()]),
media_type: MediaType::JavaScript,
}
);
}
#[tokio::test] #[tokio::test]
async fn test_emit_tsbuildinfo() { async fn test_emit_tsbuildinfo() {
let mut state = setup(None, None, None).await; let mut state = setup(None, None, None).await;
@ -973,12 +871,10 @@ mod tests {
EmitArgs { EmitArgs {
data: "some file content".to_string(), data: "some file content".to_string(),
file_name: "deno:///.tsbuildinfo".to_string(), file_name: "deno:///.tsbuildinfo".to_string(),
maybe_specifiers: None,
}, },
); );
assert!(actual); assert!(actual);
let state = state.borrow::<State>(); let state = state.borrow::<State>();
assert_eq!(state.emitted_files.len(), 0);
assert_eq!( assert_eq!(
state.maybe_tsbuildinfo, state.maybe_tsbuildinfo,
Some("some file content".to_string()) Some("some file content".to_string())
@ -1169,7 +1065,6 @@ mod tests {
.expect("exec should not have errored"); .expect("exec should not have errored");
eprintln!("diagnostics {:#?}", actual.diagnostics); eprintln!("diagnostics {:#?}", actual.diagnostics);
assert!(actual.diagnostics.is_empty()); assert!(actual.diagnostics.is_empty());
assert!(actual.emitted_files.is_empty());
assert!(actual.maybe_tsbuildinfo.is_some()); assert!(actual.maybe_tsbuildinfo.is_some());
assert_eq!(actual.stats.0.len(), 12); assert_eq!(actual.stats.0.len(), 12);
} }
@ -1182,7 +1077,6 @@ mod tests {
.expect("exec should not have errored"); .expect("exec should not have errored");
eprintln!("diagnostics {:#?}", actual.diagnostics); eprintln!("diagnostics {:#?}", actual.diagnostics);
assert!(actual.diagnostics.is_empty()); assert!(actual.diagnostics.is_empty());
assert!(actual.emitted_files.is_empty());
assert!(actual.maybe_tsbuildinfo.is_some()); assert!(actual.maybe_tsbuildinfo.is_some());
assert_eq!(actual.stats.0.len(), 12); assert_eq!(actual.stats.0.len(), 12);
} }

View file

@ -336,15 +336,11 @@ delete Object.prototype.__proto__;
getDefaultLibLocation() { getDefaultLibLocation() {
return ASSETS; return ASSETS;
}, },
writeFile(fileName, data, _writeByteOrderMark, _onError, sourceFiles) { writeFile(fileName, data, _writeByteOrderMark, _onError, _sourceFiles) {
debug(`host.writeFile("${fileName}")`); debug(`host.writeFile("${fileName}")`);
let maybeSpecifiers;
if (sourceFiles) {
maybeSpecifiers = sourceFiles.map((sf) => sf.moduleName);
}
return core.opSync( return core.opSync(
"op_emit", "op_emit",
{ maybeSpecifiers, fileName, data }, { fileName, data },
); );
}, },
getCurrentDirectory() { getCurrentDirectory() {
@ -557,16 +553,18 @@ delete Object.prototype.__proto__;
configFileParsingDiagnostics, configFileParsingDiagnostics,
}); });
const { diagnostics: emitDiagnostics } = program.emit();
const diagnostics = [ const diagnostics = [
...program.getConfigFileParsingDiagnostics(), ...program.getConfigFileParsingDiagnostics(),
...program.getSyntacticDiagnostics(), ...program.getSyntacticDiagnostics(),
...program.getOptionsDiagnostics(), ...program.getOptionsDiagnostics(),
...program.getGlobalDiagnostics(), ...program.getGlobalDiagnostics(),
...program.getSemanticDiagnostics(), ...program.getSemanticDiagnostics(),
...emitDiagnostics,
].filter(({ code }) => !IGNORED_DIAGNOSTICS.includes(code)); ].filter(({ code }) => !IGNORED_DIAGNOSTICS.includes(code));
// emit the tsbuildinfo file
// @ts-ignore: emitBuildInfo is not exposed (https://github.com/microsoft/TypeScript/issues/49871)
program.emitBuildInfo(host.writeFile);
performanceProgram({ program }); performanceProgram({ program });
core.opSync("op_respond", { core.opSync("op_respond", {