0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-10-29 08:58:01 -04:00

fix(lsp): allow on disk files to change (#9746)

Fixes #9348
This commit is contained in:
Kitson Kelly 2021-03-10 21:39:16 +11:00 committed by GitHub
parent db96be7cdc
commit 88a7fa36aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 150 additions and 74 deletions

View file

@ -163,14 +163,6 @@ impl Sources {
self.0.lock().unwrap().contains_key(specifier)
}
/// Provides the length of the source content, calculated in a way that should
/// match the behavior of JavaScript, where strings are stored effectively as
/// `&[u16]` and when counting "chars" we need to represent the string as a
/// UTF-16 string in Rust.
pub fn get_length_utf16(&self, specifier: &ModuleSpecifier) -> Option<usize> {
self.0.lock().unwrap().get_length_utf16(specifier)
}
pub fn get_line_index(
&self,
specifier: &ModuleSpecifier,
@ -248,13 +240,6 @@ impl Inner {
false
}
fn get_length_utf16(&mut self, specifier: &ModuleSpecifier) -> Option<usize> {
let specifier =
resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?;
let metadata = self.get_metadata(&specifier)?;
Some(metadata.length_utf16)
}
fn get_line_index(
&mut self,
specifier: &ModuleSpecifier,
@ -471,19 +456,6 @@ mod tests {
assert_eq!(actual, "console.log(\"Hello World\");\n");
}
#[test]
fn test_sources_get_length_utf16() {
let (sources, _) = setup();
let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let tests = c.join("tests");
let specifier =
resolve_path(&tests.join("001_hello.js").to_string_lossy()).unwrap();
let actual = sources.get_length_utf16(&specifier);
assert!(actual.is_some());
let actual = actual.unwrap();
assert_eq!(actual, 28);
}
#[test]
fn test_resolve_dependency_types() {
let (sources, location) = setup();

View file

@ -1084,7 +1084,7 @@ impl<'a> State<'a> {
last_id: 1,
response: None,
state_snapshot,
snapshots: Default::default(),
snapshots: HashMap::default(),
}
}
}
@ -1099,13 +1099,19 @@ fn cache_snapshot(
.snapshots
.contains_key(&(specifier.clone(), version.clone().into()))
{
let content = state
.state_snapshot
.documents
.content(specifier)?
.ok_or_else(|| {
anyhow!("Specifier unexpectedly doesn't have content: {}", specifier)
})?;
let content = if state.state_snapshot.documents.contains_key(specifier) {
state
.state_snapshot
.documents
.content(specifier)?
.ok_or_else(|| {
anyhow!("Specifier unexpectedly doesn't have content: {}", specifier)
})?
} else {
state.state_snapshot.sources.get_source(specifier).ok_or_else(|| {
anyhow!("Specifier (\"{}\") is not an in memory document or on disk resource.", specifier)
})?
};
state
.snapshots
.insert((specifier.clone(), version.into()), content);
@ -1156,16 +1162,14 @@ struct GetChangeRangeArgs {
}
/// The language service wants to compare an old snapshot with a new snapshot to
/// determine what source hash changed.
/// determine what source has changed.
fn get_change_range(
state: &mut State,
args: GetChangeRangeArgs,
) -> Result<Value, AnyError> {
let mark = state.state_snapshot.performance.mark("op_get_change_range");
let specifier = resolve_url(&args.specifier)?;
if state.state_snapshot.documents.contains_key(&specifier) {
cache_snapshot(state, &specifier, args.version.clone())?;
}
cache_snapshot(state, &specifier, args.version.clone())?;
if let Some(current) = state
.snapshots
.get(&(specifier.clone(), args.version.clone().into()))
@ -1211,7 +1215,7 @@ fn get_length(
let specifier = resolve_url(&args.specifier)?;
if let Some(Some(asset)) = state.state_snapshot.assets.get(&specifier) {
Ok(asset.length)
} else if state.state_snapshot.documents.contains_key(&specifier) {
} else {
cache_snapshot(state, &specifier, args.version.clone())?;
let content = state
.snapshots
@ -1219,10 +1223,6 @@ fn get_length(
.unwrap();
state.state_snapshot.performance.measure(mark);
Ok(content.encode_utf16().count())
} else {
let sources = &mut state.state_snapshot.sources;
state.state_snapshot.performance.measure(mark);
Ok(sources.get_length_utf16(&specifier).unwrap())
}
}
@ -1241,15 +1241,13 @@ fn get_text(state: &mut State, args: GetTextArgs) -> Result<String, AnyError> {
let content =
if let Some(Some(content)) = state.state_snapshot.assets.get(&specifier) {
content.text.clone()
} else if state.state_snapshot.documents.contains_key(&specifier) {
} else {
cache_snapshot(state, &specifier, args.version.clone())?;
state
.snapshots
.get(&(specifier, args.version.into()))
.unwrap()
.clone()
} else {
state.state_snapshot.sources.get_source(&specifier).unwrap()
};
state.state_snapshot.performance.measure(mark);
Ok(text::slice(&content, args.start..args.end).to_string())
@ -1735,17 +1733,37 @@ pub fn request(
#[cfg(test)]
mod tests {
use super::*;
use crate::http_cache::HttpCache;
use crate::http_util::HeadersMap;
use crate::lsp::analysis;
use crate::lsp::documents::DocumentCache;
use crate::lsp::sources::Sources;
use std::path::Path;
use std::path::PathBuf;
use tempfile::TempDir;
fn mock_state_snapshot(sources: Vec<(&str, &str, i32)>) -> StateSnapshot {
fn mock_state_snapshot(
fixtures: &[(&str, &str, i32)],
location: &Path,
) -> StateSnapshot {
let mut documents = DocumentCache::default();
for (specifier, content, version) in sources {
for (specifier, source, version) in fixtures {
let specifier =
resolve_url(specifier).expect("failed to create specifier");
documents.open(specifier, version, content);
documents.open(specifier.clone(), *version, source);
if let Some((deps, _)) = analysis::analyze_dependencies(
&specifier,
source,
&MediaType::from(&specifier),
&None,
) {
documents.set_dependencies(&specifier, Some(deps)).unwrap();
}
}
let sources = Sources::new(location);
StateSnapshot {
documents,
sources,
..Default::default()
}
}
@ -1753,9 +1771,11 @@ mod tests {
fn setup(
debug: bool,
config: Value,
sources: Vec<(&str, &str, i32)>,
) -> (JsRuntime, StateSnapshot) {
let state_snapshot = mock_state_snapshot(sources.clone());
sources: &[(&str, &str, i32)],
) -> (JsRuntime, StateSnapshot, PathBuf) {
let temp_dir = TempDir::new().expect("could not create temp dir");
let location = temp_dir.path().join("deps");
let state_snapshot = mock_state_snapshot(sources, &location);
let mut runtime = start(debug).expect("could not start server");
let ts_config = TsConfig::new(config);
assert_eq!(
@ -1767,7 +1787,7 @@ mod tests {
.expect("failed request"),
json!(true)
);
(runtime, state_snapshot)
(runtime, state_snapshot, location)
}
#[test]
@ -1794,20 +1814,20 @@ mod tests {
"module": "esnext",
"noEmit": true,
}),
vec![],
&[],
);
}
#[test]
fn test_project_reconfigure() {
let (mut runtime, state_snapshot) = setup(
let (mut runtime, state_snapshot, _) = setup(
false,
json!({
"target": "esnext",
"module": "esnext",
"noEmit": true,
}),
vec![],
&[],
);
let ts_config = TsConfig::new(json!({
"target": "esnext",
@ -1827,14 +1847,14 @@ mod tests {
#[test]
fn test_get_diagnostics() {
let (mut runtime, state_snapshot) = setup(
let (mut runtime, state_snapshot, _) = setup(
false,
json!({
"target": "esnext",
"module": "esnext",
"noEmit": true,
}),
vec![("file:///a.ts", r#"console.log("hello deno");"#, 1)],
&[("file:///a.ts", r#"console.log("hello deno");"#, 1)],
);
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
let result = request(
@ -1870,7 +1890,7 @@ mod tests {
#[test]
fn test_get_diagnostics_lib() {
let (mut runtime, state_snapshot) = setup(
let (mut runtime, state_snapshot, _) = setup(
false,
json!({
"target": "esnext",
@ -1879,7 +1899,7 @@ mod tests {
"lib": ["esnext", "dom", "deno.ns"],
"noEmit": true,
}),
vec![("file:///a.ts", r#"console.log(document.location);"#, 1)],
&[("file:///a.ts", r#"console.log(document.location);"#, 1)],
);
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
let result = request(
@ -1894,7 +1914,7 @@ mod tests {
#[test]
fn test_module_resolution() {
let (mut runtime, state_snapshot) = setup(
let (mut runtime, state_snapshot, _) = setup(
false,
json!({
"target": "esnext",
@ -1902,7 +1922,7 @@ mod tests {
"lib": ["deno.ns", "deno.window"],
"noEmit": true,
}),
vec![(
&[(
"file:///a.ts",
r#"
import { B } from "https://deno.land/x/b/mod.ts";
@ -1927,7 +1947,7 @@ mod tests {
#[test]
fn test_bad_module_specifiers() {
let (mut runtime, state_snapshot) = setup(
let (mut runtime, state_snapshot, _) = setup(
false,
json!({
"target": "esnext",
@ -1935,7 +1955,7 @@ mod tests {
"lib": ["deno.ns", "deno.window"],
"noEmit": true,
}),
vec![(
&[(
"file:///a.ts",
r#"
import { A } from ".";
@ -1976,7 +1996,7 @@ mod tests {
#[test]
fn test_remote_modules() {
let (mut runtime, state_snapshot) = setup(
let (mut runtime, state_snapshot, _) = setup(
false,
json!({
"target": "esnext",
@ -1984,7 +2004,7 @@ mod tests {
"lib": ["deno.ns", "deno.window"],
"noEmit": true,
}),
vec![(
&[(
"file:///a.ts",
r#"
import { B } from "https://deno.land/x/b/mod.ts";
@ -2009,7 +2029,7 @@ mod tests {
#[test]
fn test_partial_modules() {
let (mut runtime, state_snapshot) = setup(
let (mut runtime, state_snapshot, _) = setup(
false,
json!({
"target": "esnext",
@ -2017,7 +2037,7 @@ mod tests {
"lib": ["deno.ns", "deno.window"],
"noEmit": true,
}),
vec![(
&[(
"file:///a.ts",
r#"
import {
@ -2079,7 +2099,7 @@ mod tests {
#[test]
fn test_no_debug_failure() {
let (mut runtime, state_snapshot) = setup(
let (mut runtime, state_snapshot, _) = setup(
false,
json!({
"target": "esnext",
@ -2087,7 +2107,7 @@ mod tests {
"lib": ["deno.ns", "deno.window"],
"noEmit": true,
}),
vec![("file:///a.ts", r#"const url = new URL("b.js", import."#, 1)],
&[("file:///a.ts", r#"const url = new URL("b.js", import."#, 1)],
);
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
let result = request(
@ -2102,7 +2122,7 @@ mod tests {
#[test]
fn test_request_asset() {
let (mut runtime, state_snapshot) = setup(
let (mut runtime, state_snapshot, _) = setup(
false,
json!({
"target": "esnext",
@ -2110,7 +2130,7 @@ mod tests {
"lib": ["deno.ns", "deno.window"],
"noEmit": true,
}),
vec![],
&[],
);
let specifier =
resolve_url("asset:///lib.esnext.d.ts").expect("could not resolve url");
@ -2124,4 +2144,88 @@ mod tests {
serde_json::from_value(result.unwrap()).unwrap();
assert!(response.is_some());
}
#[test]
fn test_modify_sources() {
let (mut runtime, state_snapshot, location) = setup(
true,
json!({
"target": "esnext",
"module": "esnext",
"lib": ["deno.ns", "deno.window"],
"noEmit": true,
}),
&[(
"file:///a.ts",
r#"
import * as a from "https://deno.land/x/example/a.ts";
if (a.a === "b") {
console.log("fail");
}
"#,
1,
)],
);
let cache = HttpCache::new(&location);
let specifier_dep =
resolve_url("https://deno.land/x/example/a.ts").unwrap();
cache
.set(
&specifier_dep,
HeadersMap::default(),
b"export const b = \"b\";\n",
)
.unwrap();
let specifier = resolve_url("file:///a.ts").unwrap();
let result = request(
&mut runtime,
state_snapshot.clone(),
RequestMethod::GetDiagnostics(vec![specifier]),
);
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(
response,
json!({
"file:///a.ts": [
{
"start": {
"line": 2,
"character": 16,
},
"end": {
"line": 2,
"character": 17
},
"fileName": "file:///a.ts",
"messageText": "Property \'a\' does not exist on type \'typeof import(\"https://deno.land/x/example/a\")\'.",
"sourceLine": " if (a.a === \"b\") {",
"code": 2339,
"category": 1,
}
]
})
);
cache
.set(
&specifier_dep,
HeadersMap::default(),
b"export const b = \"b\";\n\nexport const a = \"b\";\n",
)
.unwrap();
let specifier = resolve_url("file:///a.ts").unwrap();
let result = request(
&mut runtime,
state_snapshot,
RequestMethod::GetDiagnostics(vec![specifier]),
);
assert!(result.is_ok());
let response = result.unwrap();
assert_eq!(
response,
json!({
"file:///a.ts": []
})
);
}
}