mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
feat(compile): support discovering modules for more dynamic arguments (#21381)
This PR causes Deno to include more files in the graph based on how a template literal looks that's provided to a dynamic import: ```ts const file = await import(`./dir/${expr}`); ``` In this case, it will search the `dir` directory and descendant directories for any .js/jsx/etc modules and include them in the graph. To opt out of this behaviour, move the template literal to a separate line: ```ts const specifier = `./dir/${expr}` const file = await import(specifier); ```
This commit is contained in:
parent
d8e8497eb3
commit
a1d823e27d
20 changed files with 327 additions and 42 deletions
21
Cargo.lock
generated
21
Cargo.lock
generated
|
@ -1139,9 +1139,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_doc"
|
name = "deno_doc"
|
||||||
version = "0.73.3"
|
version = "0.73.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "729bacea0b5a1b2406f70c105ba82354e0f4bd99c51ede4fd29b15ce750dcdd4"
|
checksum = "76d4d2960ce5c7af64e57ec4e7fd99023bd1ab664cedc4ec267db1d294b6002a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
@ -1162,9 +1162,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_emit"
|
name = "deno_emit"
|
||||||
version = "0.31.4"
|
version = "0.31.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8910a6da498d0eb2a28d9ea613c47291a86377a85b3771dd90d624004814aeb"
|
checksum = "5b98917905e9be9740722f89c3b2d3a963beaed8a05dce58e642947d0f6aa17d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
|
@ -1230,9 +1230,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_graph"
|
name = "deno_graph"
|
||||||
version = "0.61.1"
|
version = "0.61.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "076c0b611c10901456b78c837408b9c40fe0c3602e767307d986f46f0cc56b51"
|
checksum = "332e6a1930b3266dc848edeaf1426bbbd3ddca25c5e107e70823efb1b3ce68be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
@ -1242,6 +1242,7 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"import_map",
|
"import_map",
|
||||||
"indexmap 2.0.2",
|
"indexmap 2.0.2",
|
||||||
|
"log",
|
||||||
"monch",
|
"monch",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot 0.12.1",
|
"parking_lot 0.12.1",
|
||||||
|
@ -2203,9 +2204,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eszip"
|
name = "eszip"
|
||||||
version = "0.55.4"
|
version = "0.55.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0535cf1ea8c46379c39d791dc87e7104e90b3730829ee6d8888285eab22fa69"
|
checksum = "455c055f28756fc7ba0d64506e6167b91878f3a39854271d7d63b577deb8b45d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.4",
|
"base64 0.21.4",
|
||||||
|
@ -2854,9 +2855,9 @@ checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "import_map"
|
name = "import_map"
|
||||||
version = "0.17.0"
|
version = "0.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e5bf51a0adfdc08afcb9e5a1c8f8c804227ec50d493c65e57e6d117d594bd1b"
|
checksum = "0ecd467768fe83c2860e70e5de5297a7366a230ff53e1da2158bdac2384cd39d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -57,16 +57,16 @@ deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "dep_gra
|
||||||
deno_cache_dir = "=0.6.1"
|
deno_cache_dir = "=0.6.1"
|
||||||
deno_config = "=0.6.5"
|
deno_config = "=0.6.5"
|
||||||
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
|
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
|
||||||
deno_doc = { version = "=0.73.3", features = ["html"] }
|
deno_doc = { version = "=0.73.5", features = ["html"] }
|
||||||
deno_emit = "=0.31.4"
|
deno_emit = "=0.31.5"
|
||||||
deno_graph = "=0.61.1"
|
deno_graph = "=0.61.5"
|
||||||
deno_lint = { version = "=0.52.2", features = ["docs"] }
|
deno_lint = { version = "=0.52.2", features = ["docs"] }
|
||||||
deno_lockfile.workspace = true
|
deno_lockfile.workspace = true
|
||||||
deno_npm = "0.15.2"
|
deno_npm = "0.15.2"
|
||||||
deno_runtime = { workspace = true, features = ["dont_create_runtime_snapshot", "exclude_runtime_main_js", "include_js_files_for_snapshotting"] }
|
deno_runtime = { workspace = true, features = ["dont_create_runtime_snapshot", "exclude_runtime_main_js", "include_js_files_for_snapshotting"] }
|
||||||
deno_semver = "0.5.1"
|
deno_semver = "0.5.1"
|
||||||
deno_task_shell = "=0.14.0"
|
deno_task_shell = "=0.14.0"
|
||||||
eszip = "=0.55.4"
|
eszip = "=0.55.5"
|
||||||
napi_sym.workspace = true
|
napi_sym.workspace = true
|
||||||
|
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
@ -100,7 +100,7 @@ glob = "0.3.1"
|
||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
http.workspace = true
|
http.workspace = true
|
||||||
hyper.workspace = true
|
hyper.workspace = true
|
||||||
import_map = { version = "=0.17.0", features = ["ext"] }
|
import_map = { version = "=0.18.0", features = ["ext"] }
|
||||||
indexmap.workspace = true
|
indexmap.workspace = true
|
||||||
jsonc-parser = { version = "=0.23.0", features = ["serde"] }
|
jsonc-parser = { version = "=0.23.0", features = ["serde"] }
|
||||||
lazy-regex.workspace = true
|
lazy-regex.workspace = true
|
||||||
|
|
|
@ -513,6 +513,7 @@ impl CliFactory {
|
||||||
.get_or_try_init_async(async {
|
.get_or_try_init_async(async {
|
||||||
Ok(Arc::new(ModuleGraphBuilder::new(
|
Ok(Arc::new(ModuleGraphBuilder::new(
|
||||||
self.options.clone(),
|
self.options.clone(),
|
||||||
|
self.fs().clone(),
|
||||||
self.resolver().await?.clone(),
|
self.resolver().await?.clone(),
|
||||||
self.npm_resolver().await?.clone(),
|
self.npm_resolver().await?.clone(),
|
||||||
self.module_info_cache()?.clone(),
|
self.module_info_cache()?.clone(),
|
||||||
|
@ -556,6 +557,7 @@ impl CliFactory {
|
||||||
.get_or_try_init_async(async {
|
.get_or_try_init_async(async {
|
||||||
Ok(Arc::new(ModuleLoadPreparer::new(
|
Ok(Arc::new(ModuleLoadPreparer::new(
|
||||||
self.options.clone(),
|
self.options.clone(),
|
||||||
|
self.fs().clone(),
|
||||||
self.graph_container().clone(),
|
self.graph_container().clone(),
|
||||||
self.maybe_lockfile().clone(),
|
self.maybe_lockfile().clone(),
|
||||||
self.maybe_file_watcher_reporter().clone(),
|
self.maybe_file_watcher_reporter().clone(),
|
||||||
|
|
|
@ -35,6 +35,7 @@ use deno_graph::ModuleGraph;
|
||||||
use deno_graph::ModuleGraphError;
|
use deno_graph::ModuleGraphError;
|
||||||
use deno_graph::ResolutionError;
|
use deno_graph::ResolutionError;
|
||||||
use deno_graph::SpecifierError;
|
use deno_graph::SpecifierError;
|
||||||
|
use deno_runtime::deno_fs::FileSystem;
|
||||||
use deno_runtime::deno_node;
|
use deno_runtime::deno_node;
|
||||||
use deno_runtime::permissions::PermissionsContainer;
|
use deno_runtime::permissions::PermissionsContainer;
|
||||||
use deno_semver::package::PackageNv;
|
use deno_semver::package::PackageNv;
|
||||||
|
@ -116,12 +117,8 @@ pub fn graph_valid(
|
||||||
|
|
||||||
if let Some(range) = error.maybe_range() {
|
if let Some(range) = error.maybe_range() {
|
||||||
if !is_root && !range.specifier.as_str().contains("/$deno$eval") {
|
if !is_root && !range.specifier.as_str().contains("/$deno$eval") {
|
||||||
message.push_str(&format!(
|
message.push_str("\n at ");
|
||||||
"\n at {}:{}:{}",
|
message.push_str(&format_range_with_colors(range));
|
||||||
colors::cyan(range.specifier.as_str()),
|
|
||||||
colors::yellow(&(range.start.line + 1).to_string()),
|
|
||||||
colors::yellow(&(range.start.character + 1).to_string())
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,6 +191,7 @@ pub struct CreateGraphOptions<'a> {
|
||||||
|
|
||||||
pub struct ModuleGraphBuilder {
|
pub struct ModuleGraphBuilder {
|
||||||
options: Arc<CliOptions>,
|
options: Arc<CliOptions>,
|
||||||
|
fs: Arc<dyn FileSystem>,
|
||||||
resolver: Arc<CliGraphResolver>,
|
resolver: Arc<CliGraphResolver>,
|
||||||
npm_resolver: Arc<dyn CliNpmResolver>,
|
npm_resolver: Arc<dyn CliNpmResolver>,
|
||||||
module_info_cache: Arc<ModuleInfoCache>,
|
module_info_cache: Arc<ModuleInfoCache>,
|
||||||
|
@ -210,6 +208,7 @@ impl ModuleGraphBuilder {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
options: Arc<CliOptions>,
|
options: Arc<CliOptions>,
|
||||||
|
fs: Arc<dyn FileSystem>,
|
||||||
resolver: Arc<CliGraphResolver>,
|
resolver: Arc<CliGraphResolver>,
|
||||||
npm_resolver: Arc<dyn CliNpmResolver>,
|
npm_resolver: Arc<dyn CliNpmResolver>,
|
||||||
module_info_cache: Arc<ModuleInfoCache>,
|
module_info_cache: Arc<ModuleInfoCache>,
|
||||||
|
@ -223,6 +222,7 @@ impl ModuleGraphBuilder {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
options,
|
options,
|
||||||
|
fs,
|
||||||
resolver,
|
resolver,
|
||||||
npm_resolver,
|
npm_resolver,
|
||||||
module_info_cache,
|
module_info_cache,
|
||||||
|
@ -295,6 +295,7 @@ impl ModuleGraphBuilder {
|
||||||
is_dynamic: false,
|
is_dynamic: false,
|
||||||
imports: maybe_imports,
|
imports: maybe_imports,
|
||||||
resolver: Some(graph_resolver),
|
resolver: Some(graph_resolver),
|
||||||
|
file_system: Some(&DenoGraphFsAdapter(self.fs.as_ref())),
|
||||||
npm_resolver: Some(graph_npm_resolver),
|
npm_resolver: Some(graph_npm_resolver),
|
||||||
module_analyzer: Some(options.analyzer),
|
module_analyzer: Some(options.analyzer),
|
||||||
reporter: maybe_file_watcher_reporter,
|
reporter: maybe_file_watcher_reporter,
|
||||||
|
@ -344,6 +345,7 @@ impl ModuleGraphBuilder {
|
||||||
deno_graph::BuildOptions {
|
deno_graph::BuildOptions {
|
||||||
is_dynamic: false,
|
is_dynamic: false,
|
||||||
imports: maybe_imports,
|
imports: maybe_imports,
|
||||||
|
file_system: Some(&DenoGraphFsAdapter(self.fs.as_ref())),
|
||||||
resolver: Some(graph_resolver),
|
resolver: Some(graph_resolver),
|
||||||
npm_resolver: Some(graph_npm_resolver),
|
npm_resolver: Some(graph_npm_resolver),
|
||||||
module_analyzer: Some(&analyzer),
|
module_analyzer: Some(&analyzer),
|
||||||
|
@ -770,6 +772,80 @@ fn workspace_member_config_try_into_workspace_member(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct DenoGraphFsAdapter<'a>(
|
||||||
|
pub &'a dyn deno_runtime::deno_fs::FileSystem,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<'a> deno_graph::source::FileSystem for DenoGraphFsAdapter<'a> {
|
||||||
|
fn read_dir(
|
||||||
|
&self,
|
||||||
|
dir_url: &deno_graph::ModuleSpecifier,
|
||||||
|
) -> Vec<deno_graph::source::DirEntry> {
|
||||||
|
use deno_core::anyhow;
|
||||||
|
use deno_graph::source::DirEntry;
|
||||||
|
use deno_graph::source::DirEntryKind;
|
||||||
|
|
||||||
|
let dir_path = match dir_url.to_file_path() {
|
||||||
|
Ok(path) => path,
|
||||||
|
// ignore, treat as non-analyzable
|
||||||
|
Err(()) => return vec![],
|
||||||
|
};
|
||||||
|
let entries = match self.0.read_dir_sync(&dir_path) {
|
||||||
|
Ok(dir) => dir,
|
||||||
|
Err(err)
|
||||||
|
if matches!(
|
||||||
|
err.kind(),
|
||||||
|
std::io::ErrorKind::PermissionDenied | std::io::ErrorKind::NotFound
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
return vec![DirEntry {
|
||||||
|
kind: DirEntryKind::Error(
|
||||||
|
anyhow::Error::from(err)
|
||||||
|
.context("Failed to read directory.".to_string()),
|
||||||
|
),
|
||||||
|
url: dir_url.clone(),
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut dir_entries = Vec::with_capacity(entries.len());
|
||||||
|
for entry in entries {
|
||||||
|
let entry_path = dir_path.join(&entry.name);
|
||||||
|
dir_entries.push(if entry.is_directory {
|
||||||
|
DirEntry {
|
||||||
|
kind: DirEntryKind::Dir,
|
||||||
|
url: ModuleSpecifier::from_directory_path(&entry_path).unwrap(),
|
||||||
|
}
|
||||||
|
} else if entry.is_file {
|
||||||
|
DirEntry {
|
||||||
|
kind: DirEntryKind::File,
|
||||||
|
url: ModuleSpecifier::from_file_path(&entry_path).unwrap(),
|
||||||
|
}
|
||||||
|
} else if entry.is_symlink {
|
||||||
|
DirEntry {
|
||||||
|
kind: DirEntryKind::Symlink,
|
||||||
|
url: ModuleSpecifier::from_file_path(&entry_path).unwrap(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dir_entries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_range_with_colors(range: &deno_graph::Range) -> String {
|
||||||
|
format!(
|
||||||
|
"{}:{}:{}",
|
||||||
|
colors::cyan(range.specifier.as_str()),
|
||||||
|
colors::yellow(&(range.start.line + 1).to_string()),
|
||||||
|
colors::yellow(&(range.start.character + 1).to_string())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
|
@ -1743,12 +1743,17 @@ fn analyze_module(
|
||||||
) -> ModuleResult {
|
) -> ModuleResult {
|
||||||
match parsed_source_result {
|
match parsed_source_result {
|
||||||
Ok(parsed_source) => Ok(deno_graph::parse_module_from_ast(
|
Ok(parsed_source) => Ok(deno_graph::parse_module_from_ast(
|
||||||
deno_graph::GraphKind::All,
|
deno_graph::ParseModuleFromAstOptions {
|
||||||
specifier,
|
graph_kind: deno_graph::GraphKind::All,
|
||||||
maybe_headers,
|
specifier,
|
||||||
parsed_source,
|
maybe_headers,
|
||||||
Some(resolver),
|
parsed_source,
|
||||||
Some(npm_resolver),
|
// use a null file system because there's no need to bother resolving
|
||||||
|
// dynamic imports like import(`./dir/${something}`) in the LSP
|
||||||
|
file_system: &deno_graph::source::NullFileSystem,
|
||||||
|
maybe_resolver: Some(resolver),
|
||||||
|
maybe_npm_resolver: Some(npm_resolver),
|
||||||
|
},
|
||||||
)),
|
)),
|
||||||
Err(err) => Err(deno_graph::ModuleGraphError::ModuleError(
|
Err(err) => Err(deno_graph::ModuleGraphError::ModuleError(
|
||||||
deno_graph::ModuleError::ParseErr(specifier.clone(), err.clone()),
|
deno_graph::ModuleError::ParseErr(specifier.clone(), err.clone()),
|
||||||
|
|
|
@ -9,6 +9,7 @@ use crate::emit::Emitter;
|
||||||
use crate::graph_util::graph_lock_or_exit;
|
use crate::graph_util::graph_lock_or_exit;
|
||||||
use crate::graph_util::graph_valid_with_cli_options;
|
use crate::graph_util::graph_valid_with_cli_options;
|
||||||
use crate::graph_util::workspace_config_to_workspace_members;
|
use crate::graph_util::workspace_config_to_workspace_members;
|
||||||
|
use crate::graph_util::DenoGraphFsAdapter;
|
||||||
use crate::graph_util::FileWatcherReporter;
|
use crate::graph_util::FileWatcherReporter;
|
||||||
use crate::graph_util::ModuleGraphBuilder;
|
use crate::graph_util::ModuleGraphBuilder;
|
||||||
use crate::graph_util::ModuleGraphContainer;
|
use crate::graph_util::ModuleGraphContainer;
|
||||||
|
@ -64,6 +65,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
pub struct ModuleLoadPreparer {
|
pub struct ModuleLoadPreparer {
|
||||||
options: Arc<CliOptions>,
|
options: Arc<CliOptions>,
|
||||||
|
fs: Arc<dyn deno_fs::FileSystem>,
|
||||||
graph_container: Arc<ModuleGraphContainer>,
|
graph_container: Arc<ModuleGraphContainer>,
|
||||||
lockfile: Option<Arc<Mutex<Lockfile>>>,
|
lockfile: Option<Arc<Mutex<Lockfile>>>,
|
||||||
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
|
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
|
||||||
|
@ -79,6 +81,7 @@ impl ModuleLoadPreparer {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
options: Arc<CliOptions>,
|
options: Arc<CliOptions>,
|
||||||
|
fs: Arc<dyn deno_fs::FileSystem>,
|
||||||
graph_container: Arc<ModuleGraphContainer>,
|
graph_container: Arc<ModuleGraphContainer>,
|
||||||
lockfile: Option<Arc<Mutex<Lockfile>>>,
|
lockfile: Option<Arc<Mutex<Lockfile>>>,
|
||||||
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
|
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
|
||||||
|
@ -91,6 +94,7 @@ impl ModuleLoadPreparer {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
options,
|
options,
|
||||||
|
fs,
|
||||||
graph_container,
|
graph_container,
|
||||||
lockfile,
|
lockfile,
|
||||||
maybe_file_watcher_reporter,
|
maybe_file_watcher_reporter,
|
||||||
|
@ -155,6 +159,7 @@ impl ModuleLoadPreparer {
|
||||||
deno_graph::BuildOptions {
|
deno_graph::BuildOptions {
|
||||||
is_dynamic,
|
is_dynamic,
|
||||||
imports: maybe_imports,
|
imports: maybe_imports,
|
||||||
|
file_system: Some(&DenoGraphFsAdapter(self.fs.as_ref())),
|
||||||
resolver: Some(graph_resolver),
|
resolver: Some(graph_resolver),
|
||||||
npm_resolver: Some(graph_npm_resolver),
|
npm_resolver: Some(graph_npm_resolver),
|
||||||
module_analyzer: Some(&analyzer),
|
module_analyzer: Some(&analyzer),
|
||||||
|
|
|
@ -1061,3 +1061,27 @@ fn compile_node_modules_symlink_outside() {
|
||||||
let output = context.new_command().name(binary_path).run();
|
let output = context.new_command().name(binary_path).run();
|
||||||
output.assert_matches_file("compile/node_modules_symlink_outside/main.out");
|
output.assert_matches_file("compile/node_modules_symlink_outside/main.out");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dynamic_imports_tmp_lit() {
|
||||||
|
let context = TestContextBuilder::new().build();
|
||||||
|
let dir = context.temp_dir();
|
||||||
|
let exe = if cfg!(windows) {
|
||||||
|
dir.path().join("app.exe")
|
||||||
|
} else {
|
||||||
|
dir.path().join("app")
|
||||||
|
};
|
||||||
|
let output = context
|
||||||
|
.new_command()
|
||||||
|
.args_vec([
|
||||||
|
"compile",
|
||||||
|
"--output",
|
||||||
|
&exe.to_string_lossy(),
|
||||||
|
"./compile/dynamic_imports_tmp_lit/main.js",
|
||||||
|
])
|
||||||
|
.run();
|
||||||
|
output.assert_exit_code(0);
|
||||||
|
output.skip_output_check();
|
||||||
|
let output = context.new_command().name(&exe).run();
|
||||||
|
output.assert_matches_text("a\nb\n{ data: 5 }\n{ data: 1 }\n");
|
||||||
|
}
|
||||||
|
|
|
@ -138,13 +138,15 @@ fn deno_doc_html() {
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
output.assert_exit_code(0);
|
output.assert_exit_code(0);
|
||||||
assert_contains!(output.stderr(), "Written 8 files to");
|
assert_contains!(output.stderr(), "Written 10 files to");
|
||||||
|
assert!(temp_dir.path().join("all_symbols.html").exists());
|
||||||
assert!(temp_dir.path().join("index.html").exists());
|
assert!(temp_dir.path().join("index.html").exists());
|
||||||
assert!(temp_dir.path().join("compound_index.html").exists());
|
|
||||||
assert!(temp_dir.path().join("fuse.js").exists());
|
assert!(temp_dir.path().join("fuse.js").exists());
|
||||||
|
assert!(temp_dir.path().join("page.css").exists());
|
||||||
assert!(temp_dir.path().join("search.js").exists());
|
assert!(temp_dir.path().join("search.js").exists());
|
||||||
assert!(temp_dir.path().join("search_index.js").exists());
|
assert!(temp_dir.path().join("search_index.js").exists());
|
||||||
assert!(temp_dir.path().join("styles.css").exists());
|
assert!(temp_dir.path().join("styles.css").exists());
|
||||||
assert!(temp_dir.path().join("MyInterface.html").exists());
|
assert!(temp_dir.path().join("~/MyInterface.html").exists());
|
||||||
assert!(temp_dir.path().join("MyClass.html").exists());
|
assert!(temp_dir.path().join("~/MyClass.html").exists());
|
||||||
|
assert!(temp_dir.path().join("~/index.html").exists());
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,3 +154,9 @@ itest!(info_import_map {
|
||||||
cwd: Some("info/with_import_map"),
|
cwd: Some("info/with_import_map"),
|
||||||
exit_code: 0,
|
exit_code: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
itest!(info_dynamic_imports_tmpl_lit {
|
||||||
|
args: "info compile/dynamic_imports_tmp_lit/main.js",
|
||||||
|
output: "compile/dynamic_imports_tmp_lit/main.info.out",
|
||||||
|
exit_code: 0,
|
||||||
|
});
|
||||||
|
|
10
cli/tests/testdata/compile/dynamic_imports_tmp_lit/main.info.out
vendored
Normal file
10
cli/tests/testdata/compile/dynamic_imports_tmp_lit/main.info.out
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
local: [WILDCARD]main.js
|
||||||
|
type: JavaScript
|
||||||
|
dependencies: 4 unique
|
||||||
|
size: [WILDCARD]
|
||||||
|
|
||||||
|
file:///[WILDCARD]/dynamic_imports_tmp_lit/main.js ([WILDCARD])
|
||||||
|
├── file:///[WILDCARD]/dynamic_imports_tmp_lit/sub/a.js ([WILDCARD])
|
||||||
|
├── file:///[WILDCARD]/dynamic_imports_tmp_lit/sub/b.ts ([WILDCARD])
|
||||||
|
├── file:///[WILDCARD]/dynamic_imports_tmp_lit/other/data.json ([WILDCARD])
|
||||||
|
└── file:///[WILDCARD]/dynamic_imports_tmp_lit/other/sub/data2.json ([WILDCARD])
|
14
cli/tests/testdata/compile/dynamic_imports_tmp_lit/main.js
vendored
Normal file
14
cli/tests/testdata/compile/dynamic_imports_tmp_lit/main.js
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
const fileNames = [
|
||||||
|
"a.js",
|
||||||
|
"b.ts",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const fileName of fileNames) {
|
||||||
|
await import(`./sub/${fileName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonFileNames = ["data.json", "sub/data2.json"];
|
||||||
|
for (const fileName of jsonFileNames) {
|
||||||
|
const mod = await import(`./other/${fileName}`, { with: { type: "json" } });
|
||||||
|
console.log(mod.default);
|
||||||
|
}
|
3
cli/tests/testdata/compile/dynamic_imports_tmp_lit/other/data.json
vendored
Normal file
3
cli/tests/testdata/compile/dynamic_imports_tmp_lit/other/data.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"data": 5
|
||||||
|
}
|
3
cli/tests/testdata/compile/dynamic_imports_tmp_lit/other/sub/data2.json
vendored
Normal file
3
cli/tests/testdata/compile/dynamic_imports_tmp_lit/other/sub/data2.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"data": 1
|
||||||
|
}
|
1
cli/tests/testdata/compile/dynamic_imports_tmp_lit/sub/a.js
vendored
Normal file
1
cli/tests/testdata/compile/dynamic_imports_tmp_lit/sub/a.js
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
console.log("a");
|
1
cli/tests/testdata/compile/dynamic_imports_tmp_lit/sub/b.ts
vendored
Normal file
1
cli/tests/testdata/compile/dynamic_imports_tmp_lit/sub/b.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
console.log("b");
|
2
cli/tests/testdata/doc/lint_success_html.out
vendored
2
cli/tests/testdata/doc/lint_success_html.out
vendored
|
@ -1 +1 @@
|
||||||
Written 7 files to "./docs/"
|
Written 9 files to "./docs/"
|
||||||
|
|
|
@ -5,11 +5,13 @@
|
||||||
"moduleGraph1": {
|
"moduleGraph1": {
|
||||||
"/mod.ts": {
|
"/mod.ts": {
|
||||||
"dependencies": [{
|
"dependencies": [{
|
||||||
|
"type": "static",
|
||||||
"kind": "import",
|
"kind": "import",
|
||||||
"range": [[0, 0], [0, 59]],
|
"range": [[0, 0], [0, 59]],
|
||||||
"specifier": "jsr:@denotest/module_graph@1/other",
|
"specifier": "jsr:@denotest/module_graph@1/other",
|
||||||
"specifierRange": [[0, 22], [0, 58]]
|
"specifierRange": [[0, 22], [0, 58]]
|
||||||
}, {
|
}, {
|
||||||
|
"type": "static",
|
||||||
"kind": "import",
|
"kind": "import",
|
||||||
"range": [[1, 0], [1, 57]],
|
"range": [[1, 0], [1, 57]],
|
||||||
"specifier": "jsr:@denotest/no_module_graph@^0.1",
|
"specifier": "jsr:@denotest/no_module_graph@^0.1",
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
"/mod.ts": {
|
"/mod.ts": {
|
||||||
"dependencies": [{
|
"dependencies": [{
|
||||||
"kind": "import",
|
"kind": "import",
|
||||||
|
"type": "static",
|
||||||
"range": [[0, 0], [0, 35]],
|
"range": [[0, 0], [0, 35]],
|
||||||
"specifier": "./other.ts",
|
"specifier": "./other.ts",
|
||||||
"specifierRange": [[0, 22], [0, 34]]
|
"specifierRange": [[0, 22], [0, 34]]
|
||||||
|
|
|
@ -28,6 +28,7 @@ use doc::DocDiagnostic;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
async fn generate_doc_nodes_for_builtin_types(
|
async fn generate_doc_nodes_for_builtin_types(
|
||||||
|
@ -185,7 +186,11 @@ async fn generate_docs_directory(
|
||||||
let output_dir_resolved = cwd.join(&html_options.output);
|
let output_dir_resolved = cwd.join(&html_options.output);
|
||||||
|
|
||||||
let options = deno_doc::html::GenerateOptions {
|
let options = deno_doc::html::GenerateOptions {
|
||||||
package_name: html_options.name,
|
package_name: Some(html_options.name),
|
||||||
|
main_entrypoint: None,
|
||||||
|
global_symbols: Default::default(),
|
||||||
|
global_symbol_href_resolver: Rc::new(|_, _| String::new()),
|
||||||
|
url_resolver: Rc::new(deno_doc::html::default_url_resolver),
|
||||||
};
|
};
|
||||||
|
|
||||||
let files = deno_doc::html::generate(options, doc_nodes_by_url)
|
let files = deno_doc::html::generate(options, doc_nodes_by_url)
|
||||||
|
|
|
@ -4,10 +4,14 @@ use deno_ast::ParsedSource;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use deno_graph::DefaultModuleAnalyzer;
|
use deno_graph::DefaultModuleAnalyzer;
|
||||||
|
use deno_graph::DependencyDescriptor;
|
||||||
|
use deno_graph::DynamicTemplatePart;
|
||||||
use deno_graph::MediaType;
|
use deno_graph::MediaType;
|
||||||
use deno_graph::TypeScriptReference;
|
use deno_graph::TypeScriptReference;
|
||||||
use import_map::ImportMap;
|
use import_map::ImportMap;
|
||||||
|
|
||||||
|
use crate::graph_util::format_range_with_colors;
|
||||||
|
|
||||||
pub struct ImportMapUnfurler<'a> {
|
pub struct ImportMapUnfurler<'a> {
|
||||||
import_map: &'a ImportMap,
|
import_map: &'a ImportMap,
|
||||||
}
|
}
|
||||||
|
@ -59,23 +63,49 @@ impl<'a> ImportMapUnfurler<'a> {
|
||||||
})?;
|
})?;
|
||||||
let mut text_changes = Vec::new();
|
let mut text_changes = Vec::new();
|
||||||
let module_info = DefaultModuleAnalyzer::module_info(&parsed_source);
|
let module_info = DefaultModuleAnalyzer::module_info(&parsed_source);
|
||||||
let mut analyze_specifier =
|
let analyze_specifier =
|
||||||
|specifier: &str, range: &deno_graph::PositionRange| {
|
|specifier: &str,
|
||||||
|
range: &deno_graph::PositionRange,
|
||||||
|
text_changes: &mut Vec<deno_ast::TextChange>| {
|
||||||
let resolved = self.import_map.resolve(specifier, url);
|
let resolved = self.import_map.resolve(specifier, url);
|
||||||
if let Ok(resolved) = resolved {
|
if let Ok(resolved) = resolved {
|
||||||
let new_text = if resolved.scheme() == "file" {
|
|
||||||
format!("./{}", url.make_relative(&resolved).unwrap())
|
|
||||||
} else {
|
|
||||||
resolved.to_string()
|
|
||||||
};
|
|
||||||
text_changes.push(deno_ast::TextChange {
|
text_changes.push(deno_ast::TextChange {
|
||||||
range: to_range(&parsed_source, range),
|
range: to_range(&parsed_source, range),
|
||||||
new_text,
|
new_text: make_relative_to(url, &resolved),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for dep in &module_info.dependencies {
|
for dep in &module_info.dependencies {
|
||||||
analyze_specifier(&dep.specifier, &dep.specifier_range);
|
match dep {
|
||||||
|
DependencyDescriptor::Static(dep) => {
|
||||||
|
analyze_specifier(
|
||||||
|
&dep.specifier,
|
||||||
|
&dep.specifier_range,
|
||||||
|
&mut text_changes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
DependencyDescriptor::Dynamic(dep) => {
|
||||||
|
let success = try_unfurl_dynamic_dep(
|
||||||
|
self.import_map,
|
||||||
|
url,
|
||||||
|
&parsed_source,
|
||||||
|
dep,
|
||||||
|
&mut text_changes,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
log::warn!(
|
||||||
|
"{} Dynamic import was not analyzable and won't use the import map once published.\n at {}",
|
||||||
|
crate::colors::yellow("Warning"),
|
||||||
|
format_range_with_colors(&deno_graph::Range {
|
||||||
|
specifier: url.clone(),
|
||||||
|
start: dep.range.start.clone(),
|
||||||
|
end: dep.range.end.clone(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for ts_ref in &module_info.ts_references {
|
for ts_ref in &module_info.ts_references {
|
||||||
let specifier_with_range = match ts_ref {
|
let specifier_with_range = match ts_ref {
|
||||||
|
@ -85,18 +115,21 @@ impl<'a> ImportMapUnfurler<'a> {
|
||||||
analyze_specifier(
|
analyze_specifier(
|
||||||
&specifier_with_range.text,
|
&specifier_with_range.text,
|
||||||
&specifier_with_range.range,
|
&specifier_with_range.range,
|
||||||
|
&mut text_changes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for specifier_with_range in &module_info.jsdoc_imports {
|
for specifier_with_range in &module_info.jsdoc_imports {
|
||||||
analyze_specifier(
|
analyze_specifier(
|
||||||
&specifier_with_range.text,
|
&specifier_with_range.text,
|
||||||
&specifier_with_range.range,
|
&specifier_with_range.range,
|
||||||
|
&mut text_changes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Some(specifier_with_range) = &module_info.jsx_import_source {
|
if let Some(specifier_with_range) = &module_info.jsx_import_source {
|
||||||
analyze_specifier(
|
analyze_specifier(
|
||||||
&specifier_with_range.text,
|
&specifier_with_range.text,
|
||||||
&specifier_with_range.range,
|
&specifier_with_range.range,
|
||||||
|
&mut text_changes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(
|
Ok(
|
||||||
|
@ -120,6 +153,81 @@ impl<'a> ImportMapUnfurler<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_relative_to(from: &ModuleSpecifier, to: &ModuleSpecifier) -> String {
|
||||||
|
if to.scheme() == "file" {
|
||||||
|
format!("./{}", from.make_relative(to).unwrap())
|
||||||
|
} else {
|
||||||
|
to.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to unfurl the dynamic dependency returning `true` on success
|
||||||
|
/// or `false` when the import was not analyzable.
|
||||||
|
fn try_unfurl_dynamic_dep(
|
||||||
|
import_map: &ImportMap,
|
||||||
|
module_url: &lsp_types::Url,
|
||||||
|
parsed_source: &ParsedSource,
|
||||||
|
dep: &deno_graph::DynamicDependencyDescriptor,
|
||||||
|
text_changes: &mut Vec<deno_ast::TextChange>,
|
||||||
|
) -> bool {
|
||||||
|
match &dep.argument {
|
||||||
|
deno_graph::DynamicArgument::String(value) => {
|
||||||
|
let range = to_range(parsed_source, &dep.argument_range);
|
||||||
|
let maybe_relative_index =
|
||||||
|
parsed_source.text_info().text_str()[range.start..].find(value);
|
||||||
|
let Some(relative_index) = maybe_relative_index else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let resolved = import_map.resolve(value, module_url);
|
||||||
|
let Ok(resolved) = resolved else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let start = range.start + relative_index;
|
||||||
|
text_changes.push(deno_ast::TextChange {
|
||||||
|
range: start..start + value.len(),
|
||||||
|
new_text: make_relative_to(module_url, &resolved),
|
||||||
|
});
|
||||||
|
true
|
||||||
|
}
|
||||||
|
deno_graph::DynamicArgument::Template(parts) => match parts.first() {
|
||||||
|
Some(DynamicTemplatePart::String { value }) => {
|
||||||
|
// relative doesn't need to be modified
|
||||||
|
let is_relative = value.starts_with("./") || value.starts_with("../");
|
||||||
|
if is_relative {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if !value.ends_with('/') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let Ok(resolved) = import_map.resolve(value, module_url) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let range = to_range(parsed_source, &dep.argument_range);
|
||||||
|
let maybe_relative_index =
|
||||||
|
parsed_source.text_info().text_str()[range.start..].find(value);
|
||||||
|
let Some(relative_index) = maybe_relative_index else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let start = range.start + relative_index;
|
||||||
|
text_changes.push(deno_ast::TextChange {
|
||||||
|
range: start..start + value.len(),
|
||||||
|
new_text: make_relative_to(module_url, &resolved),
|
||||||
|
});
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Some(DynamicTemplatePart::Expr) => {
|
||||||
|
false // failed analyzing
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
true // ignore
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deno_graph::DynamicArgument::Expr => {
|
||||||
|
false // failed analyzing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn to_range(
|
fn to_range(
|
||||||
parsed_source: &ParsedSource,
|
parsed_source: &ParsedSource,
|
||||||
range: &deno_graph::PositionRange,
|
range: &deno_graph::PositionRange,
|
||||||
|
@ -166,6 +274,14 @@ mod tests {
|
||||||
import foo from "lib/foo.ts";
|
import foo from "lib/foo.ts";
|
||||||
import bar from "lib/bar.ts";
|
import bar from "lib/bar.ts";
|
||||||
import fizz from "fizz";
|
import fizz from "fizz";
|
||||||
|
|
||||||
|
const test1 = await import("lib/foo.ts");
|
||||||
|
const test2 = await import(`lib/foo.ts`);
|
||||||
|
const test3 = await import(`lib/${expr}`);
|
||||||
|
const test4 = await import(`./lib/${expr}`);
|
||||||
|
// will warn
|
||||||
|
const test5 = await import(`lib${expr}`);
|
||||||
|
const test6 = await import(`${expr}`);
|
||||||
"#;
|
"#;
|
||||||
let specifier = ModuleSpecifier::parse("file:///dev/mod.ts").unwrap();
|
let specifier = ModuleSpecifier::parse("file:///dev/mod.ts").unwrap();
|
||||||
let unfurled_source = unfurler
|
let unfurled_source = unfurler
|
||||||
|
@ -175,6 +291,14 @@ import fizz from "fizz";
|
||||||
import foo from "./lib/foo.ts";
|
import foo from "./lib/foo.ts";
|
||||||
import bar from "./lib/bar.ts";
|
import bar from "./lib/bar.ts";
|
||||||
import fizz from "./fizz/mod.ts";
|
import fizz from "./fizz/mod.ts";
|
||||||
|
|
||||||
|
const test1 = await import("./lib/foo.ts");
|
||||||
|
const test2 = await import(`./lib/foo.ts`);
|
||||||
|
const test3 = await import(`./lib/${expr}`);
|
||||||
|
const test4 = await import(`./lib/${expr}`);
|
||||||
|
// will warn
|
||||||
|
const test5 = await import(`lib${expr}`);
|
||||||
|
const test6 = await import(`${expr}`);
|
||||||
"#;
|
"#;
|
||||||
assert_eq!(unfurled_source, expected_source);
|
assert_eq!(unfurled_source, expected_source);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue