mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
refactor: extract out sloppy imports resolution from CLI crate (#25920)
This is slow progress towards creating a `deno_resolver` crate. Waiting on: * https://github.com/denoland/deno/pull/25918 * https://github.com/denoland/deno/pull/25916
This commit is contained in:
parent
3138478f66
commit
5faf769ac6
28 changed files with 665 additions and 527 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -1150,7 +1150,6 @@ version = "2.0.0-rc.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base32",
|
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"bincode",
|
"bincode",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -1175,6 +1174,7 @@ dependencies = [
|
||||||
"deno_npm",
|
"deno_npm",
|
||||||
"deno_package_json",
|
"deno_package_json",
|
||||||
"deno_path_util",
|
"deno_path_util",
|
||||||
|
"deno_resolver",
|
||||||
"deno_runtime",
|
"deno_runtime",
|
||||||
"deno_semver",
|
"deno_semver",
|
||||||
"deno_task_shell",
|
"deno_task_shell",
|
||||||
|
@ -1949,6 +1949,16 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deno_resolver"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"deno_media_type",
|
||||||
|
"deno_path_util",
|
||||||
|
"test_server",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_runtime"
|
name = "deno_runtime"
|
||||||
version = "0.177.0"
|
version = "0.177.0"
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -21,13 +21,14 @@ members = [
|
||||||
"ext/napi",
|
"ext/napi",
|
||||||
"ext/net",
|
"ext/net",
|
||||||
"ext/node",
|
"ext/node",
|
||||||
"ext/node_resolver",
|
|
||||||
"ext/url",
|
"ext/url",
|
||||||
"ext/web",
|
"ext/web",
|
||||||
"ext/webgpu",
|
"ext/webgpu",
|
||||||
"ext/webidl",
|
"ext/webidl",
|
||||||
"ext/websocket",
|
"ext/websocket",
|
||||||
"ext/webstorage",
|
"ext/webstorage",
|
||||||
|
"resolvers/deno",
|
||||||
|
"resolvers/node",
|
||||||
"runtime",
|
"runtime",
|
||||||
"runtime/permissions",
|
"runtime/permissions",
|
||||||
"tests",
|
"tests",
|
||||||
|
@ -50,6 +51,7 @@ deno_core = { version = "0.311.0" }
|
||||||
deno_bench_util = { version = "0.162.0", path = "./bench_util" }
|
deno_bench_util = { version = "0.162.0", path = "./bench_util" }
|
||||||
deno_lockfile = "=0.23.1"
|
deno_lockfile = "=0.23.1"
|
||||||
deno_media_type = { version = "0.1.4", features = ["module_specifier"] }
|
deno_media_type = { version = "0.1.4", features = ["module_specifier"] }
|
||||||
|
deno_npm = "=0.25.2"
|
||||||
deno_path_util = "=0.1.1"
|
deno_path_util = "=0.1.1"
|
||||||
deno_permissions = { version = "0.28.0", path = "./runtime/permissions" }
|
deno_permissions = { version = "0.28.0", path = "./runtime/permissions" }
|
||||||
deno_runtime = { version = "0.177.0", path = "./runtime" }
|
deno_runtime = { version = "0.177.0", path = "./runtime" }
|
||||||
|
@ -86,7 +88,10 @@ deno_webgpu = { version = "0.135.0", path = "./ext/webgpu" }
|
||||||
deno_webidl = { version = "0.168.0", path = "./ext/webidl" }
|
deno_webidl = { version = "0.168.0", path = "./ext/webidl" }
|
||||||
deno_websocket = { version = "0.173.0", path = "./ext/websocket" }
|
deno_websocket = { version = "0.173.0", path = "./ext/websocket" }
|
||||||
deno_webstorage = { version = "0.163.0", path = "./ext/webstorage" }
|
deno_webstorage = { version = "0.163.0", path = "./ext/webstorage" }
|
||||||
node_resolver = { version = "0.7.0", path = "./ext/node_resolver" }
|
|
||||||
|
# resolvers
|
||||||
|
deno_resolver = { version = "0.0.1", path = "./resolvers/deno" }
|
||||||
|
node_resolver = { version = "0.7.0", path = "./resolvers/node" }
|
||||||
|
|
||||||
aes = "=0.8.3"
|
aes = "=0.8.3"
|
||||||
anyhow = "1.0.57"
|
anyhow = "1.0.57"
|
||||||
|
@ -102,6 +107,7 @@ cbc = { version = "=0.1.2", features = ["alloc"] }
|
||||||
# Instead use util::time::utc_now()
|
# Instead use util::time::utc_now()
|
||||||
chrono = { version = "0.4", default-features = false, features = ["std", "serde"] }
|
chrono = { version = "0.4", default-features = false, features = ["std", "serde"] }
|
||||||
console_static_text = "=0.8.1"
|
console_static_text = "=0.8.1"
|
||||||
|
dashmap = "5.5.3"
|
||||||
data-encoding = "2.3.3"
|
data-encoding = "2.3.3"
|
||||||
data-url = "=0.3.0"
|
data-url = "=0.3.0"
|
||||||
deno_cache_dir = "=0.12.0"
|
deno_cache_dir = "=0.12.0"
|
||||||
|
|
|
@ -71,9 +71,10 @@ deno_doc = { version = "0.150.0", features = ["html", "syntect"] }
|
||||||
deno_graph = { version = "=0.82.3" }
|
deno_graph = { version = "=0.82.3" }
|
||||||
deno_lint = { version = "=0.67.0", features = ["docs"] }
|
deno_lint = { version = "=0.67.0", features = ["docs"] }
|
||||||
deno_lockfile.workspace = true
|
deno_lockfile.workspace = true
|
||||||
deno_npm = "=0.25.2"
|
deno_npm.workspace = true
|
||||||
deno_package_json.workspace = true
|
deno_package_json.workspace = true
|
||||||
deno_path_util.workspace = true
|
deno_path_util.workspace = true
|
||||||
|
deno_resolver.workspace = true
|
||||||
deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] }
|
deno_runtime = { workspace = true, features = ["include_js_files_for_snapshotting"] }
|
||||||
deno_semver.workspace = true
|
deno_semver.workspace = true
|
||||||
deno_task_shell = "=0.17.0"
|
deno_task_shell = "=0.17.0"
|
||||||
|
@ -85,7 +86,6 @@ node_resolver.workspace = true
|
||||||
|
|
||||||
anstream = "0.6.14"
|
anstream = "0.6.14"
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
base32.workspace = true
|
|
||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
bincode = "=1.3.3"
|
bincode = "=1.3.3"
|
||||||
bytes.workspace = true
|
bytes.workspace = true
|
||||||
|
@ -96,7 +96,7 @@ clap_complete = "=4.5.24"
|
||||||
clap_complete_fig = "=4.5.2"
|
clap_complete_fig = "=4.5.2"
|
||||||
color-print = "0.3.5"
|
color-print = "0.3.5"
|
||||||
console_static_text.workspace = true
|
console_static_text.workspace = true
|
||||||
dashmap = "5.5.3"
|
dashmap.workspace = true
|
||||||
data-encoding.workspace = true
|
data-encoding.workspace = true
|
||||||
dissimilar = "=1.0.4"
|
dissimilar = "=1.0.4"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
|
|
|
@ -41,8 +41,9 @@ use crate::resolver::CjsResolutionStore;
|
||||||
use crate::resolver::CliGraphResolver;
|
use crate::resolver::CliGraphResolver;
|
||||||
use crate::resolver::CliGraphResolverOptions;
|
use crate::resolver::CliGraphResolverOptions;
|
||||||
use crate::resolver::CliNodeResolver;
|
use crate::resolver::CliNodeResolver;
|
||||||
|
use crate::resolver::CliSloppyImportsResolver;
|
||||||
use crate::resolver::NpmModuleLoader;
|
use crate::resolver::NpmModuleLoader;
|
||||||
use crate::resolver::SloppyImportsResolver;
|
use crate::resolver::SloppyImportsCachedFs;
|
||||||
use crate::standalone::DenoCompileBinaryWriter;
|
use crate::standalone::DenoCompileBinaryWriter;
|
||||||
use crate::tools::check::TypeChecker;
|
use crate::tools::check::TypeChecker;
|
||||||
use crate::tools::coverage::CoverageCollector;
|
use crate::tools::coverage::CoverageCollector;
|
||||||
|
@ -186,7 +187,7 @@ struct CliFactoryServices {
|
||||||
npm_resolver: Deferred<Arc<dyn CliNpmResolver>>,
|
npm_resolver: Deferred<Arc<dyn CliNpmResolver>>,
|
||||||
permission_desc_parser: Deferred<Arc<RuntimePermissionDescriptorParser>>,
|
permission_desc_parser: Deferred<Arc<RuntimePermissionDescriptorParser>>,
|
||||||
root_permissions_container: Deferred<PermissionsContainer>,
|
root_permissions_container: Deferred<PermissionsContainer>,
|
||||||
sloppy_imports_resolver: Deferred<Option<Arc<SloppyImportsResolver>>>,
|
sloppy_imports_resolver: Deferred<Option<Arc<CliSloppyImportsResolver>>>,
|
||||||
text_only_progress_bar: Deferred<ProgressBar>,
|
text_only_progress_bar: Deferred<ProgressBar>,
|
||||||
type_checker: Deferred<Arc<TypeChecker>>,
|
type_checker: Deferred<Arc<TypeChecker>>,
|
||||||
cjs_resolutions: Deferred<Arc<CjsResolutionStore>>,
|
cjs_resolutions: Deferred<Arc<CjsResolutionStore>>,
|
||||||
|
@ -404,17 +405,16 @@ impl CliFactory {
|
||||||
|
|
||||||
pub fn sloppy_imports_resolver(
|
pub fn sloppy_imports_resolver(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<Option<&Arc<SloppyImportsResolver>>, AnyError> {
|
) -> Result<Option<&Arc<CliSloppyImportsResolver>>, AnyError> {
|
||||||
self
|
self
|
||||||
.services
|
.services
|
||||||
.sloppy_imports_resolver
|
.sloppy_imports_resolver
|
||||||
.get_or_try_init(|| {
|
.get_or_try_init(|| {
|
||||||
Ok(
|
Ok(self.cli_options()?.unstable_sloppy_imports().then(|| {
|
||||||
self
|
Arc::new(CliSloppyImportsResolver::new(SloppyImportsCachedFs::new(
|
||||||
.cli_options()?
|
self.fs().clone(),
|
||||||
.unstable_sloppy_imports()
|
)))
|
||||||
.then(|| Arc::new(SloppyImportsResolver::new(self.fs().clone()))),
|
}))
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.map(|maybe| maybe.as_ref())
|
.map(|maybe| maybe.as_ref())
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@ use crate::errors::get_error_class_name;
|
||||||
use crate::file_fetcher::FileFetcher;
|
use crate::file_fetcher::FileFetcher;
|
||||||
use crate::npm::CliNpmResolver;
|
use crate::npm::CliNpmResolver;
|
||||||
use crate::resolver::CliGraphResolver;
|
use crate::resolver::CliGraphResolver;
|
||||||
use crate::resolver::SloppyImportsResolver;
|
use crate::resolver::CliSloppyImportsResolver;
|
||||||
|
use crate::resolver::SloppyImportsCachedFs;
|
||||||
use crate::tools::check;
|
use crate::tools::check;
|
||||||
use crate::tools::check::TypeChecker;
|
use crate::tools::check::TypeChecker;
|
||||||
use crate::util::file_watcher::WatcherCommunicator;
|
use crate::util::file_watcher::WatcherCommunicator;
|
||||||
|
@ -31,7 +32,6 @@ use deno_core::error::AnyError;
|
||||||
use deno_core::parking_lot::Mutex;
|
use deno_core::parking_lot::Mutex;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use deno_graph::source::Loader;
|
use deno_graph::source::Loader;
|
||||||
use deno_graph::source::ResolutionMode;
|
|
||||||
use deno_graph::source::ResolveError;
|
use deno_graph::source::ResolveError;
|
||||||
use deno_graph::GraphKind;
|
use deno_graph::GraphKind;
|
||||||
use deno_graph::ModuleError;
|
use deno_graph::ModuleError;
|
||||||
|
@ -40,6 +40,7 @@ use deno_graph::ModuleGraphError;
|
||||||
use deno_graph::ResolutionError;
|
use deno_graph::ResolutionError;
|
||||||
use deno_graph::SpecifierError;
|
use deno_graph::SpecifierError;
|
||||||
use deno_path_util::url_to_file_path;
|
use deno_path_util::url_to_file_path;
|
||||||
|
use deno_resolver::sloppy_imports::SloppyImportsResolutionMode;
|
||||||
use deno_runtime::deno_fs::FileSystem;
|
use deno_runtime::deno_fs::FileSystem;
|
||||||
use deno_runtime::deno_node;
|
use deno_runtime::deno_node;
|
||||||
use deno_runtime::deno_permissions::PermissionsContainer;
|
use deno_runtime::deno_permissions::PermissionsContainer;
|
||||||
|
@ -765,8 +766,8 @@ fn enhanced_sloppy_imports_error_message(
|
||||||
match error {
|
match error {
|
||||||
ModuleError::LoadingErr(specifier, _, ModuleLoadError::Loader(_)) // ex. "Is a directory" error
|
ModuleError::LoadingErr(specifier, _, ModuleLoadError::Loader(_)) // ex. "Is a directory" error
|
||||||
| ModuleError::Missing(specifier, _) => {
|
| ModuleError::Missing(specifier, _) => {
|
||||||
let additional_message = SloppyImportsResolver::new(fs.clone())
|
let additional_message = CliSloppyImportsResolver::new(SloppyImportsCachedFs::new(fs.clone()))
|
||||||
.resolve(specifier, ResolutionMode::Execution)?
|
.resolve(specifier, SloppyImportsResolutionMode::Execution)?
|
||||||
.as_suggestion_message();
|
.as_suggestion_message();
|
||||||
Some(format!(
|
Some(format!(
|
||||||
"{} {} or run with --unstable-sloppy-imports",
|
"{} {} or run with --unstable-sloppy-imports",
|
||||||
|
|
|
@ -59,7 +59,8 @@ use crate::args::LintOptions;
|
||||||
use crate::cache::FastInsecureHasher;
|
use crate::cache::FastInsecureHasher;
|
||||||
use crate::file_fetcher::FileFetcher;
|
use crate::file_fetcher::FileFetcher;
|
||||||
use crate::lsp::logging::lsp_warn;
|
use crate::lsp::logging::lsp_warn;
|
||||||
use crate::resolver::SloppyImportsResolver;
|
use crate::resolver::CliSloppyImportsResolver;
|
||||||
|
use crate::resolver::SloppyImportsCachedFs;
|
||||||
use crate::tools::lint::CliLinter;
|
use crate::tools::lint::CliLinter;
|
||||||
use crate::tools::lint::CliLinterOptions;
|
use crate::tools::lint::CliLinterOptions;
|
||||||
use crate::tools::lint::LintRuleProvider;
|
use crate::tools::lint::LintRuleProvider;
|
||||||
|
@ -1181,7 +1182,7 @@ pub struct ConfigData {
|
||||||
pub lockfile: Option<Arc<CliLockfile>>,
|
pub lockfile: Option<Arc<CliLockfile>>,
|
||||||
pub npmrc: Option<Arc<ResolvedNpmRc>>,
|
pub npmrc: Option<Arc<ResolvedNpmRc>>,
|
||||||
pub resolver: Arc<WorkspaceResolver>,
|
pub resolver: Arc<WorkspaceResolver>,
|
||||||
pub sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
pub sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
|
||||||
pub import_map_from_settings: Option<ModuleSpecifier>,
|
pub import_map_from_settings: Option<ModuleSpecifier>,
|
||||||
watched_files: HashMap<ModuleSpecifier, ConfigWatchedFileType>,
|
watched_files: HashMap<ModuleSpecifier, ConfigWatchedFileType>,
|
||||||
}
|
}
|
||||||
|
@ -1584,9 +1585,11 @@ impl ConfigData {
|
||||||
.is_ok()
|
.is_ok()
|
||||||
|| member_dir.workspace.has_unstable("sloppy-imports");
|
|| member_dir.workspace.has_unstable("sloppy-imports");
|
||||||
let sloppy_imports_resolver = unstable_sloppy_imports.then(|| {
|
let sloppy_imports_resolver = unstable_sloppy_imports.then(|| {
|
||||||
Arc::new(SloppyImportsResolver::new_without_stat_cache(Arc::new(
|
Arc::new(CliSloppyImportsResolver::new(
|
||||||
deno_runtime::deno_fs::RealFs,
|
SloppyImportsCachedFs::new_without_stat_cache(Arc::new(
|
||||||
)))
|
deno_runtime::deno_fs::RealFs,
|
||||||
|
)),
|
||||||
|
))
|
||||||
});
|
});
|
||||||
let resolver = Arc::new(resolver);
|
let resolver = Arc::new(resolver);
|
||||||
let lint_rule_provider = LintRuleProvider::new(
|
let lint_rule_provider = LintRuleProvider::new(
|
||||||
|
|
|
@ -19,8 +19,8 @@ use super::urls::LspUrlMap;
|
||||||
use crate::graph_util;
|
use crate::graph_util;
|
||||||
use crate::graph_util::enhanced_resolution_error_message;
|
use crate::graph_util::enhanced_resolution_error_message;
|
||||||
use crate::lsp::lsp_custom::DiagnosticBatchNotificationParams;
|
use crate::lsp::lsp_custom::DiagnosticBatchNotificationParams;
|
||||||
use crate::resolver::SloppyImportsResolution;
|
use crate::resolver::CliSloppyImportsResolver;
|
||||||
use crate::resolver::SloppyImportsResolver;
|
use crate::resolver::SloppyImportsCachedFs;
|
||||||
use crate::tools::lint::CliLinter;
|
use crate::tools::lint::CliLinter;
|
||||||
use crate::tools::lint::CliLinterOptions;
|
use crate::tools::lint::CliLinterOptions;
|
||||||
use crate::tools::lint::LintRuleProvider;
|
use crate::tools::lint::LintRuleProvider;
|
||||||
|
@ -40,11 +40,12 @@ use deno_core::unsync::spawn_blocking;
|
||||||
use deno_core::unsync::JoinHandle;
|
use deno_core::unsync::JoinHandle;
|
||||||
use deno_core::url::Url;
|
use deno_core::url::Url;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use deno_graph::source::ResolutionMode;
|
|
||||||
use deno_graph::source::ResolveError;
|
use deno_graph::source::ResolveError;
|
||||||
use deno_graph::Resolution;
|
use deno_graph::Resolution;
|
||||||
use deno_graph::ResolutionError;
|
use deno_graph::ResolutionError;
|
||||||
use deno_graph::SpecifierError;
|
use deno_graph::SpecifierError;
|
||||||
|
use deno_resolver::sloppy_imports::SloppyImportsResolution;
|
||||||
|
use deno_resolver::sloppy_imports::SloppyImportsResolutionMode;
|
||||||
use deno_runtime::deno_fs;
|
use deno_runtime::deno_fs;
|
||||||
use deno_runtime::deno_node;
|
use deno_runtime::deno_node;
|
||||||
use deno_runtime::tokio_util::create_basic_runtime;
|
use deno_runtime::tokio_util::create_basic_runtime;
|
||||||
|
@ -1263,7 +1264,9 @@ impl DenoDiagnostic {
|
||||||
Self::NotInstalledJsr(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("JSR package \"{pkg_req}\" is not installed or doesn't exist."), Some(json!({ "specifier": specifier }))),
|
Self::NotInstalledJsr(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("JSR package \"{pkg_req}\" is not installed or doesn't exist."), Some(json!({ "specifier": specifier }))),
|
||||||
Self::NotInstalledNpm(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("NPM package \"{pkg_req}\" is not installed or doesn't exist."), Some(json!({ "specifier": specifier }))),
|
Self::NotInstalledNpm(pkg_req, specifier) => (lsp::DiagnosticSeverity::ERROR, format!("NPM package \"{pkg_req}\" is not installed or doesn't exist."), Some(json!({ "specifier": specifier }))),
|
||||||
Self::NoLocal(specifier) => {
|
Self::NoLocal(specifier) => {
|
||||||
let maybe_sloppy_resolution = SloppyImportsResolver::new(Arc::new(deno_fs::RealFs)).resolve(specifier, ResolutionMode::Execution);
|
let maybe_sloppy_resolution = CliSloppyImportsResolver::new(
|
||||||
|
SloppyImportsCachedFs::new(Arc::new(deno_fs::RealFs))
|
||||||
|
).resolve(specifier, SloppyImportsResolutionMode::Execution);
|
||||||
let data = maybe_sloppy_resolution.as_ref().map(|res| {
|
let data = maybe_sloppy_resolution.as_ref().map(|res| {
|
||||||
json!({
|
json!({
|
||||||
"specifier": specifier,
|
"specifier": specifier,
|
||||||
|
|
525
cli/resolver.rs
525
cli/resolver.rs
|
@ -22,7 +22,8 @@ use deno_graph::NpmLoadError;
|
||||||
use deno_graph::NpmResolvePkgReqsResult;
|
use deno_graph::NpmResolvePkgReqsResult;
|
||||||
use deno_npm::resolution::NpmResolutionError;
|
use deno_npm::resolution::NpmResolutionError;
|
||||||
use deno_package_json::PackageJsonDepValue;
|
use deno_package_json::PackageJsonDepValue;
|
||||||
use deno_path_util::url_to_file_path;
|
use deno_resolver::sloppy_imports::SloppyImportsResolutionMode;
|
||||||
|
use deno_resolver::sloppy_imports::SloppyImportsResolver;
|
||||||
use deno_runtime::colors;
|
use deno_runtime::colors;
|
||||||
use deno_runtime::deno_fs;
|
use deno_runtime::deno_fs;
|
||||||
use deno_runtime::deno_fs::FileSystem;
|
use deno_runtime::deno_fs::FileSystem;
|
||||||
|
@ -421,13 +422,16 @@ impl CjsResolutionStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type CliSloppyImportsResolver =
|
||||||
|
SloppyImportsResolver<SloppyImportsCachedFs>;
|
||||||
|
|
||||||
/// A resolver that takes care of resolution, taking into account loaded
|
/// A resolver that takes care of resolution, taking into account loaded
|
||||||
/// import map, JSX settings.
|
/// import map, JSX settings.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CliGraphResolver {
|
pub struct CliGraphResolver {
|
||||||
node_resolver: Option<Arc<CliNodeResolver>>,
|
node_resolver: Option<Arc<CliNodeResolver>>,
|
||||||
npm_resolver: Option<Arc<dyn CliNpmResolver>>,
|
npm_resolver: Option<Arc<dyn CliNpmResolver>>,
|
||||||
sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
|
||||||
workspace_resolver: Arc<WorkspaceResolver>,
|
workspace_resolver: Arc<WorkspaceResolver>,
|
||||||
maybe_default_jsx_import_source: Option<String>,
|
maybe_default_jsx_import_source: Option<String>,
|
||||||
maybe_default_jsx_import_source_types: Option<String>,
|
maybe_default_jsx_import_source_types: Option<String>,
|
||||||
|
@ -441,7 +445,7 @@ pub struct CliGraphResolver {
|
||||||
pub struct CliGraphResolverOptions<'a> {
|
pub struct CliGraphResolverOptions<'a> {
|
||||||
pub node_resolver: Option<Arc<CliNodeResolver>>,
|
pub node_resolver: Option<Arc<CliNodeResolver>>,
|
||||||
pub npm_resolver: Option<Arc<dyn CliNpmResolver>>,
|
pub npm_resolver: Option<Arc<dyn CliNpmResolver>>,
|
||||||
pub sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
pub sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
|
||||||
pub workspace_resolver: Arc<WorkspaceResolver>,
|
pub workspace_resolver: Arc<WorkspaceResolver>,
|
||||||
pub bare_node_builtins_enabled: bool,
|
pub bare_node_builtins_enabled: bool,
|
||||||
pub maybe_jsx_import_source_config: Option<JsxImportSourceConfig>,
|
pub maybe_jsx_import_source_config: Option<JsxImportSourceConfig>,
|
||||||
|
@ -565,7 +569,15 @@ impl Resolver for CliGraphResolver {
|
||||||
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
|
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
|
||||||
Ok(
|
Ok(
|
||||||
sloppy_imports_resolver
|
sloppy_imports_resolver
|
||||||
.resolve(&specifier, mode)
|
.resolve(
|
||||||
|
&specifier,
|
||||||
|
match mode {
|
||||||
|
ResolutionMode::Execution => {
|
||||||
|
SloppyImportsResolutionMode::Execution
|
||||||
|
}
|
||||||
|
ResolutionMode::Types => SloppyImportsResolutionMode::Types,
|
||||||
|
},
|
||||||
|
)
|
||||||
.map(|s| s.into_specifier())
|
.map(|s| s.into_specifier())
|
||||||
.unwrap_or(specifier),
|
.unwrap_or(specifier),
|
||||||
)
|
)
|
||||||
|
@ -847,96 +859,18 @@ impl<'a> deno_graph::source::NpmResolver for WorkerCliNpmGraphResolver<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum SloppyImportsFsEntry {
|
|
||||||
File,
|
|
||||||
Dir,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SloppyImportsFsEntry {
|
|
||||||
pub fn from_fs_stat(
|
|
||||||
stat: &deno_runtime::deno_io::fs::FsStat,
|
|
||||||
) -> Option<SloppyImportsFsEntry> {
|
|
||||||
if stat.is_file {
|
|
||||||
Some(SloppyImportsFsEntry::File)
|
|
||||||
} else if stat.is_directory {
|
|
||||||
Some(SloppyImportsFsEntry::Dir)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum SloppyImportsResolution {
|
|
||||||
/// Ex. `./file.js` to `./file.ts`
|
|
||||||
JsToTs(ModuleSpecifier),
|
|
||||||
/// Ex. `./file` to `./file.ts`
|
|
||||||
NoExtension(ModuleSpecifier),
|
|
||||||
/// Ex. `./dir` to `./dir/index.ts`
|
|
||||||
Directory(ModuleSpecifier),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SloppyImportsResolution {
|
|
||||||
pub fn as_specifier(&self) -> &ModuleSpecifier {
|
|
||||||
match self {
|
|
||||||
Self::JsToTs(specifier) => specifier,
|
|
||||||
Self::NoExtension(specifier) => specifier,
|
|
||||||
Self::Directory(specifier) => specifier,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_specifier(self) -> ModuleSpecifier {
|
|
||||||
match self {
|
|
||||||
Self::JsToTs(specifier) => specifier,
|
|
||||||
Self::NoExtension(specifier) => specifier,
|
|
||||||
Self::Directory(specifier) => specifier,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_suggestion_message(&self) -> String {
|
|
||||||
format!("Maybe {}", self.as_base_message())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_quick_fix_message(&self) -> String {
|
|
||||||
let message = self.as_base_message();
|
|
||||||
let mut chars = message.chars();
|
|
||||||
format!(
|
|
||||||
"{}{}.",
|
|
||||||
chars.next().unwrap().to_uppercase(),
|
|
||||||
chars.as_str()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_base_message(&self) -> String {
|
|
||||||
match self {
|
|
||||||
SloppyImportsResolution::JsToTs(specifier) => {
|
|
||||||
let media_type = MediaType::from_specifier(specifier);
|
|
||||||
format!("change the extension to '{}'", media_type.as_ts_extension())
|
|
||||||
}
|
|
||||||
SloppyImportsResolution::NoExtension(specifier) => {
|
|
||||||
let media_type = MediaType::from_specifier(specifier);
|
|
||||||
format!("add a '{}' extension", media_type.as_ts_extension())
|
|
||||||
}
|
|
||||||
SloppyImportsResolution::Directory(specifier) => {
|
|
||||||
let file_name = specifier
|
|
||||||
.path()
|
|
||||||
.rsplit_once('/')
|
|
||||||
.map(|(_, file_name)| file_name)
|
|
||||||
.unwrap_or(specifier.path());
|
|
||||||
format!("specify path to '{}' file in directory instead", file_name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SloppyImportsResolver {
|
pub struct SloppyImportsCachedFs {
|
||||||
fs: Arc<dyn FileSystem>,
|
fs: Arc<dyn deno_fs::FileSystem>,
|
||||||
cache: Option<DashMap<PathBuf, Option<SloppyImportsFsEntry>>>,
|
cache: Option<
|
||||||
|
DashMap<
|
||||||
|
PathBuf,
|
||||||
|
Option<deno_resolver::sloppy_imports::SloppyImportsFsEntry>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SloppyImportsResolver {
|
impl SloppyImportsCachedFs {
|
||||||
pub fn new(fs: Arc<dyn FileSystem>) -> Self {
|
pub fn new(fs: Arc<dyn FileSystem>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fs,
|
fs,
|
||||||
|
@ -947,409 +881,34 @@ impl SloppyImportsResolver {
|
||||||
pub fn new_without_stat_cache(fs: Arc<dyn FileSystem>) -> Self {
|
pub fn new_without_stat_cache(fs: Arc<dyn FileSystem>) -> Self {
|
||||||
Self { fs, cache: None }
|
Self { fs, cache: None }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resolve(
|
impl deno_resolver::sloppy_imports::SloppyImportResolverFs
|
||||||
|
for SloppyImportsCachedFs
|
||||||
|
{
|
||||||
|
fn stat_sync(
|
||||||
&self,
|
&self,
|
||||||
specifier: &ModuleSpecifier,
|
path: &Path,
|
||||||
mode: ResolutionMode,
|
) -> Option<deno_resolver::sloppy_imports::SloppyImportsFsEntry> {
|
||||||
) -> Option<SloppyImportsResolution> {
|
|
||||||
fn path_without_ext(
|
|
||||||
path: &Path,
|
|
||||||
media_type: MediaType,
|
|
||||||
) -> Option<Cow<str>> {
|
|
||||||
let old_path_str = path.to_string_lossy();
|
|
||||||
match media_type {
|
|
||||||
MediaType::Unknown => Some(old_path_str),
|
|
||||||
_ => old_path_str
|
|
||||||
.strip_suffix(media_type.as_ts_extension())
|
|
||||||
.map(|s| Cow::Owned(s.to_string())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn media_types_to_paths(
|
|
||||||
path_no_ext: &str,
|
|
||||||
original_media_type: MediaType,
|
|
||||||
probe_media_type_types: Vec<MediaType>,
|
|
||||||
reason: SloppyImportsResolutionReason,
|
|
||||||
) -> Vec<(PathBuf, SloppyImportsResolutionReason)> {
|
|
||||||
probe_media_type_types
|
|
||||||
.into_iter()
|
|
||||||
.filter(|media_type| *media_type != original_media_type)
|
|
||||||
.map(|media_type| {
|
|
||||||
(
|
|
||||||
PathBuf::from(format!(
|
|
||||||
"{}{}",
|
|
||||||
path_no_ext,
|
|
||||||
media_type.as_ts_extension()
|
|
||||||
)),
|
|
||||||
reason,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
if specifier.scheme() != "file" {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = url_to_file_path(specifier).ok()?;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
enum SloppyImportsResolutionReason {
|
|
||||||
JsToTs,
|
|
||||||
NoExtension,
|
|
||||||
Directory,
|
|
||||||
}
|
|
||||||
|
|
||||||
let probe_paths: Vec<(PathBuf, SloppyImportsResolutionReason)> =
|
|
||||||
match self.stat_sync(&path) {
|
|
||||||
Some(SloppyImportsFsEntry::File) => {
|
|
||||||
if mode.is_types() {
|
|
||||||
let media_type = MediaType::from_specifier(specifier);
|
|
||||||
// attempt to resolve the .d.ts file before the .js file
|
|
||||||
let probe_media_type_types = match media_type {
|
|
||||||
MediaType::JavaScript => {
|
|
||||||
vec![(MediaType::Dts), MediaType::JavaScript]
|
|
||||||
}
|
|
||||||
MediaType::Mjs => {
|
|
||||||
vec![MediaType::Dmts, MediaType::Dts, MediaType::Mjs]
|
|
||||||
}
|
|
||||||
MediaType::Cjs => {
|
|
||||||
vec![MediaType::Dcts, MediaType::Dts, MediaType::Cjs]
|
|
||||||
}
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
let path_no_ext = path_without_ext(&path, media_type)?;
|
|
||||||
media_types_to_paths(
|
|
||||||
&path_no_ext,
|
|
||||||
media_type,
|
|
||||||
probe_media_type_types,
|
|
||||||
SloppyImportsResolutionReason::JsToTs,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entry @ None | entry @ Some(SloppyImportsFsEntry::Dir) => {
|
|
||||||
let media_type = MediaType::from_specifier(specifier);
|
|
||||||
let probe_media_type_types = match media_type {
|
|
||||||
MediaType::JavaScript => (
|
|
||||||
if mode.is_types() {
|
|
||||||
vec![MediaType::TypeScript, MediaType::Tsx, MediaType::Dts]
|
|
||||||
} else {
|
|
||||||
vec![MediaType::TypeScript, MediaType::Tsx]
|
|
||||||
},
|
|
||||||
SloppyImportsResolutionReason::JsToTs,
|
|
||||||
),
|
|
||||||
MediaType::Jsx => {
|
|
||||||
(vec![MediaType::Tsx], SloppyImportsResolutionReason::JsToTs)
|
|
||||||
}
|
|
||||||
MediaType::Mjs => (
|
|
||||||
if mode.is_types() {
|
|
||||||
vec![MediaType::Mts, MediaType::Dmts, MediaType::Dts]
|
|
||||||
} else {
|
|
||||||
vec![MediaType::Mts]
|
|
||||||
},
|
|
||||||
SloppyImportsResolutionReason::JsToTs,
|
|
||||||
),
|
|
||||||
MediaType::Cjs => (
|
|
||||||
if mode.is_types() {
|
|
||||||
vec![MediaType::Cts, MediaType::Dcts, MediaType::Dts]
|
|
||||||
} else {
|
|
||||||
vec![MediaType::Cts]
|
|
||||||
},
|
|
||||||
SloppyImportsResolutionReason::JsToTs,
|
|
||||||
),
|
|
||||||
MediaType::TypeScript
|
|
||||||
| MediaType::Mts
|
|
||||||
| MediaType::Cts
|
|
||||||
| MediaType::Dts
|
|
||||||
| MediaType::Dmts
|
|
||||||
| MediaType::Dcts
|
|
||||||
| MediaType::Tsx
|
|
||||||
| MediaType::Json
|
|
||||||
| MediaType::Wasm
|
|
||||||
| MediaType::TsBuildInfo
|
|
||||||
| MediaType::SourceMap => {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
MediaType::Unknown => (
|
|
||||||
if mode.is_types() {
|
|
||||||
vec![
|
|
||||||
MediaType::TypeScript,
|
|
||||||
MediaType::Tsx,
|
|
||||||
MediaType::Mts,
|
|
||||||
MediaType::Dts,
|
|
||||||
MediaType::Dmts,
|
|
||||||
MediaType::Dcts,
|
|
||||||
MediaType::JavaScript,
|
|
||||||
MediaType::Jsx,
|
|
||||||
MediaType::Mjs,
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
vec![
|
|
||||||
MediaType::TypeScript,
|
|
||||||
MediaType::JavaScript,
|
|
||||||
MediaType::Tsx,
|
|
||||||
MediaType::Jsx,
|
|
||||||
MediaType::Mts,
|
|
||||||
MediaType::Mjs,
|
|
||||||
]
|
|
||||||
},
|
|
||||||
SloppyImportsResolutionReason::NoExtension,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
let mut probe_paths = match path_without_ext(&path, media_type) {
|
|
||||||
Some(path_no_ext) => media_types_to_paths(
|
|
||||||
&path_no_ext,
|
|
||||||
media_type,
|
|
||||||
probe_media_type_types.0,
|
|
||||||
probe_media_type_types.1,
|
|
||||||
),
|
|
||||||
None => vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
if matches!(entry, Some(SloppyImportsFsEntry::Dir)) {
|
|
||||||
// try to resolve at the index file
|
|
||||||
if mode.is_types() {
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.ts"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.mts"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.d.ts"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.d.mts"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.js"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.mjs"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.tsx"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.jsx"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.ts"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.mts"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.tsx"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.js"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.mjs"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
probe_paths.push((
|
|
||||||
path.join("index.jsx"),
|
|
||||||
SloppyImportsResolutionReason::Directory,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if probe_paths.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
probe_paths
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (probe_path, reason) in probe_paths {
|
|
||||||
if self.stat_sync(&probe_path) == Some(SloppyImportsFsEntry::File) {
|
|
||||||
if let Ok(specifier) = ModuleSpecifier::from_file_path(probe_path) {
|
|
||||||
match reason {
|
|
||||||
SloppyImportsResolutionReason::JsToTs => {
|
|
||||||
return Some(SloppyImportsResolution::JsToTs(specifier));
|
|
||||||
}
|
|
||||||
SloppyImportsResolutionReason::NoExtension => {
|
|
||||||
return Some(SloppyImportsResolution::NoExtension(specifier));
|
|
||||||
}
|
|
||||||
SloppyImportsResolutionReason::Directory => {
|
|
||||||
return Some(SloppyImportsResolution::Directory(specifier));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stat_sync(&self, path: &Path) -> Option<SloppyImportsFsEntry> {
|
|
||||||
if let Some(cache) = &self.cache {
|
if let Some(cache) = &self.cache {
|
||||||
if let Some(entry) = cache.get(path) {
|
if let Some(entry) = cache.get(path) {
|
||||||
return *entry;
|
return *entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry = self
|
let entry = self.fs.stat_sync(path).ok().and_then(|stat| {
|
||||||
.fs
|
if stat.is_file {
|
||||||
.stat_sync(path)
|
Some(deno_resolver::sloppy_imports::SloppyImportsFsEntry::File)
|
||||||
.ok()
|
} else if stat.is_directory {
|
||||||
.and_then(|stat| SloppyImportsFsEntry::from_fs_stat(&stat));
|
Some(deno_resolver::sloppy_imports::SloppyImportsFsEntry::Dir)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(cache) = &self.cache {
|
if let Some(cache) = &self.cache {
|
||||||
cache.insert(path.to_owned(), entry);
|
cache.insert(path.to_owned(), entry);
|
||||||
}
|
}
|
||||||
entry
|
entry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use test_util::TestContext;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unstable_sloppy_imports() {
|
|
||||||
fn resolve(specifier: &ModuleSpecifier) -> Option<SloppyImportsResolution> {
|
|
||||||
resolve_with_mode(specifier, ResolutionMode::Execution)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_types(
|
|
||||||
specifier: &ModuleSpecifier,
|
|
||||||
) -> Option<SloppyImportsResolution> {
|
|
||||||
resolve_with_mode(specifier, ResolutionMode::Types)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_with_mode(
|
|
||||||
specifier: &ModuleSpecifier,
|
|
||||||
mode: ResolutionMode,
|
|
||||||
) -> Option<SloppyImportsResolution> {
|
|
||||||
SloppyImportsResolver::new(Arc::new(deno_fs::RealFs))
|
|
||||||
.resolve(specifier, mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
let context = TestContext::default();
|
|
||||||
let temp_dir = context.temp_dir().path();
|
|
||||||
|
|
||||||
// scenarios like resolving ./example.js to ./example.ts
|
|
||||||
for (ext_from, ext_to) in [("js", "ts"), ("js", "tsx"), ("mjs", "mts")] {
|
|
||||||
let ts_file = temp_dir.join(format!("file.{}", ext_to));
|
|
||||||
ts_file.write("");
|
|
||||||
assert_eq!(resolve(&ts_file.url_file()), None);
|
|
||||||
assert_eq!(
|
|
||||||
resolve(
|
|
||||||
&temp_dir
|
|
||||||
.url_dir()
|
|
||||||
.join(&format!("file.{}", ext_from))
|
|
||||||
.unwrap()
|
|
||||||
),
|
|
||||||
Some(SloppyImportsResolution::JsToTs(ts_file.url_file())),
|
|
||||||
);
|
|
||||||
ts_file.remove_file();
|
|
||||||
}
|
|
||||||
|
|
||||||
// no extension scenarios
|
|
||||||
for ext in ["js", "ts", "js", "tsx", "jsx", "mjs", "mts"] {
|
|
||||||
let file = temp_dir.join(format!("file.{}", ext));
|
|
||||||
file.write("");
|
|
||||||
assert_eq!(
|
|
||||||
resolve(
|
|
||||||
&temp_dir
|
|
||||||
.url_dir()
|
|
||||||
.join("file") // no ext
|
|
||||||
.unwrap()
|
|
||||||
),
|
|
||||||
Some(SloppyImportsResolution::NoExtension(file.url_file()))
|
|
||||||
);
|
|
||||||
file.remove_file();
|
|
||||||
}
|
|
||||||
|
|
||||||
// .ts and .js exists, .js specified (goes to specified)
|
|
||||||
{
|
|
||||||
let ts_file = temp_dir.join("file.ts");
|
|
||||||
ts_file.write("");
|
|
||||||
let js_file = temp_dir.join("file.js");
|
|
||||||
js_file.write("");
|
|
||||||
assert_eq!(resolve(&js_file.url_file()), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
// only js exists, .js specified
|
|
||||||
{
|
|
||||||
let js_only_file = temp_dir.join("js_only.js");
|
|
||||||
js_only_file.write("");
|
|
||||||
assert_eq!(resolve(&js_only_file.url_file()), None);
|
|
||||||
assert_eq!(resolve_types(&js_only_file.url_file()), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolving a directory to an index file
|
|
||||||
{
|
|
||||||
let routes_dir = temp_dir.join("routes");
|
|
||||||
routes_dir.create_dir_all();
|
|
||||||
let index_file = routes_dir.join("index.ts");
|
|
||||||
index_file.write("");
|
|
||||||
assert_eq!(
|
|
||||||
resolve(&routes_dir.url_file()),
|
|
||||||
Some(SloppyImportsResolution::Directory(index_file.url_file())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// both a directory and a file with specifier is present
|
|
||||||
{
|
|
||||||
let api_dir = temp_dir.join("api");
|
|
||||||
api_dir.create_dir_all();
|
|
||||||
let bar_file = api_dir.join("bar.ts");
|
|
||||||
bar_file.write("");
|
|
||||||
let api_file = temp_dir.join("api.ts");
|
|
||||||
api_file.write("");
|
|
||||||
assert_eq!(
|
|
||||||
resolve(&api_dir.url_file()),
|
|
||||||
Some(SloppyImportsResolution::NoExtension(api_file.url_file())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sloppy_import_resolution_suggestion_message() {
|
|
||||||
// directory
|
|
||||||
assert_eq!(
|
|
||||||
SloppyImportsResolution::Directory(
|
|
||||||
ModuleSpecifier::parse("file:///dir/index.js").unwrap()
|
|
||||||
)
|
|
||||||
.as_suggestion_message(),
|
|
||||||
"Maybe specify path to 'index.js' file in directory instead"
|
|
||||||
);
|
|
||||||
// no ext
|
|
||||||
assert_eq!(
|
|
||||||
SloppyImportsResolution::NoExtension(
|
|
||||||
ModuleSpecifier::parse("file:///dir/index.mjs").unwrap()
|
|
||||||
)
|
|
||||||
.as_suggestion_message(),
|
|
||||||
"Maybe add a '.mjs' extension"
|
|
||||||
);
|
|
||||||
// js to ts
|
|
||||||
assert_eq!(
|
|
||||||
SloppyImportsResolution::JsToTs(
|
|
||||||
ModuleSpecifier::parse("file:///dir/index.mts").unwrap()
|
|
||||||
)
|
|
||||||
.as_suggestion_message(),
|
|
||||||
"Maybe change the extension to '.mts'"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ use deno_graph::ModuleGraph;
|
||||||
use deno_lint::diagnostic::LintDiagnostic;
|
use deno_lint::diagnostic::LintDiagnostic;
|
||||||
use deno_lint::rules::LintRule;
|
use deno_lint::rules::LintRule;
|
||||||
|
|
||||||
use crate::resolver::SloppyImportsResolver;
|
use crate::resolver::CliSloppyImportsResolver;
|
||||||
|
|
||||||
mod no_sloppy_imports;
|
mod no_sloppy_imports;
|
||||||
mod no_slow_types;
|
mod no_slow_types;
|
||||||
|
@ -144,13 +144,13 @@ impl ConfiguredRules {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LintRuleProvider {
|
pub struct LintRuleProvider {
|
||||||
sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
|
||||||
workspace_resolver: Option<Arc<WorkspaceResolver>>,
|
workspace_resolver: Option<Arc<WorkspaceResolver>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LintRuleProvider {
|
impl LintRuleProvider {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
|
||||||
workspace_resolver: Option<Arc<WorkspaceResolver>>,
|
workspace_resolver: Option<Arc<WorkspaceResolver>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -16,24 +16,25 @@ use deno_lint::diagnostic::LintDiagnosticRange;
|
||||||
use deno_lint::diagnostic::LintFix;
|
use deno_lint::diagnostic::LintFix;
|
||||||
use deno_lint::diagnostic::LintFixChange;
|
use deno_lint::diagnostic::LintFixChange;
|
||||||
use deno_lint::rules::LintRule;
|
use deno_lint::rules::LintRule;
|
||||||
|
use deno_resolver::sloppy_imports::SloppyImportsResolution;
|
||||||
|
use deno_resolver::sloppy_imports::SloppyImportsResolutionMode;
|
||||||
use text_lines::LineAndColumnIndex;
|
use text_lines::LineAndColumnIndex;
|
||||||
|
|
||||||
use crate::graph_util::CliJsrUrlProvider;
|
use crate::graph_util::CliJsrUrlProvider;
|
||||||
use crate::resolver::SloppyImportsResolution;
|
use crate::resolver::CliSloppyImportsResolver;
|
||||||
use crate::resolver::SloppyImportsResolver;
|
|
||||||
|
|
||||||
use super::ExtendedLintRule;
|
use super::ExtendedLintRule;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NoSloppyImportsRule {
|
pub struct NoSloppyImportsRule {
|
||||||
sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
|
||||||
// None for making printing out the lint rules easy
|
// None for making printing out the lint rules easy
|
||||||
workspace_resolver: Option<Arc<WorkspaceResolver>>,
|
workspace_resolver: Option<Arc<WorkspaceResolver>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NoSloppyImportsRule {
|
impl NoSloppyImportsRule {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sloppy_imports_resolver: Option<Arc<SloppyImportsResolver>>,
|
sloppy_imports_resolver: Option<Arc<CliSloppyImportsResolver>>,
|
||||||
workspace_resolver: Option<Arc<WorkspaceResolver>>,
|
workspace_resolver: Option<Arc<WorkspaceResolver>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
NoSloppyImportsRule {
|
NoSloppyImportsRule {
|
||||||
|
@ -172,7 +173,7 @@ impl LintRule for NoSloppyImportsRule {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SloppyImportCaptureResolver<'a> {
|
struct SloppyImportCaptureResolver<'a> {
|
||||||
workspace_resolver: &'a WorkspaceResolver,
|
workspace_resolver: &'a WorkspaceResolver,
|
||||||
sloppy_imports_resolver: &'a SloppyImportsResolver,
|
sloppy_imports_resolver: &'a CliSloppyImportsResolver,
|
||||||
captures: RefCell<HashMap<Range, SloppyImportsResolution>>,
|
captures: RefCell<HashMap<Range, SloppyImportsResolution>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +195,13 @@ impl<'a> deno_graph::source::Resolver for SloppyImportCaptureResolver<'a> {
|
||||||
}
|
}
|
||||||
| deno_config::workspace::MappedResolution::ImportMap {
|
| deno_config::workspace::MappedResolution::ImportMap {
|
||||||
specifier, ..
|
specifier, ..
|
||||||
} => match self.sloppy_imports_resolver.resolve(&specifier, mode) {
|
} => match self.sloppy_imports_resolver.resolve(
|
||||||
|
&specifier,
|
||||||
|
match mode {
|
||||||
|
ResolutionMode::Execution => SloppyImportsResolutionMode::Execution,
|
||||||
|
ResolutionMode::Types => SloppyImportsResolutionMode::Types,
|
||||||
|
},
|
||||||
|
) {
|
||||||
Some(res) => {
|
Some(res) => {
|
||||||
self
|
self
|
||||||
.captures
|
.captures
|
||||||
|
|
|
@ -43,7 +43,8 @@ use crate::cache::ParsedSourceCache;
|
||||||
use crate::factory::CliFactory;
|
use crate::factory::CliFactory;
|
||||||
use crate::graph_util::ModuleGraphCreator;
|
use crate::graph_util::ModuleGraphCreator;
|
||||||
use crate::http_util::HttpClient;
|
use crate::http_util::HttpClient;
|
||||||
use crate::resolver::SloppyImportsResolver;
|
use crate::resolver::CliSloppyImportsResolver;
|
||||||
|
use crate::resolver::SloppyImportsCachedFs;
|
||||||
use crate::tools::check::CheckOptions;
|
use crate::tools::check::CheckOptions;
|
||||||
use crate::tools::lint::collect_no_slow_type_diagnostics;
|
use crate::tools::lint::collect_no_slow_type_diagnostics;
|
||||||
use crate::tools::registry::diagnostics::PublishDiagnostic;
|
use crate::tools::registry::diagnostics::PublishDiagnostic;
|
||||||
|
@ -108,7 +109,9 @@ pub async fn publish(
|
||||||
}
|
}
|
||||||
let specifier_unfurler = Arc::new(SpecifierUnfurler::new(
|
let specifier_unfurler = Arc::new(SpecifierUnfurler::new(
|
||||||
if cli_options.unstable_sloppy_imports() {
|
if cli_options.unstable_sloppy_imports() {
|
||||||
Some(SloppyImportsResolver::new(cli_factory.fs().clone()))
|
Some(CliSloppyImportsResolver::new(SloppyImportsCachedFs::new(
|
||||||
|
cli_factory.fs().clone(),
|
||||||
|
)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,9 +12,10 @@ use deno_graph::DynamicTemplatePart;
|
||||||
use deno_graph::ParserModuleAnalyzer;
|
use deno_graph::ParserModuleAnalyzer;
|
||||||
use deno_graph::TypeScriptReference;
|
use deno_graph::TypeScriptReference;
|
||||||
use deno_package_json::PackageJsonDepValue;
|
use deno_package_json::PackageJsonDepValue;
|
||||||
|
use deno_resolver::sloppy_imports::SloppyImportsResolutionMode;
|
||||||
use deno_runtime::deno_node::is_builtin_node_module;
|
use deno_runtime::deno_node::is_builtin_node_module;
|
||||||
|
|
||||||
use crate::resolver::SloppyImportsResolver;
|
use crate::resolver::CliSloppyImportsResolver;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SpecifierUnfurlerDiagnostic {
|
pub enum SpecifierUnfurlerDiagnostic {
|
||||||
|
@ -42,14 +43,14 @@ impl SpecifierUnfurlerDiagnostic {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SpecifierUnfurler {
|
pub struct SpecifierUnfurler {
|
||||||
sloppy_imports_resolver: Option<SloppyImportsResolver>,
|
sloppy_imports_resolver: Option<CliSloppyImportsResolver>,
|
||||||
workspace_resolver: WorkspaceResolver,
|
workspace_resolver: WorkspaceResolver,
|
||||||
bare_node_builtins: bool,
|
bare_node_builtins: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpecifierUnfurler {
|
impl SpecifierUnfurler {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
sloppy_imports_resolver: Option<SloppyImportsResolver>,
|
sloppy_imports_resolver: Option<CliSloppyImportsResolver>,
|
||||||
workspace_resolver: WorkspaceResolver,
|
workspace_resolver: WorkspaceResolver,
|
||||||
bare_node_builtins: bool,
|
bare_node_builtins: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -179,7 +180,7 @@ impl SpecifierUnfurler {
|
||||||
let resolved =
|
let resolved =
|
||||||
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
|
if let Some(sloppy_imports_resolver) = &self.sloppy_imports_resolver {
|
||||||
sloppy_imports_resolver
|
sloppy_imports_resolver
|
||||||
.resolve(&resolved, deno_graph::source::ResolutionMode::Execution)
|
.resolve(&resolved, SloppyImportsResolutionMode::Execution)
|
||||||
.map(|res| res.into_specifier())
|
.map(|res| res.into_specifier())
|
||||||
.unwrap_or(resolved)
|
.unwrap_or(resolved)
|
||||||
} else {
|
} else {
|
||||||
|
@ -388,6 +389,8 @@ fn to_range(
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::resolver::SloppyImportsCachedFs;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use deno_ast::MediaType;
|
use deno_ast::MediaType;
|
||||||
use deno_ast::ModuleSpecifier;
|
use deno_ast::ModuleSpecifier;
|
||||||
|
@ -455,7 +458,9 @@ mod tests {
|
||||||
);
|
);
|
||||||
let fs = Arc::new(RealFs);
|
let fs = Arc::new(RealFs);
|
||||||
let unfurler = SpecifierUnfurler::new(
|
let unfurler = SpecifierUnfurler::new(
|
||||||
Some(SloppyImportsResolver::new(fs)),
|
Some(CliSloppyImportsResolver::new(SloppyImportsCachedFs::new(
|
||||||
|
fs,
|
||||||
|
))),
|
||||||
workspace_resolver,
|
workspace_resolver,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
24
resolvers/deno/Cargo.toml
Normal file
24
resolvers/deno/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "deno_resolver"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
readme = "README.md"
|
||||||
|
repository.workspace = true
|
||||||
|
description = "Deno resolution algorithm"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "lib.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
deno_media_type.workspace = true
|
||||||
|
deno_path_util.workspace = true
|
||||||
|
url.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
test_util.workspace = true
|
3
resolvers/deno/README.md
Normal file
3
resolvers/deno/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# deno_resolver
|
||||||
|
|
||||||
|
Deno resolution algorithm.
|
3
resolvers/deno/lib.rs
Normal file
3
resolvers/deno/lib.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
pub mod sloppy_imports;
|
511
resolvers/deno/sloppy_imports.rs
Normal file
511
resolvers/deno/sloppy_imports.rs
Normal file
|
@ -0,0 +1,511 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use deno_media_type::MediaType;
|
||||||
|
use deno_path_util::url_to_file_path;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum SloppyImportsFsEntry {
|
||||||
|
File,
|
||||||
|
Dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum SloppyImportsResolution {
|
||||||
|
/// Ex. `./file.js` to `./file.ts`
|
||||||
|
JsToTs(Url),
|
||||||
|
/// Ex. `./file` to `./file.ts`
|
||||||
|
NoExtension(Url),
|
||||||
|
/// Ex. `./dir` to `./dir/index.ts`
|
||||||
|
Directory(Url),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SloppyImportsResolution {
|
||||||
|
pub fn as_specifier(&self) -> &Url {
|
||||||
|
match self {
|
||||||
|
Self::JsToTs(specifier) => specifier,
|
||||||
|
Self::NoExtension(specifier) => specifier,
|
||||||
|
Self::Directory(specifier) => specifier,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_specifier(self) -> Url {
|
||||||
|
match self {
|
||||||
|
Self::JsToTs(specifier) => specifier,
|
||||||
|
Self::NoExtension(specifier) => specifier,
|
||||||
|
Self::Directory(specifier) => specifier,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_suggestion_message(&self) -> String {
|
||||||
|
format!("Maybe {}", self.as_base_message())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_quick_fix_message(&self) -> String {
|
||||||
|
let message = self.as_base_message();
|
||||||
|
let mut chars = message.chars();
|
||||||
|
format!(
|
||||||
|
"{}{}.",
|
||||||
|
chars.next().unwrap().to_uppercase(),
|
||||||
|
chars.as_str()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_base_message(&self) -> String {
|
||||||
|
match self {
|
||||||
|
SloppyImportsResolution::JsToTs(specifier) => {
|
||||||
|
let media_type = MediaType::from_specifier(specifier);
|
||||||
|
format!("change the extension to '{}'", media_type.as_ts_extension())
|
||||||
|
}
|
||||||
|
SloppyImportsResolution::NoExtension(specifier) => {
|
||||||
|
let media_type = MediaType::from_specifier(specifier);
|
||||||
|
format!("add a '{}' extension", media_type.as_ts_extension())
|
||||||
|
}
|
||||||
|
SloppyImportsResolution::Directory(specifier) => {
|
||||||
|
let file_name = specifier
|
||||||
|
.path()
|
||||||
|
.rsplit_once('/')
|
||||||
|
.map(|(_, file_name)| file_name)
|
||||||
|
.unwrap_or(specifier.path());
|
||||||
|
format!("specify path to '{}' file in directory instead", file_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of resolution currently being done.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum SloppyImportsResolutionMode {
|
||||||
|
/// Resolving for code that will be executed.
|
||||||
|
Execution,
|
||||||
|
/// Resolving for code that will be used for type information.
|
||||||
|
Types,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SloppyImportsResolutionMode {
|
||||||
|
pub fn is_types(&self) -> bool {
|
||||||
|
*self == SloppyImportsResolutionMode::Types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait SloppyImportResolverFs {
|
||||||
|
fn stat_sync(&self, path: &Path) -> Option<SloppyImportsFsEntry>;
|
||||||
|
|
||||||
|
fn is_file(&self, path: &Path) -> bool {
|
||||||
|
self.stat_sync(path) == Some(SloppyImportsFsEntry::File)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SloppyImportsResolver<Fs: SloppyImportResolverFs> {
|
||||||
|
fs: Fs,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Fs: SloppyImportResolverFs> SloppyImportsResolver<Fs> {
|
||||||
|
pub fn new(fs: Fs) -> Self {
|
||||||
|
Self { fs }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(
|
||||||
|
&self,
|
||||||
|
specifier: &Url,
|
||||||
|
mode: SloppyImportsResolutionMode,
|
||||||
|
) -> Option<SloppyImportsResolution> {
|
||||||
|
fn path_without_ext(
|
||||||
|
path: &Path,
|
||||||
|
media_type: MediaType,
|
||||||
|
) -> Option<Cow<str>> {
|
||||||
|
let old_path_str = path.to_string_lossy();
|
||||||
|
match media_type {
|
||||||
|
MediaType::Unknown => Some(old_path_str),
|
||||||
|
_ => old_path_str
|
||||||
|
.strip_suffix(media_type.as_ts_extension())
|
||||||
|
.map(|s| Cow::Owned(s.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn media_types_to_paths(
|
||||||
|
path_no_ext: &str,
|
||||||
|
original_media_type: MediaType,
|
||||||
|
probe_media_type_types: Vec<MediaType>,
|
||||||
|
reason: SloppyImportsResolutionReason,
|
||||||
|
) -> Vec<(PathBuf, SloppyImportsResolutionReason)> {
|
||||||
|
probe_media_type_types
|
||||||
|
.into_iter()
|
||||||
|
.filter(|media_type| *media_type != original_media_type)
|
||||||
|
.map(|media_type| {
|
||||||
|
(
|
||||||
|
PathBuf::from(format!(
|
||||||
|
"{}{}",
|
||||||
|
path_no_ext,
|
||||||
|
media_type.as_ts_extension()
|
||||||
|
)),
|
||||||
|
reason,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
if specifier.scheme() != "file" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = url_to_file_path(specifier).ok()?;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum SloppyImportsResolutionReason {
|
||||||
|
JsToTs,
|
||||||
|
NoExtension,
|
||||||
|
Directory,
|
||||||
|
}
|
||||||
|
|
||||||
|
let probe_paths: Vec<(PathBuf, SloppyImportsResolutionReason)> =
|
||||||
|
match self.fs.stat_sync(&path) {
|
||||||
|
Some(SloppyImportsFsEntry::File) => {
|
||||||
|
if mode.is_types() {
|
||||||
|
let media_type = MediaType::from_specifier(specifier);
|
||||||
|
// attempt to resolve the .d.ts file before the .js file
|
||||||
|
let probe_media_type_types = match media_type {
|
||||||
|
MediaType::JavaScript => {
|
||||||
|
vec![(MediaType::Dts), MediaType::JavaScript]
|
||||||
|
}
|
||||||
|
MediaType::Mjs => {
|
||||||
|
vec![MediaType::Dmts, MediaType::Dts, MediaType::Mjs]
|
||||||
|
}
|
||||||
|
MediaType::Cjs => {
|
||||||
|
vec![MediaType::Dcts, MediaType::Dts, MediaType::Cjs]
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
let path_no_ext = path_without_ext(&path, media_type)?;
|
||||||
|
media_types_to_paths(
|
||||||
|
&path_no_ext,
|
||||||
|
media_type,
|
||||||
|
probe_media_type_types,
|
||||||
|
SloppyImportsResolutionReason::JsToTs,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry @ None | entry @ Some(SloppyImportsFsEntry::Dir) => {
|
||||||
|
let media_type = MediaType::from_specifier(specifier);
|
||||||
|
let probe_media_type_types = match media_type {
|
||||||
|
MediaType::JavaScript => (
|
||||||
|
if mode.is_types() {
|
||||||
|
vec![MediaType::TypeScript, MediaType::Tsx, MediaType::Dts]
|
||||||
|
} else {
|
||||||
|
vec![MediaType::TypeScript, MediaType::Tsx]
|
||||||
|
},
|
||||||
|
SloppyImportsResolutionReason::JsToTs,
|
||||||
|
),
|
||||||
|
MediaType::Jsx => {
|
||||||
|
(vec![MediaType::Tsx], SloppyImportsResolutionReason::JsToTs)
|
||||||
|
}
|
||||||
|
MediaType::Mjs => (
|
||||||
|
if mode.is_types() {
|
||||||
|
vec![MediaType::Mts, MediaType::Dmts, MediaType::Dts]
|
||||||
|
} else {
|
||||||
|
vec![MediaType::Mts]
|
||||||
|
},
|
||||||
|
SloppyImportsResolutionReason::JsToTs,
|
||||||
|
),
|
||||||
|
MediaType::Cjs => (
|
||||||
|
if mode.is_types() {
|
||||||
|
vec![MediaType::Cts, MediaType::Dcts, MediaType::Dts]
|
||||||
|
} else {
|
||||||
|
vec![MediaType::Cts]
|
||||||
|
},
|
||||||
|
SloppyImportsResolutionReason::JsToTs,
|
||||||
|
),
|
||||||
|
MediaType::TypeScript
|
||||||
|
| MediaType::Mts
|
||||||
|
| MediaType::Cts
|
||||||
|
| MediaType::Dts
|
||||||
|
| MediaType::Dmts
|
||||||
|
| MediaType::Dcts
|
||||||
|
| MediaType::Tsx
|
||||||
|
| MediaType::Json
|
||||||
|
| MediaType::Wasm
|
||||||
|
| MediaType::TsBuildInfo
|
||||||
|
| MediaType::SourceMap => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
MediaType::Unknown => (
|
||||||
|
if mode.is_types() {
|
||||||
|
vec![
|
||||||
|
MediaType::TypeScript,
|
||||||
|
MediaType::Tsx,
|
||||||
|
MediaType::Mts,
|
||||||
|
MediaType::Dts,
|
||||||
|
MediaType::Dmts,
|
||||||
|
MediaType::Dcts,
|
||||||
|
MediaType::JavaScript,
|
||||||
|
MediaType::Jsx,
|
||||||
|
MediaType::Mjs,
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
MediaType::TypeScript,
|
||||||
|
MediaType::JavaScript,
|
||||||
|
MediaType::Tsx,
|
||||||
|
MediaType::Jsx,
|
||||||
|
MediaType::Mts,
|
||||||
|
MediaType::Mjs,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
SloppyImportsResolutionReason::NoExtension,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let mut probe_paths = match path_without_ext(&path, media_type) {
|
||||||
|
Some(path_no_ext) => media_types_to_paths(
|
||||||
|
&path_no_ext,
|
||||||
|
media_type,
|
||||||
|
probe_media_type_types.0,
|
||||||
|
probe_media_type_types.1,
|
||||||
|
),
|
||||||
|
None => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
if matches!(entry, Some(SloppyImportsFsEntry::Dir)) {
|
||||||
|
// try to resolve at the index file
|
||||||
|
if mode.is_types() {
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.ts"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.mts"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.d.ts"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.d.mts"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.js"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.mjs"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.tsx"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.jsx"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.ts"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.mts"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.tsx"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.js"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.mjs"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
probe_paths.push((
|
||||||
|
path.join("index.jsx"),
|
||||||
|
SloppyImportsResolutionReason::Directory,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if probe_paths.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
probe_paths
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (probe_path, reason) in probe_paths {
|
||||||
|
if self.fs.is_file(&probe_path) {
|
||||||
|
if let Ok(specifier) = Url::from_file_path(probe_path) {
|
||||||
|
match reason {
|
||||||
|
SloppyImportsResolutionReason::JsToTs => {
|
||||||
|
return Some(SloppyImportsResolution::JsToTs(specifier));
|
||||||
|
}
|
||||||
|
SloppyImportsResolutionReason::NoExtension => {
|
||||||
|
return Some(SloppyImportsResolution::NoExtension(specifier));
|
||||||
|
}
|
||||||
|
SloppyImportsResolutionReason::Directory => {
|
||||||
|
return Some(SloppyImportsResolution::Directory(specifier));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use test_util::TestContext;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unstable_sloppy_imports() {
|
||||||
|
fn resolve(specifier: &Url) -> Option<SloppyImportsResolution> {
|
||||||
|
resolve_with_mode(specifier, SloppyImportsResolutionMode::Execution)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_types(specifier: &Url) -> Option<SloppyImportsResolution> {
|
||||||
|
resolve_with_mode(specifier, SloppyImportsResolutionMode::Types)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_with_mode(
|
||||||
|
specifier: &Url,
|
||||||
|
mode: SloppyImportsResolutionMode,
|
||||||
|
) -> Option<SloppyImportsResolution> {
|
||||||
|
struct RealSloppyImportsResolverFs;
|
||||||
|
impl SloppyImportResolverFs for RealSloppyImportsResolverFs {
|
||||||
|
fn stat_sync(&self, path: &Path) -> Option<SloppyImportsFsEntry> {
|
||||||
|
let stat = std::fs::metadata(path).ok()?;
|
||||||
|
if stat.is_dir() {
|
||||||
|
Some(SloppyImportsFsEntry::Dir)
|
||||||
|
} else if stat.is_file() {
|
||||||
|
Some(SloppyImportsFsEntry::File)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SloppyImportsResolver::new(RealSloppyImportsResolverFs)
|
||||||
|
.resolve(specifier, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = TestContext::default();
|
||||||
|
let temp_dir = context.temp_dir().path();
|
||||||
|
|
||||||
|
// scenarios like resolving ./example.js to ./example.ts
|
||||||
|
for (ext_from, ext_to) in [("js", "ts"), ("js", "tsx"), ("mjs", "mts")] {
|
||||||
|
let ts_file = temp_dir.join(format!("file.{}", ext_to));
|
||||||
|
ts_file.write("");
|
||||||
|
assert_eq!(resolve(&ts_file.url_file()), None);
|
||||||
|
assert_eq!(
|
||||||
|
resolve(
|
||||||
|
&temp_dir
|
||||||
|
.url_dir()
|
||||||
|
.join(&format!("file.{}", ext_from))
|
||||||
|
.unwrap()
|
||||||
|
),
|
||||||
|
Some(SloppyImportsResolution::JsToTs(ts_file.url_file())),
|
||||||
|
);
|
||||||
|
ts_file.remove_file();
|
||||||
|
}
|
||||||
|
|
||||||
|
// no extension scenarios
|
||||||
|
for ext in ["js", "ts", "js", "tsx", "jsx", "mjs", "mts"] {
|
||||||
|
let file = temp_dir.join(format!("file.{}", ext));
|
||||||
|
file.write("");
|
||||||
|
assert_eq!(
|
||||||
|
resolve(
|
||||||
|
&temp_dir
|
||||||
|
.url_dir()
|
||||||
|
.join("file") // no ext
|
||||||
|
.unwrap()
|
||||||
|
),
|
||||||
|
Some(SloppyImportsResolution::NoExtension(file.url_file()))
|
||||||
|
);
|
||||||
|
file.remove_file();
|
||||||
|
}
|
||||||
|
|
||||||
|
// .ts and .js exists, .js specified (goes to specified)
|
||||||
|
{
|
||||||
|
let ts_file = temp_dir.join("file.ts");
|
||||||
|
ts_file.write("");
|
||||||
|
let js_file = temp_dir.join("file.js");
|
||||||
|
js_file.write("");
|
||||||
|
assert_eq!(resolve(&js_file.url_file()), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// only js exists, .js specified
|
||||||
|
{
|
||||||
|
let js_only_file = temp_dir.join("js_only.js");
|
||||||
|
js_only_file.write("");
|
||||||
|
assert_eq!(resolve(&js_only_file.url_file()), None);
|
||||||
|
assert_eq!(resolve_types(&js_only_file.url_file()), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolving a directory to an index file
|
||||||
|
{
|
||||||
|
let routes_dir = temp_dir.join("routes");
|
||||||
|
routes_dir.create_dir_all();
|
||||||
|
let index_file = routes_dir.join("index.ts");
|
||||||
|
index_file.write("");
|
||||||
|
assert_eq!(
|
||||||
|
resolve(&routes_dir.url_file()),
|
||||||
|
Some(SloppyImportsResolution::Directory(index_file.url_file())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// both a directory and a file with specifier is present
|
||||||
|
{
|
||||||
|
let api_dir = temp_dir.join("api");
|
||||||
|
api_dir.create_dir_all();
|
||||||
|
let bar_file = api_dir.join("bar.ts");
|
||||||
|
bar_file.write("");
|
||||||
|
let api_file = temp_dir.join("api.ts");
|
||||||
|
api_file.write("");
|
||||||
|
assert_eq!(
|
||||||
|
resolve(&api_dir.url_file()),
|
||||||
|
Some(SloppyImportsResolution::NoExtension(api_file.url_file())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sloppy_import_resolution_suggestion_message() {
|
||||||
|
// directory
|
||||||
|
assert_eq!(
|
||||||
|
SloppyImportsResolution::Directory(
|
||||||
|
Url::parse("file:///dir/index.js").unwrap()
|
||||||
|
)
|
||||||
|
.as_suggestion_message(),
|
||||||
|
"Maybe specify path to 'index.js' file in directory instead"
|
||||||
|
);
|
||||||
|
// no ext
|
||||||
|
assert_eq!(
|
||||||
|
SloppyImportsResolution::NoExtension(
|
||||||
|
Url::parse("file:///dir/index.mjs").unwrap()
|
||||||
|
)
|
||||||
|
.as_suggestion_message(),
|
||||||
|
"Maybe add a '.mjs' extension"
|
||||||
|
);
|
||||||
|
// js to ts
|
||||||
|
assert_eq!(
|
||||||
|
SloppyImportsResolution::JsToTs(
|
||||||
|
Url::parse("file:///dir/index.mts").unwrap()
|
||||||
|
)
|
||||||
|
.as_suggestion_message(),
|
||||||
|
"Maybe change the extension to '.mts'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue