mirror of
https://github.com/denoland/deno.git
synced 2024-12-01 16:51:13 -05:00
feat(lsp): support formatting json and markdown files (#10180)
Resolves #9447 Resolves #9415
This commit is contained in:
parent
8ffeabc678
commit
d69a5fbe1a
4 changed files with 180 additions and 55 deletions
|
@ -218,6 +218,17 @@ 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 {
|
fn get_diagnostic_message(diagnostic: &diagnostics::Diagnostic) -> String {
|
||||||
if let Some(message) = diagnostic.message_text.clone() {
|
if let Some(message) = diagnostic.message_text.clone() {
|
||||||
message
|
message
|
||||||
|
@ -312,8 +323,8 @@ async fn generate_lint_diagnostics(
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.get_version(specifier, &DiagnosticSource::DenoLint);
|
.get_version(specifier, &DiagnosticSource::DenoLint);
|
||||||
if version != current_version {
|
|
||||||
let media_type = MediaType::from(specifier);
|
let media_type = MediaType::from(specifier);
|
||||||
|
if version != current_version && is_diagnosable(media_type) {
|
||||||
if let Ok(Some(source_code)) = documents.content(specifier) {
|
if let Ok(Some(source_code)) = documents.content(specifier) {
|
||||||
if let Ok(references) = analysis::get_lint_references(
|
if let Ok(references) = analysis::get_lint_references(
|
||||||
specifier,
|
specifier,
|
||||||
|
@ -354,10 +365,11 @@ async fn generate_ts_diagnostics(
|
||||||
let version = snapshot.documents.version(s);
|
let version = snapshot.documents.version(s);
|
||||||
let current_version =
|
let current_version =
|
||||||
collection.get_version(s, &DiagnosticSource::TypeScript);
|
collection.get_version(s, &DiagnosticSource::TypeScript);
|
||||||
if version == current_version {
|
let media_type = MediaType::from(s);
|
||||||
None
|
if version != current_version && is_diagnosable(media_type) {
|
||||||
} else {
|
|
||||||
Some(s.clone())
|
Some(s.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
@ -10,7 +10,6 @@ use deno_core::serde_json;
|
||||||
use deno_core::serde_json::json;
|
use deno_core::serde_json::json;
|
||||||
use deno_core::serde_json::Value;
|
use deno_core::serde_json::Value;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use dprint_plugin_typescript as dprint;
|
|
||||||
use log::error;
|
use log::error;
|
||||||
use log::info;
|
use log::info;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
@ -30,13 +29,6 @@ use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
use crate::config_file::ConfigFile;
|
|
||||||
use crate::config_file::TsConfig;
|
|
||||||
use crate::deno_dir;
|
|
||||||
use crate::import_map::ImportMap;
|
|
||||||
use crate::logger;
|
|
||||||
use crate::media_type::MediaType;
|
|
||||||
|
|
||||||
use super::analysis;
|
use super::analysis;
|
||||||
use super::analysis::ts_changes_to_edit;
|
use super::analysis::ts_changes_to_edit;
|
||||||
use super::analysis::CodeActionCollection;
|
use super::analysis::CodeActionCollection;
|
||||||
|
@ -62,6 +54,15 @@ use super::tsc::AssetDocument;
|
||||||
use super::tsc::Assets;
|
use super::tsc::Assets;
|
||||||
use super::tsc::TsServer;
|
use super::tsc::TsServer;
|
||||||
use super::urls;
|
use super::urls;
|
||||||
|
use crate::config_file::ConfigFile;
|
||||||
|
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;
|
||||||
|
|
||||||
pub const REGISTRIES_PATH: &str = "registries";
|
pub const REGISTRIES_PATH: &str = "registries";
|
||||||
const SOURCES_PATH: &str = "deps";
|
const SOURCES_PATH: &str = "deps";
|
||||||
|
@ -785,6 +786,11 @@ impl Inner {
|
||||||
if !self.config.specifier_enabled(&specifier) {
|
if !self.config.specifier_enabled(&specifier) {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
let media_type = MediaType::from(&specifier);
|
||||||
|
if !is_diagnosable(media_type) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
let mark = self.performance.mark("document_symbol", Some(¶ms));
|
let mark = self.performance.mark("document_symbol", Some(¶ms));
|
||||||
|
|
||||||
let line_index =
|
let line_index =
|
||||||
|
@ -845,12 +851,8 @@ impl Inner {
|
||||||
|
|
||||||
// TODO(lucacasonato): handle error properly
|
// TODO(lucacasonato): handle error properly
|
||||||
let text_edits = tokio::task::spawn_blocking(move || {
|
let text_edits = tokio::task::spawn_blocking(move || {
|
||||||
let config = dprint::configuration::ConfigurationBuilder::new()
|
let config = get_typescript_config();
|
||||||
.deno()
|
match format_file(&file_path, &file_text, config) {
|
||||||
.build();
|
|
||||||
// TODO(@kitsonk) this could be handled better in `cli/tools/fmt.rs` in the
|
|
||||||
// future.
|
|
||||||
match dprint::format_text(&file_path, &file_text, &config) {
|
|
||||||
Ok(new_text) => {
|
Ok(new_text) => {
|
||||||
Some(text::get_edits(&file_text, &new_text, line_index))
|
Some(text::get_edits(&file_text, &new_text, line_index))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1659,3 +1659,126 @@ fn lsp_performance() {
|
||||||
}
|
}
|
||||||
shutdown(&mut client);
|
shutdown(&mut client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsp_format_json() {
|
||||||
|
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::<_, _, Value>(
|
||||||
|
"textDocument/formatting",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.json"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"tabSize": 2,
|
||||||
|
"insertSpaces": true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(maybe_err.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
maybe_res,
|
||||||
|
Some(json!([
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 1
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"newText": " "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 7 },
|
||||||
|
"end": { "line": 0, "character": 7 }
|
||||||
|
},
|
||||||
|
"newText": " "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 14 },
|
||||||
|
"end": { "line": 0, "character": 15 }
|
||||||
|
},
|
||||||
|
"newText": " }\n"
|
||||||
|
}
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
shutdown(&mut client);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsp_format_markdown() {
|
||||||
|
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::<_, _, Value>(
|
||||||
|
"textDocument/formatting",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.md"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"tabSize": 2,
|
||||||
|
"insertSpaces": true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(maybe_err.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
maybe_res,
|
||||||
|
Some(json!([
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 1 },
|
||||||
|
"end": { "line": 0, "character": 3 }
|
||||||
|
},
|
||||||
|
"newText": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 15 },
|
||||||
|
"end": { "line": 0, "character": 15 }
|
||||||
|
},
|
||||||
|
"newText": "\n"
|
||||||
|
}
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
shutdown(&mut client);
|
||||||
|
}
|
||||||
|
|
|
@ -152,6 +152,22 @@ fn format_json(file_text: &str) -> Result<String, String> {
|
||||||
dprint_plugin_json::format_text(&file_text, &json_config)
|
dprint_plugin_json::format_text(&file_text, &json_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Formats a single TS, TSX, JS, JSX, JSONC, JSON, or MD file.
|
||||||
|
pub fn format_file(
|
||||||
|
file_path: &Path,
|
||||||
|
file_text: &str,
|
||||||
|
config: dprint_plugin_typescript::configuration::Configuration,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let ext = get_extension(file_path).unwrap_or_else(String::new);
|
||||||
|
if ext == "md" {
|
||||||
|
format_markdown(&file_text, config)
|
||||||
|
} else if matches!(ext.as_str(), "json" | "jsonc") {
|
||||||
|
format_json(&file_text)
|
||||||
|
} else {
|
||||||
|
dprint_plugin_typescript::format_text(&file_path, &file_text, &config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn check_source_files(
|
async fn check_source_files(
|
||||||
config: dprint_plugin_typescript::configuration::Configuration,
|
config: dprint_plugin_typescript::configuration::Configuration,
|
||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
|
@ -168,15 +184,8 @@ async fn check_source_files(
|
||||||
move |file_path| {
|
move |file_path| {
|
||||||
checked_files_count.fetch_add(1, Ordering::Relaxed);
|
checked_files_count.fetch_add(1, Ordering::Relaxed);
|
||||||
let file_text = read_file_contents(&file_path)?.text;
|
let file_text = read_file_contents(&file_path)?.text;
|
||||||
let ext = get_extension(&file_path).unwrap_or_else(String::new);
|
|
||||||
let r = if ext == "md" {
|
match format_file(&file_path, &file_text, config) {
|
||||||
format_markdown(&file_text, config.clone())
|
|
||||||
} else if matches!(ext.as_str(), "json" | "jsonc") {
|
|
||||||
format_json(&file_text)
|
|
||||||
} else {
|
|
||||||
dprint_plugin_typescript::format_text(&file_path, &file_text, &config)
|
|
||||||
};
|
|
||||||
match r {
|
|
||||||
Ok(formatted_text) => {
|
Ok(formatted_text) => {
|
||||||
if formatted_text != file_text {
|
if formatted_text != file_text {
|
||||||
not_formatted_files_count.fetch_add(1, Ordering::Relaxed);
|
not_formatted_files_count.fetch_add(1, Ordering::Relaxed);
|
||||||
|
@ -229,19 +238,8 @@ async fn format_source_files(
|
||||||
move |file_path| {
|
move |file_path| {
|
||||||
checked_files_count.fetch_add(1, Ordering::Relaxed);
|
checked_files_count.fetch_add(1, Ordering::Relaxed);
|
||||||
let file_contents = read_file_contents(&file_path)?;
|
let file_contents = read_file_contents(&file_path)?;
|
||||||
let ext = get_extension(&file_path).unwrap_or_else(String::new);
|
|
||||||
let r = if ext == "md" {
|
match format_file(&file_path, &file_contents.text, config) {
|
||||||
format_markdown(&file_contents.text, config.clone())
|
|
||||||
} else if matches!(ext.as_str(), "json" | "jsonc") {
|
|
||||||
format_json(&file_contents.text)
|
|
||||||
} else {
|
|
||||||
dprint_plugin_typescript::format_text(
|
|
||||||
&file_path,
|
|
||||||
&file_contents.text,
|
|
||||||
&config,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
match r {
|
|
||||||
Ok(formatted_text) => {
|
Ok(formatted_text) => {
|
||||||
if formatted_text != file_contents.text {
|
if formatted_text != file_contents.text {
|
||||||
write_file_contents(
|
write_file_contents(
|
||||||
|
@ -293,19 +291,9 @@ pub fn format_stdin(check: bool, ext: String) -> Result<(), AnyError> {
|
||||||
return Err(generic_error("Failed to read from stdin"));
|
return Err(generic_error("Failed to read from stdin"));
|
||||||
}
|
}
|
||||||
let config = get_typescript_config();
|
let config = get_typescript_config();
|
||||||
let r = if ext.as_str() == "md" {
|
let file_path = PathBuf::from(format!("_stdin.{}", ext));
|
||||||
format_markdown(&source, config)
|
|
||||||
} else if matches!(ext.as_str(), "json" | "jsonc") {
|
match format_file(&file_path, &source, config) {
|
||||||
format_json(&source)
|
|
||||||
} else {
|
|
||||||
// dprint will fallback to jsx parsing if parsing this as a .ts file doesn't work
|
|
||||||
dprint_plugin_typescript::format_text(
|
|
||||||
&PathBuf::from("_stdin.ts"),
|
|
||||||
&source,
|
|
||||||
&config,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
match r {
|
|
||||||
Ok(formatted_text) => {
|
Ok(formatted_text) => {
|
||||||
if check {
|
if check {
|
||||||
if formatted_text != source {
|
if formatted_text != source {
|
||||||
|
@ -330,7 +318,7 @@ fn files_str(len: usize) -> &'static str {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_typescript_config(
|
pub fn get_typescript_config(
|
||||||
) -> dprint_plugin_typescript::configuration::Configuration {
|
) -> dprint_plugin_typescript::configuration::Configuration {
|
||||||
dprint_plugin_typescript::configuration::ConfigurationBuilder::new()
|
dprint_plugin_typescript::configuration::ConfigurationBuilder::new()
|
||||||
.deno()
|
.deno()
|
||||||
|
|
Loading…
Reference in a new issue