summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2021-11-09 12:26:39 +1100
committerGitHub <noreply@github.com>2021-11-09 12:26:39 +1100
commitf5eb177f50a0bf37bc6bd9d87b447c73a53b6ea5 (patch)
tree1990dadf311de59b45c677e234219a161f3ebf9d /cli
parent45425c114610516287c8e5831c9b6f023dfc8180 (diff)
feat(cli): support React 17 JSX transforms (#12631)
Closes #8440
Diffstat (limited to 'cli')
-rw-r--r--cli/Cargo.toml4
-rw-r--r--cli/ast/mod.rs143
-rw-r--r--cli/compat/esm_resolver.rs10
-rw-r--r--cli/config_file.rs55
-rw-r--r--cli/dts/lib.deno.unstable.d.ts9
-rw-r--r--cli/http_util.rs18
-rw-r--r--cli/lsp/cache.rs32
-rw-r--r--cli/lsp/documents.rs203
-rw-r--r--cli/lsp/language_server.rs25
-rw-r--r--cli/lsp/mod.rs1
-rw-r--r--cli/lsp/resolver.rs33
-rw-r--r--cli/main.rs71
-rw-r--r--cli/ops/runtime_compiler.rs80
-rw-r--r--cli/proc_state.rs40
-rw-r--r--cli/resolver.rs53
-rw-r--r--cli/schemas/config-file.v1.json10
-rw-r--r--cli/tests/integration/lsp_tests.rs79
-rw-r--r--cli/tests/integration/mod.rs2
-rw-r--r--cli/tests/integration/run_tests.rs112
-rw-r--r--cli/tests/testdata/compiler_api_test.ts78
-rw-r--r--cli/tests/testdata/jsx/deno-jsx-import-map.jsonc6
-rw-r--r--cli/tests/testdata/jsx/deno-jsx.jsonc6
-rw-r--r--cli/tests/testdata/jsx/deno-jsxdev-import-map.jsonc6
-rw-r--r--cli/tests/testdata/jsx/deno-jsxdev.jsonc6
-rw-r--r--cli/tests/testdata/jsx/import-map.json6
-rw-r--r--cli/tests/testdata/jsx/jsx-dev-runtime/index.ts12
-rw-r--r--cli/tests/testdata/jsx/jsx-runtime/index.ts12
-rw-r--r--cli/tests/testdata/jsx_import_source.out2
-rw-r--r--cli/tests/testdata/jsx_import_source_dev.out2
-rw-r--r--cli/tests/testdata/jsx_import_source_import_map.out2
-rw-r--r--cli/tests/testdata/jsx_import_source_import_map_dev.out2
-rw-r--r--cli/tests/testdata/jsx_import_source_no_pragma.tsx7
-rw-r--r--cli/tests/testdata/jsx_import_source_pragma.tsx9
-rw-r--r--cli/tests/testdata/jsx_import_source_pragma_import_map.tsx9
-rw-r--r--cli/tools/doc.rs2
-rw-r--r--cli/tools/repl.rs3
-rw-r--r--cli/tools/test.rs31
-rw-r--r--cli/tsc.rs70
38 files changed, 1084 insertions, 167 deletions
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 32527180b..0fe47934d 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -41,8 +41,8 @@ winres = "0.1.11"
[dependencies]
deno_ast = { version = "0.5.0", features = ["bundler", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
deno_core = { version = "0.105.0", path = "../core" }
-deno_doc = "0.19.0"
-deno_graph = "0.10.0"
+deno_doc = "0.20.0"
+deno_graph = "0.11.1"
deno_lint = { version = "0.19.0", features = ["docs"] }
deno_runtime = { version = "0.31.0", path = "../runtime" }
deno_tls = { version = "0.10.0", path = "../ext/tls" }
diff --git a/cli/ast/mod.rs b/cli/ast/mod.rs
index 626e75b45..e3722c149 100644
--- a/cli/ast/mod.rs
+++ b/cli/ast/mod.rs
@@ -122,15 +122,26 @@ pub struct EmitOptions {
pub inline_source_map: bool,
/// Should the sources be inlined in the source map. Defaults to `true`.
pub inline_sources: bool,
- // Should a corresponding .map file be created for the output. This should be
- // false if inline_source_map is true. Defaults to `false`.
+ /// Should a corresponding .map file be created for the output. This should be
+ /// false if inline_source_map is true. Defaults to `false`.
pub source_map: bool,
+ /// `true` if the program should use an implicit JSX import source/the "new"
+ /// JSX transforms.
+ pub jsx_automatic: bool,
+ /// If JSX is automatic, if it is in development mode, meaning that it should
+ /// import `jsx-dev-runtime` and transform JSX using `jsxDEV` import from the
+ /// JSX import source as well as provide additional debug information to the
+ /// JSX factory.
+ pub jsx_development: bool,
/// When transforming JSX, what value should be used for the JSX factory.
/// Defaults to `React.createElement`.
pub jsx_factory: String,
/// When transforming JSX, what value should be used for the JSX fragment
/// factory. Defaults to `React.Fragment`.
pub jsx_fragment_factory: String,
+ /// The string module specifier to implicitly import JSX factories from when
+ /// transpiling JSX.
+ pub jsx_import_source: Option<String>,
/// Should JSX be transformed or preserved. Defaults to `true`.
pub transform_jsx: bool,
/// Should import declarations be transformed to variable declarations.
@@ -146,8 +157,11 @@ impl Default for EmitOptions {
inline_source_map: true,
inline_sources: true,
source_map: false,
+ jsx_automatic: false,
+ jsx_development: false,
jsx_factory: "React.createElement".into(),
jsx_fragment_factory: "React.Fragment".into(),
+ jsx_import_source: None,
transform_jsx: true,
repl_imports: false,
}
@@ -164,15 +178,25 @@ impl From<config_file::TsConfig> for EmitOptions {
"error" => ImportsNotUsedAsValues::Error,
_ => ImportsNotUsedAsValues::Remove,
};
+ let (transform_jsx, jsx_automatic, jsx_development) =
+ match options.jsx.as_str() {
+ "react" => (true, false, false),
+ "react-jsx" => (true, true, false),
+ "react-jsxdev" => (true, true, true),
+ _ => (false, false, false),
+ };
EmitOptions {
emit_metadata: options.emit_decorator_metadata,
imports_not_used_as_values,
inline_source_map: options.inline_source_map,
inline_sources: options.inline_sources,
source_map: options.source_map,
+ jsx_automatic,
+ jsx_development,
jsx_factory: options.jsx_factory,
jsx_fragment_factory: options.jsx_fragment_factory,
- transform_jsx: options.jsx == "react",
+ jsx_import_source: options.jsx_import_source,
+ transform_jsx,
repl_imports: false,
}
}
@@ -355,6 +379,13 @@ fn fold_program(
// this will use `Object.assign()` instead of the `_extends` helper
// when spreading props.
use_builtins: true,
+ runtime: if options.jsx_automatic {
+ Some(react::Runtime::Automatic)
+ } else {
+ None
+ },
+ development: options.jsx_development,
+ import_source: options.jsx_import_source.clone().unwrap_or_default(),
..Default::default()
},
top_level_mark,
@@ -496,6 +527,112 @@ function App() {
}
#[test]
+ fn test_transpile_jsx_import_source_pragma() {
+ let specifier = resolve_url_or_path("https://deno.land/x/mod.tsx")
+ .expect("could not resolve specifier");
+ let source = r#"
+/** @jsxImportSource jsx_lib */
+
+function App() {
+ return (
+ <div><></></div>
+ );
+}"#;
+ let module = parse_module(ParseParams {
+ specifier: specifier.as_str().to_string(),
+ source: SourceTextInfo::from_string(source.to_string()),
+ media_type: deno_ast::MediaType::Jsx,
+ capture_tokens: false,
+ maybe_syntax: None,
+ scope_analysis: true,
+ })
+ .unwrap();
+ let (code, _) = transpile(&module, &EmitOptions::default()).unwrap();
+ let expected = r#"import { jsx as _jsx, Fragment as _Fragment } from "jsx_lib/jsx-runtime";
+/** @jsxImportSource jsx_lib */ function App() {
+ return(/*#__PURE__*/ _jsx("div", {
+ children: /*#__PURE__*/ _jsx(_Fragment, {
+ })
+ }));
+"#;
+ assert_eq!(&code[..expected.len()], expected);
+ }
+
+ #[test]
+ fn test_transpile_jsx_import_source_no_pragma() {
+ let specifier = resolve_url_or_path("https://deno.land/x/mod.tsx")
+ .expect("could not resolve specifier");
+ let source = r#"
+function App() {
+ return (
+ <div><></></div>
+ );
+}"#;
+ let module = parse_module(ParseParams {
+ specifier: specifier.as_str().to_string(),
+ source: SourceTextInfo::from_string(source.to_string()),
+ media_type: deno_ast::MediaType::Jsx,
+ capture_tokens: false,
+ maybe_syntax: None,
+ scope_analysis: true,
+ })
+ .unwrap();
+ let emit_options = EmitOptions {
+ jsx_automatic: true,
+ jsx_import_source: Some("jsx_lib".to_string()),
+ ..Default::default()
+ };
+ let (code, _) = transpile(&module, &emit_options).unwrap();
+ let expected = r#"import { jsx as _jsx, Fragment as _Fragment } from "jsx_lib/jsx-runtime";
+function App() {
+ return(/*#__PURE__*/ _jsx("div", {
+ children: /*#__PURE__*/ _jsx(_Fragment, {
+ })
+ }));
+}
+"#;
+ assert_eq!(&code[..expected.len()], expected);
+ }
+
+ // TODO(@kitsonk) https://github.com/swc-project/swc/issues/2656
+ // #[test]
+ // fn test_transpile_jsx_import_source_no_pragma_dev() {
+ // let specifier = resolve_url_or_path("https://deno.land/x/mod.tsx")
+ // .expect("could not resolve specifier");
+ // let source = r#"
+ // function App() {
+ // return (
+ // <div><></></div>
+ // );
+ // }"#;
+ // let module = parse_module(ParseParams {
+ // specifier: specifier.as_str().to_string(),
+ // source: SourceTextInfo::from_string(source.to_string()),
+ // media_type: deno_ast::MediaType::Jsx,
+ // capture_tokens: false,
+ // maybe_syntax: None,
+ // scope_analysis: true,
+ // })
+ // .unwrap();
+ // let emit_options = EmitOptions {
+ // jsx_automatic: true,
+ // jsx_import_source: Some("jsx_lib".to_string()),
+ // jsx_development: true,
+ // ..Default::default()
+ // };
+ // let (code, _) = transpile(&module, &emit_options).unwrap();
+ // let expected = r#"import { jsx as _jsx, Fragment as _Fragment } from "jsx_lib/jsx-dev-runtime";
+ // function App() {
+ // return(/*#__PURE__*/ _jsx("div", {
+ // children: /*#__PURE__*/ _jsx(_Fragment, {
+ // })
+ // }));
+ // }
+ // "#;
+ // assert_eq!(&code[..expected.len()], expected);
+ // }
+
+ #[test]
fn test_transpile_decorators() {
let specifier = resolve_url_or_path("https://deno.land/x/mod.ts")
.expect("could not resolve specifier");
diff --git a/cli/compat/esm_resolver.rs b/cli/compat/esm_resolver.rs
index 0709c66a3..257a52f3e 100644
--- a/cli/compat/esm_resolver.rs
+++ b/cli/compat/esm_resolver.rs
@@ -14,12 +14,12 @@ use regex::Regex;
use std::path::PathBuf;
#[derive(Debug, Default)]
-pub(crate) struct NodeEsmResolver<'a> {
- maybe_import_map_resolver: Option<ImportMapResolver<'a>>,
+pub(crate) struct NodeEsmResolver {
+ maybe_import_map_resolver: Option<ImportMapResolver>,
}
-impl<'a> NodeEsmResolver<'a> {
- pub fn new(maybe_import_map_resolver: Option<ImportMapResolver<'a>>) -> Self {
+impl NodeEsmResolver {
+ pub fn new(maybe_import_map_resolver: Option<ImportMapResolver>) -> Self {
Self {
maybe_import_map_resolver,
}
@@ -30,7 +30,7 @@ impl<'a> NodeEsmResolver<'a> {
}
}
-impl Resolver for NodeEsmResolver<'_> {
+impl Resolver for NodeEsmResolver {
fn resolve(
&self,
specifier: &str,
diff --git a/cli/config_file.rs b/cli/config_file.rs
index 3a71d41a9..20e254dd0 100644
--- a/cli/config_file.rs
+++ b/cli/config_file.rs
@@ -1,7 +1,9 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::fs_util::canonicalize_path;
+
use deno_core::error::anyhow;
+use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::error::Context;
use deno_core::serde::Deserialize;
@@ -17,6 +19,9 @@ use std::fmt;
use std::path::Path;
use std::path::PathBuf;
+pub(crate) type MaybeImportsResult =
+ Result<Option<Vec<(ModuleSpecifier, Vec<String>)>>, AnyError>;
+
/// The transpile options that are significant out of a user provided tsconfig
/// file, that we want to deserialize out of the final config for a transpile.
#[derive(Debug, Deserialize)]
@@ -31,6 +36,7 @@ pub struct EmitConfigOptions {
pub jsx: String,
pub jsx_factory: String,
pub jsx_fragment_factory: String,
+ pub jsx_import_source: Option<String>,
}
/// There are certain compiler options that can impact what modules are part of
@@ -38,6 +44,8 @@ pub struct EmitConfigOptions {
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CompilerOptions {
+ pub jsx: Option<String>,
+ pub jsx_import_source: Option<String>,
pub types: Option<Vec<String>>,
}
@@ -404,15 +412,50 @@ impl ConfigFile {
/// If the configuration file contains "extra" modules (like TypeScript
/// `"types"`) options, return them as imports to be added to a module graph.
- pub fn to_maybe_imports(
- &self,
- ) -> Option<Vec<(ModuleSpecifier, Vec<String>)>> {
+ pub fn to_maybe_imports(&self) -> MaybeImportsResult {
+ let mut imports = Vec::new();
+ let compiler_options_value =
+ if let Some(value) = self.json.compiler_options.as_ref() {
+ value
+ } else {
+ return Ok(None);
+ };
+ let compiler_options: CompilerOptions =
+ serde_json::from_value(compiler_options_value.clone())?;
+ let referrer = ModuleSpecifier::from_file_path(&self.path)
+ .map_err(|_| custom_error("TypeError", "bad config file specifier"))?;
+ if let Some(types) = compiler_options.types {
+ imports.extend(types);
+ }
+ if compiler_options.jsx == Some("react-jsx".to_string()) {
+ imports.push(format!(
+ "{}/jsx-runtime",
+ compiler_options.jsx_import_source.ok_or_else(|| custom_error("TypeError", "Compiler option 'jsx' set to 'react-jsx', but no 'jsxImportSource' defined."))?
+ ));
+ } else if compiler_options.jsx == Some("react-jsxdev".to_string()) {
+ imports.push(format!(
+ "{}/jsx-dev-runtime",
+ compiler_options.jsx_import_source.ok_or_else(|| custom_error("TypeError", "Compiler option 'jsx' set to 'react-jsxdev', but no 'jsxImportSource' defined."))?
+ ));
+ }
+ if !imports.is_empty() {
+ Ok(Some(vec![(referrer, imports)]))
+ } else {
+ Ok(None)
+ }
+ }
+
+ /// Based on the compiler options in the configuration file, return the
+ /// implied JSX import source module.
+ pub fn to_maybe_jsx_import_source_module(&self) -> Option<String> {
let compiler_options_value = self.json.compiler_options.as_ref()?;
let compiler_options: CompilerOptions =
serde_json::from_value(compiler_options_value.clone()).ok()?;
- let referrer = ModuleSpecifier::from_file_path(&self.path).ok()?;
- let types = compiler_options.types?;
- Some(vec![(referrer, types)])
+ match compiler_options.jsx.as_deref() {
+ Some("react-jsx") => Some("jsx-runtime".to_string()),
+ Some("react-jsxdev") => Some("jsx-dev-runtime".to_string()),
+ _ => None,
+ }
}
pub fn to_fmt_config(&self) -> Result<Option<FmtConfig>, AnyError> {
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts
index 116b510f0..ddf597a0a 100644
--- a/cli/dts/lib.deno.unstable.d.ts
+++ b/cli/dts/lib.deno.unstable.d.ts
@@ -277,15 +277,20 @@ declare namespace Deno {
/** Emit the source alongside the source maps within a single file; requires
* `inlineSourceMap` or `sourceMap` to be set. Defaults to `false`. */
inlineSources?: boolean;
- /** Support JSX in `.tsx` files: `"react"`, `"preserve"`, `"react-native"`.
+ /** Support JSX in `.tsx` files: `"react"`, `"preserve"`, `"react-native"`,
+ * `"react-jsx", `"react-jsxdev"`.
* Defaults to `"react"`. */
- jsx?: "react" | "preserve" | "react-native";
+ jsx?: "react" | "preserve" | "react-native" | "react-jsx" | "react-jsx-dev";
/** Specify the JSX factory function to use when targeting react JSX emit,
* e.g. `React.createElement` or `h`. Defaults to `React.createElement`. */
jsxFactory?: string;
/** Specify the JSX fragment factory function to use when targeting react
* JSX emit, e.g. `Fragment`. Defaults to `React.Fragment`. */
jsxFragmentFactory?: string;
+ /** Declares the module specifier to be used for importing the `jsx` and
+ * `jsxs` factory functions when using jsx as `"react-jsx"` or
+ * `"react-jsxdev"`. Defaults to `"react"`. */
+ jsxImportSource?: string;
/** Resolve keyof to string valued property names only (no numbers or
* symbols). Defaults to `false`. */
keyofStringsOnly?: string;
diff --git a/cli/http_util.rs b/cli/http_util.rs
index 521acadfa..1a2aaf0d5 100644
--- a/cli/http_util.rs
+++ b/cli/http_util.rs
@@ -1,6 +1,7 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::auth_tokens::AuthToken;
+use deno_core::error::custom_error;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
use deno_core::url::Url;
@@ -123,11 +124,18 @@ pub async fn fetch_once(
if response.status().is_client_error() || response.status().is_server_error()
{
- let err = generic_error(format!(
- "Import '{}' failed: {}",
- args.url,
- response.status()
- ));
+ let err = if response.status() == StatusCode::NOT_FOUND {
+ custom_error(
+ "NotFound",
+ format!("Import '{}' failed, not found.", args.url),
+ )
+ } else {
+ generic_error(format!(
+ "Import '{}' failed: {}",
+ args.url,
+ response.status()
+ ))
+ };
return Err(err);
}
diff --git a/cli/lsp/cache.rs b/cli/lsp/cache.rs
index 1ea317240..b7bdb90c5 100644
--- a/cli/lsp/cache.rs
+++ b/cli/lsp/cache.rs
@@ -2,9 +2,11 @@
use crate::cache::CacherLoader;
use crate::cache::FetchCacher;
+use crate::config_file::ConfigFile;
use crate::flags::Flags;
use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver;
+use crate::resolver::JsxResolver;
use deno_core::error::anyhow;
use deno_core::error::AnyError;
@@ -13,6 +15,7 @@ use deno_runtime::permissions::Permissions;
use deno_runtime::tokio_util::create_basic_runtime;
use import_map::ImportMap;
use std::path::PathBuf;
+use std::sync::Arc;
use std::thread;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
@@ -27,7 +30,8 @@ pub(crate) struct CacheServer(mpsc::UnboundedSender<Request>);
impl CacheServer {
pub async fn new(
maybe_cache_path: Option<PathBuf>,
- maybe_import_map: Option<ImportMap>,
+ maybe_import_map: Option<Arc<ImportMap>>,
+ maybe_config_file: Option<ConfigFile>,
) -> Self {
let (tx, mut rx) = mpsc::unbounded_channel::<Request>();
let _join_handle = thread::spawn(move || {
@@ -39,8 +43,26 @@ impl CacheServer {
})
.await
.unwrap();
- let maybe_resolver =
- maybe_import_map.as_ref().map(ImportMapResolver::new);
+ let maybe_import_map_resolver =
+ maybe_import_map.map(ImportMapResolver::new);
+ let maybe_jsx_resolver = maybe_config_file
+ .as_ref()
+ .map(|cf| {
+ cf.to_maybe_jsx_import_source_module()
+ .map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
+ })
+ .flatten();
+ let maybe_resolver = if maybe_jsx_resolver.is_some() {
+ maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
+ } else {
+ maybe_import_map_resolver
+ .as_ref()
+ .map(|im| im.as_resolver())
+ };
+ let maybe_imports = maybe_config_file
+ .map(|cf| cf.to_maybe_imports().ok())
+ .flatten()
+ .flatten();
let mut cache = FetchCacher::new(
ps.dir.gen_cache.clone(),
ps.file_fetcher.clone(),
@@ -52,9 +74,9 @@ impl CacheServer {
let graph = deno_graph::create_graph(
roots,
false,
- None,
+ maybe_imports.clone(),
cache.as_mut_loader(),
- maybe_resolver.as_ref().map(|r| r.as_resolver()),
+ maybe_resolver,
None,
None,
)
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs
index dc5b0f004..ce7e4e36f 100644
--- a/cli/lsp/documents.rs
+++ b/cli/lsp/documents.rs
@@ -1,14 +1,16 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-use super::resolver::ImportMapResolver;
use super::text::LineIndex;
use super::tsc;
+use crate::config_file::ConfigFile;
use crate::file_fetcher::get_source_from_bytes;
use crate::file_fetcher::map_content_type;
use crate::file_fetcher::SUPPORTED_SCHEMES;
use crate::http_cache;
use crate::http_cache::HttpCache;
+use crate::resolver::ImportMapResolver;
+use crate::resolver::JsxResolver;
use crate::text_encoding;
use deno_ast::MediaType;
@@ -19,6 +21,7 @@ use deno_core::parking_lot::Mutex;
use deno_core::url;
use deno_core::ModuleSpecifier;
use lspower::lsp;
+use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fs;
@@ -131,6 +134,59 @@ impl IndexValid {
}
}
+// TODO(@kitsonk) expose the synthetic module from deno_graph
+#[derive(Debug)]
+struct SyntheticModule {
+ dependencies: BTreeMap<String, deno_graph::Resolved>,
+ specifier: ModuleSpecifier,
+}
+
+impl SyntheticModule {
+ pub fn new(
+ specifier: ModuleSpecifier,
+ dependencies: Vec<(String, Option<lsp::Range>)>,
+ maybe_resolver: Option<&dyn deno_graph::source::Resolver>,
+ ) -> Self {
+ let dependencies = dependencies
+ .iter()
+ .map(|(dep, maybe_range)| {
+ let range = to_deno_graph_range(&specifier, maybe_range.as_ref());
+ let result = if let Some(resolver) = maybe_resolver {
+ resolver.resolve(dep, &specifier).map_err(|err| {
+ if let Some(specifier_error) =
+ err.downcast_ref::<deno_graph::SpecifierError>()
+ {
+ deno_graph::ResolutionError::InvalidSpecifier(
+ specifier_error.clone(),
+ range.clone(),
+ )
+ } else {
+ deno_graph::ResolutionError::ResolverError(
+ Arc::new(err),
+ dep.to_string(),
+ range.clone(),
+ )
+ }
+ })
+ } else {
+ deno_core::resolve_import(dep, specifier.as_str()).map_err(|err| {
+ deno_graph::ResolutionError::ResolverError(
+ Arc::new(err.into()),
+ dep.to_string(),
+ range.clone(),
+ )
+ })
+ };
+ (dep.to_string(), Some(result.map(|s| (s, range))))
+ })
+ .collect();
+ Self {
+ dependencies,
+ specifier,
+ }
+ }
+}
+
#[derive(Debug)]
pub(crate) struct Document {
line_index: Arc<LineIndex>,
@@ -347,6 +403,32 @@ pub(crate) fn to_lsp_range(range: &deno_graph::Range) -> lsp::Range {
}
}
+fn to_deno_graph_range(
+ specifier: &ModuleSpecifier,
+ maybe_range: Option<&lsp::Range>,
+) -> deno_graph::Range {
+ let specifier = specifier.clone();
+ if let Some(range) = maybe_range {
+ deno_graph::Range {
+ specifier,
+ start: deno_graph::Position {
+ line: range.start.line as usize,
+ character: range.start.character as usize,
+ },
+ end: deno_graph::Position {
+ line: range.end.line as usize,
+ character: range.end.character as usize,
+ },
+ }
+ } else {
+ deno_graph::Range {
+ specifier,
+ start: deno_graph::Position::zeroed(),
+ end: deno_graph::Position::zeroed(),
+ }
+ }
+}
+
/// Recurse and collect specifiers that appear in the dependent map.
fn recurse_dependents(
specifier: &ModuleSpecifier,
@@ -376,8 +458,13 @@ struct Inner {
/// A map of documents that can either be "open" in the language server, or
/// just present on disk.
docs: HashMap<ModuleSpecifier, Document>,
+ /// Any imports to the context supplied by configuration files. This is like
+ /// the imports into the a module graph in CLI.
+ imports: HashMap<ModuleSpecifier, SyntheticModule>,
/// The optional import map that should be used when resolving dependencies.
maybe_import_map: Option<ImportMapResolver>,
+ /// The optional JSX resolver, which is used when JSX imports are configured.
+ maybe_jsx_resolver: Option<JsxResolver>,
redirects: HashMap<ModuleSpecifier, ModuleSpecifier>,
}
@@ -388,7 +475,9 @@ impl Inner {
dirty: true,
dependents_map: HashMap::default(),
docs: HashMap::default(),
+ imports: HashMap::default(),
maybe_import_map: None,
+ maybe_jsx_resolver: None,
redirects: HashMap::default(),
}
}
@@ -407,7 +496,7 @@ impl Inner {
version,
None,
content,
- self.maybe_import_map.as_ref().map(|r| r.as_resolver()),
+ self.get_maybe_resolver(),
)
} else {
let cache_filename = self.cache.get_cache_filename(&specifier)?;
@@ -421,7 +510,7 @@ impl Inner {
version,
maybe_headers,
content,
- self.maybe_import_map.as_ref().map(|r| r.as_resolver()),
+ self.get_maybe_resolver(),
)
};
self.dirty = true;
@@ -481,6 +570,14 @@ impl Inner {
version: i32,
changes: Vec<lsp::TextDocumentContentChangeEvent>,
) -> Result<(), AnyError> {
+ // this duplicates the .get_resolver() method, because there is no easy
+ // way to avoid the double borrow of self that occurs here with getting the
+ // mut doc out.
+ let maybe_resolver = if self.maybe_jsx_resolver.is_some() {
+ self.maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
+ } else {
+ self.maybe_import_map.as_ref().map(|im| im.as_resolver())
+ };
let doc = self.docs.get_mut(specifier).map_or_else(
|| {
Err(custom_error(
@@ -491,11 +588,7 @@ impl Inner {
Ok,
)?;
self.dirty = true;
- doc.change(
- version,
- changes,
- self.maybe_import_map.as_ref().map(|r| r.as_resolver()),
- )
+ doc.change(version, changes, maybe_resolver)
}
fn close(&mut self, specifier: &ModuleSpecifier) -> Result<(), AnyError> {
@@ -518,8 +611,7 @@ impl Inner {
specifier: &str,
referrer: &ModuleSpecifier,
) -> bool {
- let maybe_resolver =
- self.maybe_import_map.as_ref().map(|im| im.as_resolver());
+ let maybe_resolver = self.get_maybe_resolver();
let maybe_specifier = if let Some(resolver) = maybe_resolver {
resolver.resolve(specifier, referrer).ok()
} else {
@@ -604,6 +696,14 @@ impl Inner {
})
}
+ fn get_maybe_resolver(&self) -> Option<&dyn deno_graph::source::Resolver> {
+ if self.maybe_jsx_resolver.is_some() {
+ self.maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
+ } else {
+ self.maybe_import_map.as_ref().map(|im| im.as_resolver())
+ }
+ }
+
fn get_maybe_types_for_dependency(
&mut self,
dependency: &deno_graph::Dependency,
@@ -706,12 +806,13 @@ impl Inner {
language_id: LanguageId,
content: Arc<String>,
) {
+ let maybe_resolver = self.get_maybe_resolver();
let document_data = Document::open(
specifier.clone(),
version,
language_id,
content,
- self.maybe_import_map.as_ref().map(|r| r.as_resolver()),
+ maybe_resolver,
);
self.docs.insert(specifier, document_data);
self.dirty = true;
@@ -758,6 +859,12 @@ impl Inner {
} else {
results.push(None);
}
+ } else if let Some(Some(Ok((specifier, _)))) =
+ self.resolve_imports_dependency(&specifier)
+ {
+ // clone here to avoid double borrow of self
+ let specifier = specifier.clone();
+ results.push(self.resolve_dependency(&specifier));
} else {
results.push(None);
}
@@ -790,6 +897,22 @@ impl Inner {
}
}
+ /// Iterate through any "imported" modules, checking to see if a dependency
+ /// is available. This is used to provide "global" imports like the JSX import
+ /// source.
+ fn resolve_imports_dependency(
+ &self,
+ specifier: &str,
+ ) -> Option<&deno_graph::Resolved> {
+ for module in self.imports.values() {
+ let maybe_dep = module.dependencies.get(specifier);
+ if maybe_dep.is_some() {
+ return maybe_dep;
+ }
+ }
+ None
+ }
+
fn resolve_remote_specifier(
&self,
specifier: &ModuleSpecifier,
@@ -832,15 +955,6 @@ impl Inner {
}
}
- fn set_import_map(
- &mut self,
- maybe_import_map: Option<Arc<import_map::ImportMap>>,
- ) {
- // TODO update resolved dependencies?
- self.maybe_import_map = maybe_import_map.map(ImportMapResolver::new);
- self.dirty = true;
- }
-
fn set_location(&mut self, location: PathBuf) {
// TODO update resolved dependencies?
self.cache = HttpCache::new(&location);
@@ -886,6 +1000,36 @@ impl Inner {
self.get(specifier).map(|d| d.source.clone())
}
+ fn update_config(
+ &mut self,
+ maybe_import_map: Option<Arc<import_map::ImportMap>>,
+ maybe_config_file: Option<&ConfigFile>,
+ ) {
+ // TODO(@kitsonk) update resolved dependencies?
+ self.maybe_import_map = maybe_import_map.map(ImportMapResolver::new);
+ self.maybe_jsx_resolver = maybe_config_file
+ .map(|cf| {
+ cf.to_maybe_jsx_import_source_module()
+ .map(|im| JsxResolver::new(im, self.maybe_import_map.clone()))
+ })
+ .flatten();
+ if let Some(Ok(Some(imports))) =
+ maybe_config_file.map(|cf| cf.to_maybe_imports())
+ {
+ for (referrer, dependencies) in imports {
+ let dependencies =
+ dependencies.into_iter().map(|s| (s, None)).collect();
+ let module = SyntheticModule::new(
+ referrer.clone(),
+ dependencies,
+ self.get_maybe_resolver(),
+ );
+ self.imports.insert(referrer, module);
+ }
+ }
+ self.dirty = true;
+ }
+
fn version(&mut self, specifier: &ModuleSpecifier) -> Option<String> {
self.get(specifier).map(|d| {
d.maybe_lsp_version
@@ -1050,14 +1194,6 @@ impl Documents {
self.0.lock().resolve(specifiers, referrer)
}
- /// Set the optional import map for the document cache.
- pub fn set_import_map(
- &self,
- maybe_import_map: Option<Arc<import_map::ImportMap>>,
- ) {
- self.0.lock().set_import_map(maybe_import_map);
- }
-
/// Update the location of the on disk cache for the document store.
pub fn set_location(&self, location: PathBuf) {
self.0.lock().set_location(location)
@@ -1095,6 +1231,17 @@ impl Documents {
self.0.lock().text_info(specifier)
}
+ pub fn update_config(
+ &self,
+ maybe_import_map: Option<Arc<import_map::ImportMap>>,
+ maybe_config_file: Option<&ConfigFile>,
+ ) {
+ self
+ .0
+ .lock()
+ .update_config(maybe_import_map, maybe_config_file)
+ }
+
/// Return the version of a document in the document cache.
pub fn version(&self, specifier: &ModuleSpecifier) -> Option<String> {
self.0.lock().version(specifier)
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 89e286718..73d028e76 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -116,7 +116,7 @@ pub(crate) struct Inner {
/// file which will be used by the Deno LSP.
maybe_config_uri: Option<Url>,
/// An optional import map which is used to resolve modules.
- pub(crate) maybe_import_map: Option<ImportMap>,
+ pub(crate) maybe_import_map: Option<Arc<ImportMap>>,
/// The URL for the import map which is used to determine relative imports.
maybe_import_map_uri: Option<Url>,
/// A collection of measurements which instrument that performance of the LSP.
@@ -481,13 +481,13 @@ impl Inner {
)
})?
};
- let import_map =
- ImportMap::from_json(&import_map_url.to_string(), &import_map_json)?;
+ let import_map = Arc::new(ImportMap::from_json(
+ &import_map_url.to_string(),
+ &import_map_json,
+ )?);
self.maybe_import_map_uri = Some(import_map_url);
- self.maybe_import_map = Some(import_map.clone());
- self.documents.set_import_map(Some(Arc::new(import_map)));
+ self.maybe_import_map = Some(import_map);
} else {
- self.documents.set_import_map(None);
self.maybe_import_map = None;
}
self.performance.measure(mark);
@@ -700,6 +700,10 @@ impl Inner {
if let Err(err) = self.update_registries().await {
self.client.show_message(MessageType::Warning, err).await;
}
+ self.documents.update_config(
+ self.maybe_import_map.clone(),
+ self.maybe_config_file.as_ref(),
+ );
self.performance.measure(mark);
Ok(InitializeResult {
@@ -908,6 +912,10 @@ impl Inner {
if let Err(err) = self.diagnostics_server.update() {
error!("{}", err);
}
+ self.documents.update_config(
+ self.maybe_import_map.clone(),
+ self.maybe_config_file.as_ref(),
+ );
self.performance.measure(mark);
}
@@ -942,6 +950,10 @@ impl Inner {
}
}
if touched {
+ self.documents.update_config(
+ self.maybe_import_map.clone(),
+ self.maybe_config_file.as_ref(),
+ );
self.diagnostics_server.invalidate_all().await;
if let Err(err) = self.diagnostics_server.update() {
error!("Cannot update diagnostics: {}", err);
@@ -2624,6 +2636,7 @@ impl Inner {
CacheServer::new(
self.maybe_cache_path.clone(),
self.maybe_import_map.clone(),
+ self.maybe_config_file.clone(),
)
.await,
);
diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs
index 725ca07b3..27795e698 100644
--- a/cli/lsp/mod.rs
+++ b/cli/lsp/mod.rs
@@ -19,7 +19,6 @@ mod path_to_regex;
mod performance;
mod refactor;
mod registries;
-mod resolver;
mod semantic_tokens;
mod text;
mod tsc;
diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs
deleted file mode 100644
index 4f768b697..000000000
--- a/cli/lsp/resolver.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-
-use deno_core::error::AnyError;
-use deno_core::ModuleSpecifier;
-use deno_graph::source::Resolver;
-use import_map::ImportMap;
-use std::sync::Arc;
-
-#[derive(Debug)]
-pub(crate) struct ImportMapResolver(Arc<ImportMap>);
-
-impl ImportMapResolver {
- pub fn new(import_map: Arc<ImportMap>) -> Self {
- Self(import_map)
- }
-
- pub fn as_resolver(&self) -> &dyn Resolver {
- self
- }
-}
-
-impl Resolver for ImportMapResolver {
- fn resolve(
- &self,
- specifier: &str,
- referrer: &ModuleSpecifier,
- ) -> Result<ModuleSpecifier, AnyError> {
- self
- .0
- .resolve(specifier, referrer.as_str())
- .map_err(|err| err.into())
- }
-}
diff --git a/cli/main.rs b/cli/main.rs
index 7bf23556c..da7848834 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -60,6 +60,7 @@ use crate::fmt_errors::PrettyJsError;
use crate::module_loader::CliModuleLoader;
use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver;
+use crate::resolver::JsxResolver;
use crate::source_maps::apply_source_map;
use crate::tools::installer::infer_name_from_url;
use deno_ast::MediaType;
@@ -468,14 +469,29 @@ async fn info_command(
Permissions::allow_all(),
);
let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
- let maybe_resolver =
- ps.maybe_import_map.as_ref().map(ImportMapResolver::new);
+ let maybe_import_map_resolver =
+ ps.maybe_import_map.clone().map(ImportMapResolver::new);
+ let maybe_jsx_resolver = ps
+ .maybe_config_file
+ .as_ref()
+ .map(|cf| {
+ cf.to_maybe_jsx_import_source_module()
+ .map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
+ })
+ .flatten();
+ let maybe_resolver = if maybe_jsx_resolver.is_some() {
+ maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
+ } else {
+ maybe_import_map_resolver
+ .as_ref()
+ .map(|im| im.as_resolver())
+ };
let graph = deno_graph::create_graph(
vec![specifier],
false,
None,
&mut cache,
- maybe_resolver.as_ref().map(|r| r.as_resolver()),
+ maybe_resolver,
maybe_locker,
None,
)
@@ -637,19 +653,35 @@ async fn create_graph_and_maybe_check(
Permissions::allow_all(),
);
let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
- let maybe_imports = ps
+ let maybe_imports = if let Some(config_file) = &ps.maybe_config_file {
+ config_file.to_maybe_imports()?
+ } else {
+ None
+ };
+ let maybe_import_map_resolver =
+ ps.maybe_import_map.clone().map(ImportMapResolver::new);
+ let maybe_jsx_resolver = ps
.maybe_config_file
.as_ref()
- .map(|cf| cf.to_maybe_imports())
+ .map(|cf| {
+ cf.to_maybe_jsx_import_source_module()
+ .map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
+ })
.flatten();
- let maybe_resolver = ps.maybe_import_map.as_ref().map(ImportMapResolver::new);
+ let maybe_resolver = if maybe_jsx_resolver.is_some() {
+ maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
+ } else {
+ maybe_import_map_resolver
+ .as_ref()
+ .map(|im| im.as_resolver())
+ };
let graph = Arc::new(
deno_graph::create_graph(
vec![root],
false,
maybe_imports,
&mut cache,
- maybe_resolver.as_ref().map(|r| r.as_resolver()),
+ maybe_resolver,
maybe_locker,
None,
)
@@ -965,19 +997,34 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {
Permissions::allow_all(),
);
let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
- let maybe_imports = ps
+ let maybe_imports = if let Some(config_file) = &ps.maybe_config_file {
+ config_file.to_maybe_imports()?
+ } else {
+ None
+ };
+ let maybe_import_map_resolver =
+ ps.maybe_import_map.clone().map(ImportMapResolver::new);
+ let maybe_jsx_resolver = ps
.maybe_config_file
.as_ref()
- .map(|cf| cf.to_maybe_imports())
+ .map(|cf| {
+ cf.to_maybe_jsx_import_source_module()
+ .map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
+ })
.flatten();
- let maybe_resolver =
- ps.maybe_import_map.as_ref().map(ImportMapResolver::new);
+ let maybe_resolver = if maybe_jsx_resolver.is_some() {
+ maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
+ } else {
+ maybe_import_map_resolver
+ .as_ref()
+ .map(|im| im.as_resolver())
+ };
let graph = deno_graph::create_graph(
vec![main_module.clone()],
false,
maybe_imports,
&mut cache,
- maybe_resolver.as_ref().map(|r| r.as_resolver()),
+ maybe_resolver,
maybe_locker,
None,
)
diff --git a/cli/ops/runtime_compiler.rs b/cli/ops/runtime_compiler.rs
index 8f7a75146..b16fbcf69 100644
--- a/cli/ops/runtime_compiler.rs
+++ b/cli/ops/runtime_compiler.rs
@@ -7,6 +7,7 @@ use crate::emit;
use crate::errors::get_error_class_name;
use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver;
+use crate::resolver::JsxResolver;
use deno_core::error::custom_error;
use deno_core::error::generic_error;
@@ -71,14 +72,66 @@ struct EmitResult {
stats: emit::Stats,
}
+/// Provides inferred imported modules from configuration options, like the
+/// `"types"` and `"jsxImportSource"` imports.
fn to_maybe_imports(
referrer: &ModuleSpecifier,
maybe_options: Option<&HashMap<String, Value>>,
) -> Option<Vec<(ModuleSpecifier, Vec<String>)>> {
- let options = maybe_options.as_ref()?;
- let types_value = options.get("types")?;
- let types: Vec<String> = serde_json::from_value(types_value.clone()).ok()?;
- Some(vec![(referrer.clone(), types)])
+ let options = maybe_options?;
+ let mut imports = Vec::new();
+ if let Some(types_value) = options.get("types") {
+ if let Ok(types) =
+ serde_json::from_value::<Vec<String>>(types_value.clone())
+ {
+ imports.extend(types);
+ }
+ }
+ if let Some(jsx_value) = options.get("jsx") {
+ if let Ok(jsx) = serde_json::from_value::<String>(jsx_value.clone()) {
+ let jsx_import_source =
+ if let Some(jsx_import_source_value) = options.get("jsxImportSource") {
+ if let Ok(jsx_import_source) =
+ serde_json::from_value::<String>(jsx_import_source_value.clone())
+ {
+ jsx_import_source
+ } else {
+ "react".to_string()
+ }
+ } else {
+ "react".to_string()
+ };
+ match jsx.as_str() {
+ "react-jsx" => {
+ imports.push(format!("{}/jsx-runtime", jsx_import_source));
+ }
+ "react-jsxdev" => {
+ imports.push(format!("{}/jsx-dev-runtime", jsx_import_source));
+ }
+ _ => (),
+ }
+ }
+ }
+ if !imports.is_empty() {
+ Some(vec![(referrer.clone(), imports)])
+ } else {
+ None
+ }
+}
+
+/// Converts the compiler options to the JSX import source module that will be
+/// loaded when transpiling JSX.
+fn to_maybe_jsx_import_source_module(
+ maybe_options: Option<&HashMap<String, Value>>,
+) -> Option<String> {
+ let options = maybe_options?;
+ let jsx_value = options.get("jsx")?;
+ let jsx: String = serde_json::from_value(jsx_value.clone()).ok()?;
+ match jsx.as_str() {
+ "react-jsx" => Some("jsx-runtime".to_string()),
+ "react-jsxdev" => Some("jsx-dev-runtime".to_string()),
+ _ => None,
+ }
}
async fn op_emit(
@@ -108,7 +161,9 @@ async fn op_emit(
runtime_permissions.clone(),
))
};
- let maybe_import_map = if let Some(import_map_str) = args.import_map_path {
+ let maybe_import_map_resolver = if let Some(import_map_str) =
+ args.import_map_path
+ {
let import_map_specifier = resolve_url_or_path(&import_map_str)
.context(format!("Bad URL (\"{}\") for import map.", import_map_str))?;
let import_map = if let Some(value) = args.import_map {
@@ -126,23 +181,32 @@ async fn op_emit(
})?;
ImportMap::from_json(import_map_specifier.as_str(), &file.source)?
};
- Some(import_map)
+ Some(ImportMapResolver::new(Arc::new(import_map)))
} else if args.import_map.is_some() {
return Err(generic_error("An importMap was specified, but no importMapPath was provided, which is required."));
} else {
None
};
+ let maybe_jsx_resolver =
+ to_maybe_jsx_import_source_module(args.compiler_options.as_ref())
+ .map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()));
+ let maybe_resolver = if maybe_jsx_resolver.is_some() {
+ maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
+ } else {
+ maybe_import_map_resolver
+ .as_ref()
+ .map(|imr| imr.as_resolver())
+ };
let roots = vec![resolve_url_or_path(&root_specifier)?];
let maybe_imports =
to_maybe_imports(&roots[0], args.compiler_options.as_ref());
- let maybe_resolver = maybe_import_map.as_ref().map(ImportMapResolver::new);
let graph = Arc::new(
deno_graph::create_graph(
roots,
true,
maybe_imports,
cache.as_mut_loader(),
- maybe_resolver.as_ref().map(|r| r.as_resolver()),
+ maybe_resolver,
None,
None,
)
diff --git a/cli/proc_state.rs b/cli/proc_state.rs
index 0c81ab2e8..ddfd43034 100644
--- a/cli/proc_state.rs
+++ b/cli/proc_state.rs
@@ -5,6 +5,7 @@ use crate::colors;
use crate::compat;
use crate::compat::NodeEsmResolver;
use crate::config_file::ConfigFile;
+use crate::config_file::MaybeImportsResult;
use crate::deno_dir;
use crate::emit;
use crate::errors::get_module_graph_error_class;
@@ -15,6 +16,7 @@ use crate::http_cache;
use crate::lockfile::as_maybe_locker;
use crate::lockfile::Lockfile;
use crate::resolver::ImportMapResolver;
+use crate::resolver::JsxResolver;
use crate::source_maps::SourceMapGetter;
use crate::version;
@@ -79,7 +81,7 @@ pub struct Inner {
graph_data: Arc<Mutex<GraphData>>,
pub lockfile: Option<Arc<Mutex<Lockfile>>>,
pub maybe_config_file: Option<ConfigFile>,
- pub maybe_import_map: Option<ImportMap>,
+ pub maybe_import_map: Option<Arc<ImportMap>>,
pub maybe_inspector_server: Option<Arc<InspectorServer>>,
pub root_cert_store: Option<RootCertStore>,
pub blob_store: BlobStore,
@@ -200,7 +202,7 @@ impl ProcState {
None
};
- let maybe_import_map: Option<ImportMap> =
+ let maybe_import_map: Option<Arc<ImportMap>> =
match flags.import_map_path.as_ref() {
None => None,
Some(import_map_url) => {
@@ -218,7 +220,7 @@ impl ProcState {
))?;
let import_map =
ImportMap::from_json(import_map_specifier.as_str(), &file.source)?;
- Some(import_map)
+ Some(Arc::new(import_map))
}
};
@@ -258,10 +260,10 @@ impl ProcState {
/// Return any imports that should be brought into the scope of the module
/// graph.
- fn get_maybe_imports(&self) -> Option<Vec<(ModuleSpecifier, Vec<String>)>> {
+ fn get_maybe_imports(&self) -> MaybeImportsResult {
let mut imports = Vec::new();
if let Some(config_file) = &self.maybe_config_file {
- if let Some(config_imports) = config_file.to_maybe_imports() {
+ if let Some(config_imports) = config_file.to_maybe_imports()? {
imports.extend(config_imports);
}
}
@@ -269,9 +271,9 @@ impl ProcState {
imports.extend(compat::get_node_imports());
}
if imports.is_empty() {
- None
+ Ok(None)
} else {
- Some(imports)
+ Ok(Some(imports))
}
}
@@ -297,16 +299,30 @@ impl ProcState {
dynamic_permissions.clone(),
);
let maybe_locker = as_maybe_locker(self.lockfile.clone());
- let maybe_imports = self.get_maybe_imports();
+ let maybe_imports = self.get_maybe_imports()?;
let node_resolver = NodeEsmResolver::new(
- self.maybe_import_map.as_ref().map(ImportMapResolver::new),
+ self.maybe_import_map.clone().map(ImportMapResolver::new),
);
- let import_map_resolver =
- self.maybe_import_map.as_ref().map(ImportMapResolver::new);
+ let maybe_import_map_resolver =
+ self.maybe_import_map.clone().map(ImportMapResolver::new);
+ let maybe_jsx_resolver = self
+ .maybe_config_file
+ .as_ref()
+ .map(|cf| {
+ cf.to_maybe_jsx_import_source_module()
+ .map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
+ })
+ .flatten();
let maybe_resolver = if self.flags.compat {
Some(node_resolver.as_resolver())
+ } else if maybe_jsx_resolver.is_some() {
+ // the JSX resolver offloads to the import map if present, otherwise uses
+ // the default Deno explicit import resolution.
+ maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
} else {
- import_map_resolver.as_ref().map(|im| im.as_resolver())
+ maybe_import_map_resolver
+ .as_ref()
+ .map(|im| im.as_resolver())
};
// TODO(bartlomieju): this is very make-shift, is there an existing API
// that we could include it like with "maybe_imports"?
diff --git a/cli/resolver.rs b/cli/resolver.rs
index d3427c58b..da8fafe67 100644
--- a/cli/resolver.rs
+++ b/cli/resolver.rs
@@ -1,27 +1,29 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use deno_core::error::AnyError;
+use deno_core::resolve_import;
use deno_core::ModuleSpecifier;
use deno_graph::source::Resolver;
use import_map::ImportMap;
+use std::sync::Arc;
/// Wraps an import map to be used when building a deno_graph module graph.
/// This is done to avoid having `import_map` be a direct dependency of
/// `deno_graph`.
-#[derive(Debug)]
-pub(crate) struct ImportMapResolver<'a>(&'a ImportMap);
+#[derive(Debug, Clone)]
+pub(crate) struct ImportMapResolver(Arc<ImportMap>);
-impl<'a> ImportMapResolver<'a> {
- pub fn new(import_map: &'a ImportMap) -> Self {
+impl ImportMapResolver {
+ pub fn new(import_map: Arc<ImportMap>) -> Self {
Self(import_map)
}
- pub fn as_resolver(&'a self) -> &'a dyn Resolver {
+ pub fn as_resolver(&self) -> &dyn Resolver {
self
}
}
-impl Resolver for ImportMapResolver<'_> {
+impl Resolver for ImportMapResolver {
fn resolve(
&self,
specifier: &str,
@@ -33,3 +35,42 @@ impl Resolver for ImportMapResolver<'_> {
.map_err(|err| err.into())
}
}
+
+#[derive(Debug, Default, Clone)]
+pub(crate) struct JsxResolver {
+ jsx_import_source_module: String,
+ maybe_import_map_resolver: Option<ImportMapResolver>,
+}
+
+impl JsxResolver {
+ pub fn new(
+ jsx_import_source_module: String,
+ maybe_import_map_resolver: Option<ImportMapResolver>,
+ ) -> Self {
+ Self {
+ jsx_import_source_module,
+ maybe_import_map_resolver,
+ }
+ }
+
+ pub fn as_resolver(&self) -> &dyn Resolver {
+ self
+ }
+}
+
+impl Resolver for JsxResolver {
+ fn jsx_import_source_module(&self) -> &str {
+ self.jsx_import_source_module.as_str()
+ }
+
+ fn resolve(
+ &self,
+ specifier: &str,
+ referrer: &ModuleSpecifier,
+ ) -> Result<ModuleSpecifier, AnyError> {
+ self.maybe_import_map_resolver.as_ref().map_or_else(
+ || resolve_import(specifier, referrer.as_str()).map_err(|err| err.into()),
+ |r| r.resolve(specifier, referrer),
+ )
+ }
+}
diff --git a/cli/schemas/config-file.v1.json b/cli/schemas/config-file.v1.json
index 8f9866f17..b8c4622a1 100644
--- a/cli/schemas/config-file.v1.json
+++ b/cli/schemas/config-file.v1.json
@@ -63,6 +63,12 @@
"default": "React.Fragment",
"markdownDescription": "Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'.\n\nSee more: https://www.typescriptlang.org/tsconfig#jsxFragmentFactory"
},
+ "jsxImportSource": {
+ "description": "Specify module specifier used to import the JSX factory functions when using jsx: 'react-jsx*'.",
+ "type": "string",
+ "default": "react",
+ "markdownDescription": "Specify module specifier used to import the JSX factory functions when using jsx: `react-jsx*`.\n\nSee more: https://www.typescriptlang.org/tsconfig/#jsxImportSource"
+ },
"keyofStringsOnly": {
"description": "Make keyof only return strings instead of string, numbers or symbols. Legacy option.",
"type": "boolean",
@@ -73,7 +79,9 @@
"description": "Specify a set of bundled library declaration files that describe the target runtime environment.",
"type": "array",
"uniqueItems": true,
- "default": ["deno.window"],
+ "default": [
+ "deno.window"
+ ],
"items": {
"type": "string"
},
diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs
index 6e009bd20..fc4f6dbc6 100644
--- a/cli/tests/integration/lsp_tests.rs
+++ b/cli/tests/integration/lsp_tests.rs
@@ -3684,3 +3684,82 @@ fn lsp_lint_with_config() {
}
shutdown(&mut client);
}
+
+#[test]
+fn lsp_jsx_import_source_pragma() {
+ let _g = http_server();
+ let mut client = init("initialize_params.json");
+ did_open(
+ &mut client,
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.tsx",
+ "languageId": "typescriptreact",
+ "version": 1,
+ "text":
+"/** @jsxImportSource http://localhost:4545/jsx */
+
+function A() {
+ return \"hello\";
+}
+
+export function B() {
+ return <A></A>;
+}
+",
+ }
+ }),
+ );
+ let (maybe_res, maybe_err) = client
+ .write_request::<_, _, Value>(
+ "deno/cache",
+ json!({
+ "referrer": {
+ "uri": "file:///a/file.tsx",
+ },
+ "uris": [
+ {
+ "uri": "http://127.0.0.1:4545/jsx/jsx-runtime",
+ }
+ ],
+ }),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ assert!(maybe_res.is_some());
+ let (maybe_res, maybe_err) = client
+ .write_request::<_, _, Value>(
+ "textDocument/hover",
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.tsx"
+ },
+ "position": {
+ "line": 0,
+ "character": 25
+ }
+ }),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ assert_eq!(
+ maybe_res,
+ Some(json!({
+ "contents": {
+ "kind": "markdown",
+ "value": "**Resolved Dependency**\n\n**Code**: http&#8203;://localhost:4545/jsx/jsx-runtime\n",
+ },
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 21
+ },
+ "end": {
+ "line": 0,
+ "character": 46
+ }
+ }
+ }))
+ );
+ shutdown(&mut client);
+}
diff --git a/cli/tests/integration/mod.rs b/cli/tests/integration/mod.rs
index 251fca515..21ffc5627 100644
--- a/cli/tests/integration/mod.rs
+++ b/cli/tests/integration/mod.rs
@@ -1100,7 +1100,7 @@ fn basic_auth_tokens() {
eprintln!("{}", stderr_str);
assert!(stderr_str.contains(
- "Import 'http://127.0.0.1:4554/001_hello.js' failed: 404 Not Found"
+ "Import 'http://127.0.0.1:4554/001_hello.js' failed, not found."
));
let output = util::deno_cmd()
diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs
index f290c6e62..214eb8ece 100644
--- a/cli/tests/integration/run_tests.rs
+++ b/cli/tests/integration/run_tests.rs
@@ -1215,6 +1215,118 @@ itest!(jsx_import_from_ts {
output: "jsx_import_from_ts.ts.out",
});
+itest!(jsx_import_source_pragma {
+ args: "run --reload jsx_import_source_pragma.tsx",
+ output: "jsx_import_source.out",
+ http_server: true,
+});
+
+itest!(jsx_import_source_pragma_with_config {
+ args: "run --reload --config jsx/deno-jsx.jsonc jsx_import_source_pragma.tsx",
+ output: "jsx_import_source.out",
+ http_server: true,
+});
+
+itest!(jsx_import_source_pragma_with_dev_config {
+ args:
+ "run --reload --config jsx/deno-jsxdev.jsonc jsx_import_source_pragma.tsx",
+ output: "jsx_import_source_dev.out",
+ http_server: true,
+});
+
+itest!(jsx_import_source_no_pragma {
+ args:
+ "run --reload --config jsx/deno-jsx.jsonc jsx_import_source_no_pragma.tsx",
+ output: "jsx_import_source.out",
+ http_server: true,
+});
+
+itest!(jsx_import_source_no_pragma_dev {
+ args: "run --reload --config jsx/deno-jsxdev.jsonc jsx_import_source_no_pragma.tsx",
+ output: "jsx_import_source_dev.out",
+ http_server: true,
+});
+
+itest!(jsx_import_source_pragma_import_map {
+ args: "run --reload --import-map jsx/import-map.json jsx_import_source_pragma_import_map.tsx",
+ output: "jsx_import_source_import_map.out",
+ http_server: true,
+});
+
+itest!(jsx_import_source_pragma_import_map_dev {
+ args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsxdev-import-map.jsonc jsx_import_source_pragma_import_map.tsx",
+ output: "jsx_import_source_import_map_dev.out",
+ http_server: true,
+});
+
+itest!(jsx_import_source_import_map {
+ args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsx-import-map.jsonc jsx_import_source_no_pragma.tsx",
+ output: "jsx_import_source_import_map.out",
+ http_server: true,
+});
+
+itest!(jsx_import_source_import_map_dev {
+ args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsxdev-import-map.jsonc jsx_import_source_no_pragma.tsx",
+ output: "jsx_import_source_import_map_dev.out",
+ http_server: true,
+});
+
+itest!(jsx_import_source_pragma_no_check {
+ args: "run --reload --no-check jsx_import_source_pragma.tsx",
+ output: "jsx_import_source.out",
+ http_server: true,
+});
+
+itest!(jsx_import_source_pragma_with_config_no_check {
+ args: "run --reload --config jsx/deno-jsx.jsonc --no-check jsx_import_source_pragma.tsx",
+ output: "jsx_import_source.out",
+ http_server: true,
+});
+
+// itest!(jsx_import_source_pragma_with_dev_config_no_check {
+// args:
+// "run --reload --config jsx/deno-jsxdev.jsonc --no-check jsx_import_source_pragma.tsx",
+// output: "jsx_import_source_dev.out",
+// http_server: true,
+// });
+
+itest!(jsx_import_source_no_pragma_no_check {
+ args:
+ "run --reload --config jsx/deno-jsx.jsonc --no-check jsx_import_source_no_pragma.tsx",
+ output: "jsx_import_source.out",
+ http_server: true,
+});
+
+// itest!(jsx_import_source_no_pragma_dev_no_check {
+// args: "run --reload --config jsx/deno-jsxdev.jsonc --no-check jsx_import_source_no_pragma.tsx",
+// output: "jsx_import_source_dev.out",
+// http_server: true,
+// });
+
+itest!(jsx_import_source_pragma_import_map_no_check {
+ args: "run --reload --import-map jsx/import-map.json --no-check jsx_import_source_pragma_import_map.tsx",
+ output: "jsx_import_source_import_map.out",
+ http_server: true,
+});
+
+// itest!(jsx_import_source_pragma_import_map_dev_no_check {
+// args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsxdev-import-map.jsonc --no-check jsx_import_source_pragma_import_map.tsx",
+// output: "jsx_import_source_import_map_dev.out",
+// http_server: true,
+// });
+
+itest!(jsx_import_source_import_map_no_check {
+ args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsx-import-map.jsonc --no-check jsx_import_source_no_pragma.tsx",
+ output: "jsx_import_source_import_map.out",
+ http_server: true,
+});
+
+// itest!(jsx_import_source_import_map_dev_no_check {
+// args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsxdev-import-map.jsonc --no-check jsx_import_source_no_pragma.tsx",
+// output: "jsx_import_source_import_map_dev.out",
+// http_server: true,
+// });
+
// TODO(#11128): Flaky. Re-enable later.
// itest!(single_compile_with_reload {
// args: "run --reload --allow-read single_compile_with_reload.ts",
diff --git a/cli/tests/testdata/compiler_api_test.ts b/cli/tests/testdata/compiler_api_test.ts
index 9870908d1..42d6f54eb 100644
--- a/cli/tests/testdata/compiler_api_test.ts
+++ b/cli/tests/testdata/compiler_api_test.ts
@@ -557,3 +557,81 @@ Deno.test({
assertEquals(sourceMap.sourcesContent.length, 1);
},
});
+
+Deno.test({
+ name: "Deno.emit() - JSX import source pragma",
+ async fn() {
+ const { files } = await Deno.emit(
+ "file:///a.tsx",
+ {
+ sources: {
+ "file:///a.tsx": `/** @jsxImportSource https://example.com/jsx */
+
+ export function App() {
+ return (
+ <div><></></div>
+ );
+ }`,
+ "https://example.com/jsx/jsx-runtime": `export function jsx(
+ _type,
+ _props,
+ _key,
+ _source,
+ _self,
+ ) {}
+ export const jsxs = jsx;
+ export const jsxDEV = jsx;
+ export const Fragment = Symbol("Fragment");
+ console.log("imported", import.meta.url);
+ `,
+ },
+ },
+ );
+ assert(files["file:///a.tsx.js"]);
+ assert(
+ files["file:///a.tsx.js"].startsWith(
+ `import { Fragment as _Fragment, jsx as _jsx } from "https://example.com/jsx/jsx-runtime";\n`,
+ ),
+ );
+ },
+});
+
+Deno.test({
+ name: "Deno.emit() - JSX import source no pragma",
+ async fn() {
+ const { files } = await Deno.emit(
+ "file:///a.tsx",
+ {
+ compilerOptions: {
+ jsx: "react-jsx",
+ jsxImportSource: "https://example.com/jsx",
+ },
+ sources: {
+ "file:///a.tsx": `export function App() {
+ return (
+ <div><></></div>
+ );
+ }`,
+ "https://example.com/jsx/jsx-runtime": `export function jsx(
+ _type,
+ _props,
+ _key,
+ _source,
+ _self,
+ ) {}
+ export const jsxs = jsx;
+ export const jsxDEV = jsx;
+ export const Fragment = Symbol("Fragment");
+ console.log("imported", import.meta.url);
+ `,
+ },
+ },
+ );
+ assert(files["file:///a.tsx.js"]);
+ assert(
+ files["file:///a.tsx.js"].startsWith(
+ `import { Fragment as _Fragment, jsx as _jsx } from "https://example.com/jsx/jsx-runtime";\n`,
+ ),
+ );
+ },
+});
diff --git a/cli/tests/testdata/jsx/deno-jsx-import-map.jsonc b/cli/tests/testdata/jsx/deno-jsx-import-map.jsonc
new file mode 100644
index 000000000..5adbfa8b5
--- /dev/null
+++ b/cli/tests/testdata/jsx/deno-jsx-import-map.jsonc
@@ -0,0 +1,6 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "jsx"
+ }
+}
diff --git a/cli/tests/testdata/jsx/deno-jsx.jsonc b/cli/tests/testdata/jsx/deno-jsx.jsonc
new file mode 100644
index 000000000..311409ea3
--- /dev/null
+++ b/cli/tests/testdata/jsx/deno-jsx.jsonc
@@ -0,0 +1,6 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "http://localhost:4545/jsx"
+ }
+}
diff --git a/cli/tests/testdata/jsx/deno-jsxdev-import-map.jsonc b/cli/tests/testdata/jsx/deno-jsxdev-import-map.jsonc
new file mode 100644
index 000000000..7481d5a2d
--- /dev/null
+++ b/cli/tests/testdata/jsx/deno-jsxdev-import-map.jsonc
@@ -0,0 +1,6 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsxdev",
+ "jsxImportSource": "jsx"
+ }
+}
diff --git a/cli/tests/testdata/jsx/deno-jsxdev.jsonc b/cli/tests/testdata/jsx/deno-jsxdev.jsonc
new file mode 100644
index 000000000..ae5bdf9f1
--- /dev/null
+++ b/cli/tests/testdata/jsx/deno-jsxdev.jsonc
@@ -0,0 +1,6 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsxdev",
+ "jsxImportSource": "http://localhost:4545/jsx"
+ }
+}
diff --git a/cli/tests/testdata/jsx/import-map.json b/cli/tests/testdata/jsx/import-map.json
new file mode 100644
index 000000000..baab76f20
--- /dev/null
+++ b/cli/tests/testdata/jsx/import-map.json
@@ -0,0 +1,6 @@
+{
+ "imports": {
+ "jsx/jsx-runtime": "http://localhost:4545/jsx/jsx-runtime/index.ts",
+ "jsx/jsx-dev-runtime": "http://localhost:4545/jsx/jsx-dev-runtime/index.ts"
+ }
+}
diff --git a/cli/tests/testdata/jsx/jsx-dev-runtime/index.ts b/cli/tests/testdata/jsx/jsx-dev-runtime/index.ts
new file mode 100644
index 000000000..15e2029c8
--- /dev/null
+++ b/cli/tests/testdata/jsx/jsx-dev-runtime/index.ts
@@ -0,0 +1,12 @@
+// deno-lint-ignore-file no-explicit-any
+export function jsx(
+ _type: any,
+ _props: any,
+ _key: any,
+ _source: any,
+ _self: any,
+) {}
+export const jsxs = jsx;
+export const jsxDEV = jsx;
+export const Fragment = Symbol("Fragment");
+console.log("imported", import.meta.url);
diff --git a/cli/tests/testdata/jsx/jsx-runtime/index.ts b/cli/tests/testdata/jsx/jsx-runtime/index.ts
new file mode 100644
index 000000000..15e2029c8
--- /dev/null
+++ b/cli/tests/testdata/jsx/jsx-runtime/index.ts
@@ -0,0 +1,12 @@
+// deno-lint-ignore-file no-explicit-any
+export function jsx(
+ _type: any,
+ _props: any,
+ _key: any,
+ _source: any,
+ _self: any,
+) {}
+export const jsxs = jsx;
+export const jsxDEV = jsx;
+export const Fragment = Symbol("Fragment");
+console.log("imported", import.meta.url);
diff --git a/cli/tests/testdata/jsx_import_source.out b/cli/tests/testdata/jsx_import_source.out
new file mode 100644
index 000000000..b9555987a
--- /dev/null
+++ b/cli/tests/testdata/jsx_import_source.out
@@ -0,0 +1,2 @@
+[WILDCARD]
+imported http://localhost:4545/jsx/jsx-runtime
diff --git a/cli/tests/testdata/jsx_import_source_dev.out b/cli/tests/testdata/jsx_import_source_dev.out
new file mode 100644
index 000000000..38d7a12f0
--- /dev/null
+++ b/cli/tests/testdata/jsx_import_source_dev.out
@@ -0,0 +1,2 @@
+[WILDCARD]
+imported http://localhost:4545/jsx/jsx-dev-runtime
diff --git a/cli/tests/testdata/jsx_import_source_import_map.out b/cli/tests/testdata/jsx_import_source_import_map.out
new file mode 100644
index 000000000..0d3238967
--- /dev/null
+++ b/cli/tests/testdata/jsx_import_source_import_map.out
@@ -0,0 +1,2 @@
+[WILDCARD]
+imported http://localhost:4545/jsx/jsx-runtime/index.ts
diff --git a/cli/tests/testdata/jsx_import_source_import_map_dev.out b/cli/tests/testdata/jsx_import_source_import_map_dev.out
new file mode 100644
index 000000000..56f514d90
--- /dev/null
+++ b/cli/tests/testdata/jsx_import_source_import_map_dev.out
@@ -0,0 +1,2 @@
+[WILDCARD]
+imported http://localhost:4545/jsx/jsx-dev-runtime/index.ts
diff --git a/cli/tests/testdata/jsx_import_source_no_pragma.tsx b/cli/tests/testdata/jsx_import_source_no_pragma.tsx
new file mode 100644
index 000000000..2c756054f
--- /dev/null
+++ b/cli/tests/testdata/jsx_import_source_no_pragma.tsx
@@ -0,0 +1,7 @@
+function A() {
+ return "hello";
+}
+
+export function B() {
+ return <A></A>;
+}
diff --git a/cli/tests/testdata/jsx_import_source_pragma.tsx b/cli/tests/testdata/jsx_import_source_pragma.tsx
new file mode 100644
index 000000000..c19e53d4f
--- /dev/null
+++ b/cli/tests/testdata/jsx_import_source_pragma.tsx
@@ -0,0 +1,9 @@
+/** @jsxImportSource http://localhost:4545/jsx */
+
+function A() {
+ return "hello";
+}
+
+export function B() {
+ return <A></A>;
+}
diff --git a/cli/tests/testdata/jsx_import_source_pragma_import_map.tsx b/cli/tests/testdata/jsx_import_source_pragma_import_map.tsx
new file mode 100644
index 000000000..548365f18
--- /dev/null
+++ b/cli/tests/testdata/jsx_import_source_pragma_import_map.tsx
@@ -0,0 +1,9 @@
+/** @jsxImportSource jsx */
+
+function A() {
+ return "hello";
+}
+
+export function B() {
+ return <A></A>;
+}
diff --git a/cli/tools/doc.rs b/cli/tools/doc.rs
index aa9d913a0..cc37df06d 100644
--- a/cli/tools/doc.rs
+++ b/cli/tools/doc.rs
@@ -38,7 +38,7 @@ impl Loader for StubDocLoader {
#[derive(Debug)]
struct DocResolver {
- import_map: Option<ImportMap>,
+ import_map: Option<Arc<ImportMap>>,
}
impl Resolver for DocResolver {
diff --git a/cli/tools/repl.rs b/cli/tools/repl.rs
index f3ba626af..b6874f574 100644
--- a/cli/tools/repl.rs
+++ b/cli/tools/repl.rs
@@ -668,8 +668,11 @@ impl ReplSession {
imports_not_used_as_values: ImportsNotUsedAsValues::Preserve,
// JSX is not supported in the REPL
transform_jsx: false,
+ jsx_automatic: false,
+ jsx_development: false,
jsx_factory: "React.createElement".into(),
jsx_fragment_factory: "React.Fragment".into(),
+ jsx_import_source: None,
repl_imports: true,
},
)?
diff --git a/cli/tools/test.rs b/cli/tools/test.rs
index d883f18a3..fba178202 100644
--- a/cli/tools/test.rs
+++ b/cli/tools/test.rs
@@ -18,6 +18,7 @@ use crate::lockfile;
use crate::ops;
use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver;
+use crate::resolver::JsxResolver;
use crate::tools::coverage::CoverageCollector;
use deno_ast::swc::common::comments::CommentKind;
@@ -1053,14 +1054,21 @@ pub async fn run_tests_with_watch(
let paths_to_watch = paths_to_watch.clone();
let paths_to_watch_clone = paths_to_watch.clone();
- let maybe_resolver =
- ps.maybe_import_map.as_ref().map(ImportMapResolver::new);
+ let maybe_import_map_resolver =
+ ps.maybe_import_map.clone().map(ImportMapResolver::new);
+ let maybe_jsx_resolver = ps
+ .maybe_config_file
+ .as_ref()
+ .map(|cf| {
+ cf.to_maybe_jsx_import_source_module()
+ .map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
+ })
+ .flatten();
let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
let maybe_imports = ps
.maybe_config_file
.as_ref()
- .map(|cf| cf.to_maybe_imports())
- .flatten();
+ .map(|cf| cf.to_maybe_imports());
let files_changed = changed.is_some();
let include = include.clone();
let ignore = ignore.clone();
@@ -1081,13 +1089,24 @@ pub async fn run_tests_with_watch(
.filter_map(|url| deno_core::resolve_url(url.as_str()).ok())
.collect()
};
-
+ let maybe_imports = if let Some(result) = maybe_imports {
+ result?
+ } else {
+ None
+ };
+ let maybe_resolver = if maybe_jsx_resolver.is_some() {
+ maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
+ } else {
+ maybe_import_map_resolver
+ .as_ref()
+ .map(|im| im.as_resolver())
+ };
let graph = deno_graph::create_graph(
test_modules.clone(),
false,
maybe_imports,
cache.as_mut_loader(),
- maybe_resolver.as_ref().map(|r| r.as_resolver()),
+ maybe_resolver,
maybe_locker,
None,
)
diff --git a/cli/tsc.rs b/cli/tsc.rs
index bb377c5d8..5f5c09539 100644
--- a/cli/tsc.rs
+++ b/cli/tsc.rs
@@ -414,6 +414,27 @@ pub struct ResolveArgs {
pub specifiers: Vec<String>,
}
+fn resolve_specifier(
+ state: &mut State,
+ specifier: &ModuleSpecifier,
+) -> (String, String) {
+ let media_type = state
+ .graph
+ .get(specifier)
+ .map_or(&MediaType::Unknown, |m| &m.media_type);
+ let specifier_str = match specifier.scheme() {
+ "data" | "blob" => {
+ let specifier_str = hash_url(specifier, media_type);
+ state
+ .data_url_map
+ .insert(specifier_str.clone(), specifier.clone());
+ specifier_str
+ }
+ _ => specifier.to_string(),
+ };
+ (specifier_str, media_type.as_ts_extension().into())
+}
+
fn op_resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
let v: ResolveArgs = serde_json::from_value(args)
.context("Invalid request from JavaScript for \"op_resolve\".")?;
@@ -434,30 +455,31 @@ fn op_resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
MediaType::from(specifier).as_ts_extension().to_string(),
));
} else {
- let resolved_dependency =
- match state.graph.resolve_dependency(specifier, &referrer, true) {
- Some(resolved_specifier) => {
- let media_type = state
- .graph
- .get(resolved_specifier)
- .map_or(&MediaType::Unknown, |m| &m.media_type);
- let resolved_specifier_str = match resolved_specifier.scheme() {
- "data" | "blob" => {
- let specifier_str = hash_url(resolved_specifier, media_type);
- state
- .data_url_map
- .insert(specifier_str.clone(), resolved_specifier.clone());
- specifier_str
- }
- _ => resolved_specifier.to_string(),
- };
- (resolved_specifier_str, media_type.as_ts_extension().into())
- }
- None => (
- "deno:///missing_dependency.d.ts".to_string(),
- ".d.ts".to_string(),
- ),
- };
+ // here, we try to resolve the specifier via the referrer, but if we can't
+ // we will try to resolve the specifier via the configuration file, if
+ // present, finally defaulting to a "placeholder" specifier. This handles
+ // situations like the jsxImportSource, which tsc tries to resolve the
+ // import source from a JSX module, but the module graph only contains the
+ // import as a dependency of the configuration file.
+ let resolved_dependency = if let Some(resolved_specifier) = state
+ .graph
+ .resolve_dependency(specifier, &referrer, true)
+ .cloned()
+ {
+ resolve_specifier(state, &resolved_specifier)
+ } else if let Some(resolved_specifier) = state
+ .maybe_config_specifier
+ .as_ref()
+ .map(|cf| state.graph.resolve_dependency(specifier, cf, true).cloned())
+ .flatten()
+ {
+ resolve_specifier(state, &resolved_specifier)
+ } else {
+ (
+ "deno:///missing_dependency.d.ts".to_string(),
+ ".d.ts".to_string(),
+ )
+ };
resolved.push(resolved_dependency);
}
}