// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use super::cache_db::CacheDB; use super::cache_db::CacheDBConfiguration; use super::cache_db::CacheDBHash; use super::cache_db::CacheFailure; use deno_ast::ModuleSpecifier; use deno_core::error::AnyError; use deno_runtime::deno_webstorage::rusqlite::params; pub static TYPE_CHECK_CACHE_DB: CacheDBConfiguration = CacheDBConfiguration { table_initializer: concat!( "CREATE TABLE IF NOT EXISTS checkcache (", "check_hash INT PRIMARY KEY", ");", "CREATE TABLE IF NOT EXISTS tsbuildinfo (", "specifier TEXT PRIMARY KEY,", "text TEXT NOT NULL", ");", ), on_version_change: concat!( "DELETE FROM checkcache;", "DELETE FROM tsbuildinfo;" ), preheat_queries: &[], // If the cache fails, just ignore all caching attempts on_failure: CacheFailure::Blackhole, }; /// The cache used to tell whether type checking should occur again. /// /// This simply stores a hash of the inputs of each successful type check /// and only clears them out when changing CLI versions. pub struct TypeCheckCache(CacheDB); impl TypeCheckCache { pub fn new(db: CacheDB) -> Self { Self(db) } pub fn has_check_hash(&self, hash: CacheDBHash) -> bool { match self.hash_check_hash_result(hash) { Ok(val) => val, Err(err) => { if cfg!(debug_assertions) { panic!("Error retrieving hash: {err}"); } else { log::debug!("Error retrieving hash: {}", err); // fail silently when not debugging false } } } } fn hash_check_hash_result( &self, hash: CacheDBHash, ) -> Result { self.0.exists( "SELECT * FROM checkcache WHERE check_hash=?1 LIMIT 1", params![hash], ) } pub fn add_check_hash(&self, check_hash: CacheDBHash) { if let Err(err) = self.add_check_hash_result(check_hash) { if cfg!(debug_assertions) { panic!("Error saving check hash: {err}"); } else { log::debug!("Error saving check hash: {}", err); } } } fn add_check_hash_result( &self, check_hash: CacheDBHash, ) -> Result<(), AnyError> { let sql = " INSERT OR REPLACE INTO checkcache (check_hash) VALUES (?1)"; self.0.execute(sql, params![check_hash])?; Ok(()) } pub fn get_tsbuildinfo(&self, specifier: &ModuleSpecifier) -> Option { self .0 .query_row( "SELECT text FROM tsbuildinfo WHERE specifier=?1 LIMIT 1", params![specifier.to_string()], |row| Ok(row.get::<_, String>(0)?), ) .ok()? } pub fn set_tsbuildinfo(&self, specifier: &ModuleSpecifier, text: &str) { if let Err(err) = self.set_tsbuildinfo_result(specifier, text) { // should never error here, but if it ever does don't fail if cfg!(debug_assertions) { panic!("Error saving tsbuildinfo: {err}"); } else { log::debug!("Error saving tsbuildinfo: {}", err); } } } fn set_tsbuildinfo_result( &self, specifier: &ModuleSpecifier, text: &str, ) -> Result<(), AnyError> { self.0.execute( "INSERT OR REPLACE INTO tsbuildinfo (specifier, text) VALUES (?1, ?2)", params![specifier.to_string(), text], )?; Ok(()) } } #[cfg(test)] mod test { use super::*; #[test] pub fn check_cache_general_use() { let conn = CacheDB::in_memory(&TYPE_CHECK_CACHE_DB, "1.0.0"); let cache = TypeCheckCache::new(conn); assert!(!cache.has_check_hash(CacheDBHash::new(1))); cache.add_check_hash(CacheDBHash::new(1)); assert!(cache.has_check_hash(CacheDBHash::new(1))); assert!(!cache.has_check_hash(CacheDBHash::new(2))); let specifier1 = ModuleSpecifier::parse("file:///test.json").unwrap(); assert_eq!(cache.get_tsbuildinfo(&specifier1), None); cache.set_tsbuildinfo(&specifier1, "test"); assert_eq!(cache.get_tsbuildinfo(&specifier1), Some("test".to_string())); // try changing the cli version (should clear) let conn = cache.0.recreate_with_version("2.0.0"); let cache = TypeCheckCache::new(conn); assert!(!cache.has_check_hash(CacheDBHash::new(1))); cache.add_check_hash(CacheDBHash::new(1)); assert!(cache.has_check_hash(CacheDBHash::new(1))); assert_eq!(cache.get_tsbuildinfo(&specifier1), None); cache.set_tsbuildinfo(&specifier1, "test"); assert_eq!(cache.get_tsbuildinfo(&specifier1), Some("test".to_string())); // recreating the cache should not remove the data because the CLI version is the same let conn = cache.0.recreate_with_version("2.0.0"); let cache = TypeCheckCache::new(conn); assert!(cache.has_check_hash(CacheDBHash::new(1))); assert!(!cache.has_check_hash(CacheDBHash::new(2))); assert_eq!(cache.get_tsbuildinfo(&specifier1), Some("test".to_string())); // adding when already exists should not cause issue cache.add_check_hash(CacheDBHash::new(1)); assert!(cache.has_check_hash(CacheDBHash::new(1))); cache.set_tsbuildinfo(&specifier1, "other"); assert_eq!( cache.get_tsbuildinfo(&specifier1), Some("other".to_string()) ); } }