mirror of
https://github.com/denoland/deno.git
synced 2024-12-24 16:19:12 -05:00
fix(lsp): auto-discover deno.json in more cases (#19894)
We weren't auto-discovering the deno.json in two cases: 1. A project that didn't have a deno.json and just added one. 2. After a syntax error in the deno.json. This now rediscovers it in both these cases. Closes https://github.com/denoland/vscode_deno/issues/867
This commit is contained in:
parent
bf775e3306
commit
235fdc243f
4 changed files with 202 additions and 18 deletions
|
@ -773,7 +773,12 @@ fn resolve_lockfile_from_path(
|
||||||
lockfile_path: PathBuf,
|
lockfile_path: PathBuf,
|
||||||
) -> Option<Arc<Mutex<Lockfile>>> {
|
) -> Option<Arc<Mutex<Lockfile>>> {
|
||||||
match Lockfile::new(lockfile_path, false) {
|
match Lockfile::new(lockfile_path, false) {
|
||||||
Ok(value) => Some(Arc::new(Mutex::new(value))),
|
Ok(value) => {
|
||||||
|
if let Ok(specifier) = ModuleSpecifier::from_file_path(&value.filename) {
|
||||||
|
lsp_log!(" Resolved lock file: \"{}\"", specifier);
|
||||||
|
}
|
||||||
|
Some(Arc::new(Mutex::new(value)))
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
lsp_warn!("Error loading lockfile: {:#}", err);
|
lsp_warn!("Error loading lockfile: {:#}", err);
|
||||||
None
|
None
|
||||||
|
|
|
@ -1493,14 +1493,23 @@ impl Inner {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// if the current deno.json has changed, we need to reload it
|
// if the current deno.json has changed, we need to reload it
|
||||||
if let Some(config_file) = self.config.maybe_config_file() {
|
let has_config_changed = match self.config.maybe_config_file() {
|
||||||
if changes.contains(&config_file.specifier)
|
Some(config_file) => changes.contains(&config_file.specifier),
|
||||||
|| self
|
None => {
|
||||||
.config
|
// check for auto-discovery
|
||||||
.maybe_lockfile()
|
changes.iter().any(|url| {
|
||||||
.map(|l| has_lockfile_changed(&l.lock(), &changes))
|
url.path().ends_with("/deno.json")
|
||||||
.unwrap_or(false)
|
|| url.path().ends_with("/deno.jsonc")
|
||||||
{
|
})
|
||||||
|
}
|
||||||
|
} || match self.config.maybe_lockfile() {
|
||||||
|
Some(lockfile) => has_lockfile_changed(&lockfile.lock(), &changes),
|
||||||
|
None => {
|
||||||
|
// check for auto-discovery
|
||||||
|
changes.iter().any(|url| url.path().ends_with("/deno.lock"))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if has_config_changed {
|
||||||
if let Err(err) = self.update_config_file().await {
|
if let Err(err) = self.update_config_file().await {
|
||||||
self.client.show_message(MessageType::WARNING, err);
|
self.client.show_message(MessageType::WARNING, err);
|
||||||
}
|
}
|
||||||
|
@ -1509,7 +1518,6 @@ impl Inner {
|
||||||
}
|
}
|
||||||
touched = true;
|
touched = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(package_json) = &self.maybe_package_json {
|
if let Some(package_json) = &self.maybe_package_json {
|
||||||
// always update the package json if the deno config changes
|
// always update the package json if the deno config changes
|
||||||
|
@ -2940,7 +2948,7 @@ impl tower_lsp::LanguageServer for LanguageServer {
|
||||||
let watch_registration_options =
|
let watch_registration_options =
|
||||||
DidChangeWatchedFilesRegistrationOptions {
|
DidChangeWatchedFilesRegistrationOptions {
|
||||||
watchers: vec![FileSystemWatcher {
|
watchers: vec![FileSystemWatcher {
|
||||||
glob_pattern: "**/*.{json,jsonc}".to_string(),
|
glob_pattern: "**/*.{json,jsonc,lock}".to_string(),
|
||||||
kind: Some(WatchKind::Change),
|
kind: Some(WatchKind::Change),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
|
@ -432,6 +432,118 @@ fn lsp_import_map_embedded_in_config_file_after_initialize() {
|
||||||
client.shutdown();
|
client.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsp_import_map_config_file_auto_discovered() {
|
||||||
|
let context = TestContextBuilder::new().use_temp_cwd().build();
|
||||||
|
let temp_dir = context.temp_dir();
|
||||||
|
temp_dir.create_dir_all("lib");
|
||||||
|
temp_dir.write("lib/b.ts", r#"export const b = "b";"#);
|
||||||
|
|
||||||
|
let mut client = context.new_lsp_command().capture_stderr().build();
|
||||||
|
client.initialize_default();
|
||||||
|
|
||||||
|
// add the deno.json
|
||||||
|
temp_dir.write("deno.jsonc", r#"{ "imports": { "/~/": "./lib/" } }"#);
|
||||||
|
client.did_change_watched_files(json!({
|
||||||
|
"changes": [{
|
||||||
|
"uri": temp_dir.uri().join("deno.jsonc").unwrap(),
|
||||||
|
"type": 2
|
||||||
|
}]
|
||||||
|
}));
|
||||||
|
client.wait_until_stderr_line(|line| {
|
||||||
|
line.contains("Auto-resolved configuration file:")
|
||||||
|
});
|
||||||
|
|
||||||
|
let uri = temp_dir.uri().join("a.ts").unwrap();
|
||||||
|
|
||||||
|
let diagnostics = client.did_open(json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": uri,
|
||||||
|
"languageId": "typescript",
|
||||||
|
"version": 1,
|
||||||
|
"text": "import { b } from \"/~/b.ts\";\n\nconsole.log(b);\n"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert_eq!(diagnostics.all().len(), 0);
|
||||||
|
|
||||||
|
let res = client.write_request(
|
||||||
|
"textDocument/hover",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": uri
|
||||||
|
},
|
||||||
|
"position": { "line": 2, "character": 12 }
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
json!({
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"language": "typescript",
|
||||||
|
"value":"(alias) const b: \"b\"\nimport b"
|
||||||
|
},
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 2, "character": 12 },
|
||||||
|
"end": { "line": 2, "character": 13 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// now cause a syntax error
|
||||||
|
temp_dir.write("deno.jsonc", r#",,#,#,,"#);
|
||||||
|
client.did_change_watched_files(json!({
|
||||||
|
"changes": [{
|
||||||
|
"uri": temp_dir.uri().join("deno.jsonc").unwrap(),
|
||||||
|
"type": 2
|
||||||
|
}]
|
||||||
|
}));
|
||||||
|
assert_eq!(client.read_diagnostics().all().len(), 1);
|
||||||
|
|
||||||
|
// now fix it, and things should work again
|
||||||
|
temp_dir.write("deno.jsonc", r#"{ "imports": { "/~/": "./lib/" } }"#);
|
||||||
|
client.did_change_watched_files(json!({
|
||||||
|
"changes": [{
|
||||||
|
"uri": temp_dir.uri().join("deno.jsonc").unwrap(),
|
||||||
|
"type": 2
|
||||||
|
}]
|
||||||
|
}));
|
||||||
|
client.wait_until_stderr_line(|line| {
|
||||||
|
line.contains("Auto-resolved configuration file:")
|
||||||
|
});
|
||||||
|
let res = client.write_request(
|
||||||
|
"textDocument/hover",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": uri
|
||||||
|
},
|
||||||
|
"position": { "line": 2, "character": 12 }
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
json!({
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"language": "typescript",
|
||||||
|
"value":"(alias) const b: \"b\"\nimport b"
|
||||||
|
},
|
||||||
|
""
|
||||||
|
],
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 2, "character": 12 },
|
||||||
|
"end": { "line": 2, "character": 13 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(client.read_diagnostics().all().len(), 0);
|
||||||
|
|
||||||
|
client.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lsp_deno_task() {
|
fn lsp_deno_task() {
|
||||||
let context = TestContextBuilder::new().use_temp_cwd().build();
|
let context = TestContextBuilder::new().use_temp_cwd().build();
|
||||||
|
|
|
@ -37,6 +37,8 @@ use serde_json::to_value;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::io::BufRead;
|
||||||
|
use std::io::BufReader;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Child;
|
use std::process::Child;
|
||||||
|
@ -44,6 +46,7 @@ use std::process::ChildStdin;
|
||||||
use std::process::ChildStdout;
|
use std::process::ChildStdout;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
|
use std::sync::mpsc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
@ -468,6 +471,7 @@ impl InitializeParamsBuilder {
|
||||||
|
|
||||||
pub struct LspClientBuilder {
|
pub struct LspClientBuilder {
|
||||||
print_stderr: bool,
|
print_stderr: bool,
|
||||||
|
capture_stderr: bool,
|
||||||
deno_exe: PathRef,
|
deno_exe: PathRef,
|
||||||
context: Option<TestContext>,
|
context: Option<TestContext>,
|
||||||
use_diagnostic_sync: bool,
|
use_diagnostic_sync: bool,
|
||||||
|
@ -478,6 +482,7 @@ impl LspClientBuilder {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
print_stderr: false,
|
print_stderr: false,
|
||||||
|
capture_stderr: false,
|
||||||
deno_exe: deno_exe_path(),
|
deno_exe: deno_exe_path(),
|
||||||
context: None,
|
context: None,
|
||||||
use_diagnostic_sync: true,
|
use_diagnostic_sync: true,
|
||||||
|
@ -497,6 +502,11 @@ impl LspClientBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn capture_stderr(&mut self) -> &mut Self {
|
||||||
|
self.capture_stderr = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether to use the synchronization messages to better sync diagnostics
|
/// Whether to use the synchronization messages to better sync diagnostics
|
||||||
/// between the test client and server.
|
/// between the test client and server.
|
||||||
pub fn use_diagnostic_sync(&mut self, value: bool) -> &mut Self {
|
pub fn use_diagnostic_sync(&mut self, value: bool) -> &mut Self {
|
||||||
|
@ -527,7 +537,9 @@ impl LspClientBuilder {
|
||||||
.arg("lsp")
|
.arg("lsp")
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped());
|
.stdout(Stdio::piped());
|
||||||
if !self.print_stderr {
|
if self.capture_stderr {
|
||||||
|
command.stderr(Stdio::piped());
|
||||||
|
} else if !self.print_stderr {
|
||||||
command.stderr(Stdio::null());
|
command.stderr(Stdio::null());
|
||||||
}
|
}
|
||||||
let mut child = command.spawn()?;
|
let mut child = command.spawn()?;
|
||||||
|
@ -538,6 +550,31 @@ impl LspClientBuilder {
|
||||||
let stdin = child.stdin.take().unwrap();
|
let stdin = child.stdin.take().unwrap();
|
||||||
let writer = io::BufWriter::new(stdin);
|
let writer = io::BufWriter::new(stdin);
|
||||||
|
|
||||||
|
let stderr_lines_rx = if self.capture_stderr {
|
||||||
|
let stderr = child.stderr.take().unwrap();
|
||||||
|
let print_stderr = self.print_stderr;
|
||||||
|
let (tx, rx) = mpsc::channel::<String>();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let stderr = BufReader::new(stderr);
|
||||||
|
for line in stderr.lines() {
|
||||||
|
match line {
|
||||||
|
Ok(line) => {
|
||||||
|
if print_stderr {
|
||||||
|
eprintln!("{}", line);
|
||||||
|
}
|
||||||
|
tx.send(line).unwrap();
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
panic!("failed to read line from stderr: {:#}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Some(rx)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(LspClient {
|
Ok(LspClient {
|
||||||
child,
|
child,
|
||||||
reader,
|
reader,
|
||||||
|
@ -549,6 +586,7 @@ impl LspClientBuilder {
|
||||||
.unwrap_or_else(|| TestContextBuilder::new().build()),
|
.unwrap_or_else(|| TestContextBuilder::new().build()),
|
||||||
writer,
|
writer,
|
||||||
deno_dir,
|
deno_dir,
|
||||||
|
stderr_lines_rx,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -561,6 +599,7 @@ pub struct LspClient {
|
||||||
writer: io::BufWriter<ChildStdin>,
|
writer: io::BufWriter<ChildStdin>,
|
||||||
deno_dir: TempDir,
|
deno_dir: TempDir,
|
||||||
context: TestContext,
|
context: TestContext,
|
||||||
|
stderr_lines_rx: Option<mpsc::Receiver<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for LspClient {
|
impl Drop for LspClient {
|
||||||
|
@ -594,6 +633,26 @@ impl LspClient {
|
||||||
self.reader.pending_len()
|
self.reader.pending_len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub fn wait_until_stderr_line(&self, condition: impl Fn(&str) -> bool) {
|
||||||
|
let timeout_time =
|
||||||
|
Instant::now().checked_add(Duration::from_secs(5)).unwrap();
|
||||||
|
let lines_rx = self
|
||||||
|
.stderr_lines_rx
|
||||||
|
.as_ref()
|
||||||
|
.expect("must setup with client_builder.capture_stderr()");
|
||||||
|
while Instant::now() < timeout_time {
|
||||||
|
if let Ok(line) = lines_rx.try_recv() {
|
||||||
|
if condition(&line) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(20));
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("Timed out.")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn initialize_default(&mut self) {
|
pub fn initialize_default(&mut self) {
|
||||||
self.initialize(|_| {})
|
self.initialize(|_| {})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue