2025-01-01 04:12:39 +09:00
|
|
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
2024-03-05 15:45:44 -05:00
|
|
|
|
|
|
|
use std::collections::HashSet;
|
2025-01-28 10:49:58 -05:00
|
|
|
use std::sync::Arc;
|
2024-03-05 15:45:44 -05:00
|
|
|
|
2025-01-28 10:49:58 -05:00
|
|
|
use deno_ast::SourceMapOption;
|
|
|
|
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::Workspace;
|
|
|
|
use deno_config::workspace::WorkspaceDirectory;
|
|
|
|
use deno_core::error::AnyError;
|
2024-03-05 15:45:44 -05:00
|
|
|
use deno_core::serde_json;
|
2025-01-28 10:49:58 -05:00
|
|
|
use deno_core::unsync::sync::AtomicFlag;
|
|
|
|
use deno_core::url::Url;
|
|
|
|
use deno_lib::util::hash::FastInsecureHasher;
|
|
|
|
use deno_lint::linter::LintConfig as DenoLintConfig;
|
2024-03-05 15:45:44 -05:00
|
|
|
use deno_semver::jsr::JsrDepPackageReq;
|
|
|
|
use deno_semver::jsr::JsrPackageReqReference;
|
|
|
|
use deno_semver::npm::NpmPackageReqReference;
|
2025-01-28 10:49:58 -05:00
|
|
|
use once_cell::sync::OnceCell;
|
|
|
|
|
|
|
|
use crate::util::collections::FolderScopedMap;
|
2024-03-05 15:45:44 -05:00
|
|
|
|
2024-12-13 10:19:37 -08:00
|
|
|
pub fn import_map_deps(
|
|
|
|
import_map: &serde_json::Value,
|
|
|
|
) -> HashSet<JsrDepPackageReq> {
|
|
|
|
let values = imports_values(import_map.get("imports"))
|
|
|
|
.into_iter()
|
|
|
|
.chain(scope_values(import_map.get("scopes")));
|
|
|
|
values_to_set(values)
|
|
|
|
}
|
|
|
|
|
2024-03-05 15:45:44 -05:00
|
|
|
pub fn deno_json_deps(
|
2024-07-19 15:56:07 -04:00
|
|
|
config: &deno_config::deno_json::ConfigFile,
|
2024-03-05 15:45:44 -05:00
|
|
|
) -> HashSet<JsrDepPackageReq> {
|
|
|
|
let values = imports_values(config.json.imports.as_ref())
|
|
|
|
.into_iter()
|
|
|
|
.chain(scope_values(config.json.scopes.as_ref()));
|
2024-11-19 19:49:19 -05:00
|
|
|
let mut set = values_to_set(values);
|
|
|
|
|
|
|
|
if let Some(serde_json::Value::Object(compiler_options)) =
|
|
|
|
&config.json.compiler_options
|
|
|
|
{
|
|
|
|
// add jsxImportSource
|
|
|
|
if let Some(serde_json::Value::String(value)) =
|
|
|
|
compiler_options.get("jsxImportSource")
|
|
|
|
{
|
|
|
|
if let Some(dep_req) = value_to_dep_req(value) {
|
|
|
|
set.insert(dep_req);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// add jsxImportSourceTypes
|
|
|
|
if let Some(serde_json::Value::String(value)) =
|
|
|
|
compiler_options.get("jsxImportSourceTypes")
|
|
|
|
{
|
|
|
|
if let Some(dep_req) = value_to_dep_req(value) {
|
|
|
|
set.insert(dep_req);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// add the dependencies in the types array
|
|
|
|
if let Some(serde_json::Value::Array(types)) = compiler_options.get("types")
|
|
|
|
{
|
|
|
|
for value in types {
|
|
|
|
if let serde_json::Value::String(value) = value {
|
|
|
|
if let Some(dep_req) = value_to_dep_req(value) {
|
|
|
|
set.insert(dep_req);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
set
|
2024-03-05 15:45:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
fn imports_values(value: Option<&serde_json::Value>) -> Vec<&String> {
|
|
|
|
let Some(obj) = value.and_then(|v| v.as_object()) else {
|
|
|
|
return Vec::new();
|
|
|
|
};
|
|
|
|
let mut items = Vec::with_capacity(obj.len());
|
|
|
|
for value in obj.values() {
|
|
|
|
if let serde_json::Value::String(value) = value {
|
|
|
|
items.push(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
items
|
|
|
|
}
|
|
|
|
|
|
|
|
fn scope_values(value: Option<&serde_json::Value>) -> Vec<&String> {
|
|
|
|
let Some(obj) = value.and_then(|v| v.as_object()) else {
|
|
|
|
return Vec::new();
|
|
|
|
};
|
|
|
|
obj.values().flat_map(|v| imports_values(Some(v))).collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn values_to_set<'a>(
|
|
|
|
values: impl Iterator<Item = &'a String>,
|
|
|
|
) -> HashSet<JsrDepPackageReq> {
|
|
|
|
let mut entries = HashSet::new();
|
|
|
|
for value in values {
|
2024-11-19 19:49:19 -05:00
|
|
|
if let Some(dep_req) = value_to_dep_req(value) {
|
|
|
|
entries.insert(dep_req);
|
2024-03-05 15:45:44 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
entries
|
|
|
|
}
|
2024-09-04 17:39:30 +02:00
|
|
|
|
2024-11-19 19:49:19 -05:00
|
|
|
fn value_to_dep_req(value: &str) -> Option<JsrDepPackageReq> {
|
|
|
|
if let Ok(req_ref) = JsrPackageReqReference::from_str(value) {
|
|
|
|
Some(JsrDepPackageReq::jsr(req_ref.into_inner().req))
|
|
|
|
} else if let Ok(req_ref) = NpmPackageReqReference::from_str(value) {
|
|
|
|
Some(JsrDepPackageReq::npm(req_ref.into_inner().req))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-28 10:49:58 -05:00
|
|
|
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);
|
|
|
|
}
|
2024-09-04 17:39:30 +02:00
|
|
|
}
|
|
|
|
let serde_json::Value::Object(obj) = &ts_config.ts_config.0 else {
|
|
|
|
return;
|
|
|
|
};
|
2025-01-28 10:49:58 -05:00
|
|
|
if obj.get("experimentalDecorators") == Some(&serde_json::Value::Bool(true))
|
|
|
|
&& logged_warnings.experimental_decorators.raise()
|
|
|
|
{
|
2024-09-04 17:39:30 +02:00
|
|
|
log::warn!(
|
2025-01-28 10:49:58 -05:00
|
|
|
"{} experimentalDecorators compiler option is deprecated and may be removed at any time",
|
|
|
|
deno_runtime::colors::yellow("Warning"),
|
|
|
|
);
|
2024-09-04 17:39:30 +02:00
|
|
|
}
|
|
|
|
}
|
2025-01-28 10:49:58 -05:00
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct TranspileAndEmitOptions {
|
|
|
|
pub transpile: deno_ast::TranspileOptions,
|
|
|
|
pub emit: deno_ast::EmitOptions,
|
|
|
|
// stored ahead of time so we don't have to recompute this a lot
|
|
|
|
pub pre_computed_hash: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Default)]
|
|
|
|
struct LoggedWarnings {
|
|
|
|
experimental_decorators: AtomicFlag,
|
|
|
|
folders: dashmap::DashSet<Url>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default, Debug)]
|
|
|
|
struct MemoizedValues {
|
|
|
|
deno_window_check_tsconfig: OnceCell<Arc<TsConfig>>,
|
|
|
|
deno_worker_check_tsconfig: OnceCell<Arc<TsConfig>>,
|
|
|
|
emit_tsconfig: OnceCell<Arc<TsConfig>>,
|
|
|
|
transpile_options: OnceCell<Arc<TranspileAndEmitOptions>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct TsConfigFolderInfo {
|
|
|
|
pub dir: WorkspaceDirectory,
|
|
|
|
logged_warnings: Arc<LoggedWarnings>,
|
|
|
|
memoized: MemoizedValues,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TsConfigFolderInfo {
|
|
|
|
pub fn lib_tsconfig(
|
|
|
|
&self,
|
|
|
|
lib: TsTypeLib,
|
|
|
|
) -> Result<&Arc<TsConfig>, 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(TsConfigType::Check { lib })?;
|
|
|
|
check_warn_tsconfig(&tsconfig_result, &self.logged_warnings);
|
|
|
|
Ok(Arc::new(tsconfig_result.ts_config))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn emit_tsconfig(
|
|
|
|
&self,
|
|
|
|
) -> Result<&Arc<TsConfig>, CompilerOptionsParseError> {
|
|
|
|
self.memoized.emit_tsconfig.get_or_try_init(|| {
|
|
|
|
let tsconfig_result =
|
|
|
|
self.dir.to_resolved_ts_config(TsConfigType::Emit)?;
|
|
|
|
check_warn_tsconfig(&tsconfig_result, &self.logged_warnings);
|
|
|
|
Ok(Arc::new(tsconfig_result.ts_config))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn transpile_options(
|
|
|
|
&self,
|
|
|
|
) -> Result<&Arc<TranspileAndEmitOptions>, 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(Arc::new)
|
|
|
|
.map_err(|source| CompilerOptionsParseError {
|
|
|
|
specifier: self
|
|
|
|
.dir
|
|
|
|
.maybe_deno_json()
|
|
|
|
.map(|d| d.specifier.clone())
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
// will never happen because each dir should have a
|
|
|
|
// deno.json if we got here
|
|
|
|
debug_assert!(false);
|
|
|
|
self.dir.dir_url().as_ref().clone()
|
|
|
|
}),
|
|
|
|
source,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct TsConfigResolver {
|
|
|
|
map: FolderScopedMap<TsConfigFolderInfo>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TsConfigResolver {
|
|
|
|
pub fn from_workspace(workspace: &Arc<Workspace>) -> Self {
|
|
|
|
// separate the workspace into directories that have a tsconfig
|
|
|
|
let root_dir = workspace.resolve_member_dir(workspace.root_dir());
|
|
|
|
let logged_warnings = Arc::new(LoggedWarnings::default());
|
|
|
|
let mut map = FolderScopedMap::new(TsConfigFolderInfo {
|
|
|
|
dir: root_dir,
|
|
|
|
logged_warnings: logged_warnings.clone(),
|
|
|
|
memoized: Default::default(),
|
|
|
|
});
|
|
|
|
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(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Self { map }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_js_for_specifier(&self, specifier: &Url) -> bool {
|
|
|
|
self.folder_for_specifier(specifier).dir.check_js()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn deno_lint_config(
|
|
|
|
&self,
|
|
|
|
specifier: &Url,
|
|
|
|
) -> Result<DenoLintConfig, AnyError> {
|
|
|
|
let transpile_options =
|
|
|
|
&self.transpile_and_emit_options(specifier)?.transpile;
|
|
|
|
// don't bother storing this in a cell because deno_lint requires an owned value
|
|
|
|
Ok(DenoLintConfig {
|
|
|
|
default_jsx_factory: (!transpile_options.jsx_automatic)
|
|
|
|
.then(|| transpile_options.jsx_factory.clone()),
|
|
|
|
default_jsx_fragment_factory: (!transpile_options.jsx_automatic)
|
|
|
|
.then(|| transpile_options.jsx_fragment_factory.clone()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn transpile_and_emit_options(
|
|
|
|
&self,
|
|
|
|
specifier: &Url,
|
|
|
|
) -> Result<&Arc<TranspileAndEmitOptions>, CompilerOptionsParseError> {
|
|
|
|
let value = self.map.get_for_specifier(specifier);
|
|
|
|
value.transpile_options()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn folder_for_specifier(&self, specifier: &Url) -> &TsConfigFolderInfo {
|
|
|
|
self.folder_for_specifier_str(specifier.as_str())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn folder_for_specifier_str(
|
|
|
|
&self,
|
|
|
|
specifier: &str,
|
|
|
|
) -> &TsConfigFolderInfo {
|
|
|
|
self.map.get_for_specifier_str(specifier)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn folder_count(&self) -> usize {
|
|
|
|
self.map.count()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl deno_graph::CheckJsResolver for TsConfigResolver {
|
|
|
|
fn resolve(&self, specifier: &deno_graph::ModuleSpecifier) -> bool {
|
|
|
|
self.check_js_for_specifier(specifier)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
SourceMapOption::Inline
|
|
|
|
} else if options.source_map {
|
|
|
|
SourceMapOption::Separate
|
|
|
|
} else {
|
|
|
|
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,
|
|
|
|
// todo(dsherret): support verbatim_module_syntax here properly
|
|
|
|
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 = {
|
|
|
|
let mut hasher = FastInsecureHasher::new_without_deno_version();
|
|
|
|
hasher.write_hashable(&transpile);
|
|
|
|
hasher.write_hashable(&emit);
|
|
|
|
hasher.finish()
|
|
|
|
};
|
|
|
|
Ok(TranspileAndEmitOptions {
|
|
|
|
transpile,
|
|
|
|
emit,
|
|
|
|
pre_computed_hash: transpile_and_emit_options_hash,
|
|
|
|
})
|
|
|
|
}
|