mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
perf(jsr): fast check cache and lazy fast check graph (#22485)
This commit is contained in:
parent
dbc4a4d632
commit
f90889e5ee
25 changed files with 669 additions and 220 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
15
cli/cache/caches.rs
vendored
|
@ -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,
|
||||
|
|
6
cli/cache/deno_dir.rs
vendored
6
cli/cache/deno_dir.rs
vendored
|
@ -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
162
cli/cache/fast_check.rs
vendored
Normal 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
8
cli/cache/mod.rs
vendored
|
@ -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;
|
||||
|
|
116
cli/factory.rs
116
cli/factory.rs
|
@ -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,14 +220,16 @@ 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(_) => {
|
||||
DenoSubcommand::Run(_)
|
||||
| DenoSubcommand::Bench(_)
|
||||
| DenoSubcommand::Test(_)
|
||||
| DenoSubcommand::Check(_) => {
|
||||
_ = caches.dep_analysis_db();
|
||||
_ = caches.node_analysis_db();
|
||||
}
|
||||
DenoSubcommand::Check(_) => {
|
||||
_ = caches.dep_analysis_db();
|
||||
_ = caches.node_analysis_db();
|
||||
_ = caches.type_checking_cache_db();
|
||||
if self.options.type_check_mode().is_true() {
|
||||
_ = caches.fast_check_db();
|
||||
_ = caches.type_checking_cache_db();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -437,7 +447,7 @@ impl CliFactory {
|
|||
npm_registry_url: crate::args::npm_registry_default_url().to_owned(),
|
||||
})
|
||||
}).await
|
||||
})
|
||||
}.boxed_local())
|
||||
.await
|
||||
}
|
||||
|
||||
|
@ -471,32 +481,37 @@ impl CliFactory {
|
|||
self
|
||||
.services
|
||||
.resolver
|
||||
.get_or_try_init_async(async {
|
||||
Ok(Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
|
||||
fs: self.fs().clone(),
|
||||
cjs_resolutions: Some(self.cjs_resolutions().clone()),
|
||||
sloppy_imports_resolver: if self.options.unstable_sloppy_imports() {
|
||||
Some(SloppyImportsResolver::new(self.fs().clone()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
node_resolver: Some(self.node_resolver().await?.clone()),
|
||||
npm_resolver: if self.options.no_npm() {
|
||||
None
|
||||
} else {
|
||||
Some(self.npm_resolver().await?.clone())
|
||||
},
|
||||
package_json_deps_provider: self.package_json_deps_provider().clone(),
|
||||
maybe_jsx_import_source_config: self
|
||||
.options
|
||||
.to_maybe_jsx_import_source_config()?,
|
||||
maybe_import_map: self.maybe_import_map().await?.clone(),
|
||||
maybe_vendor_dir: self.options.vendor_dir_path(),
|
||||
bare_node_builtins_enabled: self
|
||||
.options
|
||||
.unstable_bare_node_builtins(),
|
||||
})))
|
||||
})
|
||||
.get_or_try_init_async(
|
||||
async {
|
||||
Ok(Arc::new(CliGraphResolver::new(CliGraphResolverOptions {
|
||||
fs: self.fs().clone(),
|
||||
cjs_resolutions: Some(self.cjs_resolutions().clone()),
|
||||
sloppy_imports_resolver: if self.options.unstable_sloppy_imports() {
|
||||
Some(SloppyImportsResolver::new(self.fs().clone()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
node_resolver: Some(self.node_resolver().await?.clone()),
|
||||
npm_resolver: if self.options.no_npm() {
|
||||
None
|
||||
} else {
|
||||
Some(self.npm_resolver().await?.clone())
|
||||
},
|
||||
package_json_deps_provider: self
|
||||
.package_json_deps_provider()
|
||||
.clone(),
|
||||
maybe_jsx_import_source_config: self
|
||||
.options
|
||||
.to_maybe_jsx_import_source_config()?,
|
||||
maybe_import_map: self.maybe_import_map().await?.clone(),
|
||||
maybe_vendor_dir: self.options.vendor_dir_path(),
|
||||
bare_node_builtins_enabled: self
|
||||
.options
|
||||
.unstable_bare_node_builtins(),
|
||||
})))
|
||||
}
|
||||
.boxed_local(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
|
@ -554,12 +569,15 @@ impl CliFactory {
|
|||
self
|
||||
.services
|
||||
.node_resolver
|
||||
.get_or_try_init_async(async {
|
||||
Ok(Arc::new(NodeResolver::new(
|
||||
self.fs().clone(),
|
||||
self.npm_resolver().await?.clone().into_npm_resolver(),
|
||||
)))
|
||||
})
|
||||
.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(),
|
||||
)))
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
Ok(graph)
|
||||
} 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
|
||||
}
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
Ok(Arc::new(graph))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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?;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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?;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(graph)
|
||||
}
|
||||
|
||||
pub async fn publish(
|
||||
|
|
|
@ -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(
|
||||
|
|
4
cli/tools/vendor/mod.rs
vendored
4
cli/tools/vendor/mod.rs
vendored
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue