fix(#10815): lsp only responds to formatting for md, json, jsonc (#10816)

Fixes #10815
This commit is contained in:
Kitson Kelly 2021-06-02 20:29:58 +10:00 committed by GitHub
parent 9ae8dbf173
commit 473713c621
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 529 additions and 169 deletions

View file

@ -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("#!") {

View file

@ -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")],
);

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {
@ -253,6 +346,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));

View file

@ -2118,6 +2118,56 @@ fn lsp_format_json() {
shutdown(&mut client);
}
#[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");
@ -2173,6 +2223,56 @@ fn lsp_format_markdown() {
shutdown(&mut client);
}
#[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();