refactor(deno_resolver): extract out CLI's deno_graph::source::Resolver (#29143)

This commit is contained in:
David Sherret 2025-05-03 15:44:08 -04:00 committed by GitHub
parent f42cb0816e
commit 7ae0f14a90
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 434 additions and 233 deletions

View file

@ -1220,12 +1220,13 @@ const ci = {
},
// we want these crates to be Wasm compatible
{
name: "Cargo build (deno_resolver)",
run: "cargo build --target wasm32-unknown-unknown -p deno_resolver",
name: "Cargo check (deno_resolver)",
run:
"cargo check --target wasm32-unknown-unknown -p deno_resolver && cargo check --target wasm32-unknown-unknown -p deno_resolver --features graph",
},
{
name: "Cargo build (deno_npm_cache)",
run: "cargo build --target wasm32-unknown-unknown -p deno_npm_cache",
name: "Cargo check (deno_npm_cache)",
run: "cargo check --target wasm32-unknown-unknown -p deno_npm_cache",
},
]),
},

View file

@ -795,11 +795,11 @@ jobs:
- name: Install wasm target
run: rustup target add wasm32-unknown-unknown
if: '!(matrix.skip)'
- name: Cargo build (deno_resolver)
run: cargo build --target wasm32-unknown-unknown -p deno_resolver
- name: Cargo check (deno_resolver)
run: cargo check --target wasm32-unknown-unknown -p deno_resolver && cargo check --target wasm32-unknown-unknown -p deno_resolver --features graph
if: '!(matrix.skip)'
- name: Cargo build (deno_npm_cache)
run: cargo build --target wasm32-unknown-unknown -p deno_npm_cache
- name: Cargo check (deno_npm_cache)
run: cargo check --target wasm32-unknown-unknown -p deno_npm_cache
if: '!(matrix.skip)'
publish-canary:
name: publish canary

3
Cargo.lock generated
View file

@ -2542,12 +2542,14 @@ dependencies = [
"deno_cache_dir",
"deno_config",
"deno_error",
"deno_graph",
"deno_media_type",
"deno_npm",
"deno_package_json",
"deno_path_util",
"deno_semver",
"deno_terminal 0.2.2",
"deno_unsync",
"futures",
"import_map",
"indexmap 2.8.0",
@ -5643,6 +5645,7 @@ dependencies = [
"dashmap",
"deno_config",
"deno_error",
"deno_graph",
"deno_media_type",
"deno_package_json",
"deno_path_util",

View file

@ -60,7 +60,7 @@ deno_cache_dir = "=0.20.0"
deno_config = { version = "=0.54.2", features = ["workspace"] }
deno_doc = "=0.172.0"
deno_error = "=0.5.6"
deno_graph = "=0.90.0"
deno_graph = { version = "=0.90.0", default-features = false }
deno_lint = "=0.74.0"
deno_lockfile = "=0.28.0"
deno_media_type = { version = "=0.2.8", features = ["module_specifier"] }

View file

@ -75,7 +75,7 @@ deno_config = { workspace = true, features = ["sync", "workspace"] }
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_doc = { workspace = true, features = ["rust", "comrak"] }
deno_error.workspace = true
deno_graph.workspace = true
deno_graph = { workspace = true, features = ["fast_check"] }
deno_lib.workspace = true
deno_lint.workspace = true
deno_lockfile.workspace = true
@ -85,7 +85,7 @@ deno_npm_cache.workspace = true
deno_package_json = { workspace = true, features = ["sync"] }
deno_panic = { version = "0.1.0", optional = true }
deno_path_util.workspace = true
deno_resolver = { workspace = true, features = ["sync"] }
deno_resolver = { workspace = true, features = ["graph", "sync"] }
deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] }
deno_semver.workspace = true
deno_snapshots.workspace = true
@ -94,7 +94,7 @@ deno_telemetry.workspace = true
deno_terminal.workspace = true
eszip.workspace = true
libsui.workspace = true
node_resolver.workspace = true
node_resolver = { workspace = true, features = ["graph", "sync"] }
anstream.workspace = true
async-trait.workspace = true

View file

@ -38,6 +38,7 @@ use deno_resolver::factory::DenoDirPathProviderOptions;
use deno_resolver::factory::NpmProcessStateOptions;
use deno_resolver::factory::ResolverFactoryOptions;
use deno_resolver::factory::SpecifiedImportMapProvider;
use deno_resolver::graph::FoundPackageJsonDepFlag;
use deno_resolver::npm::managed::NpmResolutionCell;
use deno_resolver::npm::DenoInNpmPackageChecker;
use deno_resolver::workspace::WorkspaceResolver;
@ -100,12 +101,12 @@ use crate::npm::CliNpmResolverManagedSnapshotOption;
use crate::npm::CliNpmTarballCache;
use crate::npm::NpmResolutionInitializer;
use crate::npm::WorkspaceNpmPatchPackages;
use crate::resolver::on_resolve_diagnostic;
use crate::resolver::CliCjsTracker;
use crate::resolver::CliDenoResolver;
use crate::resolver::CliNpmGraphResolver;
use crate::resolver::CliNpmReqResolver;
use crate::resolver::CliResolver;
use crate::resolver::FoundPackageJsonDepFlag;
use crate::standalone::binary::DenoCompileBinaryWriter;
use crate::sys::CliSys;
use crate::tools::coverage::CoverageCollector;
@ -825,6 +826,7 @@ impl CliFactory {
Ok(Arc::new(CliResolver::new(
self.deno_resolver().await?.clone(),
self.services.found_pkg_json_dep_flag.clone(),
Box::new(on_resolve_diagnostic),
)))
}
.boxed_local(),

View file

@ -1,6 +1,5 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::collections::BTreeMap;
use std::collections::HashSet;
use std::error::Error;
use std::path::PathBuf;
@ -11,7 +10,6 @@ use deno_config::deno_json;
use deno_config::deno_json::CompilerOptionTypesDeserializeError;
use deno_config::deno_json::NodeModulesDirMode;
use deno_config::workspace::JsrPackageConfig;
use deno_config::workspace::JsxImportSourceConfig;
use deno_config::workspace::ToMaybeJsxImportSourceConfigError;
use deno_core::error::AnyError;
use deno_core::parking_lot::Mutex;
@ -21,7 +19,6 @@ use deno_error::JsErrorBox;
use deno_error::JsErrorClass;
use deno_graph::source::Loader;
use deno_graph::source::LoaderChecksum;
use deno_graph::source::ResolutionKind;
use deno_graph::source::ResolveError;
use deno_graph::CheckJsOption;
use deno_graph::FillFromLockfileOptions;
@ -37,6 +34,7 @@ use deno_graph::WorkspaceFastCheckOption;
use deno_path_util::url_to_file_path;
use deno_resolver::npm::DenoInNpmPackageChecker;
use deno_resolver::workspace::sloppy_imports_resolve;
use deno_resolver::workspace::ScopedJsxImportSourceConfig;
use deno_runtime::deno_node;
use deno_runtime::deno_permissions::PermissionsContainer;
use deno_semver::jsr::JsrDepPackageReq;
@ -751,7 +749,12 @@ impl ModuleGraphBuilder {
Some(loader) => MutLoaderRef::Borrowed(loader),
None => MutLoaderRef::Owned(self.create_graph_loader()),
};
let graph_resolver = self.create_graph_resolver()?;
let scoped_jsx_config = ScopedJsxImportSourceConfig::from_workspace_dir(
&self.cli_options.start_dir,
)?;
let graph_resolver = self
.resolver
.as_graph_resolver(self.cjs_tracker.as_ref(), &scoped_jsx_config);
let maybe_file_watcher_reporter = self
.maybe_file_watcher_reporter
.as_ref()
@ -893,7 +896,12 @@ impl ModuleGraphBuilder {
None
};
let parser = self.parsed_source_cache.as_capturing_parser();
let graph_resolver = self.create_graph_resolver()?;
let scoped_jsx_config = ScopedJsxImportSourceConfig::from_workspace_dir(
&self.cli_options.start_dir,
)?;
let graph_resolver = self
.resolver
.as_graph_resolver(self.cjs_tracker.as_ref(), &scoped_jsx_config);
graph.build_fast_check_type_graph(
deno_graph::BuildFastCheckTypeGraphOptions {
@ -967,29 +975,6 @@ impl ModuleGraphBuilder {
},
)
}
fn create_graph_resolver(
&self,
) -> Result<CliGraphResolver, ToMaybeJsxImportSourceConfigError> {
let jsx_import_source_config_unscoped = self
.cli_options
.start_dir
.to_maybe_jsx_import_source_config()?;
let mut jsx_import_source_config_by_scope = BTreeMap::default();
for (dir_url, _) in self.cli_options.workspace().config_folders() {
let dir = self.cli_options.workspace().resolve_member_dir(dir_url);
let jsx_import_source_config_unscoped =
dir.to_maybe_jsx_import_source_config()?;
jsx_import_source_config_by_scope
.insert(dir_url.clone(), jsx_import_source_config_unscoped);
}
Ok(CliGraphResolver {
cjs_tracker: &self.cjs_tracker,
resolver: &self.resolver,
jsx_import_source_config_unscoped,
jsx_import_source_config_by_scope,
})
}
}
/// Adds more explanatory information to a resolution error.
@ -1309,8 +1294,6 @@ impl deno_graph::source::JsrUrlProvider for CliJsrUrlProvider {
}
}
// todo(dsherret): We should change ModuleError to use thiserror so that
// we don't need to do this.
fn format_deno_graph_error(err: &dyn Error) -> String {
use std::fmt::Write;
@ -1352,100 +1335,6 @@ fn format_deno_graph_error(err: &dyn Error) -> String {
message
}
#[derive(Debug)]
struct CliGraphResolver<'a> {
cjs_tracker: &'a CliCjsTracker,
resolver: &'a CliResolver,
jsx_import_source_config_unscoped: Option<JsxImportSourceConfig>,
jsx_import_source_config_by_scope:
BTreeMap<Arc<ModuleSpecifier>, Option<JsxImportSourceConfig>>,
}
impl CliGraphResolver<'_> {
fn resolve_jsx_import_source_config(
&self,
referrer: &ModuleSpecifier,
) -> Option<&JsxImportSourceConfig> {
self
.jsx_import_source_config_by_scope
.iter()
.rfind(|(s, _)| referrer.as_str().starts_with(s.as_str()))
.map(|(_, c)| c.as_ref())
.unwrap_or(self.jsx_import_source_config_unscoped.as_ref())
}
}
impl deno_graph::source::Resolver for CliGraphResolver<'_> {
fn default_jsx_import_source(
&self,
referrer: &ModuleSpecifier,
) -> Option<String> {
self
.resolve_jsx_import_source_config(referrer)
.and_then(|c| c.import_source.as_ref().map(|s| s.specifier.clone()))
}
fn default_jsx_import_source_types(
&self,
referrer: &ModuleSpecifier,
) -> Option<String> {
self
.resolve_jsx_import_source_config(referrer)
.and_then(|c| c.import_source_types.as_ref().map(|s| s.specifier.clone()))
}
fn jsx_import_source_module(&self, referrer: &ModuleSpecifier) -> &str {
self
.resolve_jsx_import_source_config(referrer)
.map(|c| c.module.as_str())
.unwrap_or(deno_graph::source::DEFAULT_JSX_IMPORT_SOURCE_MODULE)
}
fn resolve(
&self,
raw_specifier: &str,
referrer_range: &deno_graph::Range,
resolution_kind: ResolutionKind,
) -> Result<ModuleSpecifier, ResolveError> {
self.resolver.resolve(
raw_specifier,
&referrer_range.specifier,
referrer_range.range.start,
referrer_range
.resolution_mode
.map(to_node_resolution_mode)
.unwrap_or_else(|| {
self
.cjs_tracker
.get_referrer_kind(&referrer_range.specifier)
}),
to_node_resolution_kind(resolution_kind),
)
}
}
pub fn to_node_resolution_kind(
kind: ResolutionKind,
) -> node_resolver::NodeResolutionKind {
match kind {
ResolutionKind::Execution => node_resolver::NodeResolutionKind::Execution,
ResolutionKind::Types => node_resolver::NodeResolutionKind::Types,
}
}
pub fn to_node_resolution_mode(
mode: deno_graph::source::ResolutionMode,
) -> node_resolver::ResolutionMode {
match mode {
deno_graph::source::ResolutionMode::Import => {
node_resolver::ResolutionMode::Import
}
deno_graph::source::ResolutionMode::Require => {
node_resolver::ResolutionMode::Require
}
}
}
#[cfg(test)]
mod test {
use std::sync::Arc;

View file

@ -35,7 +35,6 @@ use super::registries::ModuleRegistry;
use super::resolver::LspResolver;
use super::search::PackageSearchApi;
use super::tsc;
use crate::graph_util::to_node_resolution_mode;
use crate::jsr::JsrFetchResolver;
use crate::util::path::is_importable_ext;
use crate::util::path::relative_specifier;
@ -167,7 +166,7 @@ pub async fn get_import_completions(
let (text, _, graph_range) = module.dependency_at_position(position)?;
let resolution_mode = graph_range
.resolution_mode
.map(to_node_resolution_mode)
.map(node_resolver::ResolutionMode::from_deno_graph)
.unwrap_or_else(|| module.resolution_mode);
let range = to_narrow_lsp_range(module.text_info(), graph_range.range);
let scoped_resolver = resolver.get_scoped_resolver(module.scope.as_deref());

View file

@ -22,6 +22,7 @@ use deno_npm::NpmSystemInfo;
use deno_npm_cache::TarballCache;
use deno_path_util::url_to_file_path;
use deno_resolver::cjs::IsCjsResolutionMode;
use deno_resolver::graph::FoundPackageJsonDepFlag;
use deno_resolver::npm::managed::ManagedInNpmPkgCheckerCreateOptions;
use deno_resolver::npm::managed::NpmResolutionCell;
use deno_resolver::npm::CreateInNpmPkgCheckerOptions;
@ -55,8 +56,6 @@ use crate::args::CliLockfile;
use crate::args::LifecycleScriptsConfig;
use crate::args::NpmInstallDepsProvider;
use crate::factory::Deferred;
use crate::graph_util::to_node_resolution_kind;
use crate::graph_util::to_node_resolution_mode;
use crate::graph_util::CliJsrUrlProvider;
use crate::http_util::HttpClientProvider;
use crate::lsp::config::Config;
@ -77,11 +76,11 @@ use crate::npm::CliNpmResolverCreateOptions;
use crate::npm::CliNpmResolverManagedSnapshotOption;
use crate::npm::NpmResolutionInitializer;
use crate::npm::WorkspaceNpmPatchPackages;
use crate::resolver::on_resolve_diagnostic;
use crate::resolver::CliDenoResolver;
use crate::resolver::CliIsCjsResolver;
use crate::resolver::CliNpmReqResolver;
use crate::resolver::CliResolver;
use crate::resolver::FoundPackageJsonDepFlag;
use crate::sys::CliSys;
use crate::tsc::into_specifier_and_media_type;
use crate::util::progress_bar::ProgressBar;
@ -952,6 +951,7 @@ impl<'a> ResolverFactory<'a> {
Arc::new(CliResolver::new(
deno_resolver,
self.services.found_pkg_json_dep_flag.clone(),
Box::new(on_resolve_diagnostic),
))
})
}
@ -1119,9 +1119,9 @@ impl deno_graph::source::Resolver for SingleReferrerGraphResolver<'_> {
referrer_range.range.start,
referrer_range
.resolution_mode
.map(to_node_resolution_mode)
.map(node_resolver::ResolutionMode::from_deno_graph)
.unwrap_or(self.module_resolution_mode),
to_node_resolution_kind(resolution_kind),
node_resolver::NodeResolutionKind::from_deno_graph(resolution_kind),
)
}
}

View file

@ -3,28 +3,21 @@
use std::sync::Arc;
use async_trait::async_trait;
use dashmap::DashSet;
use deno_core::ModuleSpecifier;
use deno_ast::ModuleSpecifier;
use deno_error::JsErrorBox;
use deno_graph::source::ResolveError;
use deno_graph::NpmLoadError;
use deno_graph::NpmResolvePkgReqsResult;
use deno_npm::resolution::NpmResolutionError;
use deno_resolver::graph::FoundPackageJsonDepFlag;
use deno_resolver::npm::DenoInNpmPackageChecker;
use deno_resolver::workspace::MappedResolutionDiagnostic;
use deno_resolver::workspace::MappedResolutionError;
use deno_runtime::colors;
use deno_semver::package::PackageReq;
use node_resolver::DenoIsBuiltInNodeModuleChecker;
use node_resolver::NodeResolutionKind;
use node_resolver::ResolutionMode;
use crate::args::NpmCachingStrategy;
use crate::npm::installer::NpmInstaller;
use crate::npm::installer::PackageCaching;
use crate::npm::CliNpmResolver;
use crate::sys::CliSys;
use crate::util::sync::AtomicFlag;
pub type CliCjsTracker =
deno_resolver::cjs::CjsTracker<DenoInNpmPackageChecker, CliSys>;
@ -42,82 +35,25 @@ pub type CliNpmReqResolver = deno_resolver::npm::NpmReqResolver<
CliNpmResolver,
CliSys,
>;
pub type CliResolver = deno_resolver::graph::DenoGraphResolver<
DenoInNpmPackageChecker,
DenoIsBuiltInNodeModuleChecker,
CliNpmResolver,
CliSys,
>;
#[derive(Debug, Default)]
pub struct FoundPackageJsonDepFlag(AtomicFlag);
/// A resolver that takes care of resolution, taking into account loaded
/// import map, JSX settings.
#[derive(Debug)]
pub struct CliResolver {
deno_resolver: Arc<CliDenoResolver>,
found_package_json_dep_flag: Arc<FoundPackageJsonDepFlag>,
warned_pkgs: DashSet<PackageReq>,
}
impl CliResolver {
pub fn new(
deno_resolver: Arc<CliDenoResolver>,
found_package_json_dep_flag: Arc<FoundPackageJsonDepFlag>,
) -> Self {
Self {
deno_resolver,
found_package_json_dep_flag,
warned_pkgs: Default::default(),
}
}
pub fn resolve(
&self,
raw_specifier: &str,
referrer: &ModuleSpecifier,
referrer_range_start: deno_graph::Position,
resolution_mode: ResolutionMode,
resolution_kind: NodeResolutionKind,
) -> Result<ModuleSpecifier, ResolveError> {
let resolution = self
.deno_resolver
.resolve(raw_specifier, referrer, resolution_mode, resolution_kind)
.map_err(|err| match err.into_kind() {
deno_resolver::DenoResolveErrorKind::MappedResolution(
mapped_resolution_error,
) => match mapped_resolution_error {
MappedResolutionError::Specifier(e) => ResolveError::Specifier(e),
// deno_graph checks specifically for an ImportMapError
MappedResolutionError::ImportMap(e) => ResolveError::ImportMap(e),
MappedResolutionError::Workspace(e) => {
ResolveError::Other(JsErrorBox::from_err(e))
}
},
err => ResolveError::Other(JsErrorBox::from_err(err)),
})?;
if resolution.found_package_json_dep {
// mark that we need to do an "npm install" later
self.found_package_json_dep_flag.0.raise();
}
if let Some(diagnostic) = resolution.maybe_diagnostic {
match &*diagnostic {
MappedResolutionDiagnostic::ConstraintNotMatchedLocalVersion {
reference,
..
} => {
if self.warned_pkgs.insert(reference.req().clone()) {
log::warn!(
"{} {}\n at {}:{}",
colors::yellow("Warning"),
diagnostic,
referrer,
referrer_range_start,
);
}
}
}
}
Ok(resolution.url)
}
pub fn on_resolve_diagnostic(
diagnostic: &deno_resolver::workspace::MappedResolutionDiagnostic,
referrer: &ModuleSpecifier,
position: deno_graph::Position,
) {
log::warn!(
"{} {}\n at {}:{}",
deno_runtime::colors::yellow("Warning"),
diagnostic,
referrer,
position,
);
}
#[derive(Debug)]
@ -159,8 +95,7 @@ impl deno_graph::source::NpmResolver for CliNpmGraphResolver {
) -> NpmResolvePkgReqsResult {
match &self.npm_installer {
Some(npm_installer) => {
let top_level_result = if self.found_package_json_dep_flag.0.is_raised()
{
let top_level_result = if self.found_package_json_dep_flag.is_raised() {
npm_installer
.ensure_top_level_package_json_install()
.await

View file

@ -14,6 +14,7 @@ description = "Deno resolution algorithm"
path = "lib.rs"
[features]
graph = ["deno_graph", "node_resolver/graph"]
sync = ["dashmap", "deno_package_json/sync", "node_resolver/sync", "deno_config/sync", "deno_cache_dir/sync"]
[dependencies]
@ -26,12 +27,14 @@ dashmap = { workspace = true, optional = true }
deno_cache_dir.workspace = true
deno_config.workspace = true
deno_error.workspace = true
deno_graph = { workspace = true, optional = true }
deno_media_type.workspace = true
deno_npm.workspace = true
deno_package_json.workspace = true
deno_path_util.workspace = true
deno_semver.workspace = true
deno_terminal.workspace = true
deno_unsync.workspace = true
futures.workspace = true
import_map.workspace = true
indexmap.workspace = true

271
resolvers/deno/graph.rs Normal file
View file

@ -0,0 +1,271 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use deno_error::JsErrorBox;
use deno_graph::source::ResolveError;
use deno_semver::package::PackageReq;
use deno_unsync::sync::AtomicFlag;
use node_resolver::InNpmPackageChecker;
use node_resolver::IsBuiltInNodeModuleChecker;
use node_resolver::NpmPackageFolderResolver;
use url::Url;
use crate::cjs::CjsTracker;
use crate::workspace::MappedResolutionDiagnostic;
use crate::workspace::MappedResolutionError;
use crate::workspace::ScopedJsxImportSourceConfig;
use crate::DenoResolveErrorKind;
use crate::DenoResolverRc;
use crate::DenoResolverSys;
#[allow(clippy::disallowed_types)]
pub type FoundPackageJsonDepFlagRc =
crate::sync::MaybeArc<FoundPackageJsonDepFlag>;
/// A flag that indicates if a package.json dependency was
/// found during resolution.
#[derive(Debug, Default)]
pub struct FoundPackageJsonDepFlag(AtomicFlag);
impl FoundPackageJsonDepFlag {
#[inline(always)]
pub fn raise(&self) -> bool {
self.0.raise()
}
#[inline(always)]
pub fn is_raised(&self) -> bool {
self.0.is_raised()
}
}
type OnWarningFn = Box<
dyn Fn(&MappedResolutionDiagnostic, &Url, deno_graph::Position) + Send + Sync,
>;
/// A resolver for interfacing with deno_graph and displaying warnings.
pub struct DenoGraphResolver<
TInNpmPackageChecker: InNpmPackageChecker,
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver: NpmPackageFolderResolver,
TSys: DenoResolverSys,
> {
resolver: DenoResolverRc<
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
>,
found_package_json_dep_flag: FoundPackageJsonDepFlagRc,
warned_pkgs: crate::sync::MaybeDashSet<PackageReq>,
on_warning: OnWarningFn,
}
impl<
TInNpmPackageChecker: InNpmPackageChecker,
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver: NpmPackageFolderResolver,
TSys: DenoResolverSys,
> std::fmt::Debug
for DenoGraphResolver<
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DenoGraphResolver").finish()
}
}
impl<
TInNpmPackageChecker: InNpmPackageChecker,
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver: NpmPackageFolderResolver,
TSys: DenoResolverSys,
>
DenoGraphResolver<
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
>
{
pub fn new(
resolver: DenoResolverRc<
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
>,
found_package_json_dep_flag: FoundPackageJsonDepFlagRc,
on_warning: OnWarningFn,
) -> Self {
Self {
resolver,
found_package_json_dep_flag,
warned_pkgs: Default::default(),
on_warning,
}
}
pub fn resolve(
&self,
raw_specifier: &str,
referrer: &Url,
referrer_range_start: deno_graph::Position,
resolution_mode: node_resolver::ResolutionMode,
resolution_kind: node_resolver::NodeResolutionKind,
) -> Result<Url, ResolveError> {
let resolution = self
.resolver
.resolve(raw_specifier, referrer, resolution_mode, resolution_kind)
.map_err(|err| match err.into_kind() {
DenoResolveErrorKind::MappedResolution(mapped_resolution_error) => {
match mapped_resolution_error {
MappedResolutionError::Specifier(e) => ResolveError::Specifier(e),
// deno_graph checks specifically for an ImportMapError
MappedResolutionError::ImportMap(e) => ResolveError::ImportMap(e),
MappedResolutionError::Workspace(e) => {
ResolveError::Other(JsErrorBox::from_err(e))
}
}
}
err => ResolveError::Other(JsErrorBox::from_err(err)),
})?;
if resolution.found_package_json_dep {
// mark that we need to do an "npm install" later
self.found_package_json_dep_flag.raise();
}
if let Some(diagnostic) = resolution.maybe_diagnostic {
let diagnostic = &*diagnostic;
match diagnostic {
MappedResolutionDiagnostic::ConstraintNotMatchedLocalVersion {
reference,
..
} => {
if self.warned_pkgs.insert(reference.req().clone()) {
(self.on_warning)(diagnostic, referrer, referrer_range_start);
}
}
}
}
Ok(resolution.url)
}
pub fn as_graph_resolver<'a>(
&'a self,
cjs_tracker: &'a CjsTracker<TInNpmPackageChecker, TSys>,
scoped_jsx_import_source_config: &'a ScopedJsxImportSourceConfig,
) -> DenoGraphResolverAdapter<
'a,
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
> {
DenoGraphResolverAdapter {
cjs_tracker,
resolver: self,
scoped_jsx_import_source_config,
}
}
}
pub struct DenoGraphResolverAdapter<
'a,
TInNpmPackageChecker: InNpmPackageChecker,
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver: NpmPackageFolderResolver,
TSys: DenoResolverSys,
> {
cjs_tracker: &'a CjsTracker<TInNpmPackageChecker, TSys>,
resolver: &'a DenoGraphResolver<
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
>,
scoped_jsx_import_source_config: &'a ScopedJsxImportSourceConfig,
}
impl<
TInNpmPackageChecker: InNpmPackageChecker,
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver: NpmPackageFolderResolver,
TSys: DenoResolverSys,
> std::fmt::Debug
for DenoGraphResolverAdapter<
'_,
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DenoGraphResolverAdapter").finish()
}
}
impl<
TInNpmPackageChecker: InNpmPackageChecker,
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver: NpmPackageFolderResolver,
TSys: DenoResolverSys,
> deno_graph::source::Resolver
for DenoGraphResolverAdapter<
'_,
TInNpmPackageChecker,
TIsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver,
TSys,
>
{
fn default_jsx_import_source(&self, referrer: &Url) -> Option<String> {
self
.scoped_jsx_import_source_config
.resolve_by_referrer(referrer)
.and_then(|c| c.import_source.as_ref().map(|s| s.specifier.clone()))
}
fn default_jsx_import_source_types(&self, referrer: &Url) -> Option<String> {
self
.scoped_jsx_import_source_config
.resolve_by_referrer(referrer)
.and_then(|c| c.import_source_types.as_ref().map(|s| s.specifier.clone()))
}
fn jsx_import_source_module(&self, referrer: &Url) -> &str {
self
.scoped_jsx_import_source_config
.resolve_by_referrer(referrer)
.map(|c| c.module.as_str())
.unwrap_or(deno_graph::source::DEFAULT_JSX_IMPORT_SOURCE_MODULE)
}
fn resolve(
&self,
raw_specifier: &str,
referrer_range: &deno_graph::Range,
resolution_kind: deno_graph::source::ResolutionKind,
) -> Result<Url, ResolveError> {
self.resolver.resolve(
raw_specifier,
&referrer_range.specifier,
referrer_range.range.start,
referrer_range
.resolution_mode
.map(node_resolver::ResolutionMode::from_deno_graph)
.unwrap_or_else(|| {
self
.cjs_tracker
.get_referrer_kind(&referrer_range.specifier)
}),
node_resolver::NodeResolutionKind::from_deno_graph(resolution_kind),
)
}
}

View file

@ -42,6 +42,8 @@ use crate::workspace::WorkspaceResolver;
pub mod cjs;
pub mod factory;
#[cfg(feature = "graph")]
pub mod graph;
pub mod npm;
pub mod npmrc;
mod sync;
@ -128,12 +130,22 @@ pub struct NodeAndNpmReqResolver<
>,
}
pub trait DenoResolverSys:
FsCanonicalize + FsMetadata + FsRead + FsReadDir + std::fmt::Debug
{
}
impl<T> DenoResolverSys for T where
T: FsCanonicalize + FsMetadata + FsRead + FsReadDir + std::fmt::Debug
{
}
pub struct DenoResolverOptions<
'a,
TInNpmPackageChecker: InNpmPackageChecker,
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver: NpmPackageFolderResolver,
TSys: FsCanonicalize + FsMetadata + FsRead + FsReadDir,
TSys: DenoResolverSys,
> {
pub in_npm_pkg_checker: TInNpmPackageChecker,
pub node_and_req_resolver: Option<
@ -185,7 +197,7 @@ pub struct DenoResolver<
TInNpmPackageChecker: InNpmPackageChecker,
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver: NpmPackageFolderResolver,
TSys: FsCanonicalize + FsMetadata + FsRead + FsReadDir,
TSys: DenoResolverSys,
> {
in_npm_pkg_checker: TInNpmPackageChecker,
node_and_npm_resolver: Option<
@ -206,7 +218,7 @@ impl<
TInNpmPackageChecker: InNpmPackageChecker,
TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
TNpmPackageFolderResolver: NpmPackageFolderResolver,
TSys: FsCanonicalize + FsMetadata + FsRead + FsReadDir,
TSys: DenoResolverSys,
>
DenoResolver<
TInNpmPackageChecker,

View file

@ -11,6 +11,8 @@ mod inner {
pub use std::sync::Arc as MaybeArc;
pub use dashmap::DashMap as MaybeDashMap;
#[cfg(feature = "graph")]
pub use dashmap::DashSet as MaybeDashSet;
}
#[cfg(not(feature = "sync"))]
@ -58,6 +60,32 @@ mod inner {
inner.insert(key, value)
}
}
// Wrapper struct that exposes a subset of `DashMap` API.
#[cfg(feature = "graph")]
#[derive(Debug)]
pub struct MaybeDashSet<V, S = RandomState>(
RefCell<std::collections::HashSet<V, S>>,
);
#[cfg(feature = "graph")]
impl<V, S> Default for MaybeDashSet<V, S>
where
V: Eq + Hash,
S: Default + BuildHasher + Clone,
{
fn default() -> Self {
Self(RefCell::new(Default::default()))
}
}
#[cfg(feature = "graph")]
impl<V: Eq + Hash, S: BuildHasher> MaybeDashSet<V, S> {
pub fn insert(&self, value: V) -> bool {
let mut inner = self.0.borrow_mut();
inner.insert(value)
}
}
}
#[allow(clippy::disallowed_types)]

View file

@ -10,8 +10,11 @@ use std::path::PathBuf;
use deno_config::deno_json::ConfigFile;
use deno_config::deno_json::ConfigFileError;
use deno_config::workspace::JsxImportSourceConfig;
use deno_config::workspace::ResolverWorkspaceJsrPackage;
use deno_config::workspace::ToMaybeJsxImportSourceConfigError;
use deno_config::workspace::Workspace;
use deno_config::workspace::WorkspaceDirectory;
use deno_error::JsError;
use deno_media_type::MediaType;
use deno_package_json::PackageJsonDepValue;
@ -1634,6 +1637,41 @@ impl BaseUrl<'_> {
}
}
#[derive(Debug, Clone)]
pub struct ScopedJsxImportSourceConfig {
unscoped: Option<JsxImportSourceConfig>,
by_scope: BTreeMap<UrlRc, Option<JsxImportSourceConfig>>,
}
impl ScopedJsxImportSourceConfig {
pub fn from_workspace_dir(
start_dir: &WorkspaceDirectory,
) -> Result<Self, ToMaybeJsxImportSourceConfigError> {
let unscoped = start_dir.to_maybe_jsx_import_source_config()?;
let mut by_scope = BTreeMap::default();
for (dir_url, _) in start_dir.workspace.config_folders() {
let dir = start_dir.workspace.resolve_member_dir(dir_url);
let jsx_import_source_config_unscoped =
dir.to_maybe_jsx_import_source_config()?;
by_scope.insert(dir_url.clone(), jsx_import_source_config_unscoped);
}
Ok(Self { unscoped, by_scope })
}
/// Resolves the `JsxImportSourceConfig` to use for the provided referrer.
pub fn resolve_by_referrer(
&self,
referrer: &Url,
) -> Option<&JsxImportSourceConfig> {
self
.by_scope
.iter()
.rfind(|(s, _)| referrer.as_str().starts_with(s.as_str()))
.map(|(_, c)| c.as_ref())
.unwrap_or(self.unscoped.as_ref())
}
}
#[cfg(test)]
mod test {
use std::path::Path;

View file

@ -14,6 +14,7 @@ description = "Node.js module resolution algorithm used in Deno"
path = "lib.rs"
[features]
graph = ["deno_graph"]
sync = ["deno_package_json/sync"]
[dependencies]
@ -23,6 +24,7 @@ boxed_error.workspace = true
dashmap.workspace = true
deno_config.workspace = true
deno_error.workspace = true
deno_graph = { workspace = true, optional = true }
deno_media_type.workspace = true
deno_package_json.workspace = true
deno_path_util.workspace = true

View file

@ -106,6 +106,15 @@ impl ResolutionMode {
ResolutionMode::Require => REQUIRE_CONDITIONS,
}
}
#[cfg(feature = "graph")]
pub fn from_deno_graph(mode: deno_graph::source::ResolutionMode) -> Self {
use deno_graph::source::ResolutionMode::*;
match mode {
Import => Self::Import,
Require => Self::Require,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -118,6 +127,15 @@ impl NodeResolutionKind {
pub fn is_types(&self) -> bool {
matches!(self, NodeResolutionKind::Types)
}
#[cfg(feature = "graph")]
pub fn from_deno_graph(kind: deno_graph::source::ResolutionKind) -> Self {
use deno_graph::source::ResolutionKind::*;
match kind {
Execution => Self::Execution,
Types => Self::Types,
}
}
}
#[derive(Debug)]