use deno_config::deno_json::CompilerOptionsParseError;
use deno_config::deno_json::TsConfig;
use deno_config::deno_json::TsConfigType;
use deno_config::deno_json::TsConfigWithIgnoredOptions;
use deno_config::deno_json::TsTypeLib;
use deno_config::workspace::WorkspaceDirectory;
use deno_terminal::colors;
use deno_unsync::sync::AtomicFlag;
#[cfg(feature = "sync")]
use once_cell::sync::OnceCell;
#[cfg(not(feature = "sync"))]
use once_cell::unsync::OnceCell;
use sys_traits::FsRead;
use url::Url;
use crate::collections::FolderScopedMap;
use crate::factory::WorkspaceRc;
use crate::sync::new_rc;
#[allow(clippy::disallowed_types)]
pub type TsConfigResolverRc<TSys> =
crate::sync::MaybeArc<TsConfigResolver<TSys>>;
#[allow(clippy::disallowed_types)]
type TsConfigRc = crate::sync::MaybeArc<TsConfig>;
#[allow(clippy::disallowed_types)]
type LoggedWarningsRc = crate::sync::MaybeArc<LoggedWarnings>;
#[cfg(feature = "deno_ast")]
#[allow(clippy::disallowed_types)]
pub type TranspileAndEmitOptionsRc =
crate::sync::MaybeArc<TranspileAndEmitOptions>;
#[cfg(feature = "deno_ast")]
#[derive(Debug)]
pub struct TranspileAndEmitOptions {
pub transpile: deno_ast::TranspileOptions,
pub emit: deno_ast::EmitOptions,
pub pre_computed_hash: u64,
}
#[derive(Debug, Default)]
struct LoggedWarnings {
experimental_decorators: AtomicFlag,
folders: crate::sync::MaybeDashSet<Url>,
}
#[derive(Default, Debug)]
struct MemoizedValues {
deno_window_check_tsconfig: OnceCell<TsConfigRc>,
deno_worker_check_tsconfig: OnceCell<TsConfigRc>,
emit_tsconfig: OnceCell<TsConfigRc>,
#[cfg(feature = "deno_ast")]
transpile_options: OnceCell<TranspileAndEmitOptionsRc>,
}
#[derive(Debug)]
pub struct TsConfigFolderInfo<TSys: FsRead> {
pub dir: WorkspaceDirectory,
logged_warnings: LoggedWarningsRc,
memoized: MemoizedValues,
sys: TSys,
}
impl<TSys: FsRead> TsConfigFolderInfo<TSys> {
pub fn lib_tsconfig(
&self,
lib: TsTypeLib,
) -> Result<&TsConfigRc, CompilerOptionsParseError> {
let cell = match lib {
TsTypeLib::DenoWindow => &self.memoized.deno_window_check_tsconfig,
TsTypeLib::DenoWorker => &self.memoized.deno_worker_check_tsconfig,
};
cell.get_or_try_init(|| {
let tsconfig_result = self
.dir
.to_resolved_ts_config(&self.sys, TsConfigType::Check { lib })?;
check_warn_tsconfig(&tsconfig_result, &self.logged_warnings);
Ok(new_rc(tsconfig_result.ts_config))
})
}
pub fn emit_tsconfig(
&self,
) -> Result<&TsConfigRc, CompilerOptionsParseError> {
self.memoized.emit_tsconfig.get_or_try_init(|| {
let tsconfig_result = self
.dir
.to_resolved_ts_config(&self.sys, TsConfigType::Emit)?;
check_warn_tsconfig(&tsconfig_result, &self.logged_warnings);
Ok(new_rc(tsconfig_result.ts_config))
})
}
#[cfg(feature = "deno_ast")]
pub fn transpile_options(
&self,
) -> Result<&TranspileAndEmitOptionsRc, CompilerOptionsParseError> {
self.memoized.transpile_options.get_or_try_init(|| {
let ts_config = self.emit_tsconfig()?;
ts_config_to_transpile_and_emit_options(ts_config.as_ref().clone())
.map(new_rc)
.map_err(|source| CompilerOptionsParseError {
specifier: self
.dir
.maybe_deno_json()
.map(|d| d.specifier.clone())
.unwrap_or_else(|| {
debug_assert!(false);
self.dir.dir_url().as_ref().clone()
}),
source,
})
})
}
}
#[derive(Debug)]
pub struct TsConfigResolver<TSys: FsRead> {
map: FolderScopedMap<TsConfigFolderInfo<TSys>>,
}
impl<TSys: FsRead + Clone> TsConfigResolver<TSys> {
pub fn from_workspace(sys: &TSys, workspace: &WorkspaceRc) -> Self {
let root_dir = workspace.resolve_member_dir(workspace.root_dir());
let logged_warnings = new_rc(LoggedWarnings::default());
let mut map = FolderScopedMap::new(TsConfigFolderInfo {
dir: root_dir,
logged_warnings: logged_warnings.clone(),
memoized: Default::default(),
sys: sys.clone(),
});
for (url, folder) in workspace.config_folders() {
let folder_has_compiler_options = folder
.deno_json
.as_ref()
.map(|d| d.json.compiler_options.is_some())
.unwrap_or(false);
if url != workspace.root_dir() && folder_has_compiler_options {
let dir = workspace.resolve_member_dir(url);
map.insert(
url.clone(),
TsConfigFolderInfo {
dir,
logged_warnings: logged_warnings.clone(),
memoized: Default::default(),
sys: sys.clone(),
},
);
}
}
Self { map }
}
}
impl<TSys: FsRead> TsConfigResolver<TSys> {
pub fn check_js_for_specifier(&self, specifier: &Url) -> bool {
self.folder_for_specifier(specifier).dir.check_js()
}
#[cfg(feature = "deno_ast")]
pub fn transpile_and_emit_options(
&self,
specifier: &Url,
) -> Result<&TranspileAndEmitOptionsRc, CompilerOptionsParseError> {
let value = self.map.get_for_specifier(specifier);
value.transpile_options()
}
pub fn folder_for_specifier(
&self,
specifier: &Url,
) -> &TsConfigFolderInfo<TSys> {
self.folder_for_specifier_str(specifier.as_str())
}
pub fn folder_for_specifier_str(
&self,
specifier: &str,
) -> &TsConfigFolderInfo<TSys> {
self.map.get_for_specifier_str(specifier)
}
pub fn folder_count(&self) -> usize {
self.map.count()
}
}
#[cfg(feature = "graph")]
impl<TSys: FsRead + std::fmt::Debug> deno_graph::CheckJsResolver
for TsConfigResolver<TSys>
{
fn resolve(&self, specifier: &deno_graph::ModuleSpecifier) -> bool {
self.check_js_for_specifier(specifier)
}
}
#[cfg(feature = "deno_ast")]
fn ts_config_to_transpile_and_emit_options(
config: deno_config::deno_json::TsConfig,
) -> Result<TranspileAndEmitOptions, serde_json::Error> {
let options: deno_config::deno_json::EmitConfigOptions =
serde_json::from_value(config.0)?;
let imports_not_used_as_values =
match options.imports_not_used_as_values.as_str() {
"preserve" => deno_ast::ImportsNotUsedAsValues::Preserve,
"error" => deno_ast::ImportsNotUsedAsValues::Error,
_ => deno_ast::ImportsNotUsedAsValues::Remove,
};
let (transform_jsx, jsx_automatic, jsx_development, precompile_jsx) =
match options.jsx.as_str() {
"react" => (true, false, false, false),
"react-jsx" => (true, true, false, false),
"react-jsxdev" => (true, true, true, false),
"precompile" => (false, false, false, true),
_ => (false, false, false, false),
};
let source_map = if options.inline_source_map {
deno_ast::SourceMapOption::Inline
} else if options.source_map {
deno_ast::SourceMapOption::Separate
} else {
deno_ast::SourceMapOption::None
};
let transpile = deno_ast::TranspileOptions {
use_ts_decorators: options.experimental_decorators,
use_decorators_proposal: !options.experimental_decorators,
emit_metadata: options.emit_decorator_metadata,
imports_not_used_as_values,
jsx_automatic,
jsx_development,
jsx_factory: options.jsx_factory,
jsx_fragment_factory: options.jsx_fragment_factory,
jsx_import_source: options.jsx_import_source,
precompile_jsx,
precompile_jsx_skip_elements: options.jsx_precompile_skip_elements,
precompile_jsx_dynamic_props: None,
transform_jsx,
var_decl_imports: false,
verbatim_module_syntax: false,
};
let emit = deno_ast::EmitOptions {
inline_sources: options.inline_sources,
remove_comments: false,
source_map,
source_map_base: None,
source_map_file: None,
};
let transpile_and_emit_options_hash = {
use std::hash::Hash;
use std::hash::Hasher;
let mut hasher = twox_hash::XxHash64::default();
transpile.hash(&mut hasher);
emit.hash(&mut hasher);
hasher.finish()
};
Ok(TranspileAndEmitOptions {
transpile,
emit,
pre_computed_hash: transpile_and_emit_options_hash,
})
}
fn check_warn_tsconfig(
ts_config: &TsConfigWithIgnoredOptions,
logged_warnings: &LoggedWarnings,
) {
for ignored_options in &ts_config.ignored_options {
if ignored_options
.maybe_specifier
.as_ref()
.map(|s| logged_warnings.folders.insert(s.clone()))
.unwrap_or(true)
{
log::warn!("{}", ignored_options);
}
}
let serde_json::Value::Object(obj) = &ts_config.ts_config.0 else {
return;
};
if obj.get("experimentalDecorators") == Some(&serde_json::Value::Bool(true))
&& logged_warnings.experimental_decorators.raise()
{
log::warn!(
"{} experimentalDecorators compiler option is deprecated and may be removed at any time",
colors::yellow("Warning"),
);
}
}