mirror of
https://github.com/denoland/deno.git
synced 2025-01-03 04:48:52 -05:00
fix(lsp): properly handle static assets (#9476)
This commit is contained in:
parent
54e53cc9ea
commit
79916226b7
7 changed files with 164 additions and 84 deletions
|
@ -32,14 +32,14 @@ lazy_static! {
|
|||
/// Diagnostic error codes which actually are the same, and so when grouping
|
||||
/// fixes we treat them the same.
|
||||
static ref FIX_ALL_ERROR_CODES: HashMap<&'static str, &'static str> =
|
||||
[("2339", "2339"), ("2345", "2339"),]
|
||||
(&[("2339", "2339"), ("2345", "2339"),])
|
||||
.iter()
|
||||
.copied()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
/// Fixes which help determine if there is a preferred fix when there are
|
||||
/// multiple fixes available.
|
||||
static ref PREFERRED_FIXES: HashMap<&'static str, (u32, bool)> = [
|
||||
static ref PREFERRED_FIXES: HashMap<&'static str, (u32, bool)> = (&[
|
||||
("annotateWithTypeFromJSDoc", (1, false)),
|
||||
("constructorForDerivedNeedSuperCall", (1, false)),
|
||||
("extendsInterfaceBecomesImplements", (1, false)),
|
||||
|
@ -52,9 +52,9 @@ lazy_static! {
|
|||
("spelling", (2, false)),
|
||||
("addMissingAwait", (1, false)),
|
||||
("fixImport", (0, true)),
|
||||
]
|
||||
])
|
||||
.iter()
|
||||
.copied()
|
||||
.cloned()
|
||||
.collect();
|
||||
}
|
||||
|
||||
|
|
|
@ -294,7 +294,7 @@ pub async fn generate_dependency_diagnostics(
|
|||
})
|
||||
}
|
||||
ResolvedDependency::Resolved(specifier) => {
|
||||
if !(state_snapshot.documents.contains(&specifier) || sources.contains(&specifier)) {
|
||||
if !(state_snapshot.documents.contains_key(&specifier) || sources.contains_key(&specifier)) {
|
||||
let is_local = specifier.as_url().scheme() == "file";
|
||||
let (code, message) = if is_local {
|
||||
(Some(lsp::NumberOrString::String("no-local".to_string())), format!("Unable to load a local module: \"{}\".\n Please check the file path.", specifier))
|
||||
|
|
|
@ -96,7 +96,7 @@ impl DocumentCache {
|
|||
version: i32,
|
||||
content_changes: Vec<TextDocumentContentChangeEvent>,
|
||||
) -> Result<Option<String>, AnyError> {
|
||||
if !self.contains(specifier) {
|
||||
if !self.contains_key(specifier) {
|
||||
return Err(custom_error(
|
||||
"NotFound",
|
||||
format!(
|
||||
|
@ -119,7 +119,7 @@ impl DocumentCache {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, specifier: &ModuleSpecifier) -> bool {
|
||||
pub fn contains_key(&self, specifier: &ModuleSpecifier) -> bool {
|
||||
self.docs.contains_key(specifier)
|
||||
}
|
||||
|
||||
|
@ -213,8 +213,8 @@ mod tests {
|
|||
let missing_specifier =
|
||||
ModuleSpecifier::resolve_url("file:///a/c.ts").unwrap();
|
||||
document_cache.open(specifier.clone(), 1, "console.log(\"Hello Deno\");\n");
|
||||
assert!(document_cache.contains(&specifier));
|
||||
assert!(!document_cache.contains(&missing_specifier));
|
||||
assert!(document_cache.contains_key(&specifier));
|
||||
assert!(!document_cache.contains_key(&missing_specifier));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -50,6 +50,7 @@ use super::text;
|
|||
use super::text::LineIndex;
|
||||
use super::tsc;
|
||||
use super::tsc::AssetDocument;
|
||||
use super::tsc::Assets;
|
||||
use super::tsc::TsServer;
|
||||
use super::utils;
|
||||
|
||||
|
@ -63,7 +64,7 @@ pub struct LanguageServer(Arc<tokio::sync::Mutex<Inner>>);
|
|||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct StateSnapshot {
|
||||
pub assets: HashMap<ModuleSpecifier, Option<AssetDocument>>,
|
||||
pub assets: Assets,
|
||||
pub documents: DocumentCache,
|
||||
pub performance: Performance,
|
||||
pub sources: Sources,
|
||||
|
@ -73,7 +74,7 @@ pub struct StateSnapshot {
|
|||
pub(crate) struct Inner {
|
||||
/// Cached versions of "fixed" assets that can either be inlined in Rust or
|
||||
/// are part of the TypeScript snapshot and have to be fetched out.
|
||||
assets: HashMap<ModuleSpecifier, Option<AssetDocument>>,
|
||||
assets: Assets,
|
||||
/// The LSP client that this LSP server is connected to.
|
||||
client: Client,
|
||||
/// Configuration information.
|
||||
|
@ -86,7 +87,7 @@ pub(crate) struct Inner {
|
|||
/// file which will be used by the Deno LSP.
|
||||
maybe_config_uri: Option<Url>,
|
||||
/// An optional import map which is used to resolve modules.
|
||||
pub maybe_import_map: Option<ImportMap>,
|
||||
pub(crate) maybe_import_map: Option<ImportMap>,
|
||||
/// The URL for the import map which is used to determine relative imports.
|
||||
maybe_import_map_uri: Option<Url>,
|
||||
/// A map of all the cached navigation trees.
|
||||
|
@ -203,7 +204,7 @@ impl Inner {
|
|||
}
|
||||
} else {
|
||||
let documents = &self.documents;
|
||||
if documents.contains(specifier) {
|
||||
if documents.contains_key(specifier) {
|
||||
documents.line_index(specifier)
|
||||
} else {
|
||||
self.sources.get_line_index(specifier)
|
||||
|
@ -529,10 +530,8 @@ impl Inner {
|
|||
if let Some(maybe_asset) = self.assets.get(specifier) {
|
||||
return Ok(maybe_asset.clone());
|
||||
} else {
|
||||
let mut state_snapshot = self.snapshot();
|
||||
let maybe_asset =
|
||||
tsc::get_asset(&specifier, &self.ts_server, &mut state_snapshot)
|
||||
.await?;
|
||||
tsc::get_asset(&specifier, &self.ts_server, self.snapshot()).await?;
|
||||
self.assets.insert(specifier.clone(), maybe_asset.clone());
|
||||
Ok(maybe_asset)
|
||||
}
|
||||
|
@ -1887,7 +1886,7 @@ impl Inner {
|
|||
}
|
||||
// now that we have dependencies loaded, we need to re-analyze them and
|
||||
// invalidate some diagnostics
|
||||
if self.documents.contains(&referrer) {
|
||||
if self.documents.contains_key(&referrer) {
|
||||
if let Some(source) = self.documents.content(&referrer).unwrap() {
|
||||
self.analyze_dependencies(&referrer, &source);
|
||||
}
|
||||
|
|
|
@ -67,8 +67,8 @@ impl Sources {
|
|||
Self(Arc::new(Mutex::new(Inner::new(location))))
|
||||
}
|
||||
|
||||
pub fn contains(&self, specifier: &ModuleSpecifier) -> bool {
|
||||
self.0.lock().unwrap().contains(specifier)
|
||||
pub fn contains_key(&self, specifier: &ModuleSpecifier) -> bool {
|
||||
self.0.lock().unwrap().contains_key(specifier)
|
||||
}
|
||||
|
||||
pub fn get_length_utf16(&self, specifier: &ModuleSpecifier) -> Option<usize> {
|
||||
|
@ -132,7 +132,7 @@ impl Inner {
|
|||
}
|
||||
}
|
||||
|
||||
fn contains(&mut self, specifier: &ModuleSpecifier) -> bool {
|
||||
fn contains_key(&mut self, specifier: &ModuleSpecifier) -> bool {
|
||||
if let Some(specifier) = self.resolve_specifier(specifier) {
|
||||
if self.get_metadata(&specifier).is_some() {
|
||||
return true;
|
||||
|
|
129
cli/lsp/tsc.rs
129
cli/lsp/tsc.rs
|
@ -89,46 +89,79 @@ impl TsServer {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct AssetDocument {
|
||||
pub text: String,
|
||||
pub length: usize,
|
||||
pub line_index: LineIndex,
|
||||
}
|
||||
|
||||
impl AssetDocument {
|
||||
pub fn new<T: AsRef<str>>(text: T) -> Self {
|
||||
let text = text.as_ref();
|
||||
Self {
|
||||
text: text.to_string(),
|
||||
length: text.encode_utf16().count(),
|
||||
line_index: LineIndex::new(text),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Assets(HashMap<ModuleSpecifier, Option<AssetDocument>>);
|
||||
|
||||
impl Default for Assets {
|
||||
fn default() -> Self {
|
||||
let assets = tsc::STATIC_ASSETS
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let url_str = format!("asset:///{}", k);
|
||||
let specifier = ModuleSpecifier::resolve_url(&url_str).unwrap();
|
||||
let asset = AssetDocument::new(v);
|
||||
(specifier, Some(asset))
|
||||
})
|
||||
.collect();
|
||||
Self(assets)
|
||||
}
|
||||
}
|
||||
|
||||
impl Assets {
|
||||
pub fn contains_key(&self, k: &ModuleSpecifier) -> bool {
|
||||
self.0.contains_key(k)
|
||||
}
|
||||
|
||||
pub fn get(&self, k: &ModuleSpecifier) -> Option<&Option<AssetDocument>> {
|
||||
self.0.get(k)
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
k: ModuleSpecifier,
|
||||
v: Option<AssetDocument>,
|
||||
) -> Option<Option<AssetDocument>> {
|
||||
self.0.insert(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
/// Optionally returns an internal asset, first checking for any static assets
|
||||
/// in Rust, then checking any previously retrieved static assets from the
|
||||
/// isolate, and then finally, the tsc isolate itself.
|
||||
pub async fn get_asset(
|
||||
specifier: &ModuleSpecifier,
|
||||
ts_server: &TsServer,
|
||||
state_snapshot: &mut StateSnapshot,
|
||||
state_snapshot: StateSnapshot,
|
||||
) -> Result<Option<AssetDocument>, AnyError> {
|
||||
let specifier_str = specifier.to_string().replace("asset:///", "");
|
||||
if let Some(text) = tsc::get_asset(&specifier_str) {
|
||||
let maybe_asset = Some(AssetDocument {
|
||||
line_index: LineIndex::new(text),
|
||||
text: text.to_string(),
|
||||
});
|
||||
state_snapshot
|
||||
.assets
|
||||
.insert(specifier.clone(), maybe_asset.clone());
|
||||
let maybe_asset = Some(AssetDocument::new(text));
|
||||
Ok(maybe_asset)
|
||||
} else {
|
||||
let res = ts_server
|
||||
.request(
|
||||
state_snapshot.clone(),
|
||||
RequestMethod::GetAsset(specifier.clone()),
|
||||
)
|
||||
.request(state_snapshot, RequestMethod::GetAsset(specifier.clone()))
|
||||
.await?;
|
||||
let maybe_text: Option<String> = serde_json::from_value(res)?;
|
||||
let maybe_asset = if let Some(text) = maybe_text {
|
||||
Some(AssetDocument {
|
||||
line_index: LineIndex::new(&text),
|
||||
text,
|
||||
})
|
||||
Some(AssetDocument::new(text))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
state_snapshot
|
||||
.assets
|
||||
.insert(specifier.clone(), maybe_asset.clone());
|
||||
Ok(maybe_asset)
|
||||
}
|
||||
}
|
||||
|
@ -1043,7 +1076,9 @@ fn get_length(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
|||
let mark = state.state_snapshot.performance.mark("op_get_length");
|
||||
let v: SourceSnapshotArgs = serde_json::from_value(args)?;
|
||||
let specifier = ModuleSpecifier::resolve_url(&v.specifier)?;
|
||||
if state.state_snapshot.documents.contains(&specifier) {
|
||||
if let Some(Some(asset)) = state.state_snapshot.assets.get(&specifier) {
|
||||
Ok(json!(asset.length))
|
||||
} else if state.state_snapshot.documents.contains_key(&specifier) {
|
||||
cache_snapshot(state, v.specifier.clone(), v.version.clone())?;
|
||||
let content = state
|
||||
.snapshots
|
||||
|
@ -1071,7 +1106,10 @@ fn get_text(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
|||
let mark = state.state_snapshot.performance.mark("op_get_text");
|
||||
let v: GetTextArgs = serde_json::from_value(args)?;
|
||||
let specifier = ModuleSpecifier::resolve_url(&v.specifier)?;
|
||||
let content = if state.state_snapshot.documents.contains(&specifier) {
|
||||
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) {
|
||||
cache_snapshot(state, v.specifier.clone(), v.version.clone())?;
|
||||
state
|
||||
.snapshots
|
||||
|
@ -1093,7 +1131,7 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
|||
let referrer = ModuleSpecifier::resolve_url(&v.base)?;
|
||||
let sources = &mut state.state_snapshot.sources;
|
||||
|
||||
if state.state_snapshot.documents.contains(&referrer) {
|
||||
if state.state_snapshot.documents.contains_key(&referrer) {
|
||||
if let Some(dependencies) =
|
||||
state.state_snapshot.documents.dependencies(&referrer)
|
||||
{
|
||||
|
@ -1115,13 +1153,17 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
|||
if let ResolvedDependency::Resolved(resolved_specifier) =
|
||||
resolved_import
|
||||
{
|
||||
if state.state_snapshot.documents.contains(&resolved_specifier) {
|
||||
if state
|
||||
.state_snapshot
|
||||
.documents
|
||||
.contains_key(&resolved_specifier)
|
||||
{
|
||||
let media_type = MediaType::from(&resolved_specifier);
|
||||
resolved.push(Some((
|
||||
resolved_specifier.to_string(),
|
||||
media_type.as_ts_extension(),
|
||||
)));
|
||||
} else if sources.contains(&resolved_specifier) {
|
||||
} else if sources.contains_key(&resolved_specifier) {
|
||||
let media_type = if let Some(media_type) =
|
||||
sources.get_media_type(&resolved_specifier)
|
||||
{
|
||||
|
@ -1142,7 +1184,7 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if sources.contains(&referrer) {
|
||||
} else if sources.contains_key(&referrer) {
|
||||
for specifier in &v.specifiers {
|
||||
if let Some((resolved_specifier, media_type)) =
|
||||
sources.resolve_import(specifier, &referrer)
|
||||
|
@ -1190,7 +1232,15 @@ fn script_version(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
|||
let mark = state.state_snapshot.performance.mark("op_script_version");
|
||||
let v: ScriptVersionArgs = serde_json::from_value(args)?;
|
||||
let specifier = ModuleSpecifier::resolve_url(&v.specifier)?;
|
||||
if let Some(version) = state.state_snapshot.documents.version(&specifier) {
|
||||
if specifier.as_url().scheme() == "asset" {
|
||||
return if state.state_snapshot.assets.contains_key(&specifier) {
|
||||
Ok(json!("1"))
|
||||
} else {
|
||||
Ok(json!(null))
|
||||
};
|
||||
} else if let Some(version) =
|
||||
state.state_snapshot.documents.version(&specifier)
|
||||
{
|
||||
return Ok(json!(version.to_string()));
|
||||
} else {
|
||||
let sources = &mut state.state_snapshot.sources;
|
||||
|
@ -1200,7 +1250,7 @@ fn script_version(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
|||
}
|
||||
|
||||
state.state_snapshot.performance.measure(mark);
|
||||
Ok(json!(None::<String>))
|
||||
Ok(json!(null))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -1626,6 +1676,31 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_diagnostics_lib() {
|
||||
let (mut runtime, state_snapshot) = setup(
|
||||
false,
|
||||
json!({
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"jsx": "react",
|
||||
"lib": ["esnext", "dom", "deno.ns"],
|
||||
"noEmit": true,
|
||||
}),
|
||||
vec![("file:///a.ts", r#"console.log(document.location);"#, 1)],
|
||||
);
|
||||
let specifier = ModuleSpecifier::resolve_url("file:///a.ts")
|
||||
.expect("could not resolve url");
|
||||
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": [] }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_module_resolution() {
|
||||
let (mut runtime, state_snapshot) = setup(
|
||||
|
|
50
cli/tsc.rs
50
cli/tsc.rs
|
@ -44,32 +44,38 @@ pub fn compiler_snapshot() -> Snapshot {
|
|||
Snapshot::Static(COMPILER_SNAPSHOT)
|
||||
}
|
||||
|
||||
/// Provide static assets that are not preloaded in the compiler snapshot.
|
||||
pub fn get_asset(asset: &str) -> Option<&'static str> {
|
||||
macro_rules! inc {
|
||||
($e:expr) => {
|
||||
Some(include_str!(concat!("dts/", $e)))
|
||||
include_str!(concat!("dts/", $e))
|
||||
};
|
||||
}
|
||||
match asset {
|
||||
"lib.dom.asynciterable.d.ts" => inc!("lib.dom.asynciterable.d.ts"),
|
||||
"lib.dom.d.ts" => inc!("lib.dom.d.ts"),
|
||||
"lib.dom.iterable.d.ts" => inc!("lib.dom.iterable.d.ts"),
|
||||
"lib.es6.d.ts" => inc!("lib.es6.d.ts"),
|
||||
"lib.es2016.full.d.ts" => inc!("lib.es2016.full.d.ts"),
|
||||
"lib.es2017.full.d.ts" => inc!("lib.es2017.full.d.ts"),
|
||||
"lib.es2018.full.d.ts" => inc!("lib.es2018.full.d.ts"),
|
||||
"lib.es2019.full.d.ts" => inc!("lib.es2019.full.d.ts"),
|
||||
"lib.es2020.full.d.ts" => inc!("lib.es2020.full.d.ts"),
|
||||
"lib.esnext.full.d.ts" => inc!("lib.esnext.full.d.ts"),
|
||||
"lib.scripthost.d.ts" => inc!("lib.scripthost.d.ts"),
|
||||
"lib.webworker.d.ts" => inc!("lib.webworker.d.ts"),
|
||||
"lib.webworker.importscripts.d.ts" => {
|
||||
inc!("lib.webworker.importscripts.d.ts")
|
||||
}
|
||||
"lib.webworker.iterable.d.ts" => inc!("lib.webworker.iterable.d.ts"),
|
||||
_ => None,
|
||||
|
||||
lazy_static! {
|
||||
/// Contains static assets that are not preloaded in the compiler snapshot.
|
||||
pub(crate) static ref STATIC_ASSETS: HashMap<&'static str, &'static str> = (&[
|
||||
("lib.dom.asynciterable.d.ts", inc!("lib.dom.asynciterable.d.ts")),
|
||||
("lib.dom.d.ts", inc!("lib.dom.d.ts")),
|
||||
("lib.dom.iterable.d.ts", inc!("lib.dom.iterable.d.ts")),
|
||||
("lib.es6.d.ts", inc!("lib.es6.d.ts")),
|
||||
("lib.es2016.full.d.ts", inc!("lib.es2016.full.d.ts")),
|
||||
("lib.es2017.full.d.ts", inc!("lib.es2017.full.d.ts")),
|
||||
("lib.es2018.full.d.ts", inc!("lib.es2018.full.d.ts")),
|
||||
("lib.es2019.full.d.ts", inc!("lib.es2019.full.d.ts")),
|
||||
("lib.es2020.full.d.ts", inc!("lib.es2020.full.d.ts")),
|
||||
("lib.esnext.full.d.ts", inc!("lib.esnext.full.d.ts")),
|
||||
("lib.scripthost.d.ts", inc!("lib.scripthost.d.ts")),
|
||||
("lib.webworker.d.ts", inc!("lib.webworker.d.ts")),
|
||||
("lib.webworker.importscripts.d.ts", inc!("lib.webworker.importscripts.d.ts")),
|
||||
("lib.webworker.iterable.d.ts", inc!("lib.webworker.iterable.d.ts")),
|
||||
])
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// Retrieve a static asset that are included in the binary.
|
||||
pub fn get_asset(asset: &str) -> Option<&'static str> {
|
||||
STATIC_ASSETS.get(asset).map(|s| s.to_owned())
|
||||
}
|
||||
|
||||
fn get_maybe_hash(
|
||||
|
@ -294,7 +300,7 @@ fn load(state: &mut State, args: Value) -> Result<Value, AnyError> {
|
|||
Some("declare const __: any;\nexport = __;\n".to_string())
|
||||
} else if v.specifier.starts_with("asset:///") {
|
||||
let name = v.specifier.replace("asset:///", "");
|
||||
let maybe_source = get_asset(&name).map(|s| s.to_string());
|
||||
let maybe_source = get_asset(&name).map(String::from);
|
||||
hash = get_maybe_hash(&maybe_source, &state.hash_data);
|
||||
media_type = MediaType::from(&v.specifier);
|
||||
maybe_source
|
||||
|
|
Loading…
Reference in a new issue