1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -05:00

perf(jsr): fast check cache and lazy fast check graph (#22485)

This commit is contained in:
David Sherret 2024-02-20 16:29:57 -05:00 committed by GitHub
parent dbc4a4d632
commit f90889e5ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 669 additions and 220 deletions

17
Cargo.lock generated
View file

@ -1324,9 +1324,9 @@ dependencies = [
[[package]]
name = "deno_doc"
version = "0.107.0"
version = "0.110.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f82478f27de7958eb6a1e48e447b8cb030a1294097ef510eec190d29e81f330f"
checksum = "87316f90c7a0d58043b3a06291c3c75228f6b14b8f3d17ebeb1bc98edc0ebd78"
dependencies = [
"ammonia",
"anyhow",
@ -1348,9 +1348,9 @@ dependencies = [
[[package]]
name = "deno_emit"
version = "0.37.0"
version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a670c56f233f85f18f1d4a3288c5241505d8aea559fe3870b45e00d4c0e731dc"
checksum = "f3871940b8ad61336c4de4645349236d12d1c3be4cb959487b2e3a3eaa498d12"
dependencies = [
"anyhow",
"base64",
@ -1418,9 +1418,9 @@ dependencies = [
[[package]]
name = "deno_graph"
version = "0.66.2"
version = "0.68.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10efbd226fb00e97c04350051cbb025957b2de025117493ee5b9e53cc7e230f"
checksum = "47a122de1af66e3b5389a914ce0bf6296271138d259fb78259b839ca7e0722a7"
dependencies = [
"anyhow",
"async-trait",
@ -1440,6 +1440,7 @@ dependencies = [
"serde_json",
"sha2",
"thiserror",
"twox-hash",
"url",
]
@ -2429,9 +2430,9 @@ dependencies = [
[[package]]
name = "eszip"
version = "0.63.0"
version = "0.64.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "731a0e44e886cb8efbbd63b8121341d505e9dab855fe487249d70c362a6bd774"
checksum = "13ed72fda5ff9a13b0710f6e4bf52c9b56f170ef2c85bdab124f9671aeb124db"
dependencies = [
"anyhow",
"base64",

View file

@ -66,9 +66,9 @@ deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposa
deno_cache_dir = { workspace = true }
deno_config = "=0.10.0"
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_doc = { version = "=0.107.0", features = ["html"] }
deno_emit = "=0.37.0"
deno_graph = "=0.66.2"
deno_doc = { version = "=0.110.0", features = ["html"] }
deno_emit = "=0.38.0"
deno_graph = "=0.68.0"
deno_lint = { version = "=0.56.0", features = ["docs"] }
deno_lockfile.workspace = true
deno_npm = "=0.17.0"
@ -76,7 +76,7 @@ deno_runtime = { workspace = true, features = ["include_js_files_for_snapshottin
deno_semver = "=0.5.4"
deno_task_shell = "=0.14.3"
deno_terminal.workspace = true
eszip = "=0.63.0"
eszip = "=0.64.0"
napi_sym.workspace = true
async-trait.workspace = true

View file

@ -1304,6 +1304,67 @@ impl CliOptions {
)
}
pub fn resolve_deno_graph_workspace_members(
&self,
) -> Result<Vec<deno_graph::WorkspaceMember>, AnyError> {
fn workspace_config_to_workspace_members(
workspace_config: &deno_config::WorkspaceConfig,
) -> Result<Vec<deno_graph::WorkspaceMember>, AnyError> {
workspace_config
.members
.iter()
.map(|member| {
config_to_workspace_member(&member.config_file).with_context(|| {
format!(
"Failed to resolve configuration for '{}' workspace member at '{}'",
member.member_name,
member.config_file.specifier.as_str()
)
})
})
.collect()
}
fn config_to_workspace_member(
config: &ConfigFile,
) -> Result<deno_graph::WorkspaceMember, AnyError> {
let nv = deno_semver::package::PackageNv {
name: match &config.json.name {
Some(name) => name.clone(),
None => bail!("Missing 'name' field in config file."),
},
version: match &config.json.version {
Some(name) => deno_semver::Version::parse_standard(name)?,
None => bail!("Missing 'version' field in config file."),
},
};
Ok(deno_graph::WorkspaceMember {
base: config.specifier.join("./").unwrap(),
nv,
exports: config.to_exports_config()?.into_map(),
})
}
let maybe_workspace_config = self.maybe_workspace_config();
if let Some(wc) = maybe_workspace_config {
workspace_config_to_workspace_members(wc)
} else {
Ok(
self
.maybe_config_file()
.as_ref()
.and_then(|c| match config_to_workspace_member(c) {
Ok(m) => Some(vec![m]),
Err(e) => {
log::debug!("Deno config was not a package: {:#}", e);
None
}
})
.unwrap_or_default(),
)
}
}
/// Vector of user script CLI arguments.
pub fn argv(&self) -> &Vec<String> {
&self.flags.argv

15
cli/cache/caches.rs vendored
View file

@ -9,6 +9,7 @@ use super::cache_db::CacheDB;
use super::cache_db::CacheDBConfiguration;
use super::check::TYPE_CHECK_CACHE_DB;
use super::deno_dir::DenoDirProvider;
use super::fast_check::FAST_CHECK_CACHE_DB;
use super::incremental::INCREMENTAL_CACHE_DB;
use super::module_info::MODULE_INFO_CACHE_DB;
use super::node::NODE_ANALYSIS_CACHE_DB;
@ -18,6 +19,7 @@ pub struct Caches {
fmt_incremental_cache_db: OnceCell<CacheDB>,
lint_incremental_cache_db: OnceCell<CacheDB>,
dep_analysis_db: OnceCell<CacheDB>,
fast_check_db: OnceCell<CacheDB>,
node_analysis_db: OnceCell<CacheDB>,
type_checking_cache_db: OnceCell<CacheDB>,
}
@ -29,6 +31,7 @@ impl Caches {
fmt_incremental_cache_db: Default::default(),
lint_incremental_cache_db: Default::default(),
dep_analysis_db: Default::default(),
fast_check_db: Default::default(),
node_analysis_db: Default::default(),
type_checking_cache_db: Default::default(),
}
@ -86,6 +89,18 @@ impl Caches {
)
}
pub fn fast_check_db(&self) -> CacheDB {
Self::make_db(
&self.fast_check_db,
&FAST_CHECK_CACHE_DB,
self
.dir_provider
.get_or_create()
.ok()
.map(|dir| dir.fast_check_cache_db_file_path()),
)
}
pub fn node_analysis_db(&self) -> CacheDB {
Self::make_db(
&self.node_analysis_db,

View file

@ -98,6 +98,12 @@ impl DenoDir {
self.root.join("dep_analysis_cache_v1")
}
/// Path for the cache used for fast check.
pub fn fast_check_cache_db_file_path(&self) -> PathBuf {
// bump this version name to invalidate the entire cache
self.root.join("fast_check_cache_v1")
}
/// Path for caching node analysis.
pub fn node_analysis_db_file_path(&self) -> PathBuf {
// bump this version name to invalidate the entire cache

162
cli/cache/fast_check.rs vendored Normal file
View file

@ -0,0 +1,162 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_core::error::AnyError;
use deno_graph::FastCheckCacheItem;
use deno_graph::FastCheckCacheKey;
use deno_runtime::deno_webstorage::rusqlite::params;
use super::cache_db::CacheDB;
use super::cache_db::CacheDBConfiguration;
use super::cache_db::CacheFailure;
pub static FAST_CHECK_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration {
table_initializer: "CREATE TABLE IF NOT EXISTS fastcheckcache (
hash TEXT PRIMARY KEY,
data TEXT NOT NULL
);",
on_version_change: "DELETE FROM fastcheckcache;",
preheat_queries: &[],
on_failure: CacheFailure::Blackhole,
};
#[derive(Clone)]
pub struct FastCheckCache {
inner: FastCheckCacheInner,
}
impl FastCheckCache {
pub fn new(db: CacheDB) -> Self {
Self {
inner: FastCheckCacheInner::new(db),
}
}
fn ensure_ok<T: Default>(res: Result<T, AnyError>) -> T {
match res {
Ok(x) => x,
Err(err) => {
// TODO(mmastrac): This behavior was inherited from before the refactoring but it probably makes sense to move it into the cache
// at some point.
// should never error here, but if it ever does don't fail
if cfg!(debug_assertions) {
panic!("Error using fast check cache: {err:#}");
} else {
log::debug!("Error using fast check cache: {:#}", err);
}
T::default()
}
}
}
}
impl deno_graph::FastCheckCache for FastCheckCache {
fn get(&self, key: FastCheckCacheKey) -> Option<FastCheckCacheItem> {
Self::ensure_ok(self.inner.get(key))
}
fn set(&self, key: FastCheckCacheKey, value: FastCheckCacheItem) {
Self::ensure_ok(self.inner.set(key, &value));
}
}
#[derive(Clone)]
struct FastCheckCacheInner {
conn: CacheDB,
}
impl FastCheckCacheInner {
pub fn new(conn: CacheDB) -> Self {
Self { conn }
}
pub fn get(
&self,
key: FastCheckCacheKey,
) -> Result<Option<FastCheckCacheItem>, AnyError> {
let query = "
SELECT
data
FROM
fastcheckcache
WHERE
hash=?1
LIMIT 1";
let res = self
.conn
// key is a string because SQLite can't handle u64
.query_row(query, params![key.as_u64().to_string()], |row| {
let value: Vec<u8> = row.get(0)?;
Ok(bincode::deserialize::<FastCheckCacheItem>(&value)?)
})?;
Ok(res)
}
pub fn set(
&self,
key: FastCheckCacheKey,
data: &FastCheckCacheItem,
) -> Result<(), AnyError> {
let sql = "
INSERT OR REPLACE INTO
fastcheckcache (hash, data)
VALUES
(?1, ?2)";
self.conn.execute(
sql,
params![key.as_u64().to_string(), &bincode::serialize(data)?],
)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use std::collections::BTreeSet;
use deno_ast::ModuleSpecifier;
use deno_graph::FastCheckCacheModuleItem;
use deno_graph::FastCheckCacheModuleItemDiagnostic;
use deno_semver::package::PackageNv;
use super::*;
#[test]
pub fn cache_general_use() {
let conn = CacheDB::in_memory(&FAST_CHECK_CACHE_DB, "1.0.0");
let cache = FastCheckCacheInner::new(conn);
let key = FastCheckCacheKey::build(
&PackageNv::from_str("@scope/a@1.0.0").unwrap(),
&Default::default(),
);
assert!(cache.get(key).unwrap().is_none());
let value = FastCheckCacheItem {
dependencies: BTreeSet::from([
PackageNv::from_str("@scope/b@1.0.0").unwrap()
]),
modules: vec![(
ModuleSpecifier::parse("https://jsr.io/test.ts").unwrap(),
FastCheckCacheModuleItem::Diagnostic(
FastCheckCacheModuleItemDiagnostic { source_hash: 123 },
),
)],
};
cache.set(key, &value).unwrap();
let stored_value = cache.get(key).unwrap().unwrap();
assert_eq!(stored_value, value);
// adding when already exists should not cause issue
cache.set(key, &value).unwrap();
// recreating with same cli version should still have it
let conn = cache.conn.recreate_with_version("1.0.0");
let cache = FastCheckCacheInner::new(conn);
let stored_value = cache.get(key).unwrap().unwrap();
assert_eq!(stored_value, value);
// now changing the cli version should clear it
let conn = cache.conn.recreate_with_version("2.0.0");
let cache = FastCheckCacheInner::new(conn);
assert!(cache.get(key).unwrap().is_none());
}
}

8
cli/cache/mod.rs vendored
View file

@ -1,6 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::args::jsr_url;
use crate::args::CacheSetting;
use crate::errors::get_error_class_name;
use crate::file_fetcher::FetchOptions;
@ -11,7 +10,6 @@ use crate::util::fs::atomic_write_file;
use deno_ast::MediaType;
use deno_core::futures;
use deno_core::futures::FutureExt;
use deno_core::url::Url;
use deno_core::ModuleSpecifier;
use deno_graph::source::CacheInfo;
use deno_graph::source::LoadFuture;
@ -31,6 +29,7 @@ mod common;
mod deno_dir;
mod disk_cache;
mod emit;
mod fast_check;
mod incremental;
mod module_info;
mod node;
@ -43,6 +42,7 @@ pub use deno_dir::DenoDir;
pub use deno_dir::DenoDirProvider;
pub use disk_cache::DiskCache;
pub use emit::EmitCache;
pub use fast_check::FastCheckCache;
pub use incremental::IncrementalCache;
pub use module_info::ModuleInfoCache;
pub use node::NodeAnalysisCache;
@ -167,10 +167,6 @@ impl FetchCacher {
}
impl Loader for FetchCacher {
fn registry_url(&self) -> &Url {
jsr_url()
}
fn get_cache_info(&self, specifier: &ModuleSpecifier) -> Option<CacheInfo> {
if !self.cache_info_enabled {
return None;

View file

@ -22,6 +22,7 @@ use crate::file_fetcher::FileFetcher;
use crate::graph_util::FileWatcherReporter;
use crate::graph_util::ModuleGraphBuilder;
use crate::graph_util::ModuleGraphContainer;
use crate::graph_util::ModuleGraphCreator;
use crate::http_util::HttpClient;
use crate::module_loader::CliModuleLoaderFactory;
use crate::module_loader::ModuleLoadPreparer;
@ -55,6 +56,7 @@ use crate::worker::CliMainWorkerOptions;
use std::path::PathBuf;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::parking_lot::Mutex;
use deno_core::FeatureChecker;
@ -116,6 +118,7 @@ impl<T> Default for Deferred<T> {
}
impl<T> Deferred<T> {
#[inline(always)]
pub fn get_or_try_init(
&self,
create: impl FnOnce() -> Result<T, AnyError>,
@ -123,12 +126,16 @@ impl<T> Deferred<T> {
self.0.get_or_try_init(create)
}
#[inline(always)]
pub fn get_or_init(&self, create: impl FnOnce() -> T) -> &T {
self.0.get_or_init(create)
}
pub async fn get_or_try_init_async(
&self,
// some futures passed here are boxed because it was discovered
// that they were called a lot, causing other futures to get
// really big causing stack overflows on Windows
create: impl Future<Output = Result<T, AnyError>>,
) -> Result<&T, AnyError> {
if self.0.get().is_none() {
@ -164,6 +171,7 @@ struct CliFactoryServices {
resolver: Deferred<Arc<CliGraphResolver>>,
maybe_file_watcher_reporter: Deferred<Option<FileWatcherReporter>>,
module_graph_builder: Deferred<Arc<ModuleGraphBuilder>>,
module_graph_creator: Deferred<Arc<ModuleGraphCreator>>,
module_load_preparer: Deferred<Arc<ModuleLoadPreparer>>,
node_code_translator: Deferred<Arc<CliNodeCodeTranslator>>,
node_resolver: Deferred<Arc<NodeResolver>>,
@ -212,15 +220,17 @@ impl CliFactory {
let caches = Arc::new(Caches::new(self.deno_dir_provider().clone()));
// Warm up the caches we know we'll likely need based on the CLI mode
match self.options.sub_command() {
DenoSubcommand::Run(_) => {
_ = caches.dep_analysis_db();
_ = caches.node_analysis_db();
}
DenoSubcommand::Check(_) => {
DenoSubcommand::Run(_)
| DenoSubcommand::Bench(_)
| DenoSubcommand::Test(_)
| DenoSubcommand::Check(_) => {
_ = caches.dep_analysis_db();
_ = caches.node_analysis_db();
if self.options.type_check_mode().is_true() {
_ = caches.fast_check_db();
_ = caches.type_checking_cache_db();
}
}
_ => {}
}
Ok(caches)
@ -437,7 +447,7 @@ impl CliFactory {
npm_registry_url: crate::args::npm_registry_default_url().to_owned(),
})
}).await
})
}.boxed_local())
.await
}
@ -471,7 +481,8 @@ impl CliFactory {
self
.services
.resolver
.get_or_try_init_async(async {
.get_or_try_init_async(
async {
Ok(Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
fs: self.fs().clone(),
cjs_resolutions: Some(self.cjs_resolutions().clone()),
@ -486,7 +497,9 @@ impl CliFactory {
} else {
Some(self.npm_resolver().await?.clone())
},
package_json_deps_provider: self.package_json_deps_provider().clone(),
package_json_deps_provider: self
.package_json_deps_provider()
.clone(),
maybe_jsx_import_source_config: self
.options
.to_maybe_jsx_import_source_config()?,
@ -496,7 +509,9 @@ impl CliFactory {
.options
.unstable_bare_node_builtins(),
})))
})
}
.boxed_local(),
)
.await
}
@ -554,12 +569,15 @@ impl CliFactory {
self
.services
.node_resolver
.get_or_try_init_async(async {
.get_or_try_init_async(
async {
Ok(Arc::new(NodeResolver::new(
self.fs().clone(),
self.npm_resolver().await?.clone().into_npm_resolver(),
)))
})
}
.boxed_local(),
)
.await
}
@ -594,6 +612,7 @@ impl CliFactory {
Ok(Arc::new(TypeChecker::new(
self.caches()?.clone(),
self.options.clone(),
self.module_graph_builder().await?.clone(),
self.node_resolver().await?.clone(),
self.npm_resolver().await?.clone(),
)))
@ -610,6 +629,7 @@ impl CliFactory {
.get_or_try_init_async(async {
Ok(Arc::new(ModuleGraphBuilder::new(
self.options.clone(),
self.caches()?.clone(),
self.fs().clone(),
self.resolver().await?.clone(),
self.npm_resolver().await?.clone(),
@ -620,6 +640,24 @@ impl CliFactory {
self.emit_cache()?.clone(),
self.file_fetcher()?.clone(),
self.global_http_cache()?.clone(),
)))
})
.await
}
pub async fn module_graph_creator(
&self,
) -> Result<&Arc<ModuleGraphCreator>, AnyError> {
self
.services
.module_graph_creator
.get_or_try_init_async(async {
Ok(Arc::new(ModuleGraphCreator::new(
self.options.clone(),
self.fs().clone(),
self.npm_resolver().await?.clone(),
self.module_graph_builder().await?.clone(),
self.maybe_lockfile().clone(),
self.type_checker().await?.clone(),
)))
})

View file

@ -22,10 +22,8 @@ use crate::util::path::specifier_to_file_path;
use crate::util::sync::TaskQueue;
use crate::util::sync::TaskQueuePermit;
use deno_config::ConfigFile;
use deno_config::WorkspaceMemberConfig;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
@ -208,56 +206,34 @@ pub struct CreateGraphOptions<'a> {
pub graph_kind: GraphKind,
pub roots: Vec<ModuleSpecifier>,
pub is_dynamic: bool,
/// Whether to do fast check on workspace members. This is mostly only
/// useful when publishing.
pub workspace_fast_check: bool,
/// Specify `None` to use the default CLI loader.
pub loader: Option<&'a mut dyn Loader>,
}
pub struct ModuleGraphBuilder {
pub struct ModuleGraphCreator {
options: Arc<CliOptions>,
fs: Arc<dyn FileSystem>,
resolver: Arc<CliGraphResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
module_info_cache: Arc<ModuleInfoCache>,
parsed_source_cache: Arc<ParsedSourceCache>,
module_graph_builder: Arc<ModuleGraphBuilder>,
lockfile: Option<Arc<Mutex<Lockfile>>>,
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
emit_cache: cache::EmitCache,
file_fetcher: Arc<FileFetcher>,
global_http_cache: Arc<GlobalHttpCache>,
type_checker: Arc<TypeChecker>,
}
impl ModuleGraphBuilder {
#[allow(clippy::too_many_arguments)]
impl ModuleGraphCreator {
pub fn new(
options: Arc<CliOptions>,
fs: Arc<dyn FileSystem>,
resolver: Arc<CliGraphResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
module_info_cache: Arc<ModuleInfoCache>,
parsed_source_cache: Arc<ParsedSourceCache>,
module_graph_builder: Arc<ModuleGraphBuilder>,
lockfile: Option<Arc<Mutex<Lockfile>>>,
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
emit_cache: cache::EmitCache,
file_fetcher: Arc<FileFetcher>,
global_http_cache: Arc<GlobalHttpCache>,
type_checker: Arc<TypeChecker>,
) -> Self {
Self {
options,
fs,
resolver,
npm_resolver,
module_info_cache,
parsed_source_cache,
lockfile,
maybe_file_watcher_reporter,
emit_cache,
file_fetcher,
global_http_cache,
module_graph_builder,
type_checker,
}
}
@ -267,7 +243,7 @@ impl ModuleGraphBuilder {
graph_kind: GraphKind,
roots: Vec<ModuleSpecifier>,
) -> Result<deno_graph::ModuleGraph, AnyError> {
let mut cache = self.create_graph_loader();
let mut cache = self.module_graph_builder.create_graph_loader();
self
.create_graph_with_loader(graph_kind, roots, &mut cache)
.await
@ -285,7 +261,6 @@ impl ModuleGraphBuilder {
graph_kind,
roots,
loader: Some(loader),
workspace_fast_check: false,
})
.await
}
@ -298,15 +273,21 @@ impl ModuleGraphBuilder {
for package in packages {
roots.extend(package.config_file.resolve_export_value_urls()?);
}
self
let mut graph = self
.create_graph_with_options(CreateGraphOptions {
is_dynamic: false,
graph_kind: deno_graph::GraphKind::All,
roots,
workspace_fast_check: true,
loader: None,
})
.await
.await?;
self.module_graph_builder.build_fast_check_graph(
&mut graph,
BuildFastCheckGraphOptions {
workspace_fast_check: true,
},
)?;
Ok(graph)
}
pub async fn create_graph_with_options(
@ -316,6 +297,7 @@ impl ModuleGraphBuilder {
let mut graph = ModuleGraph::new(options.graph_kind);
self
.module_graph_builder
.build_graph_with_npm_resolution(&mut graph, options)
.await?;
@ -340,11 +322,9 @@ impl ModuleGraphBuilder {
graph_kind,
roots,
loader: None,
workspace_fast_check: false,
})
.await?;
let graph = Arc::new(graph);
graph_valid_with_cli_options(
&graph,
self.fs.as_ref(),
@ -356,43 +336,76 @@ impl ModuleGraphBuilder {
}
if self.options.type_check_mode().is_true() {
self
// provide the graph to the type checker, then get it back after it's done
let graph = self
.type_checker
.check(
graph.clone(),
graph,
check::CheckOptions {
build_fast_check_graph: true,
lib: self.options.ts_type_lib_window(),
log_ignored_options: true,
reload: self.options.reload_flag(),
},
)
.await?;
}
Ok(graph)
}
fn get_deno_graph_workspace_members(
&self,
) -> Result<Vec<deno_graph::WorkspaceMember>, AnyError> {
let maybe_workspace_config = self.options.maybe_workspace_config();
if let Some(wc) = maybe_workspace_config {
workspace_config_to_workspace_members(wc)
} else {
Ok(
self
.options
.maybe_config_file()
.as_ref()
.and_then(|c| match config_to_workspace_member(c) {
Ok(m) => Some(vec![m]),
Err(e) => {
log::debug!("Deno config was not a package: {:#}", e);
None
Ok(Arc::new(graph))
}
})
.unwrap_or_default(),
)
}
}
pub struct BuildFastCheckGraphOptions {
/// Whether to do fast check on workspace members. This
/// is mostly only useful when publishing.
pub workspace_fast_check: bool,
}
pub struct ModuleGraphBuilder {
options: Arc<CliOptions>,
caches: Arc<cache::Caches>,
fs: Arc<dyn FileSystem>,
resolver: Arc<CliGraphResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
module_info_cache: Arc<ModuleInfoCache>,
parsed_source_cache: Arc<ParsedSourceCache>,
lockfile: Option<Arc<Mutex<Lockfile>>>,
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
emit_cache: cache::EmitCache,
file_fetcher: Arc<FileFetcher>,
global_http_cache: Arc<GlobalHttpCache>,
}
impl ModuleGraphBuilder {
#[allow(clippy::too_many_arguments)]
pub fn new(
options: Arc<CliOptions>,
caches: Arc<cache::Caches>,
fs: Arc<dyn FileSystem>,
resolver: Arc<CliGraphResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
module_info_cache: Arc<ModuleInfoCache>,
parsed_source_cache: Arc<ParsedSourceCache>,
lockfile: Option<Arc<Mutex<Lockfile>>>,
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
emit_cache: cache::EmitCache,
file_fetcher: Arc<FileFetcher>,
global_http_cache: Arc<GlobalHttpCache>,
) -> Self {
Self {
options,
caches,
fs,
resolver,
npm_resolver,
module_info_cache,
parsed_source_cache,
lockfile,
maybe_file_watcher_reporter,
emit_cache,
file_fetcher,
global_http_cache,
}
}
@ -422,13 +435,15 @@ impl ModuleGraphBuilder {
Some(loader) => MutLoaderRef::Borrowed(loader),
None => MutLoaderRef::Owned(self.create_graph_loader()),
};
let cli_resolver = self.resolver.clone();
let cli_resolver = &self.resolver;
let graph_resolver = cli_resolver.as_graph_resolver();
let graph_npm_resolver = cli_resolver.as_graph_npm_resolver();
let maybe_file_watcher_reporter = self
.maybe_file_watcher_reporter
.as_ref()
.map(|r| r.as_reporter());
let workspace_members =
self.options.resolve_deno_graph_workspace_members()?;
self
.build_graph_with_npm_resolution_and_build_options(
graph,
@ -436,6 +451,7 @@ impl ModuleGraphBuilder {
loader.as_mut_loader(),
deno_graph::BuildOptions {
is_dynamic: options.is_dynamic,
jsr_url_provider: Some(&CliJsrUrlProvider),
imports: maybe_imports,
resolver: Some(graph_resolver),
file_system: Some(&DenoGraphFsAdapter(self.fs.as_ref())),
@ -443,8 +459,7 @@ impl ModuleGraphBuilder {
module_analyzer: Some(&analyzer),
module_parser: Some(&parser),
reporter: maybe_file_watcher_reporter,
workspace_fast_check: options.workspace_fast_check,
workspace_members: self.get_deno_graph_workspace_members()?,
workspace_members: &workspace_members,
},
)
.await
@ -567,6 +582,49 @@ impl ModuleGraphBuilder {
Ok(())
}
pub fn build_fast_check_graph(
&self,
graph: &mut ModuleGraph,
options: BuildFastCheckGraphOptions,
) -> Result<(), AnyError> {
if !graph.graph_kind().include_types() {
return Ok(());
}
log::debug!("Building fast check graph");
let fast_check_cache = if !options.workspace_fast_check {
Some(cache::FastCheckCache::new(self.caches.fast_check_db()))
} else {
None
};
let parser = self.parsed_source_cache.as_capturing_parser();
let cli_resolver = &self.resolver;
let graph_resolver = cli_resolver.as_graph_resolver();
let graph_npm_resolver = cli_resolver.as_graph_npm_resolver();
let workspace_members = if options.workspace_fast_check {
Some(self.options.resolve_deno_graph_workspace_members()?)
} else {
None
};
graph.build_fast_check_type_graph(
deno_graph::BuildFastCheckTypeGraphOptions {
jsr_url_provider: Some(&CliJsrUrlProvider),
fast_check_cache: fast_check_cache.as_ref().map(|c| c as _),
fast_check_dts: false,
module_parser: Some(&parser),
resolver: Some(graph_resolver),
npm_resolver: Some(graph_npm_resolver),
workspace_fast_check: if let Some(members) = &workspace_members {
deno_graph::WorkspaceFastCheckOption::Enabled(members)
} else {
deno_graph::WorkspaceFastCheckOption::Disabled
},
},
);
Ok(())
}
/// Creates the default loader used for creating a graph.
pub fn create_graph_loader(&self) -> cache::FetchCacher {
self.create_fetch_cacher(PermissionsContainer::allow_all())
@ -851,44 +909,6 @@ impl deno_graph::source::Reporter for FileWatcherReporter {
}
}
fn workspace_config_to_workspace_members(
workspace_config: &deno_config::WorkspaceConfig,
) -> Result<Vec<deno_graph::WorkspaceMember>, AnyError> {
workspace_config
.members
.iter()
.map(|member| {
config_to_workspace_member(&member.config_file).with_context(|| {
format!(
"Failed to resolve configuration for '{}' workspace member at '{}'",
member.member_name,
member.config_file.specifier.as_str()
)
})
})
.collect()
}
fn config_to_workspace_member(
config: &ConfigFile,
) -> Result<deno_graph::WorkspaceMember, AnyError> {
let nv = deno_semver::package::PackageNv {
name: match &config.json.name {
Some(name) => name.clone(),
None => bail!("Missing 'name' field in config file."),
},
version: match &config.json.version {
Some(name) => deno_semver::Version::parse_standard(name)?,
None => bail!("Missing 'version' field in config file."),
},
};
Ok(deno_graph::WorkspaceMember {
base: config.specifier.join("./").unwrap(),
nv,
exports: config.to_exports_config()?.into_map(),
})
}
pub struct DenoGraphFsAdapter<'a>(
pub &'a dyn deno_runtime::deno_fs::FileSystem,
);
@ -963,6 +983,15 @@ pub fn format_range_with_colors(range: &deno_graph::Range) -> String {
)
}
#[derive(Debug, Default, Clone, Copy)]
struct CliJsrUrlProvider;
impl deno_graph::source::JsrUrlProvider for CliJsrUrlProvider {
fn url(&self) -> &'static ModuleSpecifier {
jsr_url()
}
}
#[cfg(test)]
mod test {
use std::sync::Arc;

View file

@ -51,7 +51,6 @@ use deno_runtime::permissions::PermissionsContainer;
use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageReq;
use indexmap::IndexMap;
use lsp::Url;
use once_cell::sync::Lazy;
use package_json::PackageJsonDepsProvider;
use std::borrow::Cow;
@ -1817,10 +1816,6 @@ impl<'a> OpenDocumentsGraphLoader<'a> {
}
impl<'a> deno_graph::source::Loader for OpenDocumentsGraphLoader<'a> {
fn registry_url(&self) -> &Url {
self.inner_loader.registry_url()
}
fn load(
&mut self,
specifier: &ModuleSpecifier,

View file

@ -299,13 +299,14 @@ impl LanguageServer {
let cli_options = Arc::new(cli_options);
let factory = CliFactory::from_cli_options(cli_options.clone());
let module_graph_builder = factory.module_graph_builder().await?;
let module_graph_creator = factory.module_graph_creator().await?;
let mut inner_loader = module_graph_builder.create_graph_loader();
let mut loader = crate::lsp::documents::OpenDocumentsGraphLoader {
inner_loader: &mut inner_loader,
open_docs: &open_docs,
unstable_sloppy_imports: cli_options.unstable_sloppy_imports(),
};
let graph = module_graph_builder
let graph = module_graph_creator
.create_graph_with_loader(GraphKind::All, roots.clone(), &mut loader)
.await?;
graph_util::graph_valid(

View file

@ -127,7 +127,6 @@ impl ModuleLoadPreparer {
graph_kind: graph.graph_kind(),
roots: roots.clone(),
loader: Some(&mut cache),
workspace_fast_check: false,
},
)
.await?;
@ -157,12 +156,13 @@ impl ModuleLoadPreparer {
if self.options.type_check_mode().is_true()
&& !self.graph_container.is_type_checked(&roots, lib)
{
let graph = Arc::new(graph.segment(&roots));
let graph = graph.segment(&roots);
self
.type_checker
.check(
graph,
check::CheckOptions {
build_fast_check_graph: true,
lib,
log_ignored_options: false,
reload: self.options.reload_flag()

View file

@ -511,7 +511,7 @@ pub async fn run_benchmarks_with_watch(
}
let graph_kind = cli_options.type_check_mode().as_graph_kind();
let module_graph_builder = factory.module_graph_builder().await?;
let module_graph_creator = factory.module_graph_creator().await?;
let module_load_preparer = factory.module_load_preparer().await?;
let bench_modules = collect_specifiers(
@ -525,7 +525,7 @@ pub async fn run_benchmarks_with_watch(
let permissions =
Permissions::from_options(&cli_options.permissions_options())?;
let graph = module_graph_builder
let graph = module_graph_creator
.create_graph(graph_kind, bench_modules.clone())
.await?;
graph_valid_with_cli_options(

View file

@ -62,10 +62,10 @@ async fn bundle_action(
let cli_options = factory.cli_options();
let module_specifier = cli_options.resolve_main_module()?;
log::debug!(">>>>> bundle START");
let module_graph_builder = factory.module_graph_builder().await?;
let module_graph_creator = factory.module_graph_creator().await?;
let cli_options = factory.cli_options();
let graph = module_graph_builder
let graph = module_graph_creator
.create_graph_and_maybe_check(vec![module_specifier.clone()])
.await?;

View file

@ -22,6 +22,8 @@ use crate::args::TypeCheckMode;
use crate::cache::Caches;
use crate::cache::FastInsecureHasher;
use crate::cache::TypeCheckCache;
use crate::graph_util::BuildFastCheckGraphOptions;
use crate::graph_util::ModuleGraphBuilder;
use crate::npm::CliNpmResolver;
use crate::tsc;
use crate::tsc::Diagnostics;
@ -30,6 +32,11 @@ use crate::version;
/// Options for performing a check of a module graph. Note that the decision to
/// emit or not is determined by the `ts_config` settings.
pub struct CheckOptions {
/// Whether to build the fast check type graph if necessary.
///
/// Note: For perf reasons, the fast check type graph is only
/// built if type checking is necessary.
pub build_fast_check_graph: bool,
/// Default type library to type check with.
pub lib: TsTypeLib,
/// Whether to log about any ignored compiler options.
@ -42,6 +49,7 @@ pub struct CheckOptions {
pub struct TypeChecker {
caches: Arc<Caches>,
cli_options: Arc<CliOptions>,
module_graph_builder: Arc<ModuleGraphBuilder>,
node_resolver: Arc<NodeResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
}
@ -50,12 +58,14 @@ impl TypeChecker {
pub fn new(
caches: Arc<Caches>,
cli_options: Arc<CliOptions>,
module_graph_builder: Arc<ModuleGraphBuilder>,
node_resolver: Arc<NodeResolver>,
npm_resolver: Arc<dyn CliNpmResolver>,
) -> Self {
Self {
caches,
cli_options,
module_graph_builder,
node_resolver,
npm_resolver,
}
@ -67,12 +77,12 @@ impl TypeChecker {
/// before the function is called.
pub async fn check(
&self,
graph: Arc<ModuleGraph>,
graph: ModuleGraph,
options: CheckOptions,
) -> Result<(), AnyError> {
let diagnostics = self.check_diagnostics(graph, options).await?;
) -> Result<Arc<ModuleGraph>, AnyError> {
let (graph, diagnostics) = self.check_diagnostics(graph, options).await?;
if diagnostics.is_empty() {
Ok(())
Ok(graph)
} else {
Err(diagnostics.into())
}
@ -84,11 +94,11 @@ impl TypeChecker {
/// before the function is called.
pub async fn check_diagnostics(
&self,
graph: Arc<ModuleGraph>,
mut graph: ModuleGraph,
options: CheckOptions,
) -> Result<Diagnostics, AnyError> {
) -> Result<(Arc<ModuleGraph>, Diagnostics), AnyError> {
if graph.roots.is_empty() {
return Ok(Default::default());
return Ok((graph.into(), Default::default()));
}
// node built-in specifiers use the @types/node package to determine
@ -112,9 +122,6 @@ impl TypeChecker {
let ts_config = ts_config_result.ts_config;
let type_check_mode = self.cli_options.type_check_mode();
let debug = self.cli_options.log_level() == Some(log::Level::Debug);
let cache = TypeCheckCache::new(self.caches.type_checking_cache_db());
let check_js = ts_config.get_check_js();
let maybe_check_hash = match self.npm_resolver.check_state_hash() {
Some(npm_check_hash) => {
match get_check_hash(
@ -123,7 +130,9 @@ impl TypeChecker {
type_check_mode,
&ts_config,
) {
CheckHashResult::NoFiles => return Ok(Default::default()),
CheckHashResult::NoFiles => {
return Ok((graph.into(), Default::default()))
}
CheckHashResult::Hash(hash) => Some(hash),
}
}
@ -131,10 +140,12 @@ impl TypeChecker {
};
// do not type check if we know this is type checked
let cache = TypeCheckCache::new(self.caches.type_checking_cache_db());
if !options.reload {
if let Some(check_hash) = maybe_check_hash {
if cache.has_check_hash(check_hash) {
return Ok(Default::default());
log::debug!("Already type checked.");
return Ok((graph.into(), Default::default()));
}
}
}
@ -144,7 +155,7 @@ impl TypeChecker {
log::info!("{} {}", colors::green("Check"), root_str);
}
let root_names = get_tsc_roots(&graph, check_js);
let check_js = ts_config.get_check_js();
// while there might be multiple roots, we can't "merge" the build info, so we
// try to retrieve the build info for first root, which is the most common use
// case.
@ -161,9 +172,21 @@ impl TypeChecker {
.write_str(version::deno())
.finish();
// add fast check to the graph before getting the roots
if options.build_fast_check_graph {
self.module_graph_builder.build_fast_check_graph(
&mut graph,
BuildFastCheckGraphOptions {
workspace_fast_check: false,
},
)?;
}
let root_names = get_tsc_roots(&graph, check_js);
let graph = Arc::new(graph);
let response = tsc::exec(tsc::Request {
config: ts_config,
debug,
debug: self.cli_options.log_level() == Some(log::Level::Debug),
graph: graph.clone(),
hash_data,
maybe_npm: Some(tsc::RequestNpmState {
@ -212,7 +235,7 @@ impl TypeChecker {
log::debug!("{}", response.stats);
Ok(diagnostics)
Ok((graph, diagnostics))
}
}
@ -277,12 +300,7 @@ fn get_check_hash(
}
hasher.write_str(module.specifier.as_str());
hasher.write_str(
module
.fast_check_module()
.map(|s| s.source.as_ref())
.unwrap_or(&module.source),
);
hasher.write_str(&module.source);
}
Module::Node(_) => {
// the @types/node package will be in the resolved

View file

@ -24,7 +24,7 @@ pub async fn compile(
) -> Result<(), AnyError> {
let factory = CliFactory::from_flags(flags).await?;
let cli_options = factory.cli_options();
let module_graph_builder = factory.module_graph_builder().await?;
let module_graph_creator = factory.module_graph_creator().await?;
let parsed_source_cache = factory.parsed_source_cache();
let binary_writer = factory.create_compile_binary_writer().await?;
let module_specifier = cli_options.resolve_main_module()?;
@ -56,7 +56,7 @@ pub async fn compile(
.await?;
let graph = Arc::try_unwrap(
module_graph_builder
module_graph_creator
.create_graph_and_maybe_check(module_roots.clone())
.await?,
)
@ -65,7 +65,7 @@ pub async fn compile(
// In this case, the previous graph creation did type checking, which will
// create a module graph with types information in it. We don't want to
// store that in the eszip so create a code only module graph from scratch.
module_graph_builder
module_graph_creator
.create_graph(GraphKind::CodeOnly, module_roots)
.await?
} else {

View file

@ -23,6 +23,7 @@ use deno_graph::GraphKind;
use deno_graph::ModuleAnalyzer;
use deno_graph::ModuleParser;
use deno_graph::ModuleSpecifier;
use doc::html::ShortPath;
use doc::DocDiagnostic;
use indexmap::IndexMap;
use std::collections::BTreeMap;
@ -89,7 +90,7 @@ pub async fn doc(flags: Flags, doc_flags: DocFlags) -> Result<(), AnyError> {
.await?
}
DocSourceFileFlag::Paths(ref source_files) => {
let module_graph_builder = factory.module_graph_builder().await?;
let module_graph_creator = factory.module_graph_creator().await?;
let maybe_lockfile = factory.maybe_lockfile();
let module_specifiers = collect_specifiers(
@ -103,7 +104,7 @@ pub async fn doc(flags: Flags, doc_flags: DocFlags) -> Result<(), AnyError> {
},
|_, _| true,
)?;
let graph = module_graph_builder
let graph = module_graph_creator
.create_graph(GraphKind::TypesOnly, module_specifiers.clone())
.await?;
@ -211,9 +212,9 @@ impl deno_doc::html::HrefResolver for DocResolver {
fn resolve_usage(
&self,
_current_specifier: &ModuleSpecifier,
current_file: Option<&str>,
current_file: Option<&ShortPath>,
) -> Option<String> {
current_file.map(|f| f.to_string())
current_file.map(|f| f.as_str().to_string())
}
fn resolve_source(&self, location: &deno_doc::Location) -> Option<String> {

View file

@ -40,6 +40,7 @@ pub async fn info(flags: Flags, info_flags: InfoFlags) -> Result<(), AnyError> {
let cli_options = factory.cli_options();
if let Some(specifier) = info_flags.file {
let module_graph_builder = factory.module_graph_builder().await?;
let module_graph_creator = factory.module_graph_creator().await?;
let npm_resolver = factory.npm_resolver().await?;
let maybe_lockfile = factory.maybe_lockfile();
let maybe_imports_map = factory.maybe_import_map().await?;
@ -63,7 +64,7 @@ pub async fn info(flags: Flags, info_flags: InfoFlags) -> Result<(), AnyError> {
let mut loader = module_graph_builder.create_graph_loader();
loader.enable_loading_cache_info(); // for displaying the cache information
let graph = module_graph_builder
let graph = module_graph_creator
.create_graph_with_loader(GraphKind::All, vec![specifier], &mut loader)
.await?;

View file

@ -178,13 +178,13 @@ async fn lint_files(
let members = config_file.to_workspace_members()?;
let has_error = has_error.clone();
let reporter_lock = reporter_lock.clone();
let module_graph_builder = factory.module_graph_builder().await?.clone();
let module_graph_creator = factory.module_graph_creator().await?.clone();
let path_urls = paths
.iter()
.filter_map(|p| ModuleSpecifier::from_file_path(p).ok())
.collect::<HashSet<_>>();
futures.push(deno_core::unsync::spawn(async move {
let graph = module_graph_builder.create_publish_graph(&members).await?;
let graph = module_graph_creator.create_publish_graph(&members).await?;
// todo(dsherret): this isn't exactly correct as linting isn't properly
// setup to handle workspaces. Iterating over the workspace members
// should be done at a higher level because it also needs to take into

View file

@ -31,7 +31,7 @@ use crate::args::PublishFlags;
use crate::cache::LazyGraphSourceParser;
use crate::cache::ParsedSourceCache;
use crate::factory::CliFactory;
use crate::graph_util::ModuleGraphBuilder;
use crate::graph_util::ModuleGraphCreator;
use crate::http_util::HttpClient;
use crate::tools::check::CheckOptions;
use crate::tools::lint::no_slow_types;
@ -656,7 +656,7 @@ async fn prepare_packages_for_publishing(
import_map: Arc<ImportMap>,
) -> Result<PreparePackagesData, AnyError> {
let members = deno_json.to_workspace_members()?;
let module_graph_builder = cli_factory.module_graph_builder().await?.as_ref();
let module_graph_creator = cli_factory.module_graph_creator().await?.as_ref();
let source_cache = cli_factory.parsed_source_cache();
let type_checker = cli_factory.type_checker().await?;
let cli_options = cli_factory.cli_options();
@ -667,7 +667,7 @@ async fn prepare_packages_for_publishing(
// create the module graph
let graph = build_and_check_graph_for_publish(
module_graph_builder,
module_graph_creator,
type_checker,
cli_options,
allow_slow_types,
@ -715,15 +715,14 @@ async fn prepare_packages_for_publishing(
}
async fn build_and_check_graph_for_publish(
module_graph_builder: &ModuleGraphBuilder,
module_graph_creator: &ModuleGraphCreator,
type_checker: &TypeChecker,
cli_options: &CliOptions,
allow_slow_types: bool,
diagnostics_collector: &PublishDiagnosticsCollector,
packages: &[WorkspaceMemberConfig],
) -> Result<Arc<deno_graph::ModuleGraph>, deno_core::anyhow::Error> {
let graph =
Arc::new(module_graph_builder.create_publish_graph(packages).await?);
let graph = module_graph_creator.create_publish_graph(packages).await?;
graph.valid()?;
// todo(dsherret): move to lint rule
@ -740,6 +739,7 @@ async fn build_and_check_graph_for_publish(
),
colors::yellow("Warning"),
);
Ok(Arc::new(graph))
} else {
log::info!("Checking for slow types in the public API...");
let mut any_pkg_had_diagnostics = false;
@ -755,12 +755,16 @@ async fn build_and_check_graph_for_publish(
}
}
if !any_pkg_had_diagnostics {
// this is a temporary measure until we know that fast check is reliable and stable
let check_diagnostics = type_checker
if any_pkg_had_diagnostics {
Ok(Arc::new(graph))
} else {
// fast check passed, type check the output as a temporary measure
// until we know that it's reliable and stable
let (graph, check_diagnostics) = type_checker
.check_diagnostics(
graph.clone(),
graph,
CheckOptions {
build_fast_check_graph: false, // already built
lib: cli_options.ts_type_lib_window(),
log_ignored_options: false,
reload: cli_options.reload_flag(),
@ -778,10 +782,9 @@ async fn build_and_check_graph_for_publish(
check_diagnostics
);
}
}
}
Ok(graph)
}
}
}
pub async fn publish(

View file

@ -1490,7 +1490,7 @@ pub async fn run_tests_with_watch(
let graph_kind = cli_options.type_check_mode().as_graph_kind();
let log_level = cli_options.log_level();
let cli_options = cli_options.clone();
let module_graph_builder = factory.module_graph_builder().await?;
let module_graph_creator = factory.module_graph_creator().await?;
let file_fetcher = factory.file_fetcher()?;
let test_modules = if test_options.doc {
collect_specifiers(test_options.files.clone(), |p, _| {
@ -1505,7 +1505,7 @@ pub async fn run_tests_with_watch(
let permissions =
Permissions::from_options(&cli_options.permissions_options())?;
let graph = module_graph_builder
let graph = module_graph_creator
.create_graph(graph_kind, test_modules.clone())
.await?;
graph_valid_with_cli_options(

View file

@ -51,12 +51,12 @@ pub async fn vendor(
let entry_points =
resolve_entry_points(&vendor_flags, cli_options.initial_cwd())?;
let jsx_import_source = cli_options.to_maybe_jsx_import_source_config()?;
let module_graph_builder = factory.module_graph_builder().await?.clone();
let module_graph_creator = factory.module_graph_creator().await?.clone();
let output = build::build(build::BuildInput {
entry_points,
build_graph: move |entry_points| {
async move {
module_graph_builder
module_graph_creator
.create_graph(GraphKind::All, entry_points)
.await
}

View file

@ -175,6 +175,7 @@ itest!(info_import_map {
args: "info preact/debug",
output: "info/with_import_map/with_import_map.out",
cwd: Some("info/with_import_map"),
copy_temp_dir: Some("info/with_import_map"),
exit_code: 0,
});

View file

@ -6,6 +6,8 @@ use deno_lockfile::Lockfile;
use test_util as util;
use test_util::itest;
use url::Url;
use util::assert_contains;
use util::assert_not_contains;
use util::env_vars_for_jsr_tests;
use util::TestContextBuilder;
@ -66,6 +68,118 @@ itest!(subset_type_graph {
exit_code: 1,
});
#[test]
fn fast_check_cache() {
let test_context = TestContextBuilder::for_jsr().use_temp_cwd().build();
let deno_dir = test_context.deno_dir();
let temp_dir = test_context.temp_dir();
let type_check_cache_path = deno_dir.path().join("check_cache_v1");
temp_dir.write(
"main.ts",
r#"import { add } from "jsr:@denotest/add@1";
const value: number = add(1, 2);
console.log(value);"#,
);
temp_dir.path().join("deno.json").write_json(&json!({
"vendor": true
}));
test_context
.new_command()
.args("check main.ts")
.run()
.skip_output_check();
type_check_cache_path.remove_file();
let check_debug_cmd = test_context
.new_command()
.args("check --log-level=debug main.ts");
let output = check_debug_cmd.run();
assert_contains!(
output.combined_output(),
"Using FastCheck cache for: @denotest/add@1.0.0"
);
// modify the file in the vendor folder
let vendor_dir = temp_dir.path().join("vendor");
let pkg_dir = vendor_dir.join("http_127.0.0.1_4250/@denotest/add/1.0.0/");
pkg_dir
.join("mod.ts")
.append("\nexport * from './other.ts';");
let nested_pkg_file = pkg_dir.join("other.ts");
nested_pkg_file.write("export function other(): string { return ''; }");
// invalidated
let output = check_debug_cmd.run();
assert_not_contains!(
output.combined_output(),
"Using FastCheck cache for: @denotest/add@1.0.0"
);
// ensure cache works
let output = check_debug_cmd.run();
assert_contains!(output.combined_output(), "Already type checked.");
let building_fast_check_msg = "Building fast check graph";
assert_not_contains!(output.combined_output(), building_fast_check_msg);
// now validated
type_check_cache_path.remove_file();
let output = check_debug_cmd.run();
assert_contains!(output.combined_output(), building_fast_check_msg);
assert_contains!(
output.combined_output(),
"Using FastCheck cache for: @denotest/add@1.0.0"
);
// cause a fast check error in the nested package
nested_pkg_file
.append("\nexport function asdf(a: number) { let err: number = ''; return Math.random(); }");
check_debug_cmd.run().skip_output_check();
// ensure the cache still picks it up for this file
type_check_cache_path.remove_file();
let output = check_debug_cmd.run();
assert_contains!(output.combined_output(), building_fast_check_msg);
assert_contains!(
output.combined_output(),
"Using FastCheck cache for: @denotest/add@1.0.0"
);
// see that the type checking error in the internal function gets surfaced with --all
test_context
.new_command()
.args("check --all main.ts")
.run()
.assert_matches_text(
"Check file:///[WILDCARD]main.ts
error: TS2322 [ERROR]: Type 'string' is not assignable to type 'number'.
export function asdf(a: number) { let err: number = ''; return Math.random(); }
~~~
at http://127.0.0.1:4250/@denotest/add/1.0.0/other.ts:2:39
",
)
.assert_exit_code(1);
// now fix the package
nested_pkg_file.write("export function test() {}");
let output = check_debug_cmd.run();
assert_contains!(output.combined_output(), building_fast_check_msg);
assert_not_contains!(
output.combined_output(),
"Using FastCheck cache for: @denotest/add@1.0.0"
);
// finally ensure it uses the cache
type_check_cache_path.remove_file();
let output = check_debug_cmd.run();
assert_contains!(output.combined_output(), building_fast_check_msg);
assert_contains!(
output.combined_output(),
"Using FastCheck cache for: @denotest/add@1.0.0"
);
}
itest!(version_not_found {
args: "run jsr/version_not_found/main.ts",
output: "jsr/version_not_found/main.out",

View file

@ -4,6 +4,8 @@ use pretty_assertions::assert_eq;
use std::borrow::Cow;
use std::ffi::OsStr;
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
@ -134,6 +136,11 @@ impl PathRef {
fs::rename(self, self.join(to)).unwrap();
}
pub fn append(&self, text: impl AsRef<str>) {
let mut file = OpenOptions::new().append(true).open(self).unwrap();
file.write_all(text.as_ref().as_bytes()).unwrap();
}
pub fn write(&self, text: impl AsRef<str>) {
fs::write(self, text.as_ref()).unwrap();
}