Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lsp): respect "typescript.preferences.quoteStyle" when deno.json is absent #20891

Merged
merged 1 commit into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions cli/lsp/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::lsp::logging::lsp_warn;
use crate::util::fs::canonicalize_path_maybe_not_exists;
use crate::util::path::specifier_to_file_path;
use deno_ast::MediaType;
use deno_config::FmtOptionsConfig;
use deno_core::parking_lot::Mutex;
use deno_core::serde::de::DeserializeOwned;
use deno_core::serde::Deserialize;
Expand Down Expand Up @@ -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)]
#[serde(rename_all = "camelCase")]
pub struct LanguagePreferences {
Expand All @@ -367,6 +391,8 @@ pub struct LanguagePreferences {
pub auto_import_file_exclude_patterns: Vec<String>,
#[serde(default = "is_true")]
pub use_aliases_for_renames: bool,
#[serde(default)]
pub quote_style: QuoteStyle,
}

impl Default for LanguagePreferences {
Expand All @@ -376,6 +402,7 @@ impl Default for LanguagePreferences {
jsx_attribute_completion_style: Default::default(),
auto_import_file_exclude_patterns: vec![],
use_aliases_for_renames: true,
quote_style: Default::default(),
}
}
}
Expand Down Expand Up @@ -1372,6 +1399,7 @@ mod tests {
jsx_attribute_completion_style: JsxAttributeCompletionStyle::Auto,
auto_import_file_exclude_patterns: vec![],
use_aliases_for_renames: true,
quote_style: QuoteStyle::Auto,
},
suggest: CompletionSettings {
complete_function_calls: false,
Expand Down Expand Up @@ -1416,6 +1444,7 @@ mod tests {
jsx_attribute_completion_style: JsxAttributeCompletionStyle::Auto,
auto_import_file_exclude_patterns: vec![],
use_aliases_for_renames: true,
quote_style: QuoteStyle::Auto,
},
suggest: CompletionSettings {
complete_function_calls: false,
Expand Down
1 change: 0 additions & 1 deletion cli/lsp/language_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2953,7 +2953,6 @@ impl Inner {
(&self.fmt_options.options).into(),
tsc::UserPreferences {
allow_text_changes_in_new_files: Some(true),
quote_preference: Some((&self.fmt_options.options).into()),
..Default::default()
},
)
Expand Down
111 changes: 76 additions & 35 deletions cli/lsp/tsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,24 +99,90 @@ type Request = (
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)]
#[serde(rename_all = "camelCase")]
pub struct FormatCodeSettings {
convert_tabs_to_spaces: Option<bool>,
base_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>,
indent_switch_case: Option<bool>,
}

impl From<&FmtOptionsConfig> for FormatCodeSettings {
fn from(config: &FmtOptionsConfig) -> Self {
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)),
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 {
Some(false) => Some(SemicolonPreference::Remove),
_ => Some(SemicolonPreference::Insert),
},
indent_switch_case: Some(true),
}
}
}
Expand Down Expand Up @@ -294,9 +360,6 @@ impl TsServer {
format_code_settings: FormatCodeSettings,
preferences: UserPreferences,
) -> 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 {
method: "getCodeFixesAtPosition",
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6257
Expand Down Expand Up @@ -363,9 +426,6 @@ impl TsServer {
format_code_settings: FormatCodeSettings,
preferences: UserPreferences,
) -> 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 {
method: "getCombinedCodeFix",
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6258
Expand Down Expand Up @@ -403,15 +463,6 @@ impl TsServer {
action_name: String,
preferences: Option<UserPreferences>,
) -> 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 {
method: "getEditsForRefactor",
// https://github.com/denoland/deno/blob/v1.37.1/cli/tsc/dts/typescript.d.ts#L6275
Expand Down Expand Up @@ -4023,23 +4074,7 @@ impl From<lsp::CompletionTriggerKind> for CompletionTriggerKind {
}
}

#[derive(Debug, Serialize)]
#[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 QuotePreference = config::QuoteStyle;

pub type ImportModuleSpecifierPreference = config::ImportModuleSpecifier;

Expand Down Expand Up @@ -4269,6 +4304,12 @@ impl UserPreferences {
provide_prefix_and_suffix_text_for_rename: Some(
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
}
}
Expand Down
145 changes: 145 additions & 0 deletions cli/tests/integration/lsp_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5436,6 +5436,151 @@ fn lsp_code_actions_imports_respects_fmt_config() {
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]
fn lsp_code_actions_refactor_no_disabled_support() {
let context = TestContextBuilder::new().use_temp_cwd().build();
Expand Down