mirror of
https://github.com/denoland/deno.git
synced 2025-01-11 08:33:43 -05:00
parent
45425c1146
commit
f5eb177f50
40 changed files with 1115 additions and 173 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -782,9 +782,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deno_doc"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08abadd9f3ede74c5ba6e3d9a688ecfe160cf7fb2988ae133ef4e3d591d091e7"
|
||||
checksum = "6d2d76b6b75a6fbfda0f529e310fc3cab960f4219403280b430ce93dcf8cf9a2"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"deno_ast",
|
||||
|
@ -827,9 +827,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deno_graph"
|
||||
version = "0.10.0"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6df7e1b135780d9424ce4fb9a8927983d27d2c094922cb84b5fd5d72a4c85b82"
|
||||
checksum = "f6d84ddee0cf83bf295721be792b6769b92214983bda29d52c2b05a89d1e968f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if 1.0.0",
|
||||
|
|
|
@ -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" }
|
||||
|
|
143
cli/ast/mod.rs
143
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,
|
||||
|
@ -495,6 +526,112 @@ function App() {
|
|||
assert_eq!(&code[..expected.len()], expected);
|
||||
}
|
||||
|
||||
#[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")
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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> {
|
||||
|
|
9
cli/dts/lib.deno.unstable.d.ts
vendored
9
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -19,7 +19,6 @@ mod path_to_regex;
|
|||
mod performance;
|
||||
mod refactor;
|
||||
mod registries;
|
||||
mod resolver;
|
||||
mod semantic_tokens;
|
||||
mod text;
|
||||
mod tsc;
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
71
cli/main.rs
71
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,
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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"?
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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​://localhost:4545/jsx/jsx-runtime\n",
|
||||
},
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 21
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 46
|
||||
}
|
||||
}
|
||||
}))
|
||||
);
|
||||
shutdown(&mut client);
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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",
|
||||
|
|
78
cli/tests/testdata/compiler_api_test.ts
vendored
78
cli/tests/testdata/compiler_api_test.ts
vendored
|
@ -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`,
|
||||
),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
6
cli/tests/testdata/jsx/deno-jsx-import-map.jsonc
vendored
Normal file
6
cli/tests/testdata/jsx/deno-jsx-import-map.jsonc
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "jsx"
|
||||
}
|
||||
}
|
6
cli/tests/testdata/jsx/deno-jsx.jsonc
vendored
Normal file
6
cli/tests/testdata/jsx/deno-jsx.jsonc
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "http://localhost:4545/jsx"
|
||||
}
|
||||
}
|
6
cli/tests/testdata/jsx/deno-jsxdev-import-map.jsonc
vendored
Normal file
6
cli/tests/testdata/jsx/deno-jsxdev-import-map.jsonc
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsxdev",
|
||||
"jsxImportSource": "jsx"
|
||||
}
|
||||
}
|
6
cli/tests/testdata/jsx/deno-jsxdev.jsonc
vendored
Normal file
6
cli/tests/testdata/jsx/deno-jsxdev.jsonc
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsxdev",
|
||||
"jsxImportSource": "http://localhost:4545/jsx"
|
||||
}
|
||||
}
|
6
cli/tests/testdata/jsx/import-map.json
vendored
Normal file
6
cli/tests/testdata/jsx/import-map.json
vendored
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
12
cli/tests/testdata/jsx/jsx-dev-runtime/index.ts
vendored
Normal file
12
cli/tests/testdata/jsx/jsx-dev-runtime/index.ts
vendored
Normal file
|
@ -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);
|
12
cli/tests/testdata/jsx/jsx-runtime/index.ts
vendored
Normal file
12
cli/tests/testdata/jsx/jsx-runtime/index.ts
vendored
Normal file
|
@ -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);
|
2
cli/tests/testdata/jsx_import_source.out
vendored
Normal file
2
cli/tests/testdata/jsx_import_source.out
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[WILDCARD]
|
||||
imported http://localhost:4545/jsx/jsx-runtime
|
2
cli/tests/testdata/jsx_import_source_dev.out
vendored
Normal file
2
cli/tests/testdata/jsx_import_source_dev.out
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[WILDCARD]
|
||||
imported http://localhost:4545/jsx/jsx-dev-runtime
|
2
cli/tests/testdata/jsx_import_source_import_map.out
vendored
Normal file
2
cli/tests/testdata/jsx_import_source_import_map.out
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[WILDCARD]
|
||||
imported http://localhost:4545/jsx/jsx-runtime/index.ts
|
2
cli/tests/testdata/jsx_import_source_import_map_dev.out
vendored
Normal file
2
cli/tests/testdata/jsx_import_source_import_map_dev.out
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
[WILDCARD]
|
||||
imported http://localhost:4545/jsx/jsx-dev-runtime/index.ts
|
7
cli/tests/testdata/jsx_import_source_no_pragma.tsx
vendored
Normal file
7
cli/tests/testdata/jsx_import_source_no_pragma.tsx
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
function A() {
|
||||
return "hello";
|
||||
}
|
||||
|
||||
export function B() {
|
||||
return <A></A>;
|
||||
}
|
9
cli/tests/testdata/jsx_import_source_pragma.tsx
vendored
Normal file
9
cli/tests/testdata/jsx_import_source_pragma.tsx
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
/** @jsxImportSource http://localhost:4545/jsx */
|
||||
|
||||
function A() {
|
||||
return "hello";
|
||||
}
|
||||
|
||||
export function B() {
|
||||
return <A></A>;
|
||||
}
|
9
cli/tests/testdata/jsx_import_source_pragma_import_map.tsx
vendored
Normal file
9
cli/tests/testdata/jsx_import_source_pragma_import_map.tsx
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
/** @jsxImportSource jsx */
|
||||
|
||||
function A() {
|
||||
return "hello";
|
||||
}
|
||||
|
||||
export function B() {
|
||||
return <A></A>;
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
)?
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
70
cli/tsc.rs
70
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -566,7 +566,9 @@ async fn absolute_redirect(
|
|||
Ok(file_resp)
|
||||
}
|
||||
|
||||
async fn main_server(req: Request<Body>) -> hyper::Result<Response<Body>> {
|
||||
async fn main_server(
|
||||
req: Request<Body>,
|
||||
) -> Result<Response<Body>, hyper::http::Error> {
|
||||
return match (req.method(), req.uri().path()) {
|
||||
(&hyper::Method::POST, "/echo_server") => {
|
||||
let (parts, body) = req.into_parts();
|
||||
|
@ -849,6 +851,27 @@ async fn main_server(req: Request<Body>) -> hyper::Result<Response<Body>> {
|
|||
let version = format!("{:?}", req.version());
|
||||
Ok(Response::new(version.into()))
|
||||
}
|
||||
(_, "/jsx/jsx-runtime") | (_, "/jsx/jsx-dev-runtime") => {
|
||||
let mut res = Response::new(Body::from(
|
||||
r#"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);
|
||||
"#,
|
||||
));
|
||||
res.headers_mut().insert(
|
||||
"Content-type",
|
||||
HeaderValue::from_static("application/javascript"),
|
||||
);
|
||||
Ok(res)
|
||||
}
|
||||
_ => {
|
||||
let mut file_path = testdata_path();
|
||||
file_path.push(&req.uri().path()[1..]);
|
||||
|
@ -857,7 +880,9 @@ async fn main_server(req: Request<Body>) -> hyper::Result<Response<Body>> {
|
|||
return Ok(file_resp);
|
||||
}
|
||||
|
||||
return Ok(Response::new(Body::empty()));
|
||||
Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Body::empty())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue