mirror of
https://github.com/denoland/deno.git
synced 2024-12-20 22:34:46 -05:00
115a306656
Ensures a dynamic import in a CJS file will consider the referrer as an import for node resolution. Also adds fixes (adds) support for `"resolution-mode"` in TypeScript.
409 lines
9.5 KiB
Rust
409 lines
9.5 KiB
Rust
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use deno_ast::MediaType;
|
|
use deno_ast::ModuleSpecifier;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::serde_json;
|
|
use deno_graph::ModuleInfo;
|
|
use deno_graph::ParserModuleAnalyzer;
|
|
use deno_runtime::deno_webstorage::rusqlite::params;
|
|
|
|
use super::cache_db::CacheDB;
|
|
use super::cache_db::CacheDBConfiguration;
|
|
use super::cache_db::CacheDBHash;
|
|
use super::cache_db::CacheFailure;
|
|
use super::ParsedSourceCache;
|
|
|
|
const SELECT_MODULE_INFO: &str = "
|
|
SELECT
|
|
module_info
|
|
FROM
|
|
moduleinfocache
|
|
WHERE
|
|
specifier=?1
|
|
AND media_type=?2
|
|
AND source_hash=?3
|
|
LIMIT 1";
|
|
|
|
pub static MODULE_INFO_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration {
|
|
table_initializer: concat!(
|
|
"CREATE TABLE IF NOT EXISTS moduleinfocache (",
|
|
"specifier TEXT PRIMARY KEY,",
|
|
"media_type INTEGER NOT NULL,",
|
|
"source_hash INTEGER NOT NULL,",
|
|
"module_info TEXT NOT NULL",
|
|
");"
|
|
),
|
|
on_version_change: "DELETE FROM moduleinfocache;",
|
|
preheat_queries: &[SELECT_MODULE_INFO],
|
|
on_failure: CacheFailure::InMemory,
|
|
};
|
|
|
|
/// A cache of `deno_graph::ModuleInfo` objects. Using this leads to a considerable
|
|
/// performance improvement because when it exists we can skip parsing a module for
|
|
/// deno_graph.
|
|
#[derive(Debug)]
|
|
pub struct ModuleInfoCache {
|
|
conn: CacheDB,
|
|
parsed_source_cache: Arc<ParsedSourceCache>,
|
|
}
|
|
|
|
impl ModuleInfoCache {
|
|
#[cfg(test)]
|
|
pub fn new_in_memory(
|
|
version: &'static str,
|
|
parsed_source_cache: Arc<ParsedSourceCache>,
|
|
) -> Self {
|
|
Self::new(
|
|
CacheDB::in_memory(&MODULE_INFO_CACHE_DB, version),
|
|
parsed_source_cache,
|
|
)
|
|
}
|
|
|
|
pub fn new(
|
|
conn: CacheDB,
|
|
parsed_source_cache: Arc<ParsedSourceCache>,
|
|
) -> Self {
|
|
Self {
|
|
conn,
|
|
parsed_source_cache,
|
|
}
|
|
}
|
|
|
|
/// Useful for testing: re-create this cache DB with a different current version.
|
|
#[cfg(test)]
|
|
pub(crate) fn recreate_with_version(self, version: &'static str) -> Self {
|
|
Self {
|
|
conn: self.conn.recreate_with_version(version),
|
|
parsed_source_cache: self.parsed_source_cache,
|
|
}
|
|
}
|
|
|
|
pub fn get_module_info(
|
|
&self,
|
|
specifier: &ModuleSpecifier,
|
|
media_type: MediaType,
|
|
expected_source_hash: CacheDBHash,
|
|
) -> Result<Option<ModuleInfo>, AnyError> {
|
|
let query = SELECT_MODULE_INFO;
|
|
let res = self.conn.query_row(
|
|
query,
|
|
params![
|
|
&specifier.as_str(),
|
|
serialize_media_type(media_type),
|
|
expected_source_hash,
|
|
],
|
|
|row| {
|
|
let module_info: String = row.get(0)?;
|
|
let module_info = serde_json::from_str(&module_info)?;
|
|
Ok(module_info)
|
|
},
|
|
)?;
|
|
Ok(res)
|
|
}
|
|
|
|
pub fn set_module_info(
|
|
&self,
|
|
specifier: &ModuleSpecifier,
|
|
media_type: MediaType,
|
|
source_hash: CacheDBHash,
|
|
module_info: &ModuleInfo,
|
|
) -> Result<(), AnyError> {
|
|
let sql = "
|
|
INSERT OR REPLACE INTO
|
|
moduleinfocache (specifier, media_type, source_hash, module_info)
|
|
VALUES
|
|
(?1, ?2, ?3, ?4)";
|
|
self.conn.execute(
|
|
sql,
|
|
params![
|
|
specifier.as_str(),
|
|
serialize_media_type(media_type),
|
|
source_hash,
|
|
&serde_json::to_string(&module_info)?,
|
|
],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn as_module_analyzer(&self) -> ModuleInfoCacheModuleAnalyzer {
|
|
ModuleInfoCacheModuleAnalyzer {
|
|
module_info_cache: self,
|
|
parsed_source_cache: &self.parsed_source_cache,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct ModuleInfoCacheModuleAnalyzer<'a> {
|
|
module_info_cache: &'a ModuleInfoCache,
|
|
parsed_source_cache: &'a Arc<ParsedSourceCache>,
|
|
}
|
|
|
|
impl<'a> ModuleInfoCacheModuleAnalyzer<'a> {
|
|
fn load_cached_module_info(
|
|
&self,
|
|
specifier: &ModuleSpecifier,
|
|
media_type: MediaType,
|
|
source_hash: CacheDBHash,
|
|
) -> Option<ModuleInfo> {
|
|
match self.module_info_cache.get_module_info(
|
|
specifier,
|
|
media_type,
|
|
source_hash,
|
|
) {
|
|
Ok(Some(info)) => Some(info),
|
|
Ok(None) => None,
|
|
Err(err) => {
|
|
log::debug!(
|
|
"Error loading module cache info for {}. {:#}",
|
|
specifier,
|
|
err
|
|
);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
fn save_module_info_to_cache(
|
|
&self,
|
|
specifier: &ModuleSpecifier,
|
|
media_type: MediaType,
|
|
source_hash: CacheDBHash,
|
|
module_info: &ModuleInfo,
|
|
) {
|
|
if let Err(err) = self.module_info_cache.set_module_info(
|
|
specifier,
|
|
media_type,
|
|
source_hash,
|
|
module_info,
|
|
) {
|
|
log::debug!(
|
|
"Error saving module cache info for {}. {:#}",
|
|
specifier,
|
|
err
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn analyze_sync(
|
|
&self,
|
|
specifier: &ModuleSpecifier,
|
|
media_type: MediaType,
|
|
source: &Arc<str>,
|
|
) -> Result<ModuleInfo, deno_ast::ParseDiagnostic> {
|
|
// attempt to load from the cache
|
|
let source_hash = CacheDBHash::from_source(source);
|
|
if let Some(info) =
|
|
self.load_cached_module_info(specifier, media_type, source_hash)
|
|
{
|
|
return Ok(info);
|
|
}
|
|
|
|
// otherwise, get the module info from the parsed source cache
|
|
let parser = self.parsed_source_cache.as_capturing_parser();
|
|
let analyzer = ParserModuleAnalyzer::new(&parser);
|
|
let module_info =
|
|
analyzer.analyze_sync(specifier, source.clone(), media_type)?;
|
|
|
|
// then attempt to cache it
|
|
self.save_module_info_to_cache(
|
|
specifier,
|
|
media_type,
|
|
source_hash,
|
|
&module_info,
|
|
);
|
|
|
|
Ok(module_info)
|
|
}
|
|
}
|
|
|
|
#[async_trait::async_trait(?Send)]
|
|
impl<'a> deno_graph::ModuleAnalyzer for ModuleInfoCacheModuleAnalyzer<'a> {
|
|
async fn analyze(
|
|
&self,
|
|
specifier: &ModuleSpecifier,
|
|
source: Arc<str>,
|
|
media_type: MediaType,
|
|
) -> Result<ModuleInfo, deno_ast::ParseDiagnostic> {
|
|
// attempt to load from the cache
|
|
let source_hash = CacheDBHash::from_source(&source);
|
|
if let Some(info) =
|
|
self.load_cached_module_info(specifier, media_type, source_hash)
|
|
{
|
|
return Ok(info);
|
|
}
|
|
|
|
// otherwise, get the module info from the parsed source cache
|
|
let module_info = deno_core::unsync::spawn_blocking({
|
|
let cache = self.parsed_source_cache.clone();
|
|
let specifier = specifier.clone();
|
|
move || {
|
|
let parser = cache.as_capturing_parser();
|
|
let analyzer = ParserModuleAnalyzer::new(&parser);
|
|
analyzer.analyze_sync(&specifier, source, media_type)
|
|
}
|
|
})
|
|
.await
|
|
.unwrap()?;
|
|
|
|
// then attempt to cache it
|
|
self.save_module_info_to_cache(
|
|
specifier,
|
|
media_type,
|
|
source_hash,
|
|
&module_info,
|
|
);
|
|
|
|
Ok(module_info)
|
|
}
|
|
}
|
|
|
|
fn serialize_media_type(media_type: MediaType) -> i64 {
|
|
use MediaType::*;
|
|
match media_type {
|
|
JavaScript => 1,
|
|
Jsx => 2,
|
|
Mjs => 3,
|
|
Cjs => 4,
|
|
TypeScript => 5,
|
|
Mts => 6,
|
|
Cts => 7,
|
|
Dts => 8,
|
|
Dmts => 9,
|
|
Dcts => 10,
|
|
Tsx => 11,
|
|
Json => 12,
|
|
Wasm => 13,
|
|
Css => 14,
|
|
SourceMap => 15,
|
|
Unknown => 16,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use deno_graph::JsDocImportInfo;
|
|
use deno_graph::PositionRange;
|
|
use deno_graph::SpecifierWithRange;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
pub fn module_info_cache_general_use() {
|
|
let cache = ModuleInfoCache::new_in_memory("1.0.0", Default::default());
|
|
let specifier1 =
|
|
ModuleSpecifier::parse("https://localhost/mod.ts").unwrap();
|
|
let specifier2 =
|
|
ModuleSpecifier::parse("https://localhost/mod2.ts").unwrap();
|
|
assert_eq!(
|
|
cache
|
|
.get_module_info(
|
|
&specifier1,
|
|
MediaType::JavaScript,
|
|
CacheDBHash::new(1)
|
|
)
|
|
.unwrap(),
|
|
None
|
|
);
|
|
|
|
let mut module_info = ModuleInfo::default();
|
|
module_info.jsdoc_imports.push(JsDocImportInfo {
|
|
specifier: SpecifierWithRange {
|
|
range: PositionRange {
|
|
start: deno_graph::Position {
|
|
line: 0,
|
|
character: 3,
|
|
},
|
|
end: deno_graph::Position {
|
|
line: 1,
|
|
character: 2,
|
|
},
|
|
},
|
|
text: "test".to_string(),
|
|
},
|
|
resolution_mode: None,
|
|
});
|
|
cache
|
|
.set_module_info(
|
|
&specifier1,
|
|
MediaType::JavaScript,
|
|
CacheDBHash::new(1),
|
|
&module_info,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
cache
|
|
.get_module_info(
|
|
&specifier1,
|
|
MediaType::JavaScript,
|
|
CacheDBHash::new(1)
|
|
)
|
|
.unwrap(),
|
|
Some(module_info.clone())
|
|
);
|
|
assert_eq!(
|
|
cache
|
|
.get_module_info(
|
|
&specifier2,
|
|
MediaType::JavaScript,
|
|
CacheDBHash::new(1)
|
|
)
|
|
.unwrap(),
|
|
None,
|
|
);
|
|
// different media type
|
|
assert_eq!(
|
|
cache
|
|
.get_module_info(
|
|
&specifier1,
|
|
MediaType::TypeScript,
|
|
CacheDBHash::new(1)
|
|
)
|
|
.unwrap(),
|
|
None,
|
|
);
|
|
// different source hash
|
|
assert_eq!(
|
|
cache
|
|
.get_module_info(
|
|
&specifier1,
|
|
MediaType::JavaScript,
|
|
CacheDBHash::new(2)
|
|
)
|
|
.unwrap(),
|
|
None,
|
|
);
|
|
|
|
// try recreating with the same version
|
|
let cache = cache.recreate_with_version("1.0.0");
|
|
|
|
// should get it
|
|
assert_eq!(
|
|
cache
|
|
.get_module_info(
|
|
&specifier1,
|
|
MediaType::JavaScript,
|
|
CacheDBHash::new(1)
|
|
)
|
|
.unwrap(),
|
|
Some(module_info)
|
|
);
|
|
|
|
// try recreating with a different version
|
|
let cache = cache.recreate_with_version("1.0.1");
|
|
|
|
// should no longer exist
|
|
assert_eq!(
|
|
cache
|
|
.get_module_info(
|
|
&specifier1,
|
|
MediaType::JavaScript,
|
|
CacheDBHash::new(1)
|
|
)
|
|
.unwrap(),
|
|
None,
|
|
);
|
|
}
|
|
}
|