diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2021-11-09 12:26:39 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-09 12:26:39 +1100 |
commit | f5eb177f50a0bf37bc6bd9d87b447c73a53b6ea5 (patch) | |
tree | 1990dadf311de59b45c677e234219a161f3ebf9d /cli/lsp | |
parent | 45425c114610516287c8e5831c9b6f023dfc8180 (diff) |
feat(cli): support React 17 JSX transforms (#12631)
Closes #8440
Diffstat (limited to 'cli/lsp')
-rw-r--r-- | cli/lsp/cache.rs | 32 | ||||
-rw-r--r-- | cli/lsp/documents.rs | 203 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 25 | ||||
-rw-r--r-- | cli/lsp/mod.rs | 1 | ||||
-rw-r--r-- | cli/lsp/resolver.rs | 33 |
5 files changed, 221 insertions, 73 deletions
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()) - } -} |