From c80313eed2bec5def829cfd4c2c28b4ae2b8c199 Mon Sep 17 00:00:00 2001 From: vrustx <279631638@qq.com> Date: Wed, 15 Jul 2026 21:18:57 +0800 Subject: [PATCH] fix(sql): support brace placeholders in formatter --- .../src/lib/__tests__/sql/sqlFormatter.spec.ts | 12 ++++++++++++ .../__tests__/sql/sqlFormatterConfig.spec.ts | 18 +++++++++++++++++- apps/desktop/src/lib/sql/sqlFormatterConfig.ts | 11 ++++++++++- packages/app-tests/sqlFormatterConfig.test.ts | 8 +++++++- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/lib/__tests__/sql/sqlFormatter.spec.ts b/apps/desktop/src/lib/__tests__/sql/sqlFormatter.spec.ts index b68414d52..3d0181684 100644 --- a/apps/desktop/src/lib/__tests__/sql/sqlFormatter.spec.ts +++ b/apps/desktop/src/lib/__tests__/sql/sqlFormatter.spec.ts @@ -14,6 +14,18 @@ describe("sqlFormatter", () => { } }); + it("preserves DBX brace placeholders in generic and MySQL SQL", async () => { + const sql = "SELECT ${x} AS shell_value, #{x} AS mybatis_value, '${date}' AS quoted_value"; + + for (const dialect of ["generic", "mysql"] as const) { + const formatted = await formatSqlText(sql, dialect); + + expect(formatted).toContain("${x}"); + expect(formatted).toContain("#{x}"); + expect(formatted).toContain("'${date}'"); + } + }); + it("falls back to the postgres formatter when the generic dialect cannot parse SQL", async () => { const formatted = await formatSqlText("SELECT 1::int AS id;", "generic"); diff --git a/apps/desktop/src/lib/__tests__/sql/sqlFormatterConfig.spec.ts b/apps/desktop/src/lib/__tests__/sql/sqlFormatterConfig.spec.ts index 92314fb7d..8739d23c1 100644 --- a/apps/desktop/src/lib/__tests__/sql/sqlFormatterConfig.spec.ts +++ b/apps/desktop/src/lib/__tests__/sql/sqlFormatterConfig.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { DEFAULT_SQL_FORMATTER_SETTINGS, parseSqlFormatterConfig, serializeSqlFormatterConfig } from "@/lib/sql/sqlFormatterConfig"; +import { DEFAULT_SQL_FORMATTER_SETTINGS, parseSqlFormatterConfig, serializeSqlFormatterConfig, sqlFormatterOptions } from "@/lib/sql/sqlFormatterConfig"; describe("sqlFormatterConfig shortcut storage", () => { it("does not serialize JSON editor shortcut settings", () => { @@ -23,4 +23,20 @@ describe("sqlFormatterConfig shortcut storage", () => { expect(result.ok).toBe(true); }); + + it("merges DBX custom parameter types with user paramTypes", () => { + const options = sqlFormatterOptions({ + paramTypes: { + positional: false, + named: ["@"], + custom: [{ regex: String.raw`\{\{[^}]+\}\}` }], + }, + }); + + expect(options.paramTypes).toEqual({ + positional: false, + named: ["@"], + custom: [{ regex: String.raw`\{\{[^}]+\}\}` }, { regex: String.raw`\$\{[^}]+\}` }, { regex: String.raw`#\{[^}]+\}` }], + }); + }); }); diff --git a/apps/desktop/src/lib/sql/sqlFormatterConfig.ts b/apps/desktop/src/lib/sql/sqlFormatterConfig.ts index a6ac64e1d..5e2933712 100644 --- a/apps/desktop/src/lib/sql/sqlFormatterConfig.ts +++ b/apps/desktop/src/lib/sql/sqlFormatterConfig.ts @@ -22,6 +22,11 @@ export interface SqlFormatterCustomParameter { regex: string; } +// DBX substitutes `${name}`/`#{name}` placeholders client-side (see sqlVariableSyntax.ts), +// but no sql-formatter dialect tokenizes them, so raw SQL containing them would fail to +// format with a parse error. Always registering them as custom params keeps formatting working. +const DBX_CUSTOM_PARAM_TYPES: SqlFormatterCustomParameter[] = [{ regex: String.raw`\$\{[^}]+\}` }, { regex: String.raw`#\{[^}]+\}` }]; + export interface SqlFormatterParamTypes { positional?: boolean; numbered?: ("?" | ":" | "$")[]; @@ -239,6 +244,7 @@ export function syncSqlFormatterConfigDraft(text: string, syncSettings: (setting export function sqlFormatterOptions(settings: unknown) { const normalized = sqlFormatterOptionSettings(settings); + const paramTypes = normalized.paramTypes ?? {}; return { keywordCase: normalized.keywordCase, dataTypeCase: normalized.dataTypeCase, @@ -252,6 +258,9 @@ export function sqlFormatterOptions(settings: unknown) { linesBetweenQueries: normalized.linesBetweenQueries, denseOperators: normalized.denseOperators, newlineBeforeSemicolon: normalized.newlineBeforeSemicolon, - ...(normalized.paramTypes !== null ? { paramTypes: normalized.paramTypes } : {}), + paramTypes: { + ...paramTypes, + custom: [...(paramTypes.custom ?? []), ...DBX_CUSTOM_PARAM_TYPES], + }, }; } diff --git a/packages/app-tests/sqlFormatterConfig.test.ts b/packages/app-tests/sqlFormatterConfig.test.ts index cbfd29853..d820a340b 100644 --- a/packages/app-tests/sqlFormatterConfig.test.ts +++ b/packages/app-tests/sqlFormatterConfig.test.ts @@ -249,6 +249,9 @@ test("maps DBX formatter settings to sql-formatter options", () => { linesBetweenQueries: 1, denseOperators: false, newlineBeforeSemicolon: true, + paramTypes: { + custom: [{ regex: String.raw`\$\{[^}]+\}` }, { regex: String.raw`#\{[^}]+\}` }], + }, }, ); @@ -270,7 +273,10 @@ test("maps DBX formatter settings to sql-formatter options", () => { linesBetweenQueries: 1, denseOperators: false, newlineBeforeSemicolon: false, - paramTypes: { positional: true }, + paramTypes: { + positional: true, + custom: [{ regex: String.raw`\$\{[^}]+\}` }, { regex: String.raw`#\{[^}]+\}` }], + }, }, ); });