mirror of
https://github.com/denoland/deno.git
synced 2025-09-02 08:47:39 +00:00
feat(lsp): respect "typescript.preferences.quoteStyle" when deno.json is absent (#20891)
This commit is contained in:
parent
ebb7fe412e
commit
659cd90758
4 changed files with 250 additions and 36 deletions
|
@ -6,6 +6,7 @@ use crate::lsp::logging::lsp_warn;
|
||||||
use crate::util::fs::canonicalize_path_maybe_not_exists;
|
use crate::util::fs::canonicalize_path_maybe_not_exists;
|
||||||
use crate::util::path::specifier_to_file_path;
|
use crate::util::path::specifier_to_file_path;
|
||||||
use deno_ast::MediaType;
|
use deno_ast::MediaType;
|
||||||
|
use deno_config::FmtOptionsConfig;
|
||||||
use deno_core::parking_lot::Mutex;
|
use deno_core::parking_lot::Mutex;
|
||||||
use deno_core::serde::de::DeserializeOwned;
|
use deno_core::serde::de::DeserializeOwned;
|
||||||
use deno_core::serde::Deserialize;
|
use deno_core::serde::Deserialize;
|
||||||
|
@ -356,6 +357,29 @@ impl Default for JsxAttributeCompletionStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum QuoteStyle {
|
||||||
|
Auto,
|
||||||
|
Double,
|
||||||
|
Single,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for QuoteStyle {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Auto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&FmtOptionsConfig> for QuoteStyle {
|
||||||
|
fn from(config: &FmtOptionsConfig) -> Self {
|
||||||
|
match config.single_quote {
|
||||||
|
Some(true) => QuoteStyle::Single,
|
||||||
|
_ => QuoteStyle::Double,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct LanguagePreferences {
|
pub struct LanguagePreferences {
|
||||||
|
@ -367,6 +391,8 @@ pub struct LanguagePreferences {
|
||||||
pub auto_import_file_exclude_patterns: Vec<String>,
|
pub auto_import_file_exclude_patterns: Vec<String>,
|
||||||
#[serde(default = "is_true")]
|
#[serde(default = "is_true")]
|
||||||
pub use_aliases_for_renames: bool,
|
pub use_aliases_for_renames: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub quote_style: QuoteStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LanguagePreferences {
|
impl Default for LanguagePreferences {
|
||||||
|
@ -376,6 +402,7 @@ impl Default for LanguagePreferences {
|
||||||
jsx_attribute_completion_style: Default::default(),
|
jsx_attribute_completion_style: Default::default(),
|
||||||
auto_import_file_exclude_patterns: vec![],
|
auto_import_file_exclude_patterns: vec![],
|
||||||
use_aliases_for_renames: true,
|
use_aliases_for_renames: true,
|
||||||
|
quote_style: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1372,6 +1399,7 @@ mod tests {
|
||||||
jsx_attribute_completion_style: JsxAttributeCompletionStyle::Auto,
|
jsx_attribute_completion_style: JsxAttributeCompletionStyle::Auto,
|
||||||
auto_import_file_exclude_patterns: vec![],
|
auto_import_file_exclude_patterns: vec![],
|
||||||
use_aliases_for_renames: true,
|
use_aliases_for_renames: true,
|
||||||
|
quote_style: QuoteStyle::Auto,
|
||||||
},
|
},
|
||||||
suggest: CompletionSettings {
|
suggest: CompletionSettings {
|
||||||
complete_function_calls: false,
|
complete_function_calls: false,
|
||||||
|
@ -1416,6 +1444,7 @@ mod tests {
|
||||||
jsx_attribute_completion_style: JsxAttributeCompletionStyle::Auto,
|
jsx_attribute_completion_style: JsxAttributeCompletionStyle::Auto,
|
||||||
auto_import_file_exclude_patterns: vec![],
|
auto_import_file_exclude_patterns: vec![],
|
||||||
use_aliases_for_renames: true,
|
use_aliases_for_renames: true,
|
||||||
|
quote_style: QuoteStyle::Auto,
|
||||||
},
|
},
|
||||||
suggest: CompletionSettings {
|
suggest: CompletionSettings {
|
||||||
complete_function_calls: false,
|
complete_function_calls: false,
|
||||||
|
|
|
@ -3014,7 +3014,6 @@ impl Inner {
|
||||||
(&self.fmt_options.options).into(),
|
(&self.fmt_options.options).into(),
|
||||||
tsc::UserPreferences {
|
tsc::UserPreferences {
|
||||||
allow_text_changes_in_new_files: Some(true),
|
allow_text_changes_in_new_files: Some(true),
|
||||||
quote_preference: Some((&self.fmt_options.options).into()),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
111
cli/lsp/tsc.rs
111
cli/lsp/tsc.rs
|
@ -99,24 +99,90 @@ type Request = (
|
||||||
CancellationToken,
|
CancellationToken,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Relevant subset of https://github.com/denoland/deno/blob/80331d1fe5b85b829ac009fdc201c128b3427e11/cli/tsc/dts/typescript.d.ts#L6658.
|
#[derive(Debug, Clone, Copy, Serialize_repr)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum IndentStyle {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
None = 0,
|
||||||
|
Block = 1,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
Smart = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Relevant subset of https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6658.
|
||||||
#[derive(Clone, Debug, Default, Serialize)]
|
#[derive(Clone, Debug, Default, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FormatCodeSettings {
|
pub struct FormatCodeSettings {
|
||||||
convert_tabs_to_spaces: Option<bool>,
|
base_indent_size: Option<u8>,
|
||||||
indent_size: Option<u8>,
|
indent_size: Option<u8>,
|
||||||
|
tab_size: Option<u8>,
|
||||||
|
new_line_character: Option<String>,
|
||||||
|
convert_tabs_to_spaces: Option<bool>,
|
||||||
|
indent_style: Option<IndentStyle>,
|
||||||
|
trim_trailing_whitespace: Option<bool>,
|
||||||
|
insert_space_after_comma_delimiter: Option<bool>,
|
||||||
|
insert_space_after_semicolon_in_for_statements: Option<bool>,
|
||||||
|
insert_space_before_and_after_binary_operators: Option<bool>,
|
||||||
|
insert_space_after_constructor: Option<bool>,
|
||||||
|
insert_space_after_keywords_in_control_flow_statements: Option<bool>,
|
||||||
|
insert_space_after_function_keyword_for_anonymous_functions: Option<bool>,
|
||||||
|
insert_space_after_opening_and_before_closing_nonempty_parenthesis:
|
||||||
|
Option<bool>,
|
||||||
|
insert_space_after_opening_and_before_closing_nonempty_brackets: Option<bool>,
|
||||||
|
insert_space_after_opening_and_before_closing_nonempty_braces: Option<bool>,
|
||||||
|
insert_space_after_opening_and_before_closing_template_string_braces:
|
||||||
|
Option<bool>,
|
||||||
|
insert_space_after_opening_and_before_closing_jsx_expression_braces:
|
||||||
|
Option<bool>,
|
||||||
|
insert_space_after_type_assertion: Option<bool>,
|
||||||
|
insert_space_before_function_parenthesis: Option<bool>,
|
||||||
|
place_open_brace_on_new_line_for_functions: Option<bool>,
|
||||||
|
place_open_brace_on_new_line_for_control_blocks: Option<bool>,
|
||||||
|
insert_space_before_type_annotation: Option<bool>,
|
||||||
|
indent_multi_line_object_literal_beginning_on_blank_line: Option<bool>,
|
||||||
semicolons: Option<SemicolonPreference>,
|
semicolons: Option<SemicolonPreference>,
|
||||||
|
indent_switch_case: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&FmtOptionsConfig> for FormatCodeSettings {
|
impl From<&FmtOptionsConfig> for FormatCodeSettings {
|
||||||
fn from(config: &FmtOptionsConfig) -> Self {
|
fn from(config: &FmtOptionsConfig) -> Self {
|
||||||
FormatCodeSettings {
|
FormatCodeSettings {
|
||||||
convert_tabs_to_spaces: Some(!config.use_tabs.unwrap_or(false)),
|
base_indent_size: Some(0),
|
||||||
indent_size: Some(config.indent_width.unwrap_or(2)),
|
indent_size: Some(config.indent_width.unwrap_or(2)),
|
||||||
|
tab_size: Some(config.indent_width.unwrap_or(2)),
|
||||||
|
new_line_character: Some("\n".to_string()),
|
||||||
|
convert_tabs_to_spaces: Some(!config.use_tabs.unwrap_or(false)),
|
||||||
|
indent_style: Some(IndentStyle::Block),
|
||||||
|
trim_trailing_whitespace: Some(false),
|
||||||
|
insert_space_after_comma_delimiter: Some(true),
|
||||||
|
insert_space_after_semicolon_in_for_statements: Some(true),
|
||||||
|
insert_space_before_and_after_binary_operators: Some(true),
|
||||||
|
insert_space_after_constructor: Some(false),
|
||||||
|
insert_space_after_keywords_in_control_flow_statements: Some(true),
|
||||||
|
insert_space_after_function_keyword_for_anonymous_functions: Some(true),
|
||||||
|
insert_space_after_opening_and_before_closing_nonempty_parenthesis: Some(
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
insert_space_after_opening_and_before_closing_nonempty_brackets: Some(
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
insert_space_after_opening_and_before_closing_nonempty_braces: Some(true),
|
||||||
|
insert_space_after_opening_and_before_closing_template_string_braces:
|
||||||
|
Some(false),
|
||||||
|
insert_space_after_opening_and_before_closing_jsx_expression_braces: Some(
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
insert_space_after_type_assertion: Some(false),
|
||||||
|
insert_space_before_function_parenthesis: Some(false),
|
||||||
|
place_open_brace_on_new_line_for_functions: Some(false),
|
||||||
|
place_open_brace_on_new_line_for_control_blocks: Some(false),
|
||||||
|
insert_space_before_type_annotation: Some(false),
|
||||||
|
indent_multi_line_object_literal_beginning_on_blank_line: Some(false),
|
||||||
semicolons: match config.semi_colons {
|
semicolons: match config.semi_colons {
|
||||||
Some(false) => Some(SemicolonPreference::Remove),
|
Some(false) => Some(SemicolonPreference::Remove),
|
||||||
_ => Some(SemicolonPreference::Insert),
|
_ => Some(SemicolonPreference::Insert),
|
||||||
},
|
},
|
||||||
|
indent_switch_case: Some(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,9 +360,6 @@ impl TsServer {
|
||||||
format_code_settings: FormatCodeSettings,
|
format_code_settings: FormatCodeSettings,
|
||||||
preferences: UserPreferences,
|
preferences: UserPreferences,
|
||||||
) -> Vec<CodeFixAction> {
|
) -> Vec<CodeFixAction> {
|
||||||
let mut format_code_settings = json!(format_code_settings);
|
|
||||||
let format_object = format_code_settings.as_object_mut().unwrap();
|
|
||||||
format_object.insert("indentStyle".to_string(), json!(1));
|
|
||||||
let req = TscRequest {
|
let req = TscRequest {
|
||||||
method: "getCodeFixesAtPosition",
|
method: "getCodeFixesAtPosition",
|
||||||
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6257
|
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6257
|
||||||
|
@ -363,9 +426,6 @@ impl TsServer {
|
||||||
format_code_settings: FormatCodeSettings,
|
format_code_settings: FormatCodeSettings,
|
||||||
preferences: UserPreferences,
|
preferences: UserPreferences,
|
||||||
) -> Result<CombinedCodeActions, LspError> {
|
) -> Result<CombinedCodeActions, LspError> {
|
||||||
let mut format_code_settings = json!(format_code_settings);
|
|
||||||
let format_object = format_code_settings.as_object_mut().unwrap();
|
|
||||||
format_object.insert("indentStyle".to_string(), json!(1));
|
|
||||||
let req = TscRequest {
|
let req = TscRequest {
|
||||||
method: "getCombinedCodeFix",
|
method: "getCombinedCodeFix",
|
||||||
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6258
|
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6258
|
||||||
|
@ -403,15 +463,6 @@ impl TsServer {
|
||||||
action_name: String,
|
action_name: String,
|
||||||
preferences: Option<UserPreferences>,
|
preferences: Option<UserPreferences>,
|
||||||
) -> Result<RefactorEditInfo, LspError> {
|
) -> Result<RefactorEditInfo, LspError> {
|
||||||
let mut format_code_settings = json!(format_code_settings);
|
|
||||||
let format_object = format_code_settings.as_object_mut().unwrap();
|
|
||||||
format_object.insert("indentStyle".to_string(), json!(2));
|
|
||||||
format_object.insert(
|
|
||||||
"insertSpaceBeforeAndAfterBinaryOperators".to_string(),
|
|
||||||
json!(true),
|
|
||||||
);
|
|
||||||
format_object
|
|
||||||
.insert("insertSpaceAfterCommaDelimiter".to_string(), json!(true));
|
|
||||||
let req = TscRequest {
|
let req = TscRequest {
|
||||||
method: "getEditsForRefactor",
|
method: "getEditsForRefactor",
|
||||||
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6275
|
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6275
|
||||||
|
@ -4024,23 +4075,7 @@ impl From<lsp::CompletionTriggerKind> for CompletionTriggerKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
pub type QuotePreference = config::QuoteStyle;
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum QuotePreference {
|
|
||||||
Auto,
|
|
||||||
Double,
|
|
||||||
Single,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&FmtOptionsConfig> for QuotePreference {
|
|
||||||
fn from(config: &FmtOptionsConfig) -> Self {
|
|
||||||
match config.single_quote {
|
|
||||||
Some(true) => QuotePreference::Single,
|
|
||||||
_ => QuotePreference::Double,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type ImportModuleSpecifierPreference = config::ImportModuleSpecifier;
|
pub type ImportModuleSpecifierPreference = config::ImportModuleSpecifier;
|
||||||
|
|
||||||
|
@ -4270,6 +4305,12 @@ impl UserPreferences {
|
||||||
provide_prefix_and_suffix_text_for_rename: Some(
|
provide_prefix_and_suffix_text_for_rename: Some(
|
||||||
language_settings.preferences.use_aliases_for_renames,
|
language_settings.preferences.use_aliases_for_renames,
|
||||||
),
|
),
|
||||||
|
// Only use workspace settings for quote style if there's no `deno.json`.
|
||||||
|
quote_preference: if config.has_config_file() {
|
||||||
|
base_preferences.quote_preference
|
||||||
|
} else {
|
||||||
|
Some(language_settings.preferences.quote_style)
|
||||||
|
},
|
||||||
..base_preferences
|
..base_preferences
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5569,6 +5569,151 @@ fn lsp_code_actions_imports_respects_fmt_config() {
|
||||||
client.shutdown();
|
client.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsp_quote_style_from_workspace_settings() {
|
||||||
|
let context = TestContextBuilder::new().use_temp_cwd().build();
|
||||||
|
let temp_dir = context.temp_dir();
|
||||||
|
temp_dir.write(
|
||||||
|
"file00.ts",
|
||||||
|
r#"
|
||||||
|
export interface MallardDuckConfigOptions extends DuckConfigOptions {
|
||||||
|
kind: "mallard";
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
temp_dir.write(
|
||||||
|
"file01.ts",
|
||||||
|
r#"
|
||||||
|
export interface DuckConfigOptions {
|
||||||
|
kind: string;
|
||||||
|
quacks: boolean;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let mut client = context.new_lsp_command().build();
|
||||||
|
client.initialize_default();
|
||||||
|
client.write_notification(
|
||||||
|
"workspace/didChangeConfiguration",
|
||||||
|
json!({
|
||||||
|
"settings": {}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let settings = json!({
|
||||||
|
"typescript": {
|
||||||
|
"preferences": {
|
||||||
|
"quoteStyle": "single",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// one for the workspace
|
||||||
|
client.handle_configuration_request(&settings);
|
||||||
|
// one for the specifier
|
||||||
|
client.handle_configuration_request(&settings);
|
||||||
|
|
||||||
|
let code_action_params = json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": temp_dir.uri().join("file00.ts").unwrap(),
|
||||||
|
},
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 0 },
|
||||||
|
"end": { "line": 4, "character": 0 },
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"diagnostics": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 1, "character": 56 },
|
||||||
|
"end": { "line": 1, "character": 73 },
|
||||||
|
},
|
||||||
|
"severity": 1,
|
||||||
|
"code": 2304,
|
||||||
|
"source": "deno-ts",
|
||||||
|
"message": "Cannot find name 'DuckConfigOptions'.",
|
||||||
|
}],
|
||||||
|
"only": ["quickfix"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let res =
|
||||||
|
client.write_request("textDocument/codeAction", code_action_params.clone());
|
||||||
|
// Expect single quotes in the auto-import.
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
json!([{
|
||||||
|
"title": "Add import from \"./file01.ts\"",
|
||||||
|
"kind": "quickfix",
|
||||||
|
"diagnostics": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 1, "character": 56 },
|
||||||
|
"end": { "line": 1, "character": 73 },
|
||||||
|
},
|
||||||
|
"severity": 1,
|
||||||
|
"code": 2304,
|
||||||
|
"source": "deno-ts",
|
||||||
|
"message": "Cannot find name 'DuckConfigOptions'.",
|
||||||
|
}],
|
||||||
|
"edit": {
|
||||||
|
"documentChanges": [{
|
||||||
|
"textDocument": {
|
||||||
|
"uri": temp_dir.uri().join("file00.ts").unwrap(),
|
||||||
|
"version": null,
|
||||||
|
},
|
||||||
|
"edits": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 0 },
|
||||||
|
"end": { "line": 0, "character": 0 },
|
||||||
|
},
|
||||||
|
"newText": "import { DuckConfigOptions } from './file01.ts';\n",
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// It should ignore the workspace setting if a `deno.json` is present.
|
||||||
|
temp_dir.write("./deno.json", json!({}).to_string());
|
||||||
|
client.did_change_watched_files(json!({
|
||||||
|
"changes": [{
|
||||||
|
"uri": temp_dir.uri().join("deno.json").unwrap(),
|
||||||
|
"type": 1,
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
|
||||||
|
let res = client.write_request("textDocument/codeAction", code_action_params);
|
||||||
|
// Expect double quotes in the auto-import.
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
json!([{
|
||||||
|
"title": "Add import from \"./file01.ts\"",
|
||||||
|
"kind": "quickfix",
|
||||||
|
"diagnostics": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 1, "character": 56 },
|
||||||
|
"end": { "line": 1, "character": 73 },
|
||||||
|
},
|
||||||
|
"severity": 1,
|
||||||
|
"code": 2304,
|
||||||
|
"source": "deno-ts",
|
||||||
|
"message": "Cannot find name 'DuckConfigOptions'.",
|
||||||
|
}],
|
||||||
|
"edit": {
|
||||||
|
"documentChanges": [{
|
||||||
|
"textDocument": {
|
||||||
|
"uri": temp_dir.uri().join("file00.ts").unwrap(),
|
||||||
|
"version": null,
|
||||||
|
},
|
||||||
|
"edits": [{
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 0, "character": 0 },
|
||||||
|
"end": { "line": 0, "character": 0 },
|
||||||
|
},
|
||||||
|
"newText": "import { DuckConfigOptions } from \"./file01.ts\";\n",
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
}]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lsp_code_actions_refactor_no_disabled_support() {
|
fn lsp_code_actions_refactor_no_disabled_support() {
|
||||||
let context = TestContextBuilder::new().use_temp_cwd().build();
|
let context = TestContextBuilder::new().use_temp_cwd().build();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue