1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 15:24:46 -05:00

feat(lsp): support specifying a tsconfig file (#8926)

This commit is contained in:
Kitson Kelly 2020-12-31 14:33:44 +11:00 committed by GitHub
parent 0163cedd80
commit 587155f86d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 122 additions and 65 deletions

View file

@ -81,7 +81,7 @@ pub async fn generate_lint_diagnostics(
tokio::task::spawn_blocking(move || {
let mut diagnostic_list = Vec::new();
let file_cache = state_snapshot.file_cache.read().unwrap();
let file_cache = state_snapshot.file_cache.lock().unwrap();
for (specifier, doc_data) in state_snapshot.doc_data.iter() {
let file_id = file_cache.lookup(specifier).unwrap();
let version = doc_data.version;
@ -242,7 +242,7 @@ pub async fn generate_ts_diagnostics(
for (specifier, doc_data) in state_snapshot_.doc_data.iter() {
let file_id = {
// TODO(lucacasonato): this is highly inefficient
let file_cache = state_snapshot_.file_cache.read().unwrap();
let file_cache = state_snapshot_.file_cache.lock().unwrap();
file_cache.lookup(specifier).unwrap()
};
let version = doc_data.version;
@ -266,8 +266,8 @@ pub async fn generate_dependency_diagnostics(
tokio::task::spawn_blocking(move || {
let mut diagnostics = Vec::new();
let file_cache = state_snapshot.file_cache.read().unwrap();
let mut sources = if let Ok(sources) = state_snapshot.sources.write() {
let file_cache = state_snapshot.file_cache.lock().unwrap();
let mut sources = if let Ok(sources) = state_snapshot.sources.lock() {
sources
} else {
return Err(custom_error("Deadlock", "deadlock locking sources"));

View file

@ -17,12 +17,13 @@ use std::collections::HashMap;
use std::env;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::Mutex;
use tokio::fs;
use crate::deno_dir;
use crate::import_map::ImportMap;
use crate::media_type::MediaType;
use crate::tsc_config::parse_config;
use crate::tsc_config::TsConfig;
use super::analysis;
@ -42,24 +43,25 @@ use super::utils;
#[derive(Debug, Clone)]
pub struct LanguageServer {
assets: Arc<RwLock<HashMap<ModuleSpecifier, Option<String>>>>,
assets: Arc<Mutex<HashMap<ModuleSpecifier, Option<String>>>>,
client: Client,
ts_server: TsServer,
config: Arc<RwLock<Config>>,
doc_data: Arc<RwLock<HashMap<ModuleSpecifier, DocumentData>>>,
file_cache: Arc<RwLock<MemoryCache>>,
sources: Arc<RwLock<Sources>>,
diagnostics: Arc<RwLock<DiagnosticCollection>>,
maybe_import_map: Arc<RwLock<Option<ImportMap>>>,
maybe_import_map_uri: Arc<RwLock<Option<Url>>>,
config: Arc<Mutex<Config>>,
doc_data: Arc<Mutex<HashMap<ModuleSpecifier, DocumentData>>>,
file_cache: Arc<Mutex<MemoryCache>>,
sources: Arc<Mutex<Sources>>,
diagnostics: Arc<Mutex<DiagnosticCollection>>,
maybe_config_uri: Arc<Mutex<Option<Url>>>,
maybe_import_map: Arc<Mutex<Option<ImportMap>>>,
maybe_import_map_uri: Arc<Mutex<Option<Url>>>,
}
#[derive(Debug, Clone, Default)]
pub struct StateSnapshot {
pub assets: Arc<RwLock<HashMap<ModuleSpecifier, Option<String>>>>,
pub assets: Arc<Mutex<HashMap<ModuleSpecifier, Option<String>>>>,
pub doc_data: HashMap<ModuleSpecifier, DocumentData>,
pub file_cache: Arc<RwLock<MemoryCache>>,
pub sources: Arc<RwLock<Sources>>,
pub file_cache: Arc<Mutex<MemoryCache>>,
pub sources: Arc<Mutex<Sources>>,
}
impl LanguageServer {
@ -68,7 +70,7 @@ impl LanguageServer {
let dir = deno_dir::DenoDir::new(maybe_custom_root)
.expect("could not access DENO_DIR");
let location = dir.root.join("deps");
let sources = Arc::new(RwLock::new(Sources::new(&location)));
let sources = Arc::new(Mutex::new(Sources::new(&location)));
LanguageServer {
assets: Default::default(),
@ -79,13 +81,14 @@ impl LanguageServer {
file_cache: Default::default(),
sources,
diagnostics: Default::default(),
maybe_config_uri: Default::default(),
maybe_import_map: Default::default(),
maybe_import_map_uri: Default::default(),
}
}
fn enabled(&self) -> bool {
let config = self.config.read().unwrap();
let config = self.config.lock().unwrap();
config.settings.enable
}
@ -103,12 +106,12 @@ impl LanguageServer {
return Err(anyhow!("asset source missing: {}", specifier));
}
} else {
let file_cache = self.file_cache.read().unwrap();
let file_cache = self.file_cache.lock().unwrap();
if let Some(file_id) = file_cache.lookup(&specifier) {
let file_text = file_cache.get_contents(file_id)?;
text::index_lines(&file_text)
} else {
let mut sources = self.sources.write().unwrap();
let mut sources = self.sources.lock().unwrap();
if let Some(line_index) = sources.get_line_index(&specifier) {
line_index
} else {
@ -121,20 +124,20 @@ impl LanguageServer {
async fn prepare_diagnostics(&self) -> Result<(), AnyError> {
let (enabled, lint_enabled) = {
let config = self.config.read().unwrap();
let config = self.config.lock().unwrap();
(config.settings.enable, config.settings.lint)
};
let lint = async {
if lint_enabled {
let diagnostic_collection = self.diagnostics.read().unwrap().clone();
let diagnostic_collection = self.diagnostics.lock().unwrap().clone();
let diagnostics = diagnostics::generate_lint_diagnostics(
self.snapshot(),
diagnostic_collection,
)
.await;
{
let mut diagnostics_collection = self.diagnostics.write().unwrap();
let mut diagnostics_collection = self.diagnostics.lock().unwrap();
for (file_id, version, diagnostics) in diagnostics {
diagnostics_collection.set(
file_id,
@ -153,7 +156,7 @@ impl LanguageServer {
let ts = async {
if enabled {
let diagnostics = {
let diagnostic_collection = self.diagnostics.read().unwrap().clone();
let diagnostic_collection = self.diagnostics.lock().unwrap().clone();
match diagnostics::generate_ts_diagnostics(
&self.ts_server,
&diagnostic_collection,
@ -169,7 +172,7 @@ impl LanguageServer {
}
};
{
let mut diagnostics_collection = self.diagnostics.write().unwrap();
let mut diagnostics_collection = self.diagnostics.lock().unwrap();
for (file_id, version, diagnostics) in diagnostics {
diagnostics_collection.set(
file_id,
@ -187,14 +190,14 @@ impl LanguageServer {
let deps = async {
if enabled {
let diagnostics_collection = self.diagnostics.read().unwrap().clone();
let diagnostics_collection = self.diagnostics.lock().unwrap().clone();
let diagnostics = diagnostics::generate_dependency_diagnostics(
self.snapshot(),
diagnostics_collection,
)
.await?;
{
let mut diagnostics_collection = self.diagnostics.write().unwrap();
let mut diagnostics_collection = self.diagnostics.lock().unwrap();
for (file_id, version, diagnostics) in diagnostics {
diagnostics_collection.set(
file_id,
@ -220,12 +223,12 @@ impl LanguageServer {
async fn publish_diagnostics(&self) -> Result<(), AnyError> {
let (maybe_changes, diagnostics_collection) = {
let mut diagnostics_collection = self.diagnostics.write().unwrap();
let mut diagnostics_collection = self.diagnostics.lock().unwrap();
let maybe_changes = diagnostics_collection.take_changes();
(maybe_changes, diagnostics_collection.clone())
};
if let Some(diagnostic_changes) = maybe_changes {
let settings = self.config.read().unwrap().settings.clone();
let settings = self.config.lock().unwrap().settings.clone();
for file_id in diagnostic_changes {
// TODO(@kitsonk) not totally happy with the way we collect and store
// different types of diagnostics and offer them up to the client, we
@ -253,12 +256,12 @@ impl LanguageServer {
);
}
let specifier = {
let file_cache = self.file_cache.read().unwrap();
let file_cache = self.file_cache.lock().unwrap();
file_cache.get_specifier(file_id).clone()
};
let uri = specifier.as_url().clone();
let version = if let Some(doc_data) =
self.doc_data.read().unwrap().get(&specifier)
self.doc_data.lock().unwrap().get(&specifier)
{
doc_data.version
} else {
@ -277,7 +280,7 @@ impl LanguageServer {
pub fn snapshot(&self) -> StateSnapshot {
StateSnapshot {
assets: self.assets.clone(),
doc_data: self.doc_data.read().unwrap().clone(),
doc_data: self.doc_data.lock().unwrap().clone(),
file_cache: self.file_cache.clone(),
sources: self.sources.clone(),
}
@ -285,11 +288,11 @@ impl LanguageServer {
pub async fn update_import_map(&self) -> Result<(), AnyError> {
let (maybe_import_map, maybe_root_uri) = {
let config = self.config.read().unwrap();
let config = self.config.lock().unwrap();
(config.settings.import_map.clone(), config.root_uri.clone())
};
if let Some(import_map_str) = &maybe_import_map {
info!("update import map");
info!("Updating import map from: \"{}\"", import_map_str);
let import_map_url = if let Ok(url) = Url::from_file_path(import_map_str)
{
Ok(url)
@ -320,10 +323,10 @@ impl LanguageServer {
})?;
let import_map =
ImportMap::from_json(&import_map_url.to_string(), &import_map_json)?;
*self.maybe_import_map_uri.write().unwrap() = Some(import_map_url);
*self.maybe_import_map.write().unwrap() = Some(import_map);
*self.maybe_import_map_uri.lock().unwrap() = Some(import_map_url);
*self.maybe_import_map.lock().unwrap() = Some(import_map);
} else {
*self.maybe_import_map.write().unwrap() = None;
*self.maybe_import_map.lock().unwrap() = None;
}
Ok(())
}
@ -339,14 +342,56 @@ impl LanguageServer {
"strict": true,
"target": "esnext",
}));
{
let config = self.config.read().unwrap();
let (maybe_config, maybe_root_uri) = {
let config = self.config.lock().unwrap();
if config.settings.unstable {
let unstable_libs = json!({
"lib": ["deno.ns", "deno.window", "deno.unstable"]
});
tsconfig.merge(&unstable_libs);
}
(config.settings.config.clone(), config.root_uri.clone())
};
if let Some(config_str) = &maybe_config {
info!("Updating TypeScript configuration from: \"{}\"", config_str);
let config_url = if let Ok(url) = Url::from_file_path(config_str) {
Ok(url)
} else if let Some(root_uri) = &maybe_root_uri {
let root_path = root_uri
.to_file_path()
.map_err(|_| anyhow!("Bad root_uri: {}", root_uri))?;
let config_path = root_path.join(config_str);
Url::from_file_path(config_path).map_err(|_| {
anyhow!("Bad file path for configuration file: \"{}\"", config_str)
})
} else {
Err(anyhow!(
"The path to the configuration file (\"{}\") is not resolvable.",
config_str
))
}?;
let config_path = config_url
.to_file_path()
.map_err(|_| anyhow!("Bad file path."))?;
let config_text =
fs::read_to_string(config_path.clone())
.await
.map_err(|err| {
anyhow!(
"Failed to load the configuration file at: {}. [{}]",
config_url,
err
)
})?;
let (value, maybe_ignored_options) =
parse_config(&config_text, &config_path)?;
tsconfig.merge(&value);
*self.maybe_config_uri.lock().unwrap() = Some(config_url);
if let Some(ignored_options) = maybe_ignored_options {
// TODO(@kitsonk) turn these into diagnostics that can be sent to the
// client
warn!("{}", ignored_options);
}
}
self
.ts_server
@ -388,7 +433,7 @@ impl lspower::LanguageServer for LanguageServer {
}
{
let mut config = self.config.write().unwrap();
let mut config = self.config.lock().unwrap();
config.root_uri = params.root_uri;
if let Some(value) = params.initialization_options {
config.update(value)?;
@ -451,10 +496,10 @@ impl lspower::LanguageServer for LanguageServer {
return;
}
let specifier = utils::normalize_url(params.text_document.uri);
let maybe_import_map = self.maybe_import_map.read().unwrap().clone();
let maybe_import_map = self.maybe_import_map.lock().unwrap().clone();
if self
.doc_data
.write()
.lock()
.unwrap()
.insert(
specifier.clone(),
@ -472,7 +517,7 @@ impl lspower::LanguageServer for LanguageServer {
self
.file_cache
.write()
.lock()
.unwrap()
.set_contents(specifier, Some(params.text_document.text.into_bytes()));
// TODO(@lucacasonato): error handling
@ -482,15 +527,15 @@ impl lspower::LanguageServer for LanguageServer {
async fn did_change(&self, params: DidChangeTextDocumentParams) {
let specifier = utils::normalize_url(params.text_document.uri);
let mut content = {
let file_cache = self.file_cache.read().unwrap();
let file_cache = self.file_cache.lock().unwrap();
let file_id = file_cache.lookup(&specifier).unwrap();
file_cache.get_contents(file_id).unwrap()
};
apply_content_changes(&mut content, params.content_changes);
{
let mut doc_data = self.doc_data.write().unwrap();
let mut doc_data = self.doc_data.lock().unwrap();
let doc_data = doc_data.get_mut(&specifier).unwrap();
let maybe_import_map = self.maybe_import_map.read().unwrap();
let maybe_import_map = self.maybe_import_map.lock().unwrap();
doc_data.update(
params.text_document.version,
&content,
@ -500,7 +545,7 @@ impl lspower::LanguageServer for LanguageServer {
self
.file_cache
.write()
.lock()
.unwrap()
.set_contents(specifier, Some(content.into_bytes()));
@ -516,7 +561,7 @@ impl lspower::LanguageServer for LanguageServer {
return;
}
let specifier = utils::normalize_url(params.text_document.uri);
if self.doc_data.write().unwrap().remove(&specifier).is_none() {
if self.doc_data.lock().unwrap().remove(&specifier).is_none() {
error!("orphaned document: {}", specifier);
}
// TODO(@kitsonk) should we do garbage collection on the diagnostics?
@ -544,7 +589,7 @@ impl lspower::LanguageServer for LanguageServer {
match res {
Err(err) => error!("failed to fetch the extension settings {:?}", err),
Ok(Some(config)) => {
if let Err(err) = self.config.write().unwrap().update(config) {
if let Err(err) = self.config.lock().unwrap().update(config) {
error!("failed to update settings: {}", err);
}
if let Err(err) = self.update_import_map().await {
@ -570,7 +615,7 @@ impl lspower::LanguageServer for LanguageServer {
) {
// if the current import map has changed, we need to reload it
let maybe_import_map_uri =
self.maybe_import_map_uri.read().unwrap().clone();
self.maybe_import_map_uri.lock().unwrap().clone();
if let Some(import_map_uri) = maybe_import_map_uri {
if params.changes.iter().any(|fe| import_map_uri == fe.uri) {
if let Err(err) = self.update_import_map().await {
@ -581,6 +626,18 @@ impl lspower::LanguageServer for LanguageServer {
}
}
}
// if the current tsconfig has changed, we need to reload it
let maybe_config_uri = self.maybe_config_uri.lock().unwrap().clone();
if let Some(config_uri) = maybe_config_uri {
if params.changes.iter().any(|fe| config_uri == fe.uri) {
if let Err(err) = self.update_tsconfig().await {
self
.client
.show_message(MessageType::Warning, err.to_string())
.await;
}
}
}
}
async fn formatting(
@ -589,7 +646,7 @@ impl lspower::LanguageServer for LanguageServer {
) -> LspResult<Option<Vec<TextEdit>>> {
let specifier = utils::normalize_url(params.text_document.uri.clone());
let file_text = {
let file_cache = self.file_cache.read().unwrap();
let file_cache = self.file_cache.lock().unwrap();
let file_id = file_cache.lookup(&specifier).unwrap();
// TODO(lucacasonato): handle error properly
file_cache.get_contents(file_id).unwrap()
@ -934,7 +991,7 @@ pub struct VirtualTextDocumentParams {
impl LanguageServer {
async fn cache(&self, params: CacheParams) -> LspResult<bool> {
let specifier = utils::normalize_url(params.text_document.uri);
let maybe_import_map = self.maybe_import_map.read().unwrap().clone();
let maybe_import_map = self.maybe_import_map.lock().unwrap().clone();
sources::cache(specifier.clone(), maybe_import_map)
.await
.map_err(|err| {
@ -942,9 +999,9 @@ impl LanguageServer {
LspError::internal_error()
})?;
{
let file_cache = self.file_cache.read().unwrap();
let file_cache = self.file_cache.lock().unwrap();
if let Some(file_id) = file_cache.lookup(&specifier) {
let mut diagnostics_collection = self.diagnostics.write().unwrap();
let mut diagnostics_collection = self.diagnostics.lock().unwrap();
diagnostics_collection.invalidate(&file_id);
}
}
@ -962,7 +1019,7 @@ impl LanguageServer {
let specifier = utils::normalize_url(params.text_document.uri);
let url = specifier.as_url();
let contents = if url.as_str() == "deno:/status.md" {
let file_cache = self.file_cache.read().unwrap();
let file_cache = self.file_cache.lock().unwrap();
Some(format!(
r#"# Deno Language Server Status
@ -987,7 +1044,7 @@ impl LanguageServer {
}
}
_ => {
let mut sources = self.sources.write().unwrap();
let mut sources = self.sources.lock().unwrap();
if let Some(text) = sources.get_text(&specifier) {
Some(text)
} else {

View file

@ -94,7 +94,7 @@ pub async fn get_asset(
Ok(Some(asset_text.to_string()))
} else {
{
let assets = state_snapshot.assets.read().unwrap();
let assets = state_snapshot.assets.lock().unwrap();
if let Some(asset) = assets.get(specifier) {
return Ok(asset.clone());
}
@ -107,7 +107,7 @@ pub async fn get_asset(
)
.await?,
)?;
let mut assets = state_snapshot.assets.write().unwrap();
let mut assets = state_snapshot.assets.lock().unwrap();
assets.insert(specifier.clone(), asset.clone());
Ok(asset)
}
@ -767,7 +767,7 @@ fn cache_snapshot(
{
let s = ModuleSpecifier::resolve_url(&specifier)?;
let content = {
let file_cache = state.state_snapshot.file_cache.read().unwrap();
let file_cache = state.state_snapshot.file_cache.lock().unwrap();
let file_id = file_cache.lookup(&s).unwrap();
file_cache.get_contents(file_id)?
};
@ -863,7 +863,7 @@ fn get_length(state: &mut State, args: Value) -> Result<Value, AnyError> {
.unwrap();
Ok(json!(content.chars().count()))
} else {
let mut sources = state.state_snapshot.sources.write().unwrap();
let mut sources = state.state_snapshot.sources.lock().unwrap();
Ok(json!(sources.get_length(&specifier).unwrap()))
}
}
@ -888,7 +888,7 @@ fn get_text(state: &mut State, args: Value) -> Result<Value, AnyError> {
.unwrap()
.clone()
} else {
let mut sources = state.state_snapshot.sources.write().unwrap();
let mut sources = state.state_snapshot.sources.lock().unwrap();
sources.get_text(&specifier).unwrap()
};
Ok(json!(text::slice(&content, v.start..v.end)))
@ -898,7 +898,7 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
let v: ResolveArgs = serde_json::from_value(args)?;
let mut resolved = Vec::<Option<(String, String)>>::new();
let referrer = ModuleSpecifier::resolve_url(&v.base)?;
let mut sources = if let Ok(sources) = state.state_snapshot.sources.write() {
let mut sources = if let Ok(sources) = state.state_snapshot.sources.lock() {
sources
} else {
return Err(custom_error("Deadlock", "deadlock locking sources"));
@ -999,7 +999,7 @@ fn script_version(state: &mut State, args: Value) -> Result<Value, AnyError> {
return Ok(json!(version.to_string()));
}
} else {
let mut sources = state.state_snapshot.sources.write().unwrap();
let mut sources = state.state_snapshot.sources.lock().unwrap();
if let Some(version) = sources.get_script_version(&specifier) {
return Ok(json!(version));
}
@ -1256,7 +1256,7 @@ mod tests {
use crate::lsp::language_server::DocumentData;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::Mutex;
fn mock_state_snapshot(sources: Vec<(&str, &str, i32)>) -> StateSnapshot {
let mut doc_data = HashMap::new();
@ -1270,7 +1270,7 @@ mod tests {
);
file_cache.set_contents(specifier, Some(content.as_bytes().to_vec()));
}
let file_cache = Arc::new(RwLock::new(file_cache));
let file_cache = Arc::new(Mutex::new(file_cache));
StateSnapshot {
assets: Default::default(),
doc_data,