summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/lsp/analysis.rs179
-rw-r--r--cli/lsp/diagnostics.rs24
-rw-r--r--cli/lsp/language_server.rs184
-rw-r--r--cli/lsp/performance.rs6
-rw-r--r--cli/lsp/sources.rs131
-rw-r--r--cli/lsp/tsc.rs32
-rw-r--r--cli/tests/lsp/code_action_request_cache.json46
-rw-r--r--cli/tests/lsp/code_action_response_cache.json36
-rw-r--r--cli/tests/lsp/did_open_notification_cache.json12
9 files changed, 488 insertions, 162 deletions
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs
index b79a9ec65..8600301a5 100644
--- a/cli/lsp/analysis.rs
+++ b/cli/lsp/analysis.rs
@@ -15,6 +15,7 @@ use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
+use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::ModuleResolutionError;
use deno_core::ModuleSpecifier;
@@ -152,6 +153,23 @@ pub enum ResolvedDependencyErr {
Missing,
}
+impl ResolvedDependencyErr {
+ pub fn as_code(&self) -> lsp::NumberOrString {
+ match self {
+ Self::InvalidDowngrade => {
+ lsp::NumberOrString::String("invalid-downgrade".to_string())
+ }
+ Self::InvalidLocalImport => {
+ lsp::NumberOrString::String("invalid-local-import".to_string())
+ }
+ Self::InvalidSpecifier(_) => {
+ lsp::NumberOrString::String("invalid-specifier".to_string())
+ }
+ Self::Missing => lsp::NumberOrString::String("missing".to_string()),
+ }
+ }
+}
+
impl fmt::Display for ResolvedDependencyErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
@@ -351,30 +369,34 @@ fn is_equivalent_code(
/// action for a given set of actions.
fn is_preferred(
action: &tsc::CodeFixAction,
- actions: &[(lsp::CodeAction, tsc::CodeFixAction)],
+ actions: &[CodeActionKind],
fix_priority: u32,
only_one: bool,
) -> bool {
- actions.iter().all(|(_, a)| {
- if action == a {
- return true;
- }
- if a.fix_id.is_some() {
- return true;
- }
- if let Some((other_fix_priority, _)) =
- PREFERRED_FIXES.get(a.fix_name.as_str())
- {
- match other_fix_priority.cmp(&fix_priority) {
- Ordering::Less => return true,
- Ordering::Greater => return false,
- Ordering::Equal => (),
+ actions.iter().all(|i| {
+ if let CodeActionKind::Tsc(_, a) = i {
+ if action == a {
+ return true;
+ }
+ if a.fix_id.is_some() {
+ return true;
}
- if only_one && action.fix_name == a.fix_name {
- return false;
+ if let Some((other_fix_priority, _)) =
+ PREFERRED_FIXES.get(a.fix_name.as_str())
+ {
+ match other_fix_priority.cmp(&fix_priority) {
+ Ordering::Less => return true,
+ Ordering::Greater => return false,
+ Ordering::Equal => (),
+ }
+ if only_one && action.fix_name == a.fix_name {
+ return false;
+ }
}
+ true
+ } else {
+ true
}
- true
})
}
@@ -404,13 +426,58 @@ pub struct CodeActionData {
pub fix_id: String,
}
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct DenoFixData {
+ pub specifier: ModuleSpecifier,
+}
+
+#[derive(Debug, Clone)]
+enum CodeActionKind {
+ Deno(lsp::CodeAction),
+ Tsc(lsp::CodeAction, tsc::CodeFixAction),
+}
+
+#[derive(Debug, Hash, PartialEq, Eq)]
+enum FixAllKind {
+ Tsc(String),
+}
+
#[derive(Debug, Default)]
pub struct CodeActionCollection {
- actions: Vec<(lsp::CodeAction, tsc::CodeFixAction)>,
- fix_all_actions: HashMap<String, (lsp::CodeAction, tsc::CodeFixAction)>,
+ actions: Vec<CodeActionKind>,
+ fix_all_actions: HashMap<FixAllKind, CodeActionKind>,
}
impl CodeActionCollection {
+ pub(crate) fn add_deno_fix_action(
+ &mut self,
+ diagnostic: &lsp::Diagnostic,
+ ) -> Result<(), AnyError> {
+ if let Some(data) = diagnostic.data.clone() {
+ let fix_data: DenoFixData = serde_json::from_value(data)?;
+ let code_action = lsp::CodeAction {
+ title: format!(
+ "Cache \"{}\" and its dependencies.",
+ fix_data.specifier
+ ),
+ kind: Some(lsp::CodeActionKind::QUICKFIX),
+ diagnostics: Some(vec![diagnostic.clone()]),
+ edit: None,
+ command: Some(lsp::Command {
+ title: "".to_string(),
+ command: "deno.cache".to_string(),
+ arguments: Some(vec![json!([fix_data.specifier])]),
+ }),
+ is_preferred: None,
+ disabled: None,
+ data: None,
+ };
+ self.actions.push(CodeActionKind::Deno(code_action));
+ }
+ Ok(())
+ }
+
/// Add a TypeScript code fix action to the code actions collection.
pub(crate) async fn add_ts_fix_action(
&mut self,
@@ -442,19 +509,28 @@ impl CodeActionCollection {
disabled: None,
data: None,
};
- self.actions.retain(|(c, a)| {
- !(action.fix_name == a.fix_name && code_action.edit == c.edit)
+ self.actions.retain(|i| match i {
+ CodeActionKind::Tsc(c, a) => {
+ !(action.fix_name == a.fix_name && code_action.edit == c.edit)
+ }
+ _ => true,
});
- self.actions.push((code_action, action.clone()));
+ self
+ .actions
+ .push(CodeActionKind::Tsc(code_action, action.clone()));
if let Some(fix_id) = &action.fix_id {
- if let Some((existing_fix_all, existing_action)) =
- self.fix_all_actions.get(fix_id)
+ if let Some(CodeActionKind::Tsc(existing_fix_all, existing_action)) =
+ self.fix_all_actions.get(&FixAllKind::Tsc(fix_id.clone()))
{
- self.actions.retain(|(c, _)| c != existing_fix_all);
- self
- .actions
- .push((existing_fix_all.clone(), existing_action.clone()));
+ self.actions.retain(|i| match i {
+ CodeActionKind::Tsc(c, _) => c != existing_fix_all,
+ _ => true,
+ });
+ self.actions.push(CodeActionKind::Tsc(
+ existing_fix_all.clone(),
+ existing_action.clone(),
+ ));
}
}
Ok(())
@@ -488,15 +564,21 @@ impl CodeActionCollection {
disabled: None,
data,
};
- if let Some((existing, _)) =
- self.fix_all_actions.get(&action.fix_id.clone().unwrap())
+ if let Some(CodeActionKind::Tsc(existing, _)) = self
+ .fix_all_actions
+ .get(&FixAllKind::Tsc(action.fix_id.clone().unwrap()))
{
- self.actions.retain(|(c, _)| c != existing);
+ self.actions.retain(|i| match i {
+ CodeActionKind::Tsc(c, _) => c != existing,
+ _ => true,
+ });
}
- self.actions.push((code_action.clone(), action.clone()));
+ self
+ .actions
+ .push(CodeActionKind::Tsc(code_action.clone(), action.clone()));
self.fix_all_actions.insert(
- action.fix_id.clone().unwrap(),
- (code_action, action.clone()),
+ FixAllKind::Tsc(action.fix_id.clone().unwrap()),
+ CodeActionKind::Tsc(code_action, action.clone()),
);
}
@@ -505,7 +587,10 @@ impl CodeActionCollection {
self
.actions
.into_iter()
- .map(|(c, _)| lsp::CodeActionOrCommand::CodeAction(c))
+ .map(|i| match i {
+ CodeActionKind::Tsc(c, _) => lsp::CodeActionOrCommand::CodeAction(c),
+ CodeActionKind::Deno(c) => lsp::CodeActionOrCommand::CodeAction(c),
+ })
.collect()
}
@@ -521,7 +606,7 @@ impl CodeActionCollection {
if action.fix_id.is_none()
|| self
.fix_all_actions
- .contains_key(&action.fix_id.clone().unwrap())
+ .contains_key(&FixAllKind::Tsc(action.fix_id.clone().unwrap()))
{
false
} else {
@@ -543,15 +628,17 @@ impl CodeActionCollection {
/// when all actions are added to the collection.
pub fn set_preferred_fixes(&mut self) {
let actions = self.actions.clone();
- for (code_action, action) in self.actions.iter_mut() {
- if action.fix_id.is_some() {
- continue;
- }
- if let Some((fix_priority, only_one)) =
- PREFERRED_FIXES.get(action.fix_name.as_str())
- {
- code_action.is_preferred =
- Some(is_preferred(action, &actions, *fix_priority, *only_one));
+ for entry in self.actions.iter_mut() {
+ if let CodeActionKind::Tsc(code_action, action) = entry {
+ if action.fix_id.is_some() {
+ continue;
+ }
+ if let Some((fix_priority, only_one)) =
+ PREFERRED_FIXES.get(action.fix_name.as_str())
+ {
+ code_action.is_preferred =
+ Some(is_preferred(action, &actions, *fix_priority, *only_one));
+ }
}
}
}
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index 437a96543..1ea2cce14 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -11,6 +11,7 @@ use crate::media_type::MediaType;
use deno_core::error::AnyError;
use deno_core::serde_json;
+use deno_core::serde_json::json;
use deno_core::ModuleSpecifier;
use lspower::lsp;
use std::collections::HashMap;
@@ -279,14 +280,14 @@ pub async fn generate_dependency_diagnostics(
&dependency.maybe_code_specifier_range,
) {
match code.clone() {
- ResolvedDependency::Err(err) => {
+ ResolvedDependency::Err(dependency_err) => {
diagnostic_list.push(lsp::Diagnostic {
range: *range,
severity: Some(lsp::DiagnosticSeverity::Error),
- code: None,
+ code: Some(dependency_err.as_code()),
code_description: None,
source: Some("deno".to_string()),
- message: format!("{}", err),
+ message: format!("{}", dependency_err),
related_information: None,
tags: None,
data: None,
@@ -295,20 +296,23 @@ pub async fn generate_dependency_diagnostics(
ResolvedDependency::Resolved(specifier) => {
if !(state_snapshot.documents.contains(&specifier) || sources.contains(&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))
+ } else {
+ (Some(lsp::NumberOrString::String("no-cache".to_string())), format!("Unable to load the remote module: \"{}\".", specifier))
+ };
diagnostic_list.push(lsp::Diagnostic {
range: *range,
severity: Some(lsp::DiagnosticSeverity::Error),
- code: None,
+ code,
code_description: None,
source: Some("deno".to_string()),
- message: if is_local {
- format!("Unable to load a local module: \"{}\".\n Please check the file path.", specifier)
- } else {
- format!("Unable to load the module: \"{}\".\n If the module exists, running `deno cache {}` should resolve this error.", specifier, specifier)
- },
+ message,
related_information: None,
tags: None,
- data: None,
+ data: Some(json!({
+ "specifier": specifier
+ })),
})
}
},
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 3f1d5f6c2..7ae4de978 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -65,6 +65,7 @@ pub struct LanguageServer(Arc<tokio::sync::Mutex<Inner>>);
pub struct StateSnapshot {
pub assets: HashMap<ModuleSpecifier, Option<AssetDocument>>,
pub documents: DocumentCache,
+ pub performance: Performance,
pub sources: Sources,
}
@@ -190,7 +191,7 @@ impl Inner {
/// Only searches already cached assets and documents for a line index. If
/// the line index cannot be found, `None` is returned.
fn get_line_index_sync(
- &mut self,
+ &self,
specifier: &ModuleSpecifier,
) -> Option<LineIndex> {
let mark = self.performance.mark("get_line_index_sync");
@@ -389,6 +390,7 @@ impl Inner {
StateSnapshot {
assets: self.assets.clone(),
documents: self.documents.clone(),
+ performance: self.performance.clone(),
sources: self.sources.clone(),
}
}
@@ -514,7 +516,7 @@ impl Inner {
}
pub(crate) fn document_version(
- &mut self,
+ &self,
specifier: ModuleSpecifier,
) -> Option<i32> {
self.documents.version(&specifier)
@@ -851,7 +853,7 @@ impl Inner {
}
}
- async fn hover(&mut self, params: HoverParams) -> LspResult<Option<Hover>> {
+ async fn hover(&self, params: HoverParams) -> LspResult<Option<Hover>> {
if !self.enabled() {
return Ok(None);
}
@@ -912,7 +914,10 @@ impl Inner {
}
_ => false,
},
- // currently only processing `deno-ts` quick fixes
+ "deno" => match &d.code {
+ Some(NumberOrString::String(code)) => code == "no-cache",
+ _ => false,
+ },
_ => false,
},
None => false,
@@ -923,53 +928,65 @@ impl Inner {
return Ok(None);
}
let line_index = self.get_line_index_sync(&specifier).unwrap();
+ let mut code_actions = CodeActionCollection::default();
let file_diagnostics: Vec<Diagnostic> = self
.diagnostics
.diagnostics_for(&specifier, &DiagnosticSource::TypeScript)
.cloned()
.collect();
- let mut code_actions = CodeActionCollection::default();
for diagnostic in &fixable_diagnostics {
- let code = match &diagnostic.code.clone().unwrap() {
- NumberOrString::String(code) => code.to_string(),
- NumberOrString::Number(code) => code.to_string(),
- };
- let codes = vec![code];
- let req = tsc::RequestMethod::GetCodeFixes((
- specifier.clone(),
- line_index.offset_tsc(diagnostic.range.start)?,
- line_index.offset_tsc(diagnostic.range.end)?,
- codes,
- ));
- let res =
- self
- .ts_server
- .request(self.snapshot(), req)
- .await
- .map_err(|err| {
- error!("Error getting actions from TypeScript: {}", err);
- LspError::internal_error()
- })?;
- let actions: Vec<tsc::CodeFixAction> =
- from_value(res).map_err(|err| {
- error!("Cannot decode actions from TypeScript: {}", err);
- LspError::internal_error()
- })?;
- for action in actions {
- code_actions
- .add_ts_fix_action(&action, diagnostic, self)
- .await
- .map_err(|err| {
- error!("Unable to convert fix: {}", err);
- LspError::internal_error()
- })?;
- if code_actions.is_fix_all_action(
- &action,
- diagnostic,
- &file_diagnostics,
- ) {
- code_actions.add_ts_fix_all_action(&action, &specifier, diagnostic);
+ match diagnostic.source.as_deref() {
+ Some("deno-ts") => {
+ let code = match diagnostic.code.as_ref().unwrap() {
+ NumberOrString::String(code) => code.to_string(),
+ NumberOrString::Number(code) => code.to_string(),
+ };
+ let codes = vec![code];
+ let req = tsc::RequestMethod::GetCodeFixes((
+ specifier.clone(),
+ line_index.offset_tsc(diagnostic.range.start)?,
+ line_index.offset_tsc(diagnostic.range.end)?,
+ codes,
+ ));
+ let res =
+ self.ts_server.request(self.snapshot(), req).await.map_err(
+ |err| {
+ error!("Error getting actions from TypeScript: {}", err);
+ LspError::internal_error()
+ },
+ )?;
+ let actions: Vec<tsc::CodeFixAction> =
+ from_value(res).map_err(|err| {
+ error!("Cannot decode actions from TypeScript: {}", err);
+ LspError::internal_error()
+ })?;
+ for action in actions {
+ code_actions
+ .add_ts_fix_action(&action, diagnostic, self)
+ .await
+ .map_err(|err| {
+ error!("Unable to convert fix: {}", err);
+ LspError::internal_error()
+ })?;
+ if code_actions.is_fix_all_action(
+ &action,
+ diagnostic,
+ &file_diagnostics,
+ ) {
+ code_actions
+ .add_ts_fix_all_action(&action, &specifier, diagnostic);
+ }
+ }
}
+ Some("deno") => {
+ code_actions
+ .add_deno_fix_action(diagnostic)
+ .map_err(|err| {
+ error!("{}", err);
+ LspError::internal_error()
+ })?
+ }
+ _ => (),
}
}
code_actions.set_preferred_fixes();
@@ -1020,9 +1037,8 @@ impl Inner {
Ok(code_action)
}
} else {
- Err(LspError::invalid_params(
- "The CodeAction's data is missing.",
- ))
+ // The code action doesn't need to be resolved
+ Ok(params)
};
self.performance.measure(mark);
result
@@ -1343,7 +1359,7 @@ impl Inner {
}
async fn document_highlight(
- &mut self,
+ &self,
params: DocumentHighlightParams,
) -> LspResult<Option<Vec<DocumentHighlight>>> {
if !self.enabled() {
@@ -1481,7 +1497,7 @@ impl Inner {
}
async fn completion(
- &mut self,
+ &self,
params: CompletionParams,
) -> LspResult<Option<CompletionResponse>> {
if !self.enabled() {
@@ -1830,7 +1846,12 @@ impl lspower::LanguageServer for LanguageServer {
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct CacheParams {
- text_document: TextDocumentIdentifier,
+ /// The document currently open in the editor. If there are no `uris`
+ /// supplied, the referrer will be cached.
+ referrer: TextDocumentIdentifier,
+ /// Any documents that have been specifically asked to be cached via the
+ /// command.
+ uris: Vec<TextDocumentIdentifier>,
}
#[derive(Debug, Deserialize, Serialize)]
@@ -1841,19 +1862,38 @@ struct VirtualTextDocumentParams {
// These are implementations of custom commands supported by the LSP
impl Inner {
+ /// Similar to `deno cache` on the command line, where modules will be cached
+ /// in the Deno cache, including any of their dependencies.
async fn cache(&mut self, params: CacheParams) -> LspResult<bool> {
let mark = self.performance.mark("cache");
- let specifier = utils::normalize_url(params.text_document.uri);
- let maybe_import_map = self.maybe_import_map.clone();
- sources::cache(specifier.clone(), maybe_import_map)
- .await
- .map_err(|err| {
- error!("{}", err);
- LspError::internal_error()
- })?;
- if self.documents.contains(&specifier) {
- self.diagnostics.invalidate(&specifier);
+ let referrer = utils::normalize_url(params.referrer.uri);
+ if !params.uris.is_empty() {
+ for identifier in &params.uris {
+ let specifier = utils::normalize_url(identifier.uri.clone());
+ sources::cache(&specifier, &self.maybe_import_map)
+ .await
+ .map_err(|err| {
+ error!("{}", err);
+ LspError::internal_error()
+ })?;
+ }
+ } else {
+ sources::cache(&referrer, &self.maybe_import_map)
+ .await
+ .map_err(|err| {
+ error!("{}", err);
+ LspError::internal_error()
+ })?;
}
+ // now that we have dependencies loaded, we need to re-analyze them and
+ // invalidate some diagnostics
+ if self.documents.contains(&referrer) {
+ if let Some(source) = self.documents.content(&referrer).unwrap() {
+ self.analyze_dependencies(&referrer, &source);
+ }
+ self.diagnostics.invalidate(&referrer);
+ }
+
self.prepare_diagnostics().await.map_err(|err| {
error!("{}", err);
LspError::internal_error()
@@ -2685,6 +2725,28 @@ mod tests {
harness.run().await;
}
+ #[tokio::test]
+ async fn test_code_actions_deno_cache() {
+ let mut harness = LspTestHarness::new(vec![
+ ("initialize_request.json", LspResponse::RequestAny),
+ ("initialized_notification.json", LspResponse::None),
+ ("did_open_notification_cache.json", LspResponse::None),
+ (
+ "code_action_request_cache.json",
+ LspResponse::RequestFixture(
+ 2,
+ "code_action_response_cache.json".to_string(),
+ ),
+ ),
+ (
+ "shutdown_request.json",
+ LspResponse::Request(3, json!(null)),
+ ),
+ ("exit_notification.json", LspResponse::None),
+ ]);
+ harness.run().await;
+ }
+
#[derive(Deserialize)]
struct PerformanceAverages {
averages: Vec<PerformanceAverage>,
@@ -2730,7 +2792,7 @@ mod tests {
LspResponse::RequestAssert(|value| {
let resp: PerformanceResponse =
serde_json::from_value(value).unwrap();
- assert_eq!(resp.result.averages.len(), 10);
+ assert_eq!(resp.result.averages.len(), 12);
}),
),
(
diff --git a/cli/lsp/performance.rs b/cli/lsp/performance.rs
index 8668519c8..537583141 100644
--- a/cli/lsp/performance.rs
+++ b/cli/lsp/performance.rs
@@ -49,7 +49,7 @@ impl From<PerformanceMark> for PerformanceMeasure {
///
/// The structure will limit the size of measurements to the most recent 1000,
/// and will roll off when that limit is reached.
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct Performance {
counts: Arc<Mutex<HashMap<String, u32>>>,
max_size: usize,
@@ -127,13 +127,15 @@ impl Performance {
/// A function which accepts a previously created performance mark which will
/// be used to finalize the duration of the span being measured, and add the
/// measurement to the internal buffer.
- pub fn measure(&self, mark: PerformanceMark) {
+ pub fn measure(&self, mark: PerformanceMark) -> Duration {
let measure = PerformanceMeasure::from(mark);
+ let duration = measure.duration;
let mut measures = self.measures.lock().unwrap();
measures.push_back(measure);
while measures.len() > self.max_size {
measures.pop_front();
}
+ duration
}
}
diff --git a/cli/lsp/sources.rs b/cli/lsp/sources.rs
index c2ef27010..3298b847b 100644
--- a/cli/lsp/sources.rs
+++ b/cli/lsp/sources.rs
@@ -28,16 +28,16 @@ use std::sync::Mutex;
use std::time::SystemTime;
pub async fn cache(
- specifier: ModuleSpecifier,
- maybe_import_map: Option<ImportMap>,
+ specifier: &ModuleSpecifier,
+ maybe_import_map: &Option<ImportMap>,
) -> Result<(), AnyError> {
let program_state = Arc::new(ProgramState::new(Default::default())?);
let handler = Arc::new(Mutex::new(FetchHandler::new(
&program_state,
Permissions::allow_all(),
)?));
- let mut builder = GraphBuilder::new(handler, maybe_import_map, None);
- builder.add(&specifier, false).await
+ let mut builder = GraphBuilder::new(handler, maybe_import_map.clone(), None);
+ builder.add(specifier, false).await
}
#[derive(Debug, Clone, Default)]
@@ -51,7 +51,7 @@ struct Metadata {
}
#[derive(Debug, Clone, Default)]
-pub struct Sources {
+struct Inner {
http_cache: HttpCache,
maybe_import_map: Option<ImportMap>,
metadata: HashMap<ModuleSpecifier, Metadata>,
@@ -59,15 +59,80 @@ pub struct Sources {
remotes: HashMap<ModuleSpecifier, PathBuf>,
}
+#[derive(Debug, Clone, Default)]
+pub struct Sources(Arc<Mutex<Inner>>);
+
impl Sources {
pub fn new(location: &Path) -> Self {
+ Self(Arc::new(Mutex::new(Inner::new(location))))
+ }
+
+ pub fn contains(&self, specifier: &ModuleSpecifier) -> bool {
+ self.0.lock().unwrap().contains(specifier)
+ }
+
+ 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,
+ ) -> Option<LineIndex> {
+ self.0.lock().unwrap().get_line_index(specifier)
+ }
+
+ pub fn get_maybe_types(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<analysis::ResolvedDependency> {
+ self.0.lock().unwrap().get_maybe_types(specifier)
+ }
+
+ pub fn get_media_type(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<MediaType> {
+ self.0.lock().unwrap().get_media_type(specifier)
+ }
+
+ pub fn get_script_version(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<String> {
+ self.0.lock().unwrap().get_script_version(specifier)
+ }
+
+ pub fn get_text(&self, specifier: &ModuleSpecifier) -> Option<String> {
+ self.0.lock().unwrap().get_text(specifier)
+ }
+
+ pub fn resolve_import(
+ &self,
+ specifier: &str,
+ referrer: &ModuleSpecifier,
+ ) -> Option<(ModuleSpecifier, MediaType)> {
+ self.0.lock().unwrap().resolve_import(specifier, referrer)
+ }
+
+ #[cfg(test)]
+ fn resolve_specifier(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<ModuleSpecifier> {
+ self.0.lock().unwrap().resolve_specifier(specifier)
+ }
+}
+
+impl Inner {
+ fn new(location: &Path) -> Self {
Self {
http_cache: HttpCache::new(location),
..Default::default()
}
}
- pub fn contains(&mut self, specifier: &ModuleSpecifier) -> bool {
+ fn contains(&mut self, specifier: &ModuleSpecifier) -> bool {
if let Some(specifier) = self.resolve_specifier(specifier) {
if self.get_metadata(&specifier).is_some() {
return true;
@@ -80,16 +145,13 @@ impl Sources {
/// 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(
- &mut self,
- specifier: &ModuleSpecifier,
- ) -> Option<usize> {
+ fn get_length_utf16(&mut self, specifier: &ModuleSpecifier) -> Option<usize> {
let specifier = self.resolve_specifier(specifier)?;
let metadata = self.get_metadata(&specifier)?;
Some(metadata.source.encode_utf16().count())
}
- pub fn get_line_index(
+ fn get_line_index(
&mut self,
specifier: &ModuleSpecifier,
) -> Option<LineIndex> {
@@ -98,7 +160,7 @@ impl Sources {
Some(metadata.line_index)
}
- pub fn get_maybe_types(
+ fn get_maybe_types(
&mut self,
specifier: &ModuleSpecifier,
) -> Option<analysis::ResolvedDependency> {
@@ -106,7 +168,7 @@ impl Sources {
metadata.maybe_types
}
- pub fn get_media_type(
+ fn get_media_type(
&mut self,
specifier: &ModuleSpecifier,
) -> Option<MediaType> {
@@ -117,12 +179,11 @@ impl Sources {
fn get_metadata(&mut self, specifier: &ModuleSpecifier) -> Option<Metadata> {
if let Some(metadata) = self.metadata.get(specifier).cloned() {
- if let Some(current_version) = self.get_script_version(specifier) {
- if metadata.version == current_version {
- return Some(metadata);
- }
+ if metadata.version == self.get_script_version(specifier)? {
+ return Some(metadata);
}
}
+
// TODO(@kitsonk) this needs to be refactored, lots of duplicate logic and
// is really difficult to follow.
let version = self.get_script_version(specifier)?;
@@ -248,28 +309,24 @@ impl Sources {
None
}
- pub fn get_script_version(
+ fn get_script_version(
&mut self,
specifier: &ModuleSpecifier,
) -> Option<String> {
- if let Some(path) = self.get_path(specifier) {
- if let Ok(metadata) = fs::metadata(path) {
- if let Ok(modified) = metadata.modified() {
- return if let Ok(n) = modified.duration_since(SystemTime::UNIX_EPOCH)
- {
- Some(format!("{}", n.as_millis()))
- } else {
- Some("1".to_string())
- };
- } else {
- return Some("1".to_string());
- }
+ let path = self.get_path(specifier)?;
+ let metadata = fs::metadata(path).ok()?;
+ if let Ok(modified) = metadata.modified() {
+ if let Ok(n) = modified.duration_since(SystemTime::UNIX_EPOCH) {
+ Some(format!("{}", n.as_millis()))
+ } else {
+ Some("1".to_string())
}
+ } else {
+ Some("1".to_string())
}
- None
}
- pub fn get_text(&mut self, specifier: &ModuleSpecifier) -> Option<String> {
+ fn get_text(&mut self, specifier: &ModuleSpecifier) -> Option<String> {
let specifier = self.resolve_specifier(specifier)?;
let metadata = self.get_metadata(&specifier)?;
Some(metadata.source)
@@ -289,7 +346,7 @@ impl Sources {
Some((resolved_specifier, media_type))
}
- pub fn resolve_import(
+ fn resolve_import(
&mut self,
specifier: &str,
referrer: &ModuleSpecifier,
@@ -385,7 +442,7 @@ mod tests {
#[test]
fn test_sources_get_script_version() {
- let (mut sources, _) = setup();
+ let (sources, _) = setup();
let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let tests = c.join("tests");
let specifier = ModuleSpecifier::resolve_path(
@@ -398,7 +455,7 @@ mod tests {
#[test]
fn test_sources_get_text() {
- let (mut sources, _) = setup();
+ let (sources, _) = setup();
let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let tests = c.join("tests");
let specifier = ModuleSpecifier::resolve_path(
@@ -413,7 +470,7 @@ mod tests {
#[test]
fn test_sources_get_length_utf16() {
- let (mut sources, _) = setup();
+ let (sources, _) = setup();
let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let tests = c.join("tests");
let specifier = ModuleSpecifier::resolve_path(
@@ -428,7 +485,7 @@ mod tests {
#[test]
fn test_sources_resolve_specifier_non_supported_schema() {
- let (mut sources, _) = setup();
+ let (sources, _) = setup();
let specifier = ModuleSpecifier::resolve_url("foo://a/b/c.ts")
.expect("could not create specifier");
let actual = sources.resolve_specifier(&specifier);
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 588487517..a66541dd2 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -978,10 +978,12 @@ struct SourceSnapshotArgs {
/// The language service is dropping a reference to a source file snapshot, and
/// we can drop our version of that document.
fn dispose(state: &mut State, args: Value) -> Result<Value, AnyError> {
+ let mark = state.state_snapshot.performance.mark("op_dispose");
let v: SourceSnapshotArgs = serde_json::from_value(args)?;
state
.snapshots
.remove(&(v.specifier.into(), v.version.into()));
+ state.state_snapshot.performance.measure(mark);
Ok(json!(true))
}
@@ -997,6 +999,7 @@ struct GetChangeRangeArgs {
/// The language service wants to compare an old snapshot with a new snapshot to
/// determine what source hash changed.
fn get_change_range(state: &mut State, args: Value) -> Result<Value, AnyError> {
+ let mark = state.state_snapshot.performance.mark("op_get_change_range");
let v: GetChangeRangeArgs = serde_json::from_value(args.clone())?;
cache_snapshot(state, v.specifier.clone(), v.version.clone())?;
if let Some(current) = state
@@ -1007,8 +1010,11 @@ fn get_change_range(state: &mut State, args: Value) -> Result<Value, AnyError> {
.snapshots
.get(&(v.specifier.clone().into(), v.old_version.clone().into()))
{
+ state.state_snapshot.performance.measure(mark);
Ok(text::get_range_change(prev, current))
} else {
+ let new_length = current.encode_utf16().count();
+ state.state_snapshot.performance.measure(mark);
// when a local file is opened up in the editor, the compiler might
// already have a snapshot of it in memory, and will request it, but we
// now are working off in memory versions of the document, and so need
@@ -1018,10 +1024,11 @@ fn get_change_range(state: &mut State, args: Value) -> Result<Value, AnyError> {
"start": 0,
"length": v.old_length,
},
- "newLength": current.encode_utf16().count(),
+ "newLength": new_length,
}))
}
} else {
+ state.state_snapshot.performance.measure(mark);
Err(custom_error(
"MissingSnapshot",
format!(
@@ -1033,6 +1040,7 @@ fn get_change_range(state: &mut State, args: Value) -> Result<Value, AnyError> {
}
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) {
@@ -1041,9 +1049,11 @@ fn get_length(state: &mut State, args: Value) -> Result<Value, AnyError> {
.snapshots
.get(&(v.specifier.into(), v.version.into()))
.unwrap();
+ state.state_snapshot.performance.measure(mark);
Ok(json!(content.encode_utf16().count()))
} else {
let sources = &mut state.state_snapshot.sources;
+ state.state_snapshot.performance.measure(mark);
Ok(json!(sources.get_length_utf16(&specifier).unwrap()))
}
}
@@ -1058,6 +1068,7 @@ struct GetTextArgs {
}
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) {
@@ -1071,10 +1082,12 @@ fn get_text(state: &mut State, args: Value) -> Result<Value, AnyError> {
let sources = &mut state.state_snapshot.sources;
sources.get_text(&specifier).unwrap()
};
+ state.state_snapshot.performance.measure(mark);
Ok(json!(text::slice(&content, v.start..v.end)))
}
fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
+ let mark = state.state_snapshot.performance.mark("op_resolve");
let v: ResolveArgs = serde_json::from_value(args)?;
let mut resolved = Vec::<Option<(String, String)>>::new();
let referrer = ModuleSpecifier::resolve_url(&v.base)?;
@@ -1102,9 +1115,13 @@ 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)
- || sources.contains(&resolved_specifier)
- {
+ if state.state_snapshot.documents.contains(&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) {
let media_type = if let Some(media_type) =
sources.get_media_type(&resolved_specifier)
{
@@ -1139,6 +1156,7 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
}
}
} else {
+ state.state_snapshot.performance.measure(mark);
return Err(custom_error(
"NotFound",
format!(
@@ -1148,6 +1166,7 @@ fn resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
));
}
+ state.state_snapshot.performance.measure(mark);
Ok(json!(resolved))
}
@@ -1167,6 +1186,7 @@ struct ScriptVersionArgs {
}
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) {
@@ -1178,6 +1198,7 @@ fn script_version(state: &mut State, args: Value) -> Result<Value, AnyError> {
}
}
+ state.state_snapshot.performance.measure(mark);
Ok(json!(None::<String>))
}
@@ -1480,9 +1501,8 @@ mod tests {
documents.open(specifier, version, content);
}
StateSnapshot {
- assets: Default::default(),
documents,
- sources: Default::default(),
+ ..Default::default()
}
}
diff --git a/cli/tests/lsp/code_action_request_cache.json b/cli/tests/lsp/code_action_request_cache.json
new file mode 100644
index 000000000..8e296be32
--- /dev/null
+++ b/cli/tests/lsp/code_action_request_cache.json
@@ -0,0 +1,46 @@
+{
+ "jsonrpc": "2.0",
+ "id": 2,
+ "method": "textDocument/codeAction",
+ "params": {
+ "textDocument": {
+ "uri": "file:///a/file.ts"
+ },
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 19
+ },
+ "end": {
+ "line": 0,
+ "character": 49
+ }
+ },
+ "context": {
+ "diagnostics": [
+ {
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 19
+ },
+ "end": {
+ "line": 0,
+ "character": 49
+ }
+ },
+ "severity": 1,
+ "code": "no-cache",
+ "source": "deno",
+ "message": "Unable to load the remote module: \"https://deno.land/x/a/mod.ts\".",
+ "data": {
+ "specifier": "https://deno.land/x/a/mod.ts"
+ }
+ }
+ ],
+ "only": [
+ "quickfix"
+ ]
+ }
+ }
+}
diff --git a/cli/tests/lsp/code_action_response_cache.json b/cli/tests/lsp/code_action_response_cache.json
new file mode 100644
index 000000000..c56b35023
--- /dev/null
+++ b/cli/tests/lsp/code_action_response_cache.json
@@ -0,0 +1,36 @@
+[
+ {
+ "title": "Cache \"https://deno.land/x/a/mod.ts\" and its dependencies.",
+ "kind": "quickfix",
+ "diagnostics": [
+ {
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 19
+ },
+ "end": {
+ "line": 0,
+ "character": 49
+ }
+ },
+ "severity": 1,
+ "code": "no-cache",
+ "source": "deno",
+ "message": "Unable to load the remote module: \"https://deno.land/x/a/mod.ts\".",
+ "data": {
+ "specifier": "https://deno.land/x/a/mod.ts"
+ }
+ }
+ ],
+ "command": {
+ "title": "",
+ "command": "deno.cache",
+ "arguments": [
+ [
+ "https://deno.land/x/a/mod.ts"
+ ]
+ ]
+ }
+ }
+]
diff --git a/cli/tests/lsp/did_open_notification_cache.json b/cli/tests/lsp/did_open_notification_cache.json
new file mode 100644
index 000000000..6f21ee5cd
--- /dev/null
+++ b/cli/tests/lsp/did_open_notification_cache.json
@@ -0,0 +1,12 @@
+{
+ "jsonrpc": "2.0",
+ "method": "textDocument/didOpen",
+ "params": {
+ "textDocument": {
+ "uri": "file:///a/file.ts",
+ "languageId": "typescript",
+ "version": 1,
+ "text": "import * as a from \"https://deno.land/x/a/mod.ts\";\n\nconsole.log(a);\n"
+ }
+ }
+}