summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2021-06-02 20:29:58 +1000
committerGitHub <noreply@github.com>2021-06-02 20:29:58 +1000
commit473713c6210ee11f11b7ae4c83165c4f87ff2d77 (patch)
tree34d361841ded4b8f0e089615b4afa7a40e37731b /cli
parent9ae8dbf17334f1cf7ae09abf585d8797f374bdc4 (diff)
fix(#10815): lsp only responds to formatting for md, json, jsonc (#10816)
Fixes #10815
Diffstat (limited to 'cli')
-rw-r--r--cli/file_fetcher.rs77
-rw-r--r--cli/lsp/completions.rs18
-rw-r--r--cli/lsp/diagnostics.rs31
-rw-r--r--cli/lsp/documents.rs128
-rw-r--r--cli/lsp/language_server.rs148
-rw-r--r--cli/lsp/tsc.rs37
-rw-r--r--cli/media_type.rs159
-rw-r--r--cli/tests/integration_tests_lsp.rs100
8 files changed, 529 insertions, 169 deletions
diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs
index 7ab2f5cce..4d3048750 100644
--- a/cli/file_fetcher.rs
+++ b/cli/file_fetcher.rs
@@ -178,33 +178,7 @@ pub fn map_content_type(
if let Some(content_type) = maybe_content_type {
let mut content_types = content_type.split(';');
let content_type = content_types.next().unwrap();
- let media_type = match content_type.trim().to_lowercase().as_ref() {
- "application/typescript"
- | "text/typescript"
- | "video/vnd.dlna.mpeg-tts"
- | "video/mp2t"
- | "application/x-typescript" => {
- map_js_like_extension(specifier, MediaType::TypeScript)
- }
- "application/javascript"
- | "text/javascript"
- | "application/ecmascript"
- | "text/ecmascript"
- | "application/x-javascript"
- | "application/node" => {
- map_js_like_extension(specifier, MediaType::JavaScript)
- }
- "text/jsx" => MediaType::Jsx,
- "text/tsx" => MediaType::Tsx,
- "application/json" | "text/json" => MediaType::Json,
- "application/wasm" => MediaType::Wasm,
- // Handle plain and possibly webassembly
- "text/plain" | "application/octet-stream" => MediaType::from(specifier),
- _ => {
- debug!("unknown content type: {}", content_type);
- MediaType::Unknown
- }
- };
+ let media_type = MediaType::from_content_type(specifier, content_type);
let charset = content_types
.map(str::trim)
.find_map(|s| s.strip_prefix("charset="))
@@ -216,55 +190,6 @@ pub fn map_content_type(
}
}
-/// Used to augment media types by using the path part of a module specifier to
-/// resolve to a more accurate media type.
-fn map_js_like_extension(
- specifier: &ModuleSpecifier,
- default: MediaType,
-) -> MediaType {
- let path = if specifier.scheme() == "file" {
- if let Ok(path) = specifier.to_file_path() {
- path
- } else {
- PathBuf::from(specifier.path())
- }
- } else {
- PathBuf::from(specifier.path())
- };
- match path.extension() {
- None => default,
- Some(os_str) => match os_str.to_str() {
- None => default,
- Some("jsx") => MediaType::Jsx,
- Some("tsx") => MediaType::Tsx,
- // Because DTS files do not have a separate media type, or a unique
- // extension, we have to "guess" at those things that we consider that
- // look like TypeScript, and end with `.d.ts` are DTS files.
- Some("ts") => {
- if default == MediaType::TypeScript {
- match path.file_stem() {
- None => default,
- Some(os_str) => {
- if let Some(file_stem) = os_str.to_str() {
- if file_stem.ends_with(".d") {
- MediaType::Dts
- } else {
- default
- }
- } else {
- default
- }
- }
- }
- } else {
- default
- }
- }
- Some(_) => default,
- },
- }
-}
-
/// Remove shebangs from the start of source code strings
fn strip_shebang(mut value: String) -> String {
if value.starts_with("#!") {
diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs
index 95a13d59a..0e78b06e3 100644
--- a/cli/lsp/completions.rs
+++ b/cli/lsp/completions.rs
@@ -559,6 +559,7 @@ mod tests {
use crate::http_cache::HttpCache;
use crate::lsp::analysis;
use crate::lsp::documents::DocumentCache;
+ use crate::lsp::documents::LanguageId;
use crate::lsp::sources::Sources;
use crate::media_type::MediaType;
use deno_core::resolve_url;
@@ -567,15 +568,15 @@ mod tests {
use tempfile::TempDir;
fn mock_state_snapshot(
- fixtures: &[(&str, &str, i32)],
+ fixtures: &[(&str, &str, i32, LanguageId)],
source_fixtures: &[(&str, &str)],
location: &Path,
) -> language_server::StateSnapshot {
let mut documents = DocumentCache::default();
- for (specifier, source, version) in fixtures {
+ for (specifier, source, version, language_id) in fixtures {
let specifier =
resolve_url(specifier).expect("failed to create specifier");
- documents.open(specifier.clone(), *version, source);
+ documents.open(specifier.clone(), *version, language_id.clone(), source);
let media_type = MediaType::from(&specifier);
let parsed_module =
analysis::parse_module(&specifier, source, &media_type).unwrap();
@@ -608,7 +609,7 @@ mod tests {
}
fn setup(
- documents: &[(&str, &str, i32)],
+ documents: &[(&str, &str, i32, LanguageId)],
sources: &[(&str, &str)],
) -> language_server::StateSnapshot {
let temp_dir = TempDir::new().expect("could not create temp dir");
@@ -885,8 +886,13 @@ mod tests {
};
let state_snapshot = setup(
&[
- ("file:///a/b/c.ts", "import * as d from \"h\"", 1),
- ("file:///a/c.ts", r#""#, 1),
+ (
+ "file:///a/b/c.ts",
+ "import * as d from \"h\"",
+ 1,
+ LanguageId::TypeScript,
+ ),
+ ("file:///a/c.ts", r#""#, 1, LanguageId::TypeScript),
],
&[("https://deno.land/x/a/b/c.ts", "console.log(1);\n")],
);
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index 9b9035ac5..c069e4666 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -222,17 +222,6 @@ impl<'a> From<&'a diagnostics::Position> for lsp::Position {
}
}
-/// Check if diagnostics can be generated for the provided media type.
-pub fn is_diagnosable(media_type: MediaType) -> bool {
- matches!(
- media_type,
- MediaType::TypeScript
- | MediaType::JavaScript
- | MediaType::Tsx
- | MediaType::Jsx
- )
-}
-
fn get_diagnostic_message(diagnostic: &diagnostics::Diagnostic) -> String {
if let Some(message) = diagnostic.message_text.clone() {
message
@@ -322,13 +311,16 @@ async fn generate_lint_diagnostics(
let mut diagnostics_vec = Vec::new();
if workspace_settings.lint {
for specifier in documents.open_specifiers() {
+ if !documents.is_diagnosable(specifier) {
+ continue;
+ }
let version = documents.version(specifier);
let current_version = collection
.lock()
.await
.get_version(specifier, &DiagnosticSource::DenoLint);
let media_type = MediaType::from(specifier);
- if version != current_version && is_diagnosable(media_type) {
+ if version != current_version {
if let Ok(Some(source_code)) = documents.content(specifier) {
if let Ok(references) = analysis::get_lint_references(
specifier,
@@ -366,12 +358,15 @@ async fn generate_ts_diagnostics(
.open_specifiers()
.iter()
.filter_map(|&s| {
- let version = snapshot.documents.version(s);
- let current_version =
- collection.get_version(s, &DiagnosticSource::TypeScript);
- let media_type = MediaType::from(s);
- if version != current_version && is_diagnosable(media_type) {
- Some(s.clone())
+ if snapshot.documents.is_diagnosable(s) {
+ let version = snapshot.documents.version(s);
+ let current_version =
+ collection.get_version(s, &DiagnosticSource::TypeScript);
+ if version != current_version {
+ Some(s.clone())
+ } else {
+ None
+ }
} else {
None
}
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs
index bdf80c5a8..048e4bedb 100644
--- a/cli/lsp/documents.rs
+++ b/cli/lsp/documents.rs
@@ -3,6 +3,9 @@
use super::analysis;
use super::text::LineIndex;
+use crate::media_type::MediaType;
+
+use deno_core::error::anyhow;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::error::Context;
@@ -10,6 +13,37 @@ use deno_core::ModuleSpecifier;
use lspower::lsp::TextDocumentContentChangeEvent;
use std::collections::HashMap;
use std::ops::Range;
+use std::str::FromStr;
+
+/// A representation of the language id sent from the LSP client, which is used
+/// to determine how the document is handled within the language server.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum LanguageId {
+ JavaScript,
+ Jsx,
+ TypeScript,
+ Tsx,
+ Json,
+ JsonC,
+ Markdown,
+}
+
+impl FromStr for LanguageId {
+ type Err = AnyError;
+
+ fn from_str(s: &str) -> Result<Self, AnyError> {
+ match s {
+ "javascript" => Ok(Self::JavaScript),
+ "javascriptreact" => Ok(Self::Jsx),
+ "typescript" => Ok(Self::TypeScript),
+ "typescriptreact" => Ok(Self::Tsx),
+ "json" => Ok(Self::Json),
+ "jsonc" => Ok(Self::JsonC),
+ "markdown" => Ok(Self::Markdown),
+ _ => Err(anyhow!("Unsupported language id: {}", s)),
+ }
+ }
+}
#[derive(Debug, PartialEq, Eq)]
enum IndexValid {
@@ -29,6 +63,7 @@ impl IndexValid {
#[derive(Debug, Clone)]
pub struct DocumentData {
bytes: Option<Vec<u8>>,
+ language_id: LanguageId,
line_index: Option<LineIndex>,
specifier: ModuleSpecifier,
dependencies: Option<HashMap<String, analysis::Dependency>>,
@@ -36,9 +71,15 @@ pub struct DocumentData {
}
impl DocumentData {
- pub fn new(specifier: ModuleSpecifier, version: i32, source: &str) -> Self {
+ pub fn new(
+ specifier: ModuleSpecifier,
+ version: i32,
+ language_id: LanguageId,
+ source: &str,
+ ) -> Self {
Self {
bytes: Some(source.as_bytes().to_owned()),
+ language_id,
line_index: Some(LineIndex::new(source)),
specifier,
dependencies: None,
@@ -150,6 +191,39 @@ impl DocumentCache {
doc.dependencies.clone()
}
+ /// Determines if the specifier should be processed for diagnostics and other
+ /// related language server features.
+ pub fn is_diagnosable(&self, specifier: &ModuleSpecifier) -> bool {
+ if specifier.scheme() != "file" {
+ // otherwise we look at the media type for the specifier.
+ matches!(
+ MediaType::from(specifier),
+ MediaType::JavaScript
+ | MediaType::Jsx
+ | MediaType::TypeScript
+ | MediaType::Tsx
+ | MediaType::Dts
+ )
+ } else if let Some(doc_data) = self.docs.get(specifier) {
+ // if the document is in the document cache, then use the client provided
+ // language id to determine if the specifier is diagnosable.
+ matches!(
+ doc_data.language_id,
+ LanguageId::JavaScript
+ | LanguageId::Jsx
+ | LanguageId::TypeScript
+ | LanguageId::Tsx
+ )
+ } else {
+ false
+ }
+ }
+
+ /// Determines if the specifier can be processed for formatting.
+ pub fn is_formattable(&self, specifier: &ModuleSpecifier) -> bool {
+ self.docs.contains_key(specifier)
+ }
+
pub fn len(&self) -> usize {
self.docs.len()
}
@@ -159,10 +233,16 @@ impl DocumentCache {
doc.line_index.clone()
}
- pub fn open(&mut self, specifier: ModuleSpecifier, version: i32, text: &str) {
+ pub fn open(
+ &mut self,
+ specifier: ModuleSpecifier,
+ version: i32,
+ language_id: LanguageId,
+ source: &str,
+ ) {
self.docs.insert(
specifier.clone(),
- DocumentData::new(specifier, version, text),
+ DocumentData::new(specifier, version, language_id, source),
);
}
@@ -219,7 +299,12 @@ mod tests {
let mut document_cache = DocumentCache::default();
let specifier = resolve_url("file:///a/b.ts").unwrap();
let missing_specifier = resolve_url("file:///a/c.ts").unwrap();
- document_cache.open(specifier.clone(), 1, "console.log(\"Hello Deno\");\n");
+ document_cache.open(
+ specifier.clone(),
+ 1,
+ LanguageId::TypeScript,
+ "console.log(\"Hello Deno\");\n",
+ );
assert!(document_cache.contains_key(&specifier));
assert!(!document_cache.contains_key(&missing_specifier));
}
@@ -228,7 +313,12 @@ mod tests {
fn test_document_cache_change() {
let mut document_cache = DocumentCache::default();
let specifier = resolve_url("file:///a/b.ts").unwrap();
- document_cache.open(specifier.clone(), 1, "console.log(\"Hello deno\");\n");
+ document_cache.open(
+ specifier.clone(),
+ 1,
+ LanguageId::TypeScript,
+ "console.log(\"Hello deno\");\n",
+ );
document_cache
.change(
&specifier,
@@ -259,7 +349,12 @@ mod tests {
fn test_document_cache_change_utf16() {
let mut document_cache = DocumentCache::default();
let specifier = resolve_url("file:///a/b.ts").unwrap();
- document_cache.open(specifier.clone(), 1, "console.log(\"Hello 🦕\");\n");
+ document_cache.open(
+ specifier.clone(),
+ 1,
+ LanguageId::TypeScript,
+ "console.log(\"Hello 🦕\");\n",
+ );
document_cache
.change(
&specifier,
@@ -285,4 +380,25 @@ mod tests {
.expect("failed to get content");
assert_eq!(actual, Some("console.log(\"Hello Deno\");\n".to_string()));
}
+
+ #[test]
+ fn test_is_diagnosable() {
+ let mut document_cache = DocumentCache::default();
+ let specifier = resolve_url("file:///a/file.ts").unwrap();
+ assert!(!document_cache.is_diagnosable(&specifier));
+ document_cache.open(
+ specifier.clone(),
+ 1,
+ LanguageId::TypeScript,
+ "console.log(\"hello world\");\n",
+ );
+ assert!(document_cache.is_diagnosable(&specifier));
+ let specifier =
+ resolve_url("asset:///lib.es2015.symbol.wellknown.d.ts").unwrap();
+ assert!(document_cache.is_diagnosable(&specifier));
+ let specifier = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap();
+ assert!(document_cache.is_diagnosable(&specifier));
+ let specifier = resolve_url("data:application/json;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap();
+ assert!(!document_cache.is_diagnosable(&specifier));
+ }
}
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 3a751e319..20d5c1ee7 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -42,6 +42,7 @@ use super::config::SETTINGS_SECTION;
use super::diagnostics;
use super::diagnostics::DiagnosticSource;
use super::documents::DocumentCache;
+use super::documents::LanguageId;
use super::lsp_custom;
use super::performance::Performance;
use super::registries;
@@ -59,7 +60,6 @@ use crate::config_file::TsConfig;
use crate::deno_dir;
use crate::import_map::ImportMap;
use crate::logger;
-use crate::lsp::diagnostics::is_diagnosable;
use crate::media_type::MediaType;
use crate::tools::fmt::format_file;
use crate::tools::fmt::get_typescript_config;
@@ -611,17 +611,27 @@ impl Inner {
// already managed by the language service
return;
}
+ let language_id = match params.text_document.language_id.parse() {
+ Ok(language_id) => language_id,
+ Err(err) => {
+ error!("{}", err);
+ LanguageId::TypeScript
+ }
+ };
self.documents.open(
specifier.clone(),
params.text_document.version,
+ language_id,
&params.text_document.text,
);
- self.analyze_dependencies(&specifier, &params.text_document.text);
- self.performance.measure(mark);
- if let Err(err) = self.diagnostics_server.update() {
- error!("{}", err);
+ if self.documents.is_diagnosable(&specifier) {
+ self.analyze_dependencies(&specifier, &params.text_document.text);
+ if let Err(err) = self.diagnostics_server.update() {
+ error!("{}", err);
+ }
}
+ self.performance.measure(mark);
}
async fn did_change(&mut self, params: DidChangeTextDocumentParams) {
@@ -632,15 +642,18 @@ impl Inner {
params.text_document.version,
params.content_changes,
) {
- Ok(Some(source)) => self.analyze_dependencies(&specifier, &source),
+ Ok(Some(source)) => {
+ if self.documents.is_diagnosable(&specifier) {
+ self.analyze_dependencies(&specifier, &source);
+ if let Err(err) = self.diagnostics_server.update() {
+ error!("{}", err);
+ }
+ }
+ }
Ok(_) => error!("No content returned from change."),
Err(err) => error!("{}", err),
}
self.performance.measure(mark);
-
- if let Err(err) = self.diagnostics_server.update() {
- error!("{}", err);
- }
}
async fn did_close(&mut self, params: DidCloseTextDocumentParams) {
@@ -655,10 +668,12 @@ impl Inner {
self.documents.close(&specifier);
self.navigation_trees.remove(&specifier);
- self.performance.measure(mark);
- if let Err(err) = self.diagnostics_server.update() {
- error!("{}", err);
+ if self.documents.is_diagnosable(&specifier) {
+ if let Err(err) = self.diagnostics_server.update() {
+ error!("{}", err);
+ }
}
+ self.performance.measure(mark);
}
async fn did_change_configuration(
@@ -751,11 +766,9 @@ impl Inner {
params: DocumentSymbolParams,
) -> LspResult<Option<DocumentSymbolResponse>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
- return Ok(None);
- }
- let media_type = MediaType::from(&specifier);
- if !is_diagnosable(media_type) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
@@ -798,8 +811,11 @@ impl Inner {
&self,
params: DocumentFormattingParams,
) -> LspResult<Option<Vec<TextEdit>>> {
- let mark = self.performance.mark("formatting", Some(&params));
let specifier = self.url_map.normalize_url(&params.text_document.uri);
+ if !self.documents.is_formattable(&specifier) {
+ return Ok(None);
+ }
+ let mark = self.performance.mark("formatting", Some(&params));
let file_text = self
.documents
.content(&specifier)
@@ -850,7 +866,9 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position_params.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
let mark = self.performance.mark("hover", Some(&params));
@@ -891,7 +909,9 @@ impl Inner {
params: CodeActionParams,
) -> LspResult<Option<CodeActionResponse>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
@@ -1054,7 +1074,8 @@ impl Inner {
params: CodeLensParams,
) -> LspResult<Option<Vec<CodeLens>>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.config.specifier_enabled(&specifier)
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
|| !self.config.get_workspace_settings().enabled_code_lens()
{
return Ok(None);
@@ -1366,7 +1387,9 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position_params.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
@@ -1416,9 +1439,12 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
+
let mark = self.performance.mark("references", Some(&params));
let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) {
@@ -1471,9 +1497,12 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position_params.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
+
let mark = self.performance.mark("goto_definition", Some(&params));
let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) {
@@ -1514,9 +1543,12 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
+
let mark = self.performance.mark("completion", Some(&params));
// Import specifiers are something wholly internal to Deno, so for
// completions, we will use internal logic and if there are completions
@@ -1632,9 +1664,12 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position_params.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
+
let mark = self.performance.mark("goto_implementation", Some(&params));
let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) {
@@ -1680,11 +1715,13 @@ impl Inner {
params: FoldingRangeParams,
) -> LspResult<Option<Vec<FoldingRange>>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
- let mark = self.performance.mark("folding_range", Some(&params));
+ let mark = self.performance.mark("folding_range", Some(&params));
let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index
@@ -1737,11 +1774,13 @@ impl Inner {
params: CallHierarchyIncomingCallsParams,
) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> {
let specifier = self.url_map.normalize_url(&params.item.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
- let mark = self.performance.mark("incoming_calls", Some(&params));
+ let mark = self.performance.mark("incoming_calls", Some(&params));
let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index
@@ -1791,11 +1830,13 @@ impl Inner {
params: CallHierarchyOutgoingCallsParams,
) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> {
let specifier = self.url_map.normalize_url(&params.item.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
- let mark = self.performance.mark("outgoing_calls", Some(&params));
+ let mark = self.performance.mark("outgoing_calls", Some(&params));
let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index
@@ -1848,13 +1889,15 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position_params.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
+
let mark = self
.performance
.mark("prepare_call_hierarchy", Some(&params));
-
let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index
@@ -1927,11 +1970,13 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
- let mark = self.performance.mark("rename", Some(&params));
+ let mark = self.performance.mark("rename", Some(&params));
let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index
@@ -2019,11 +2064,13 @@ impl Inner {
params: SelectionRangeParams,
) -> LspResult<Option<Vec<SelectionRange>>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
- let mark = self.performance.mark("selection_range", Some(&params));
+ let mark = self.performance.mark("selection_range", Some(&params));
let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index
@@ -2061,11 +2108,13 @@ impl Inner {
params: SemanticTokensParams,
) -> LspResult<Option<SemanticTokensResult>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
- let mark = self.performance.mark("semantic_tokens_full", Some(&params));
+ let mark = self.performance.mark("semantic_tokens_full", Some(&params));
let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index
@@ -2108,13 +2157,15 @@ impl Inner {
params: SemanticTokensRangeParams,
) -> LspResult<Option<SemanticTokensRangeResult>> {
let specifier = self.url_map.normalize_url(&params.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
+
let mark = self
.performance
.mark("semantic_tokens_range", Some(&params));
-
let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) {
line_index
@@ -2158,9 +2209,12 @@ impl Inner {
let specifier = self
.url_map
.normalize_url(&params.text_document_position_params.text_document.uri);
- if !self.config.specifier_enabled(&specifier) {
+ if !self.documents.is_diagnosable(&specifier)
+ || !self.config.specifier_enabled(&specifier)
+ {
return Ok(None);
}
+
let mark = self.performance.mark("signature_help", Some(&params));
let line_index =
if let Some(line_index) = self.get_line_index_sync(&specifier) {
@@ -2427,8 +2481,12 @@ impl Inner {
&mut self,
params: lsp_custom::CacheParams,
) -> LspResult<Option<Value>> {
- let mark = self.performance.mark("cache", Some(&params));
let referrer = self.url_map.normalize_url(&params.referrer.uri);
+ if !self.documents.is_diagnosable(&referrer) {
+ return Ok(None);
+ }
+
+ let mark = self.performance.mark("cache", Some(&params));
if !params.uris.is_empty() {
for identifier in &params.uris {
let specifier = self.url_map.normalize_url(&identifier.uri);
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 00be3e50b..6a476054f 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -2566,6 +2566,7 @@ mod tests {
use crate::http_util::HeadersMap;
use crate::lsp::analysis;
use crate::lsp::documents::DocumentCache;
+ use crate::lsp::documents::LanguageId;
use crate::lsp::sources::Sources;
use crate::lsp::text::LineIndex;
use std::path::Path;
@@ -2573,14 +2574,14 @@ mod tests {
use tempfile::TempDir;
fn mock_state_snapshot(
- fixtures: &[(&str, &str, i32)],
+ fixtures: &[(&str, &str, i32, LanguageId)],
location: &Path,
) -> StateSnapshot {
let mut documents = DocumentCache::default();
- for (specifier, source, version) in fixtures {
+ for (specifier, source, version, language_id) in fixtures {
let specifier =
resolve_url(specifier).expect("failed to create specifier");
- documents.open(specifier.clone(), *version, source);
+ documents.open(specifier.clone(), *version, language_id.clone(), source);
let media_type = MediaType::from(&specifier);
if let Ok(parsed_module) =
analysis::parse_module(&specifier, source, &media_type)
@@ -2605,7 +2606,7 @@ mod tests {
fn setup(
debug: bool,
config: Value,
- sources: &[(&str, &str, i32)],
+ sources: &[(&str, &str, i32, LanguageId)],
) -> (JsRuntime, StateSnapshot, PathBuf) {
let temp_dir = TempDir::new().expect("could not create temp dir");
let location = temp_dir.path().join("deps");
@@ -2688,7 +2689,12 @@ mod tests {
"module": "esnext",
"noEmit": true,
}),
- &[("file:///a.ts", r#"console.log("hello deno");"#, 1)],
+ &[(
+ "file:///a.ts",
+ r#"console.log("hello deno");"#,
+ 1,
+ LanguageId::TypeScript,
+ )],
);
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
let result = request(
@@ -2733,7 +2739,12 @@ mod tests {
"lib": ["esnext", "dom", "deno.ns"],
"noEmit": true,
}),
- &[("file:///a.ts", r#"console.log(document.location);"#, 1)],
+ &[(
+ "file:///a.ts",
+ r#"console.log(document.location);"#,
+ 1,
+ LanguageId::TypeScript,
+ )],
);
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
let result = request(
@@ -2766,6 +2777,7 @@ mod tests {
console.log(b);
"#,
1,
+ LanguageId::TypeScript,
)],
);
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
@@ -2795,6 +2807,7 @@ mod tests {
import { A } from ".";
"#,
1,
+ LanguageId::TypeScript,
)],
);
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
@@ -2848,6 +2861,7 @@ mod tests {
console.log(b);
"#,
1,
+ LanguageId::TypeScript,
)],
);
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
@@ -2884,6 +2898,7 @@ mod tests {
import * as test from
"#,
1,
+ LanguageId::TypeScript,
)],
);
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
@@ -2941,7 +2956,12 @@ mod tests {
"lib": ["deno.ns", "deno.window"],
"noEmit": true,
}),
- &[("file:///a.ts", r#"const url = new URL("b.js", import."#, 1)],
+ &[(
+ "file:///a.ts",
+ r#"const url = new URL("b.js", import."#,
+ 1,
+ LanguageId::TypeScript,
+ )],
);
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
let result = request(
@@ -2998,6 +3018,7 @@ mod tests {
}
"#,
1,
+ LanguageId::TypeScript,
)],
);
let cache = HttpCache::new(&location);
@@ -3099,7 +3120,7 @@ mod tests {
"lib": ["deno.ns", "deno.window"],
"noEmit": true,
}),
- &[("file:///a.ts", fixture, 1)],
+ &[("file:///a.ts", fixture, 1, LanguageId::TypeScript)],
);
let specifier = resolve_url("file:///a.ts").expect("could not resolve url");
let result = request(
diff --git a/cli/media_type.rs b/cli/media_type.rs
index c83716f67..bfb869c13 100644
--- a/cli/media_type.rs
+++ b/cli/media_type.rs
@@ -1,5 +1,6 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+use data_url::DataUrl;
use deno_core::serde::Serialize;
use deno_core::serde::Serializer;
use deno_core::ModuleSpecifier;
@@ -45,34 +46,40 @@ impl fmt::Display for MediaType {
impl<'a> From<&'a Path> for MediaType {
fn from(path: &'a Path) -> Self {
- MediaType::from_path(path)
+ Self::from_path(path)
}
}
impl<'a> From<&'a PathBuf> for MediaType {
fn from(path: &'a PathBuf) -> Self {
- MediaType::from_path(path)
+ Self::from_path(path)
}
}
impl<'a> From<&'a String> for MediaType {
fn from(specifier: &'a String) -> Self {
- MediaType::from_path(&PathBuf::from(specifier))
+ Self::from_path(&PathBuf::from(specifier))
}
}
impl<'a> From<&'a ModuleSpecifier> for MediaType {
fn from(specifier: &'a ModuleSpecifier) -> Self {
- let path = if specifier.scheme() == "file" {
- if let Ok(path) = specifier.to_file_path() {
- path
+ if specifier.scheme() != "data" {
+ let path = if specifier.scheme() == "file" {
+ if let Ok(path) = specifier.to_file_path() {
+ path
+ } else {
+ PathBuf::from(specifier.path())
+ }
} else {
PathBuf::from(specifier.path())
- }
+ };
+ Self::from_path(&path)
+ } else if let Ok(data_url) = DataUrl::process(specifier.as_str()) {
+ Self::from_content_type(specifier, data_url.mime_type().to_string())
} else {
- PathBuf::from(specifier.path())
- };
- MediaType::from_path(&path)
+ Self::Unknown
+ }
}
}
@@ -83,6 +90,40 @@ impl Default for MediaType {
}
impl MediaType {
+ pub fn from_content_type<S: AsRef<str>>(
+ specifier: &ModuleSpecifier,
+ content_type: S,
+ ) -> Self {
+ match content_type.as_ref().trim().to_lowercase().as_ref() {
+ "application/typescript"
+ | "text/typescript"
+ | "video/vnd.dlna.mpeg-tts"
+ | "video/mp2t"
+ | "application/x-typescript" => {
+ map_js_like_extension(specifier, Self::TypeScript)
+ }
+ "application/javascript"
+ | "text/javascript"
+ | "application/ecmascript"
+ | "text/ecmascript"
+ | "application/x-javascript"
+ | "application/node" => {
+ map_js_like_extension(specifier, Self::JavaScript)
+ }
+ "text/jsx" => Self::Jsx,
+ "text/tsx" => Self::Tsx,
+ "application/json" | "text/json" => Self::Json,
+ "application/wasm" => Self::Wasm,
+ // Handle plain and possibly webassembly
+ "text/plain" | "application/octet-stream"
+ if specifier.scheme() != "data" =>
+ {
+ Self::from(specifier)
+ }
+ _ => Self::Unknown,
+ }
+ }
+
fn from_path(path: &Path) -> Self {
match path.extension() {
None => match path.file_name() {
@@ -197,6 +238,55 @@ where
}
}
+/// Used to augment media types by using the path part of a module specifier to
+/// resolve to a more accurate media type.
+fn map_js_like_extension(
+ specifier: &ModuleSpecifier,
+ default: MediaType,
+) -> MediaType {
+ let path = if specifier.scheme() == "file" {
+ if let Ok(path) = specifier.to_file_path() {
+ path
+ } else {
+ PathBuf::from(specifier.path())
+ }
+ } else {
+ PathBuf::from(specifier.path())
+ };
+ match path.extension() {
+ None => default,
+ Some(os_str) => match os_str.to_str() {
+ None => default,
+ Some("jsx") => MediaType::Jsx,
+ Some("tsx") => MediaType::Tsx,
+ // Because DTS files do not have a separate media type, or a unique
+ // extension, we have to "guess" at those things that we consider that
+ // look like TypeScript, and end with `.d.ts` are DTS files.
+ Some("ts") => {
+ if default == MediaType::TypeScript {
+ match path.file_stem() {
+ None => default,
+ Some(os_str) => {
+ if let Some(file_stem) = os_str.to_str() {
+ if file_stem.ends_with(".d") {
+ MediaType::Dts
+ } else {
+ default
+ }
+ } else {
+ default
+ }
+ }
+ }
+ } else {
+ default
+ }
+ }
+ Some(_) => default,
+ },
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -245,6 +335,9 @@ mod tests {
("https://deno.land/x/mod.ts", MediaType::TypeScript),
("https://deno.land/x/mod.js", MediaType::JavaScript),
("https://deno.land/x/mod.txt", MediaType::Unknown),
+ ("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=", MediaType::TypeScript),
+ ("data:application/javascript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=", MediaType::JavaScript),
+ ("data:text/plain;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=", MediaType::Unknown),
];
for (specifier, expected) in fixtures {
@@ -254,6 +347,52 @@ mod tests {
}
#[test]
+ fn test_from_content_type() {
+ let fixtures = vec![
+ (
+ "https://deno.land/x/mod.ts",
+ "application/typescript",
+ MediaType::TypeScript,
+ ),
+ (
+ "https://deno.land/x/mod.d.ts",
+ "application/typescript",
+ MediaType::Dts,
+ ),
+ ("https://deno.land/x/mod.tsx", "text/tsx", MediaType::Tsx),
+ (
+ "https://deno.land/x/mod.js",
+ "application/javascript",
+ MediaType::JavaScript,
+ ),
+ ("https://deno.land/x/mod.jsx", "text/jsx", MediaType::Jsx),
+ (
+ "https://deno.land/x/mod.ts",
+ "text/plain",
+ MediaType::TypeScript,
+ ),
+ (
+ "https://deno.land/x/mod.js",
+ "text/plain",
+ MediaType::JavaScript,
+ ),
+ (
+ "https://deno.land/x/mod.wasm",
+ "text/plain",
+ MediaType::Wasm,
+ ),
+ ];
+
+ for (specifier, content_type, expected) in fixtures {
+ let fixture = deno_core::resolve_url_or_path(specifier).unwrap();
+ assert_eq!(
+ MediaType::from_content_type(&fixture, content_type),
+ expected
+ );
+ }
+ }
+
+ #[test]
fn test_serialization() {
assert_eq!(json!(MediaType::JavaScript), json!(0));
assert_eq!(json!(MediaType::Jsx), json!(1));
diff --git a/cli/tests/integration_tests_lsp.rs b/cli/tests/integration_tests_lsp.rs
index e4b963f2b..2fe984bca 100644
--- a/cli/tests/integration_tests_lsp.rs
+++ b/cli/tests/integration_tests_lsp.rs
@@ -2119,6 +2119,56 @@ fn lsp_format_json() {
}
#[test]
+fn lsp_json_no_diagnostics() {
+ let mut client = init("initialize_params.json");
+ client
+ .write_notification(
+ "textDocument/didOpen",
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.json",
+ "languageId": "json",
+ "version": 1,
+ "text": "{\"key\":\"value\"}"
+ }
+ }),
+ )
+ .unwrap();
+
+ let (maybe_res, maybe_err) = client
+ .write_request(
+ "textDocument/semanticTokens/full",
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.json"
+ }
+ }),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ assert_eq!(maybe_res, Some(json!(null)));
+
+ let (maybe_res, maybe_err) = client
+ .write_request::<_, _, Value>(
+ "textDocument/hover",
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.json"
+ },
+ "position": {
+ "line": 0,
+ "character": 3
+ }
+ }),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ assert_eq!(maybe_res, Some(json!(null)));
+
+ shutdown(&mut client);
+}
+
+#[test]
fn lsp_format_markdown() {
let mut client = init("initialize_params.json");
client
@@ -2174,6 +2224,56 @@ fn lsp_format_markdown() {
}
#[test]
+fn lsp_markdown_no_diagnostics() {
+ let mut client = init("initialize_params.json");
+ client
+ .write_notification(
+ "textDocument/didOpen",
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.md",
+ "languageId": "markdown",
+ "version": 1,
+ "text": "# Hello World"
+ }
+ }),
+ )
+ .unwrap();
+
+ let (maybe_res, maybe_err) = client
+ .write_request(
+ "textDocument/semanticTokens/full",
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.md"
+ }
+ }),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ assert_eq!(maybe_res, Some(json!(null)));
+
+ let (maybe_res, maybe_err) = client
+ .write_request::<_, _, Value>(
+ "textDocument/hover",
+ json!({
+ "textDocument": {
+ "uri": "file:///a/file.md"
+ },
+ "position": {
+ "line": 0,
+ "character": 3
+ }
+ }),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ assert_eq!(maybe_res, Some(json!(null)));
+
+ shutdown(&mut client);
+}
+
+#[test]
fn lsp_configuration_did_change() {
let _g = http_server();
let mut client = init("initialize_params_did_config_change.json");