summaryrefslogtreecommitdiff
path: root/cli/lsp/tsc.rs
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2021-03-10 21:39:16 +1100
committerGitHub <noreply@github.com>2021-03-10 21:39:16 +1100
commit88a7fa36aa40df3c461137d3bbbfea03db69446f (patch)
tree0c7d9497a2dcb3a7950d27a43dbd99ead22bd9c6 /cli/lsp/tsc.rs
parentdb96be7cdc3b9d8c8d5373e90ca6c2c57599529b (diff)
fix(lsp): allow on disk files to change (#9746)
Fixes #9348
Diffstat (limited to 'cli/lsp/tsc.rs')
-rw-r--r--cli/lsp/tsc.rs198
1 files changed, 151 insertions, 47 deletions
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index abb591957..31434f529 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -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;
-
- fn mock_state_snapshot(sources: Vec<(&str, &str, i32)>) -> StateSnapshot {
+ use crate::lsp::sources::Sources;
+ use std::path::Path;
+ use std::path::PathBuf;
+ use tempfile::TempDir;
+
+ 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": []
+ })
+ );
+ }
}