summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/lsp/analysis.rs13
-rw-r--r--cli/lsp/config.rs2
-rw-r--r--cli/lsp/mod.rs66
-rw-r--r--cli/lsp/sources.rs10
-rw-r--r--cli/lsp/state.rs109
-rw-r--r--cli/lsp/tsc.rs27
6 files changed, 202 insertions, 25 deletions
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs
index 370b41c45..95e21ed9a 100644
--- a/cli/lsp/analysis.rs
+++ b/cli/lsp/analysis.rs
@@ -13,9 +13,10 @@ use deno_core::ModuleSpecifier;
use deno_lint::rules;
use lsp_types::Position;
use lsp_types::Range;
-use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
+use std::sync::Arc;
+use std::sync::RwLock;
/// Category of self-generated diagnostic messages (those not coming from)
/// TypeScript.
@@ -113,11 +114,13 @@ pub enum ResolvedImport {
pub fn resolve_import(
specifier: &str,
referrer: &ModuleSpecifier,
- maybe_import_map: Option<Rc<RefCell<ImportMap>>>,
+ maybe_import_map: Option<Arc<RwLock<ImportMap>>>,
) -> ResolvedImport {
let maybe_mapped = if let Some(import_map) = maybe_import_map {
- if let Ok(maybe_specifier) =
- import_map.borrow().resolve(specifier, referrer.as_str())
+ if let Ok(maybe_specifier) = import_map
+ .read()
+ .unwrap()
+ .resolve(specifier, referrer.as_str())
{
maybe_specifier
} else {
@@ -159,7 +162,7 @@ pub fn analyze_dependencies(
specifier: &ModuleSpecifier,
source: &str,
media_type: &MediaType,
- maybe_import_map: Option<Rc<RefCell<ImportMap>>>,
+ maybe_import_map: Option<Arc<RwLock<ImportMap>>>,
) -> Option<(HashMap<String, Dependency>, Option<ResolvedImport>)> {
let specifier_str = specifier.to_string();
let source_map = Rc::new(swc_common::SourceMap::default());
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index ebc145708..fc3f030c9 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -4,6 +4,7 @@ use deno_core::error::AnyError;
use deno_core::serde::Deserialize;
use deno_core::serde_json;
use deno_core::serde_json::Value;
+use deno_core::url::Url;
#[derive(Debug, Clone, Default)]
pub struct ClientCapabilities {
@@ -23,6 +24,7 @@ pub struct WorkspaceSettings {
#[derive(Debug, Clone, Default)]
pub struct Config {
pub client_capabilities: ClientCapabilities,
+ pub root_uri: Option<Url>,
pub settings: WorkspaceSettings,
}
diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs
index e3092d815..784f3503d 100644
--- a/cli/lsp/mod.rs
+++ b/cli/lsp/mod.rs
@@ -18,6 +18,7 @@ use config::Config;
use diagnostics::DiagnosticSource;
use dispatch::NotificationDispatcher;
use dispatch::RequestDispatcher;
+use state::update_import_map;
use state::DocumentData;
use state::Event;
use state::ServerState;
@@ -87,13 +88,13 @@ pub fn start() -> Result<(), AnyError> {
}
let mut config = Config::default();
+ config.root_uri = initialize_params.root_uri.clone();
if let Some(value) = initialize_params.initialization_options {
config.update(value)?;
}
config.update_capabilities(&initialize_params.capabilities);
let mut server_state = state::ServerState::new(connection.sender, config);
- let state = server_state.snapshot();
// TODO(@kitsonk) need to make this configurable, respect unstable
let ts_config = TsConfig::new(json!({
@@ -106,6 +107,7 @@ pub fn start() -> Result<(), AnyError> {
"strict": true,
"target": "esnext",
}));
+ let state = server_state.snapshot();
tsc::request(
&mut server_state.ts_runtime,
&state,
@@ -259,7 +261,7 @@ impl ServerState {
specifier.clone(),
params.text_document.version,
&params.text_document.text,
- None,
+ state.maybe_import_map.clone(),
),
)
.is_some()
@@ -281,7 +283,11 @@ impl ServerState {
let mut content = file_cache.get_contents(file_id)?;
apply_content_changes(&mut content, params.content_changes);
let doc_data = state.doc_data.get_mut(&specifier).unwrap();
- doc_data.update(params.text_document.version, &content, None);
+ doc_data.update(
+ params.text_document.version,
+ &content,
+ state.maybe_import_map.clone(),
+ );
file_cache.set_contents(specifier, Some(content.into_bytes()));
Ok(())
@@ -326,6 +332,15 @@ impl ServerState {
if let Err(err) = state.config.update(config.clone()) {
error!("failed to update settings: {}", err);
}
+ if let Err(err) = update_import_map(state) {
+ state
+ .send_notification::<lsp_types::notification::ShowMessage>(
+ lsp_types::ShowMessageParams {
+ typ: lsp_types::MessageType::Warning,
+ message: err.to_string(),
+ },
+ );
+ }
}
}
(None, None) => {
@@ -337,6 +352,15 @@ impl ServerState {
Ok(())
})?
+ .on::<lsp_types::notification::DidChangeWatchedFiles>(|state, params| {
+ // if the current import map has changed, we need to reload it
+ if let Some(import_map_uri) = &state.maybe_import_map_uri {
+ if params.changes.iter().any(|fe| import_map_uri == &fe.uri) {
+ update_import_map(state)?;
+ }
+ }
+ Ok(())
+ })?
.finish();
Ok(())
@@ -395,8 +419,40 @@ impl ServerState {
/// Start consuming events from the provided receiver channel.
pub fn run(mut self, inbox: Receiver<Message>) -> Result<(), AnyError> {
- // currently we don't need to do any other loading or tasks, so as soon as
- // we run we are "ready"
+ // Check to see if we need to setup the import map
+ if let Err(err) = update_import_map(&mut self) {
+ self.send_notification::<lsp_types::notification::ShowMessage>(
+ lsp_types::ShowMessageParams {
+ typ: lsp_types::MessageType::Warning,
+ message: err.to_string(),
+ },
+ );
+ }
+
+ // we are going to watch all the JSON files in the workspace, and the
+ // notification handler will pick up any of the changes of those files we
+ // are interested in.
+ let watch_registration_options =
+ lsp_types::DidChangeWatchedFilesRegistrationOptions {
+ watchers: vec![lsp_types::FileSystemWatcher {
+ glob_pattern: "**/*.json".to_string(),
+ kind: Some(lsp_types::WatchKind::Change),
+ }],
+ };
+ let registration = lsp_types::Registration {
+ id: "workspace/didChangeWatchedFiles".to_string(),
+ method: "workspace/didChangeWatchedFiles".to_string(),
+ register_options: Some(
+ serde_json::to_value(watch_registration_options).unwrap(),
+ ),
+ };
+ self.send_request::<lsp_types::request::RegisterCapability>(
+ lsp_types::RegistrationParams {
+ registrations: vec![registration],
+ },
+ |_, _| (),
+ );
+
self.transition(Status::Ready);
while let Some(event) = self.next_event(&inbox) {
diff --git a/cli/lsp/sources.rs b/cli/lsp/sources.rs
index 4f80044a2..c6a15461f 100644
--- a/cli/lsp/sources.rs
+++ b/cli/lsp/sources.rs
@@ -7,6 +7,7 @@ use crate::file_fetcher::get_source_from_bytes;
use crate::file_fetcher::map_content_type;
use crate::http_cache;
use crate::http_cache::HttpCache;
+use crate::import_map::ImportMap;
use crate::media_type::MediaType;
use crate::text_encoding;
@@ -16,6 +17,8 @@ use std::collections::HashMap;
use std::fs;
use std::path::Path;
use std::path::PathBuf;
+use std::sync::Arc;
+use std::sync::RwLock;
use std::time::SystemTime;
#[derive(Debug, Clone, Default)]
@@ -30,6 +33,7 @@ struct Metadata {
#[derive(Debug, Clone, Default)]
pub struct Sources {
http_cache: HttpCache,
+ maybe_import_map: Option<Arc<RwLock<ImportMap>>>,
metadata: HashMap<ModuleSpecifier, Metadata>,
redirects: HashMap<ModuleSpecifier, ModuleSpecifier>,
remotes: HashMap<ModuleSpecifier, PathBuf>,
@@ -124,7 +128,11 @@ impl Sources {
if let Ok(source) = get_source_from_bytes(bytes, maybe_charset) {
let mut maybe_types =
if let Some(types) = headers.get("x-typescript-types") {
- Some(analysis::resolve_import(types, &specifier, None))
+ Some(analysis::resolve_import(
+ types,
+ &specifier,
+ self.maybe_import_map.clone(),
+ ))
} else {
None
};
diff --git a/cli/lsp/state.rs b/cli/lsp/state.rs
index 18a1e4023..579a749f6 100644
--- a/cli/lsp/state.rs
+++ b/cli/lsp/state.rs
@@ -18,6 +18,9 @@ use crossbeam_channel::select;
use crossbeam_channel::unbounded;
use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
+use deno_core::error::anyhow;
+use deno_core::error::AnyError;
+use deno_core::url::Url;
use deno_core::JsRuntime;
use deno_core::ModuleSpecifier;
use lsp_server::Message;
@@ -25,11 +28,10 @@ use lsp_server::Notification;
use lsp_server::Request;
use lsp_server::RequestId;
use lsp_server::Response;
-use std::cell::RefCell;
use std::collections::HashMap;
use std::env;
use std::fmt;
-use std::rc::Rc;
+use std::fs;
use std::sync::Arc;
use std::sync::RwLock;
use std::time::Instant;
@@ -37,6 +39,45 @@ use std::time::Instant;
type ReqHandler = fn(&mut ServerState, Response);
type ReqQueue = lsp_server::ReqQueue<(String, Instant), ReqHandler>;
+pub fn update_import_map(state: &mut ServerState) -> Result<(), AnyError> {
+ if let Some(import_map_str) = &state.config.settings.import_map {
+ let import_map_url = if let Ok(url) = Url::from_file_path(import_map_str) {
+ Ok(url)
+ } else if let Some(root_uri) = &state.config.root_uri {
+ let root_path = root_uri
+ .to_file_path()
+ .map_err(|_| anyhow!("Bad root_uri: {}", root_uri))?;
+ let import_map_path = root_path.join(import_map_str);
+ Url::from_file_path(import_map_path).map_err(|_| {
+ anyhow!("Bad file path for import map: {:?}", import_map_str)
+ })
+ } else {
+ Err(anyhow!(
+ "The path to the import map (\"{}\") is not resolvable.",
+ import_map_str
+ ))
+ }?;
+ let import_map_path = import_map_url
+ .to_file_path()
+ .map_err(|_| anyhow!("Bad file path."))?;
+ let import_map_json =
+ fs::read_to_string(import_map_path).map_err(|err| {
+ anyhow!(
+ "Failed to load the import map at: {}. [{}]",
+ import_map_url,
+ err
+ )
+ })?;
+ let import_map =
+ ImportMap::from_json(&import_map_url.to_string(), &import_map_json)?;
+ state.maybe_import_map_uri = Some(import_map_url);
+ state.maybe_import_map = Some(Arc::new(RwLock::new(import_map)));
+ } else {
+ state.maybe_import_map = None;
+ }
+ Ok(())
+}
+
pub enum Event {
Message(Message),
Task(Task),
@@ -107,7 +148,7 @@ impl DocumentData {
specifier: ModuleSpecifier,
version: i32,
source: &str,
- maybe_import_map: Option<Rc<RefCell<ImportMap>>>,
+ maybe_import_map: Option<Arc<RwLock<ImportMap>>>,
) -> Self {
let dependencies = if let Some((dependencies, _)) =
analysis::analyze_dependencies(
@@ -131,7 +172,7 @@ impl DocumentData {
&mut self,
version: i32,
source: &str,
- maybe_import_map: Option<Rc<RefCell<ImportMap>>>,
+ maybe_import_map: Option<Arc<RwLock<ImportMap>>>,
) {
self.dependencies = if let Some((dependencies, _)) =
analysis::analyze_dependencies(
@@ -163,6 +204,8 @@ pub struct ServerState {
pub diagnostics: DiagnosticCollection,
pub doc_data: HashMap<ModuleSpecifier, DocumentData>,
pub file_cache: Arc<RwLock<MemoryCache>>,
+ pub maybe_import_map: Option<Arc<RwLock<ImportMap>>>,
+ pub maybe_import_map_uri: Option<Url>,
req_queue: ReqQueue,
sender: Sender<Message>,
pub sources: Arc<RwLock<Sources>>,
@@ -189,8 +232,10 @@ impl ServerState {
Self {
config,
diagnostics: Default::default(),
- doc_data: HashMap::new(),
+ doc_data: Default::default(),
file_cache: Arc::new(RwLock::new(Default::default())),
+ maybe_import_map: None,
+ maybe_import_map_uri: None,
req_queue: Default::default(),
sender,
sources: Arc::new(RwLock::new(sources)),
@@ -290,3 +335,57 @@ impl ServerState {
self.status = new_status;
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use deno_core::serde_json::json;
+ use deno_core::serde_json::Value;
+ use lsp_server::Connection;
+ use tempfile::TempDir;
+
+ #[test]
+ fn test_update_import_map() {
+ let temp_dir = TempDir::new().expect("could not create temp dir");
+ let import_map_path = temp_dir.path().join("import_map.json");
+ let import_map_str = &import_map_path.to_string_lossy();
+ fs::write(
+ import_map_path.clone(),
+ r#"{
+ "imports": {
+ "denoland/": "https://deno.land/x/"
+ }
+ }"#,
+ )
+ .expect("could not write file");
+ let mut config = Config::default();
+ config
+ .update(json!({
+ "enable": false,
+ "config": Value::Null,
+ "lint": false,
+ "importMap": import_map_str,
+ "unstable": true,
+ }))
+ .expect("could not update config");
+ let (connection, _) = Connection::memory();
+ let mut state = ServerState::new(connection.sender, config);
+ let result = update_import_map(&mut state);
+ assert!(result.is_ok());
+ assert!(state.maybe_import_map.is_some());
+ let expected =
+ Url::from_file_path(import_map_path).expect("could not parse url");
+ assert_eq!(state.maybe_import_map_uri, Some(expected));
+ let import_map = state.maybe_import_map.unwrap();
+ let import_map = import_map.read().unwrap();
+ assert_eq!(
+ import_map
+ .resolve("denoland/mod.ts", "https://example.com/index.js")
+ .expect("bad response"),
+ Some(
+ ModuleSpecifier::resolve_url("https://deno.land/x/mod.ts")
+ .expect("could not create URL")
+ )
+ );
+ }
+}
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 86c9a2980..649dd1bb5 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -844,17 +844,26 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
};
if let ResolvedImport::Resolved(resolved_specifier) = resolved_import
{
- let media_type = if let Some(media_type) =
- sources.get_media_type(&resolved_specifier)
+ if state
+ .server_state
+ .doc_data
+ .contains_key(&resolved_specifier)
+ || sources.contains(&resolved_specifier)
{
- media_type
+ let media_type = if let Some(media_type) =
+ sources.get_media_type(&resolved_specifier)
+ {
+ media_type
+ } else {
+ MediaType::from(&resolved_specifier)
+ };
+ resolved.push(Some((
+ resolved_specifier.to_string(),
+ media_type.as_ts_extension(),
+ )));
} else {
- MediaType::from(&resolved_specifier)
- };
- resolved.push(Some((
- resolved_specifier.to_string(),
- media_type.as_ts_extension(),
- )));
+ resolved.push(None);
+ }
} else {
resolved.push(None);
}