mirror of
https://github.com/denoland/deno.git
synced 2024-12-23 15:49:44 -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]]
|
||||
name = "deno_doc"
|
||||
version = "0.73.3"
|
||||
version = "0.73.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "729bacea0b5a1b2406f70c105ba82354e0f4bd99c51ede4fd29b15ce750dcdd4"
|
||||
checksum = "76d4d2960ce5c7af64e57ec4e7fd99023bd1ab664cedc4ec267db1d294b6002a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
|
@ -1162,9 +1162,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deno_emit"
|
||||
version = "0.31.4"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8910a6da498d0eb2a28d9ea613c47291a86377a85b3771dd90d624004814aeb"
|
||||
checksum = "5b98917905e9be9740722f89c3b2d3a963beaed8a05dce58e642947d0f6aa17d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
|
@ -1230,9 +1230,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deno_graph"
|
||||
version = "0.61.1"
|
||||
version = "0.61.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "076c0b611c10901456b78c837408b9c40fe0c3602e767307d986f46f0cc56b51"
|
||||
checksum = "332e6a1930b3266dc848edeaf1426bbbd3ddca25c5e107e70823efb1b3ce68be"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
@ -1242,6 +1242,7 @@ dependencies = [
|
|||
"futures",
|
||||
"import_map",
|
||||
"indexmap 2.0.2",
|
||||
"log",
|
||||
"monch",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
|
@ -2203,9 +2204,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "eszip"
|
||||
version = "0.55.4"
|
||||
version = "0.55.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0535cf1ea8c46379c39d791dc87e7104e90b3730829ee6d8888285eab22fa69"
|
||||
checksum = "455c055f28756fc7ba0d64506e6167b91878f3a39854271d7d63b577deb8b45d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.4",
|
||||
|
@ -2854,9 +2855,9 @@ checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
|||
|
||||
[[package]]
|
||||
name = "import_map"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e5bf51a0adfdc08afcb9e5a1c8f8c804227ec50d493c65e57e6d117d594bd1b"
|
||||
checksum = "0ecd467768fe83c2860e70e5de5297a7366a230ff53e1da2158bdac2384cd39d"
|
||||
dependencies = [
|
||||
"indexmap 1.9.3",
|
||||
"log",
|
||||
|
|
|
@ -57,16 +57,16 @@ deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "dep_gra
|
|||
deno_cache_dir = "=0.6.1"
|
||||
deno_config = "=0.6.5"
|
||||
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] }
|
||||
deno_doc = { version = "=0.73.3", features = ["html"] }
|
||||
deno_emit = "=0.31.4"
|
||||
deno_graph = "=0.61.1"
|
||||
deno_doc = { version = "=0.73.5", features = ["html"] }
|
||||
deno_emit = "=0.31.5"
|
||||
deno_graph = "=0.61.5"
|
||||
deno_lint = { version = "=0.52.2", features = ["docs"] }
|
||||
deno_lockfile.workspace = true
|
||||
deno_npm = "0.15.2"
|
||||
deno_runtime = { workspace = true, features = ["dont_create_runtime_snapshot", "exclude_runtime_main_js", "include_js_files_for_snapshotting"] }
|
||||
deno_semver = "0.5.1"
|
||||
deno_task_shell = "=0.14.0"
|
||||
eszip = "=0.55.4"
|
||||
eszip = "=0.55.5"
|
||||
napi_sym.workspace = true
|
||||
|
||||
async-trait.workspace = true
|
||||
|
@ -100,7 +100,7 @@ glob = "0.3.1"
|
|||
hex.workspace = true
|
||||
http.workspace = true
|
||||
hyper.workspace = true
|
||||
import_map = { version = "=0.17.0", features = ["ext"] }
|
||||
import_map = { version = "=0.18.0", features = ["ext"] }
|
||||
indexmap.workspace = true
|
||||
jsonc-parser = { version = "=0.23.0", features = ["serde"] }
|
||||
lazy-regex.workspace = true
|
||||
|
|
|
@ -513,6 +513,7 @@ impl CliFactory {
|
|||
.get_or_try_init_async(async {
|
||||
Ok(Arc::new(ModuleGraphBuilder::new(
|
||||
self.options.clone(),
|
||||
self.fs().clone(),
|
||||
self.resolver().await?.clone(),
|
||||
self.npm_resolver().await?.clone(),
|
||||
self.module_info_cache()?.clone(),
|
||||
|
@ -556,6 +557,7 @@ impl CliFactory {
|
|||
.get_or_try_init_async(async {
|
||||
Ok(Arc::new(ModuleLoadPreparer::new(
|
||||
self.options.clone(),
|
||||
self.fs().clone(),
|
||||
self.graph_container().clone(),
|
||||
self.maybe_lockfile().clone(),
|
||||
self.maybe_file_watcher_reporter().clone(),
|
||||
|
|
|
@ -35,6 +35,7 @@ use deno_graph::ModuleGraph;
|
|||
use deno_graph::ModuleGraphError;
|
||||
use deno_graph::ResolutionError;
|
||||
use deno_graph::SpecifierError;
|
||||
use deno_runtime::deno_fs::FileSystem;
|
||||
use deno_runtime::deno_node;
|
||||
use deno_runtime::permissions::PermissionsContainer;
|
||||
use deno_semver::package::PackageNv;
|
||||
|
@ -116,12 +117,8 @@ pub fn graph_valid(
|
|||
|
||||
if let Some(range) = error.maybe_range() {
|
||||
if !is_root && !range.specifier.as_str().contains("/$deno$eval") {
|
||||
message.push_str(&format!(
|
||||
"\n at {}:{}:{}",
|
||||
colors::cyan(range.specifier.as_str()),
|
||||
colors::yellow(&(range.start.line + 1).to_string()),
|
||||
colors::yellow(&(range.start.character + 1).to_string())
|
||||
));
|
||||
message.push_str("\n at ");
|
||||
message.push_str(&format_range_with_colors(range));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,6 +191,7 @@ pub struct CreateGraphOptions<'a> {
|
|||
|
||||
pub struct ModuleGraphBuilder {
|
||||
options: Arc<CliOptions>,
|
||||
fs: Arc<dyn FileSystem>,
|
||||
resolver: Arc<CliGraphResolver>,
|
||||
npm_resolver: Arc<dyn CliNpmResolver>,
|
||||
module_info_cache: Arc<ModuleInfoCache>,
|
||||
|
@ -210,6 +208,7 @@ impl ModuleGraphBuilder {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
options: Arc<CliOptions>,
|
||||
fs: Arc<dyn FileSystem>,
|
||||
resolver: Arc<CliGraphResolver>,
|
||||
npm_resolver: Arc<dyn CliNpmResolver>,
|
||||
module_info_cache: Arc<ModuleInfoCache>,
|
||||
|
@ -223,6 +222,7 @@ impl ModuleGraphBuilder {
|
|||
) -> Self {
|
||||
Self {
|
||||
options,
|
||||
fs,
|
||||
resolver,
|
||||
npm_resolver,
|
||||
module_info_cache,
|
||||
|
@ -295,6 +295,7 @@ impl ModuleGraphBuilder {
|
|||
is_dynamic: false,
|
||||
imports: maybe_imports,
|
||||
resolver: Some(graph_resolver),
|
||||
file_system: Some(&DenoGraphFsAdapter(self.fs.as_ref())),
|
||||
npm_resolver: Some(graph_npm_resolver),
|
||||
module_analyzer: Some(options.analyzer),
|
||||
reporter: maybe_file_watcher_reporter,
|
||||
|
@ -344,6 +345,7 @@ impl ModuleGraphBuilder {
|
|||
deno_graph::BuildOptions {
|
||||
is_dynamic: false,
|
||||
imports: maybe_imports,
|
||||
file_system: Some(&DenoGraphFsAdapter(self.fs.as_ref())),
|
||||
resolver: Some(graph_resolver),
|
||||
npm_resolver: Some(graph_npm_resolver),
|
||||
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)]
|
||||
mod test {
|
||||
use std::sync::Arc;
|
||||
|
|
|
@ -1743,12 +1743,17 @@ fn analyze_module(
|
|||
) -> ModuleResult {
|
||||
match parsed_source_result {
|
||||
Ok(parsed_source) => Ok(deno_graph::parse_module_from_ast(
|
||||
deno_graph::GraphKind::All,
|
||||
specifier,
|
||||
maybe_headers,
|
||||
parsed_source,
|
||||
Some(resolver),
|
||||
Some(npm_resolver),
|
||||
deno_graph::ParseModuleFromAstOptions {
|
||||
graph_kind: deno_graph::GraphKind::All,
|
||||
specifier,
|
||||
maybe_headers,
|
||||
parsed_source,
|
||||
// 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(
|
||||
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_valid_with_cli_options;
|
||||
use crate::graph_util::workspace_config_to_workspace_members;
|
||||
use crate::graph_util::DenoGraphFsAdapter;
|
||||
use crate::graph_util::FileWatcherReporter;
|
||||
use crate::graph_util::ModuleGraphBuilder;
|
||||
use crate::graph_util::ModuleGraphContainer;
|
||||
|
@ -64,6 +65,7 @@ use std::sync::Arc;
|
|||
|
||||
pub struct ModuleLoadPreparer {
|
||||
options: Arc<CliOptions>,
|
||||
fs: Arc<dyn deno_fs::FileSystem>,
|
||||
graph_container: Arc<ModuleGraphContainer>,
|
||||
lockfile: Option<Arc<Mutex<Lockfile>>>,
|
||||
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
|
||||
|
@ -79,6 +81,7 @@ impl ModuleLoadPreparer {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
options: Arc<CliOptions>,
|
||||
fs: Arc<dyn deno_fs::FileSystem>,
|
||||
graph_container: Arc<ModuleGraphContainer>,
|
||||
lockfile: Option<Arc<Mutex<Lockfile>>>,
|
||||
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
|
||||
|
@ -91,6 +94,7 @@ impl ModuleLoadPreparer {
|
|||
) -> Self {
|
||||
Self {
|
||||
options,
|
||||
fs,
|
||||
graph_container,
|
||||
lockfile,
|
||||
maybe_file_watcher_reporter,
|
||||
|
@ -155,6 +159,7 @@ impl ModuleLoadPreparer {
|
|||
deno_graph::BuildOptions {
|
||||
is_dynamic,
|
||||
imports: maybe_imports,
|
||||
file_system: Some(&DenoGraphFsAdapter(self.fs.as_ref())),
|
||||
resolver: Some(graph_resolver),
|
||||
npm_resolver: Some(graph_npm_resolver),
|
||||
module_analyzer: Some(&analyzer),
|
||||
|
|
|
@ -1061,3 +1061,27 @@ fn compile_node_modules_symlink_outside() {
|
|||
let output = context.new_command().name(binary_path).run();
|
||||
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();
|
||||
|
||||
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("compound_index.html").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_index.js").exists());
|
||||
assert!(temp_dir.path().join("styles.css").exists());
|
||||
assert!(temp_dir.path().join("MyInterface.html").exists());
|
||||
assert!(temp_dir.path().join("MyClass.html").exists());
|
||||
assert!(temp_dir.path().join("~/MyInterface.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"),
|
||||
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": {
|
||||
"/mod.ts": {
|
||||
"dependencies": [{
|
||||
"type": "static",
|
||||
"kind": "import",
|
||||
"range": [[0, 0], [0, 59]],
|
||||
"specifier": "jsr:@denotest/module_graph@1/other",
|
||||
"specifierRange": [[0, 22], [0, 58]]
|
||||
}, {
|
||||
"type": "static",
|
||||
"kind": "import",
|
||||
"range": [[1, 0], [1, 57]],
|
||||
"specifier": "jsr:@denotest/no_module_graph@^0.1",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"/mod.ts": {
|
||||
"dependencies": [{
|
||||
"kind": "import",
|
||||
"type": "static",
|
||||
"range": [[0, 0], [0, 35]],
|
||||
"specifier": "./other.ts",
|
||||
"specifierRange": [[0, 22], [0, 34]]
|
||||
|
|
|
@ -28,6 +28,7 @@ use doc::DocDiagnostic;
|
|||
use indexmap::IndexMap;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
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 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)
|
||||
|
|
|
@ -4,10 +4,14 @@ use deno_ast::ParsedSource;
|
|||
use deno_core::error::AnyError;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_graph::DefaultModuleAnalyzer;
|
||||
use deno_graph::DependencyDescriptor;
|
||||
use deno_graph::DynamicTemplatePart;
|
||||
use deno_graph::MediaType;
|
||||
use deno_graph::TypeScriptReference;
|
||||
use import_map::ImportMap;
|
||||
|
||||
use crate::graph_util::format_range_with_colors;
|
||||
|
||||
pub struct ImportMapUnfurler<'a> {
|
||||
import_map: &'a ImportMap,
|
||||
}
|
||||
|
@ -59,23 +63,49 @@ impl<'a> ImportMapUnfurler<'a> {
|
|||
})?;
|
||||
let mut text_changes = Vec::new();
|
||||
let module_info = DefaultModuleAnalyzer::module_info(&parsed_source);
|
||||
let mut analyze_specifier =
|
||||
|specifier: &str, range: &deno_graph::PositionRange| {
|
||||
let analyze_specifier =
|
||||
|specifier: &str,
|
||||
range: &deno_graph::PositionRange,
|
||||
text_changes: &mut Vec<deno_ast::TextChange>| {
|
||||
let resolved = self.import_map.resolve(specifier, url);
|
||||
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 {
|
||||
range: to_range(&parsed_source, range),
|
||||
new_text,
|
||||
new_text: make_relative_to(url, &resolved),
|
||||
});
|
||||
}
|
||||
};
|
||||
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 {
|
||||
let specifier_with_range = match ts_ref {
|
||||
|
@ -85,18 +115,21 @@ impl<'a> ImportMapUnfurler<'a> {
|
|||
analyze_specifier(
|
||||
&specifier_with_range.text,
|
||||
&specifier_with_range.range,
|
||||
&mut text_changes,
|
||||
);
|
||||
}
|
||||
for specifier_with_range in &module_info.jsdoc_imports {
|
||||
analyze_specifier(
|
||||
&specifier_with_range.text,
|
||||
&specifier_with_range.range,
|
||||
&mut text_changes,
|
||||
);
|
||||
}
|
||||
if let Some(specifier_with_range) = &module_info.jsx_import_source {
|
||||
analyze_specifier(
|
||||
&specifier_with_range.text,
|
||||
&specifier_with_range.range,
|
||||
&mut text_changes,
|
||||
);
|
||||
}
|
||||
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(
|
||||
parsed_source: &ParsedSource,
|
||||
range: &deno_graph::PositionRange,
|
||||
|
@ -166,6 +274,14 @@ mod tests {
|
|||
import foo from "lib/foo.ts";
|
||||
import bar from "lib/bar.ts";
|
||||
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 unfurled_source = unfurler
|
||||
|
@ -175,6 +291,14 @@ import fizz from "fizz";
|
|||
import foo from "./lib/foo.ts";
|
||||
import bar from "./lib/bar.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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue