summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/lsp/documents.rs8
-rw-r--r--cli/proc_state.rs30
-rw-r--r--cli/tests/repl_tests.rs52
-rw-r--r--cli/tools/repl/mod.rs2
-rw-r--r--cli/tools/repl/session.rs114
-rw-r--r--im.json5
6 files changed, 201 insertions, 10 deletions
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs
index 18464b178..82e2618b3 100644
--- a/cli/lsp/documents.rs
+++ b/cli/lsp/documents.rs
@@ -699,10 +699,10 @@ fn get_document_path(
cache: &HttpCache,
specifier: &ModuleSpecifier,
) -> Option<PathBuf> {
- if specifier.scheme() == "file" {
- specifier_to_file_path(specifier).ok()
- } else {
- cache.get_cache_filename(specifier)
+ match specifier.scheme() {
+ "npm" | "node" => None,
+ "file" => specifier_to_file_path(specifier).ok(),
+ _ => cache.get_cache_filename(specifier),
}
}
diff --git a/cli/proc_state.rs b/cli/proc_state.rs
index 13c8d2414..cdfc04c08 100644
--- a/cli/proc_state.rs
+++ b/cli/proc_state.rs
@@ -93,7 +93,7 @@ pub struct Inner {
pub shared_array_buffer_store: SharedArrayBufferStore,
pub compiled_wasm_module_store: CompiledWasmModuleStore,
pub parsed_source_cache: ParsedSourceCache,
- maybe_resolver: Option<Arc<CliResolver>>,
+ pub maybe_resolver: Option<Arc<CliResolver>>,
maybe_file_watcher_reporter: Option<FileWatcherReporter>,
pub node_analysis_cache: NodeAnalysisCache,
pub npm_cache: NpmCache,
@@ -594,14 +594,36 @@ impl ProcState {
// FIXME(bartlomieju): this is a hacky way to provide compatibility with REPL
// and `Deno.core.evalContext` API. Ideally we should always have a referrer filled
// but sadly that's not the case due to missing APIs in V8.
- let referrer = if referrer.is_empty()
- && matches!(self.options.sub_command(), DenoSubcommand::Repl(_))
- {
+ let is_repl = matches!(self.options.sub_command(), DenoSubcommand::Repl(_));
+ let referrer = if referrer.is_empty() && is_repl {
deno_core::resolve_url_or_path("./$deno$repl.ts").unwrap()
} else {
deno_core::resolve_url_or_path(referrer).unwrap()
};
+ // FIXME(bartlomieju): this is another hack way to provide NPM specifier
+ // support in REPL. This should be fixed.
+ if is_repl {
+ let specifier = self
+ .maybe_resolver
+ .as_ref()
+ .and_then(|resolver| {
+ resolver.resolve(specifier, &referrer).to_result().ok()
+ })
+ .or_else(|| ModuleSpecifier::parse(specifier).ok());
+ if let Some(specifier) = specifier {
+ if let Ok(reference) = NpmPackageReference::from_specifier(&specifier) {
+ return self
+ .handle_node_resolve_result(node::node_resolve_npm_reference(
+ &reference,
+ deno_runtime::deno_node::NodeResolutionMode::Execution,
+ &self.npm_resolver,
+ ))
+ .with_context(|| format!("Could not resolve '{}'.", reference));
+ }
+ }
+ }
+
if let Some(resolver) = &self.maybe_resolver {
resolver.resolve(specifier, &referrer).to_result()
} else {
diff --git a/cli/tests/repl_tests.rs b/cli/tests/repl_tests.rs
index d1af6d844..a5c64f3b6 100644
--- a/cli/tests/repl_tests.rs
+++ b/cli/tests/repl_tests.rs
@@ -897,4 +897,56 @@ mod repl {
assert_ends_with!(out, "\"done\"\n");
assert!(err.is_empty());
}
+
+ #[test]
+ fn npm_packages() {
+ let mut env_vars = util::env_vars_for_npm_tests();
+ env_vars.push(("NO_COLOR".to_owned(), "1".to_owned()));
+
+ {
+ let (out, err) = util::run_and_collect_output_with_args(
+ true,
+ vec!["repl", "--quiet", "--allow-read", "--allow-env"],
+ Some(vec![
+ r#"import chalk from "npm:chalk";"#,
+ "chalk.red('hel' + 'lo')",
+ ]),
+ Some(env_vars.clone()),
+ true,
+ );
+
+ assert_contains!(out, "hello");
+ assert!(err.is_empty());
+ }
+
+ {
+ let (out, err) = util::run_and_collect_output_with_args(
+ true,
+ vec!["repl", "--quiet", "--allow-read", "--allow-env"],
+ Some(vec![
+ r#"const chalk = await import("npm:chalk");"#,
+ "chalk.default.red('hel' + 'lo')",
+ ]),
+ Some(env_vars.clone()),
+ true,
+ );
+
+ assert_contains!(out, "hello");
+ assert!(err.is_empty());
+ }
+
+ {
+ let (out, err) = util::run_and_collect_output_with_args(
+ true,
+ vec!["repl", "--quiet", "--allow-read", "--allow-env"],
+ Some(vec![r#"export {} from "npm:chalk";"#]),
+ Some(env_vars),
+ true,
+ );
+
+ assert_contains!(out, "Module {");
+ assert_contains!(out, "Chalk: [Function: Chalk],");
+ assert!(err.is_empty());
+ }
+ }
}
diff --git a/cli/tools/repl/mod.rs b/cli/tools/repl/mod.rs
index cde4efa28..ff438aeb2 100644
--- a/cli/tools/repl/mod.rs
+++ b/cli/tools/repl/mod.rs
@@ -89,7 +89,7 @@ pub async fn run(flags: Flags, repl_flags: ReplFlags) -> Result<i32, AnyError> {
.await?;
worker.setup_repl().await?;
let worker = worker.into_main_worker();
- let mut repl_session = ReplSession::initialize(worker).await?;
+ let mut repl_session = ReplSession::initialize(ps.clone(), worker).await?;
let mut rustyline_channel = rustyline_channel();
let mut should_exit_on_interrupt = false;
diff --git a/cli/tools/repl/session.rs b/cli/tools/repl/session.rs
index 361f5abb5..3b30a140d 100644
--- a/cli/tools/repl/session.rs
+++ b/cli/tools/repl/session.rs
@@ -2,13 +2,21 @@
use crate::colors;
use crate::lsp::ReplLanguageServer;
+use crate::npm::NpmPackageReference;
+use crate::ProcState;
+use deno_ast::swc::ast as swc_ast;
+use deno_ast::swc::visit::noop_visit_type;
+use deno_ast::swc::visit::Visit;
+use deno_ast::swc::visit::VisitWith;
use deno_ast::DiagnosticsError;
use deno_ast::ImportsNotUsedAsValues;
+use deno_ast::ModuleSpecifier;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::serde_json;
use deno_core::serde_json::Value;
use deno_core::LocalInspectorSession;
+use deno_graph::source::Resolver;
use deno_runtime::worker::MainWorker;
use super::cdp;
@@ -66,14 +74,20 @@ struct TsEvaluateResponse {
}
pub struct ReplSession {
+ proc_state: ProcState,
pub worker: MainWorker,
session: LocalInspectorSession,
pub context_id: u64,
pub language_server: ReplLanguageServer,
+ has_initialized_node_runtime: bool,
+ referrer: ModuleSpecifier,
}
impl ReplSession {
- pub async fn initialize(mut worker: MainWorker) -> Result<Self, AnyError> {
+ pub async fn initialize(
+ proc_state: ProcState,
+ mut worker: MainWorker,
+ ) -> Result<Self, AnyError> {
let language_server = ReplLanguageServer::new_initialized().await?;
let mut session = worker.create_inspector_session().await;
@@ -106,11 +120,16 @@ impl ReplSession {
}
assert_ne!(context_id, 0);
+ let referrer = deno_core::resolve_url_or_path("./$deno$repl.ts").unwrap();
+
let mut repl_session = ReplSession {
+ proc_state,
worker,
session,
context_id,
language_server,
+ has_initialized_node_runtime: false,
+ referrer,
};
// inject prelude
@@ -348,6 +367,8 @@ impl ReplSession {
scope_analysis: false,
})?;
+ self.check_for_npm_imports(&parsed_module.program()).await?;
+
let transpiled_src = parsed_module
.transpile(&deno_ast::EmitOptions {
emit_metadata: false,
@@ -379,6 +400,45 @@ impl ReplSession {
})
}
+ async fn check_for_npm_imports(
+ &mut self,
+ program: &swc_ast::Program,
+ ) -> Result<(), AnyError> {
+ let mut collector = ImportCollector::new();
+ program.visit_with(&mut collector);
+
+ let npm_imports = collector
+ .imports
+ .iter()
+ .flat_map(|i| {
+ self
+ .proc_state
+ .maybe_resolver
+ .as_ref()
+ .and_then(|resolver| {
+ resolver.resolve(i, &self.referrer).to_result().ok()
+ })
+ .or_else(|| ModuleSpecifier::parse(i).ok())
+ .and_then(|url| NpmPackageReference::from_specifier(&url).ok())
+ })
+ .map(|r| r.req)
+ .collect::<Vec<_>>();
+ if !npm_imports.is_empty() {
+ if !self.has_initialized_node_runtime {
+ self.proc_state.prepare_node_std_graph().await?;
+ crate::node::initialize_runtime(&mut self.worker.js_runtime).await?;
+ self.has_initialized_node_runtime = true;
+ }
+
+ self
+ .proc_state
+ .npm_resolver
+ .add_package_reqs(npm_imports)
+ .await?;
+ }
+ Ok(())
+ }
+
async fn evaluate_expression(
&mut self,
expression: &str,
@@ -408,3 +468,55 @@ impl ReplSession {
.and_then(|res| serde_json::from_value(res).map_err(|e| e.into()))
}
}
+
+/// Walk an AST and get all import specifiers for analysis if any of them is
+/// an npm specifier.
+struct ImportCollector {
+ pub imports: Vec<String>,
+}
+
+impl ImportCollector {
+ pub fn new() -> Self {
+ Self { imports: vec![] }
+ }
+}
+
+impl Visit for ImportCollector {
+ noop_visit_type!();
+
+ fn visit_call_expr(&mut self, call_expr: &swc_ast::CallExpr) {
+ if !matches!(call_expr.callee, swc_ast::Callee::Import(_)) {
+ return;
+ }
+
+ if !call_expr.args.is_empty() {
+ let arg = &call_expr.args[0];
+ if let swc_ast::Expr::Lit(swc_ast::Lit::Str(str_lit)) = &*arg.expr {
+ self.imports.push(str_lit.value.to_string());
+ }
+ }
+ }
+
+ fn visit_module_decl(&mut self, module_decl: &swc_ast::ModuleDecl) {
+ use deno_ast::swc::ast::*;
+
+ match module_decl {
+ ModuleDecl::Import(import_decl) => {
+ if import_decl.type_only {
+ return;
+ }
+
+ self.imports.push(import_decl.src.value.to_string());
+ }
+ ModuleDecl::ExportAll(export_all) => {
+ self.imports.push(export_all.src.value.to_string());
+ }
+ ModuleDecl::ExportNamed(export_named) => {
+ if let Some(src) = &export_named.src {
+ self.imports.push(src.value.to_string());
+ }
+ }
+ _ => {}
+ }
+ }
+}
diff --git a/im.json b/im.json
new file mode 100644
index 000000000..a2827b171
--- /dev/null
+++ b/im.json
@@ -0,0 +1,5 @@
+{
+ "imports": {
+ "chalk": "npm:chalk"
+ }
+}