Skip to content

Commit

Permalink
fix(check): compiler options from workspace members (#27785)
Browse files Browse the repository at this point in the history
Co-authored-by: Nayeem Rahman <nayeemrmn99@gmail.com>
  • Loading branch information
dsherret and nayeemrmn authored Jan 28, 2025
1 parent 9931fb5 commit 4648fc4
Show file tree
Hide file tree
Showing 163 changed files with 1,680 additions and 715 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ deno_ast = { version = "=0.44.0", features = ["transpiling"] }
deno_core = { version = "0.333.0" }

deno_bench_util = { version = "0.181.0", path = "./bench_util" }
deno_config = { version = "=0.45.0", features = ["workspace"] }
deno_config = { version = "=0.46.0", features = ["workspace"] }
deno_lockfile = "=0.24.0"
deno_media_type = { version = "=0.2.5", features = ["module_specifier"] }
deno_npm = "=0.27.2"
Expand Down
2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ deno_config = { workspace = true, features = ["sync", "workspace"] }
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_doc = { version = "=0.164.0", features = ["rust", "comrak"] }
deno_error.workspace = true
deno_graph = { version = "=0.87.0" }
deno_graph = { version = "=0.87.2" }
deno_lib.workspace = true
deno_lint = { version = "0.70.0" }
deno_lockfile.workspace = true
Expand Down
276 changes: 268 additions & 8 deletions cli/args/deno_json.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
// Copyright 2018-2025 the Deno authors. MIT license.

use std::collections::HashSet;
use std::sync::Arc;

use deno_config::deno_json::TsConfigForEmit;
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;
use deno_core::serde_json;
use deno_core::unsync::sync::AtomicFlag;
use deno_core::url::Url;
use deno_lib::util::hash::FastInsecureHasher;
use deno_lint::linter::LintConfig as DenoLintConfig;
use deno_semver::jsr::JsrDepPackageReq;
use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference;
use once_cell::sync::OnceCell;

use crate::util::collections::FolderScopedMap;

pub fn import_map_deps(
import_map: &serde_json::Value,
Expand Down Expand Up @@ -102,17 +118,261 @@ fn value_to_dep_req(value: &str) -> Option<JsrDepPackageReq> {
}
}

pub fn check_warn_tsconfig(ts_config: &TsConfigForEmit) {
if let Some(ignored_options) = &ts_config.maybe_ignored_options {
log::warn!("{}", ignored_options);
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)) {
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",
deno_runtime::colors::yellow("Warning"),
);
"{} experimentalDecorators compiler option is deprecated and may be removed at any time",
deno_runtime::colors::yellow("Warning"),
);
}
}

#[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,
})
}
Loading

0 comments on commit 4648fc4

Please sign in to comment.