From 50c389b64ffa65c6a889f725f53c748d7e3bc507 Mon Sep 17 00:00:00 2001 From: leaysgur <6259812+leaysgur@users.noreply.github.com> Date: Tue, 7 Apr 2026 02:03:35 +0000 Subject: [PATCH] fix(oxfmt): Support `.editorconfig` `quote_type` (#20989) Fixes #20563 --- - [x] Merge https://github.com/oxc-project/editorconfig-parser/pull/42 - [x] Release https://github.com/oxc-project/editorconfig-parser/pull/43 - [x] Update crates version - [x] Implement - [x] Test - [x] Update schema, snapshots --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- apps/oxfmt/src-js/config.generated.ts | 2 ++ apps/oxfmt/src/core/config.rs | 17 ++++++++++++++++- apps/oxfmt/src/core/oxfmtrc/format_config.rs | 1 + .../__snapshots__/editorconfig.test.ts.snap | 14 ++++++++++++++ .../test/cli/editorconfig/editorconfig.test.ts | 11 +++++++++++ .../fixtures/quote_type/.editorconfig | 2 ++ .../editorconfig/fixtures/quote_type/test.js | 2 ++ npm/oxfmt/configuration_schema.json | 8 ++++---- .../src/snapshots/schema_json.snap | 8 ++++---- .../src/snapshots/schema_markdown.snap | 2 ++ 12 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 apps/oxfmt/test/cli/editorconfig/fixtures/quote_type/.editorconfig create mode 100644 apps/oxfmt/test/cli/editorconfig/fixtures/quote_type/test.js diff --git a/Cargo.lock b/Cargo.lock index 25835a89bae0e..556e0beb22bc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -632,9 +632,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "editorconfig-parser" -version = "0.0.3" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e6e29ee299de275dfcc1f52a23cff9fc668cb29edd148960254c14972f7a2bc" +checksum = "e587c7336689e4dbb79157eb50ca5de9b3f75a48b85f17c1297d9c31e9d3bbc3" dependencies = [ "globset", ] diff --git a/Cargo.toml b/Cargo.toml index f484707798893..f6fa6ec44f508 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -236,7 +236,7 @@ tracing-subscriber = "0.3.22" # Tracing implementation ureq = { version = "3.1.4", default-features = false } # HTTP client url = { version = "2.5.8" } # URL parsing walkdir = "2.5.0" # Directory traversal -editorconfig-parser = "0.0.3" +editorconfig-parser = "0.0.4" natord = "1.0.9" oxfmt = { path = "apps/oxfmt" } sort-package-json = "0.0.12" diff --git a/apps/oxfmt/src-js/config.generated.ts b/apps/oxfmt/src-js/config.generated.ts index 336f8d883a1e5..a833b98e11a8f 100644 --- a/apps/oxfmt/src-js/config.generated.ts +++ b/apps/oxfmt/src-js/config.generated.ts @@ -158,6 +158,7 @@ export interface Oxfmtrc { * For JSX, you can set the `jsxSingleQuote` option. * * - Default: `false` + * - Overrides `.editorconfig.quote_type` */ singleQuote?: boolean; /** @@ -435,6 +436,7 @@ export interface FormatConfig { * For JSX, you can set the `jsxSingleQuote` option. * * - Default: `false` + * - Overrides `.editorconfig.quote_type` */ singleQuote?: boolean; /** diff --git a/apps/oxfmt/src/core/config.rs b/apps/oxfmt/src/core/config.rs index 3b938aec6b9b1..6aaa5eda54a53 100644 --- a/apps/oxfmt/src/core/config.rs +++ b/apps/oxfmt/src/core/config.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use editorconfig_parser::{ EditorConfig, EditorConfigProperties, EditorConfigProperty, EndOfLine, IndentStyle, - MaxLineLength, + MaxLineLength, QuoteType, }; use fast_glob::glob_match; use serde_json::Value; @@ -613,6 +613,7 @@ fn load_editorconfig( /// - indent_size /// - tab_width /// - insert_final_newline +/// - quote_type fn has_editorconfig_overrides(editorconfig: &EditorConfig, path: &Path) -> bool { let sections = editorconfig.sections(); @@ -635,6 +636,7 @@ fn has_editorconfig_overrides(editorconfig: &EditorConfig, path: &Path) -> bool || resolved.indent_size != root.indent_size || resolved.tab_width != root.tab_width || resolved.insert_final_newline != root.insert_final_newline + || resolved.quote_type != root.quote_type } // No `[*]` section means any resolved property is an override None => { @@ -644,6 +646,7 @@ fn has_editorconfig_overrides(editorconfig: &EditorConfig, path: &Path) -> bool || resolved.indent_size != EditorConfigProperty::Unset || resolved.tab_width != EditorConfigProperty::Unset || resolved.insert_final_newline != EditorConfigProperty::Unset + || resolved.quote_type != EditorConfigProperty::Unset } } } @@ -697,4 +700,16 @@ fn apply_editorconfig(config: &mut FormatConfig, props: &EditorConfigProperties) { config.insert_final_newline = Some(v); } + + if config.single_quote.is_none() { + match props.quote_type { + EditorConfigProperty::Value(QuoteType::Single) => { + config.single_quote = Some(true); + } + EditorConfigProperty::Value(QuoteType::Double) => { + config.single_quote = Some(false); + } + _ => {} + } + } } diff --git a/apps/oxfmt/src/core/oxfmtrc/format_config.rs b/apps/oxfmt/src/core/oxfmtrc/format_config.rs index 3e8d550474f81..58aa8721e7b19 100644 --- a/apps/oxfmt/src/core/oxfmtrc/format_config.rs +++ b/apps/oxfmt/src/core/oxfmtrc/format_config.rs @@ -88,6 +88,7 @@ pub struct FormatConfig { /// For JSX, you can set the `jsxSingleQuote` option. /// /// - Default: `false` + /// - Overrides `.editorconfig.quote_type` #[serde(skip_serializing_if = "Option::is_none")] pub single_quote: Option, /// Use single quotes instead of double quotes in JSX. diff --git a/apps/oxfmt/test/cli/editorconfig/__snapshots__/editorconfig.test.ts.snap b/apps/oxfmt/test/cli/editorconfig/__snapshots__/editorconfig.test.ts.snap index 53155cc38a210..53e7835dd8c39 100644 --- a/apps/oxfmt/test/cli/editorconfig/__snapshots__/editorconfig.test.ts.snap +++ b/apps/oxfmt/test/cli/editorconfig/__snapshots__/editorconfig.test.ts.snap @@ -154,6 +154,20 @@ nested/deep/test.json --------------------" `; +exports[`editorconfig > quote_type maps to singleQuote 1`] = ` +"--- FILE ----------- +test.js +--- BEFORE --------- +const a = "hello"; +const b = "world"; + +--- AFTER ---------- +const a = 'hello'; +const b = 'world'; + +--------------------" +`; + exports[`editorconfig > tab_width fallback when indent_size is not set 1`] = ` "--- FILE ----------- test.js diff --git a/apps/oxfmt/test/cli/editorconfig/editorconfig.test.ts b/apps/oxfmt/test/cli/editorconfig/editorconfig.test.ts index 26cda5a909467..79e2d28b02bfe 100644 --- a/apps/oxfmt/test/cli/editorconfig/editorconfig.test.ts +++ b/apps/oxfmt/test/cli/editorconfig/editorconfig.test.ts @@ -90,6 +90,17 @@ describe("editorconfig", () => { expect(snapshot).toMatchSnapshot(); }); + // .editorconfig: + // [*] quote_type=single + // + // Expected: singleQuote=true + // - String literals should use single quotes instead of double quotes + it("quote_type maps to singleQuote", async () => { + const cwd = join(fixturesDir, "quote_type"); + const snapshot = await runWriteModeAndSnapshot(cwd, ["test.js"]); + expect(snapshot).toMatchSnapshot(); + }); + // .editorconfig: (empty file) // // Expected: default settings (useTabs=false, tabWidth=2) diff --git a/apps/oxfmt/test/cli/editorconfig/fixtures/quote_type/.editorconfig b/apps/oxfmt/test/cli/editorconfig/fixtures/quote_type/.editorconfig new file mode 100644 index 0000000000000..5e9ac3b0ed3c4 --- /dev/null +++ b/apps/oxfmt/test/cli/editorconfig/fixtures/quote_type/.editorconfig @@ -0,0 +1,2 @@ +[*] +quote_type = single diff --git a/apps/oxfmt/test/cli/editorconfig/fixtures/quote_type/test.js b/apps/oxfmt/test/cli/editorconfig/fixtures/quote_type/test.js new file mode 100644 index 0000000000000..80b3759b1f8ec --- /dev/null +++ b/apps/oxfmt/test/cli/editorconfig/fixtures/quote_type/test.js @@ -0,0 +1,2 @@ +const a = "hello"; +const b = "world"; diff --git a/npm/oxfmt/configuration_schema.json b/npm/oxfmt/configuration_schema.json index 2fa1c4432a30a..e1b6409fd5a1b 100644 --- a/npm/oxfmt/configuration_schema.json +++ b/npm/oxfmt/configuration_schema.json @@ -130,9 +130,9 @@ "markdownDescription": "Enforce single attribute per line in HTML, Vue, and JSX.\n\n- Default: `false`" }, "singleQuote": { - "description": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`", + "description": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`\n- Overrides `.editorconfig.quote_type`", "type": "boolean", - "markdownDescription": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`" + "markdownDescription": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`\n- Overrides `.editorconfig.quote_type`" }, "sortImports": { "description": "Sort import statements.\n\nUsing the similar algorithm as [eslint-plugin-perfectionist/sort-imports](https://perfectionist.dev/rules/sort-imports).\nFor details, see each field's documentation.\n\nPass `true` or an object to enable with defaults, or omit/set `false` to disable.\n\n- Default: Disabled", @@ -359,9 +359,9 @@ "markdownDescription": "Enforce single attribute per line in HTML, Vue, and JSX.\n\n- Default: `false`" }, "singleQuote": { - "description": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`", + "description": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`\n- Overrides `.editorconfig.quote_type`", "type": "boolean", - "markdownDescription": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`" + "markdownDescription": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`\n- Overrides `.editorconfig.quote_type`" }, "sortImports": { "description": "Sort import statements.\n\nUsing the similar algorithm as [eslint-plugin-perfectionist/sort-imports](https://perfectionist.dev/rules/sort-imports).\nFor details, see each field's documentation.\n\nPass `true` or an object to enable with defaults, or omit/set `false` to disable.\n\n- Default: Disabled", diff --git a/tasks/website_formatter/src/snapshots/schema_json.snap b/tasks/website_formatter/src/snapshots/schema_json.snap index 4205ce222ee55..62b2d03706696 100644 --- a/tasks/website_formatter/src/snapshots/schema_json.snap +++ b/tasks/website_formatter/src/snapshots/schema_json.snap @@ -134,9 +134,9 @@ expression: json "markdownDescription": "Enforce single attribute per line in HTML, Vue, and JSX.\n\n- Default: `false`" }, "singleQuote": { - "description": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`", + "description": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`\n- Overrides `.editorconfig.quote_type`", "type": "boolean", - "markdownDescription": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`" + "markdownDescription": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`\n- Overrides `.editorconfig.quote_type`" }, "sortImports": { "description": "Sort import statements.\n\nUsing the similar algorithm as [eslint-plugin-perfectionist/sort-imports](https://perfectionist.dev/rules/sort-imports).\nFor details, see each field's documentation.\n\nPass `true` or an object to enable with defaults, or omit/set `false` to disable.\n\n- Default: Disabled", @@ -363,9 +363,9 @@ expression: json "markdownDescription": "Enforce single attribute per line in HTML, Vue, and JSX.\n\n- Default: `false`" }, "singleQuote": { - "description": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`", + "description": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`\n- Overrides `.editorconfig.quote_type`", "type": "boolean", - "markdownDescription": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`" + "markdownDescription": "Use single quotes instead of double quotes.\n\nFor JSX, you can set the `jsxSingleQuote` option.\n\n- Default: `false`\n- Overrides `.editorconfig.quote_type`" }, "sortImports": { "description": "Sort import statements.\n\nUsing the similar algorithm as [eslint-plugin-perfectionist/sort-imports](https://perfectionist.dev/rules/sort-imports).\nFor details, see each field's documentation.\n\nPass `true` or an object to enable with defaults, or omit/set `false` to disable.\n\n- Default: Disabled", diff --git a/tasks/website_formatter/src/snapshots/schema_markdown.snap b/tasks/website_formatter/src/snapshots/schema_markdown.snap index 72d72d61fe425..dd487b3005f94 100644 --- a/tasks/website_formatter/src/snapshots/schema_markdown.snap +++ b/tasks/website_formatter/src/snapshots/schema_markdown.snap @@ -600,6 +600,7 @@ Use single quotes instead of double quotes. For JSX, you can set the `jsxSingleQuote` option. - Default: `false` +- Overrides `.editorconfig.quote_type` ##### overrides[n].options.sortImports @@ -1073,6 +1074,7 @@ Use single quotes instead of double quotes. For JSX, you can set the `jsxSingleQuote` option. - Default: `false` +- Overrides `.editorconfig.quote_type` ## sortImports