1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-22 15:06:54 -05:00

feat(lsp): add deno cache code actions (#9471)

This commit is contained in:
Kitson Kelly 2021-02-12 15:17:48 +11:00 committed by GitHub
parent 46da7c6aff
commit d6c05b09dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 488 additions and 162 deletions

View file

@ -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 only_one && action.fix_name == a.fix_name {
return false;
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 => (),
}
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));
}
}
}
}

View file

@ -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
})),
})
}
},

View file

@ -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);
}),
),
(

View file

@ -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
}
}

View file

@ -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);

View file

@ -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()
}
}

View file

@ -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"
]
}
}
}

View file

@ -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"
]
]
}
}
]

View file

@ -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"
}
}
}