diff options
Diffstat (limited to 'cli/lsp')
-rw-r--r-- | cli/lsp/analysis.rs | 67 | ||||
-rw-r--r-- | cli/lsp/cache.rs | 86 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 55 | ||||
-rw-r--r-- | cli/lsp/mod.rs | 1 | ||||
-rw-r--r-- | cli/lsp/sources.rs | 29 | ||||
-rw-r--r-- | cli/lsp/tsc.rs | 11 |
6 files changed, 187 insertions, 62 deletions
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index d1cf14500..a0718e8b3 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -6,12 +6,12 @@ use super::tsc; use crate::ast; use crate::ast::Location; use crate::lsp::documents::DocumentData; -use crate::module_graph::parse_deno_types; -use crate::module_graph::parse_ts_reference; -use crate::module_graph::TypeScriptReference; use crate::tools::lint::create_linter; use deno_ast::swc::ast as swc_ast; +use deno_ast::swc::common::comments::Comment; +use deno_ast::swc::common::BytePos; +use deno_ast::swc::common::Span; use deno_ast::swc::common::DUMMY_SP; use deno_ast::swc::visit::Node; use deno_ast::swc::visit::Visit; @@ -68,10 +68,71 @@ lazy_static::lazy_static! { .collect(); static ref IMPORT_SPECIFIER_RE: Regex = Regex::new(r#"\sfrom\s+["']([^"']*)["']"#).unwrap(); + + static ref DENO_TYPES_RE: Regex = + Regex::new(r#"(?i)^\s*@deno-types\s*=\s*(?:["']([^"']+)["']|(\S+))"#) + .unwrap(); + static ref TRIPLE_SLASH_REFERENCE_RE: Regex = + Regex::new(r"(?i)^/\s*<reference\s.*?/>").unwrap(); + static ref PATH_REFERENCE_RE: Regex = + Regex::new(r#"(?i)\spath\s*=\s*["']([^"']*)["']"#).unwrap(); + static ref TYPES_REFERENCE_RE: Regex = + Regex::new(r#"(?i)\stypes\s*=\s*["']([^"']*)["']"#).unwrap(); + } const SUPPORTED_EXTENSIONS: &[&str] = &[".ts", ".tsx", ".js", ".jsx", ".mjs"]; +// TODO(@kitsonk) remove after deno_graph migration +#[derive(Debug, Clone, Eq, PartialEq)] +enum TypeScriptReference { + Path(String), + Types(String), +} + +fn match_to_span(comment: &Comment, m: ®ex::Match) -> Span { + Span { + lo: comment.span.lo + BytePos((m.start() + 1) as u32), + hi: comment.span.lo + BytePos((m.end() + 1) as u32), + ctxt: comment.span.ctxt, + } +} + +// TODO(@kitsonk) remove after deno_graph migration +fn parse_deno_types(comment: &Comment) -> Option<(String, Span)> { + let captures = DENO_TYPES_RE.captures(&comment.text)?; + if let Some(m) = captures.get(1) { + Some((m.as_str().to_string(), match_to_span(comment, &m))) + } else if let Some(m) = captures.get(2) { + Some((m.as_str().to_string(), match_to_span(comment, &m))) + } else { + unreachable!(); + } +} + +// TODO(@kitsonk) remove after deno_graph migration +fn parse_ts_reference( + comment: &Comment, +) -> Option<(TypeScriptReference, Span)> { + if !TRIPLE_SLASH_REFERENCE_RE.is_match(&comment.text) { + None + } else if let Some(captures) = PATH_REFERENCE_RE.captures(&comment.text) { + let m = captures.get(1).unwrap(); + Some(( + TypeScriptReference::Path(m.as_str().to_string()), + match_to_span(comment, &m), + )) + } else { + TYPES_REFERENCE_RE.captures(&comment.text).map(|captures| { + let m = captures.get(1).unwrap(); + ( + TypeScriptReference::Types(m.as_str().to_string()), + match_to_span(comment, &m), + ) + }) + } +} + /// Category of self-generated diagnostic messages (those not coming from) /// TypeScript. #[derive(Debug, PartialEq, Eq)] diff --git a/cli/lsp/cache.rs b/cli/lsp/cache.rs new file mode 100644 index 000000000..39b9dca60 --- /dev/null +++ b/cli/lsp/cache.rs @@ -0,0 +1,86 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use crate::cache::CacherLoader; +use crate::cache::FetchCacher; +use crate::flags::Flags; +use crate::proc_state::ProcState; +use crate::resolver::ImportMapResolver; +use crate::tokio_util::create_basic_runtime; + +use deno_core::error::anyhow; +use deno_core::error::AnyError; +use deno_core::ModuleSpecifier; +use deno_runtime::permissions::Permissions; +use import_map::ImportMap; +use std::path::PathBuf; +use std::thread; +use tokio::sync::mpsc; +use tokio::sync::oneshot; + +type Request = (Vec<ModuleSpecifier>, oneshot::Sender<Result<(), AnyError>>); + +/// A "server" that handles requests from the language server to cache modules +/// in its own thread. +#[derive(Debug)] +pub(crate) struct CacheServer(mpsc::UnboundedSender<Request>); + +impl CacheServer { + pub async fn new( + maybe_cache_path: Option<PathBuf>, + maybe_import_map: Option<ImportMap>, + ) -> Self { + let (tx, mut rx) = mpsc::unbounded_channel::<Request>(); + let _join_handle = thread::spawn(move || { + let runtime = create_basic_runtime(); + runtime.block_on(async { + let ps = ProcState::build(Flags { + cache_path: maybe_cache_path, + ..Default::default() + }) + .await + .unwrap(); + let maybe_resolver = + maybe_import_map.as_ref().map(ImportMapResolver::new); + let mut cache = FetchCacher::new( + ps.dir.gen_cache.clone(), + ps.file_fetcher.clone(), + Permissions::allow_all(), + Permissions::allow_all(), + ); + + while let Some((roots, tx)) = rx.recv().await { + let graph = deno_graph::create_graph( + roots, + false, + None, + cache.as_mut_loader(), + maybe_resolver.as_ref().map(|r| r.as_resolver()), + None, + None, + ) + .await; + + if tx.send(graph.valid().map_err(|err| err.into())).is_err() { + log::warn!("cannot send to client"); + } + } + }) + }); + + Self(tx) + } + + /// Attempt to cache the supplied module specifiers and their dependencies in + /// the current DENO_DIR, returning any errors, so they can be returned to the + /// client. + pub async fn cache( + &self, + roots: Vec<ModuleSpecifier>, + ) -> Result<(), AnyError> { + let (tx, rx) = oneshot::channel::<Result<(), AnyError>>(); + if self.0.send((roots, tx)).is_err() { + return Err(anyhow!("failed to send request to cache thread")); + } + rx.await? + } +} diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index ef659cd05..524a59191 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -29,6 +29,7 @@ use super::analysis::ts_changes_to_edit; use super::analysis::CodeActionCollection; use super::analysis::CodeActionData; use super::analysis::ResolvedDependency; +use super::cache::CacheServer; use super::capabilities; use super::code_lens; use super::completions; @@ -44,7 +45,6 @@ use super::parent_process_checker; use super::performance::Performance; use super::refactor; use super::registries; -use super::sources; use super::sources::Sources; use super::text; use super::text::LineIndex; @@ -99,6 +99,8 @@ pub(crate) struct Inner { /// An optional path to the DENO_DIR which has been specified in the client /// options. maybe_cache_path: Option<PathBuf>, + /// A lazily created "server" for handling cache requests. + maybe_cache_server: Option<CacheServer>, /// An optional configuration file which has been specified in the client /// options. maybe_config_file: Option<ConfigFile>, @@ -149,6 +151,7 @@ impl Inner { diagnostics_server, documents: Default::default(), maybe_cache_path: None, + maybe_cache_server: None, maybe_config_file: None, maybe_config_uri: None, maybe_import_map: None, @@ -424,6 +427,7 @@ impl Inner { pub fn update_cache(&mut self) -> Result<(), AnyError> { let mark = self.performance.mark("update_cache", None::<()>); self.performance.measure(mark); + self.maybe_cache_server = None; let (maybe_cache, maybe_root_uri) = { let config = &self.config; ( @@ -479,6 +483,7 @@ impl Inner { pub async fn update_import_map(&mut self) -> Result<(), AnyError> { let mark = self.performance.mark("update_import_map", None::<()>); + self.maybe_cache_server = None; let (maybe_import_map, maybe_root_uri) = { let config = &self.config; ( @@ -2630,34 +2635,30 @@ impl Inner { } let mark = self.performance.mark("cache", Some(¶ms)); - if !params.uris.is_empty() { - for identifier in ¶ms.uris { - let specifier = self.url_map.normalize_url(&identifier.uri); - sources::cache( - &specifier, - &self.maybe_import_map, - &self.maybe_config_file, - &self.maybe_cache_path, - ) - .await - .map_err(|err| { - error!("{}", err); - LspError::internal_error() - })?; - } + let roots = if !params.uris.is_empty() { + params + .uris + .iter() + .map(|t| self.url_map.normalize_url(&t.uri)) + .collect() } else { - sources::cache( - &referrer, - &self.maybe_import_map, - &self.maybe_config_file, - &self.maybe_cache_path, - ) - .await - .map_err(|err| { - error!("{}", err); - LspError::internal_error() - })?; + vec![referrer.clone()] + }; + + if self.maybe_cache_server.is_none() { + self.maybe_cache_server = Some( + CacheServer::new( + self.maybe_cache_path.clone(), + self.maybe_import_map.clone(), + ) + .await, + ); } + let cache_server = self.maybe_cache_server.as_ref().unwrap(); + if let Err(err) = cache_server.cache(roots).await { + self.client.show_message(MessageType::Warning, err).await; + } + // now that we have dependencies loaded, we need to re-analyze them and // invalidate some diagnostics if self.documents.contains_key(&referrer) { diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs index fda2ac82b..e6cc88208 100644 --- a/cli/lsp/mod.rs +++ b/cli/lsp/mod.rs @@ -5,6 +5,7 @@ use lspower::LspService; use lspower::Server; mod analysis; +mod cache; mod capabilities; mod code_lens; mod completions; diff --git a/cli/lsp/sources.rs b/cli/lsp/sources.rs index bd5835cf2..69de6d976 100644 --- a/cli/lsp/sources.rs +++ b/cli/lsp/sources.rs @@ -6,18 +6,12 @@ use super::text::LineIndex; use super::tsc; use super::urls::INVALID_SPECIFIER; -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::flags::Flags; use crate::http_cache; use crate::http_cache::HttpCache; -use crate::module_graph::GraphBuilder; -use crate::proc_state::ProcState; -use crate::specifier_handler::FetchHandler; use crate::text_encoding; -use import_map::ImportMap; use deno_ast::MediaType; use deno_core::error::anyhow; @@ -25,7 +19,7 @@ use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use deno_core::serde_json; use deno_core::ModuleSpecifier; -use deno_runtime::permissions::Permissions; +use import_map::ImportMap; use std::collections::HashMap; use std::fs; use std::path::Path; @@ -34,27 +28,6 @@ use std::sync::Arc; use std::time::SystemTime; use tsc::NavigationTree; -pub async fn cache( - specifier: &ModuleSpecifier, - maybe_import_map: &Option<ImportMap>, - maybe_config_file: &Option<ConfigFile>, - maybe_cache_path: &Option<PathBuf>, -) -> Result<(), AnyError> { - let ps = ProcState::build(Flags { - cache_path: maybe_cache_path.clone(), - ..Default::default() - }) - .await?; - let handler = Arc::new(Mutex::new(FetchHandler::new( - &ps, - Permissions::allow_all(), - Permissions::allow_all(), - )?)); - let mut builder = GraphBuilder::new(handler, maybe_import_map.clone(), None); - builder.analyze_config_file(maybe_config_file).await?; - builder.add(specifier, false).await -} - fn get_remote_headers( cache_filename: &Path, ) -> Option<HashMap<String, String>> { diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 05606cb79..9899fad72 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -44,12 +44,15 @@ use log::warn; use lspower::lsp; use regex::Captures; use regex::Regex; +use std::borrow::Cow; +use std::cmp; +use std::collections::HashMap; use std::collections::HashSet; +use std::path::Path; use std::sync::Arc; use std::thread; -use std::{borrow::Cow, cmp}; -use std::{collections::HashMap, path::Path}; -use text_size::{TextRange, TextSize}; +use text_size::TextRange; +use text_size::TextSize; use tokio::sync::mpsc; use tokio::sync::oneshot; @@ -3375,7 +3378,7 @@ mod tests { #[test] fn test_modify_sources() { let (mut runtime, state_snapshot, location) = setup( - true, + false, json!({ "target": "esnext", "module": "esnext", |