diff --git a/src/languages/sqlite/sqlite.formatter.ts b/src/languages/sqlite/sqlite.formatter.ts index b2115ea4b..26591c5a0 100644 --- a/src/languages/sqlite/sqlite.formatter.ts +++ b/src/languages/sqlite/sqlite.formatter.ts @@ -90,7 +90,19 @@ export const sqlite: DialectOptions = { ], identTypes: [`""-qq`, '``', '[]'], // https://www.sqlite.org/lang_expr.html#parameters - paramTypes: { positional: true, numbered: ['?'], named: [':', '@', '$'] }, + // Note: the $-prefixed form follows Tcl variable syntax and may include + // one or more "::"-separated suffixes and an optional "(...)" trailer. + paramTypes: { + positional: true, + numbered: ['?'], + named: [':', '@'], + custom: [ + { + regex: String.raw`\$[a-zA-Z_][a-zA-Z0-9_]*(?:::[a-zA-Z_][a-zA-Z0-9_]*)*(?:\([^)]*\))?`, + key: v => v.slice(1), + }, + ], + }, operators: ['%', '~', '&', '|', '<<', '>>', '==', '->', '->>', '||'], }, formatOptions: { diff --git a/test/sqlite.test.ts b/test/sqlite.test.ts index 36057ab35..662e11e85 100644 --- a/test/sqlite.test.ts +++ b/test/sqlite.test.ts @@ -55,7 +55,46 @@ describe('SqliteFormatter', () => { supportsJoin(format); supportsSetOperations(format, ['UNION', 'UNION ALL', 'EXCEPT', 'INTERSECT']); supportsOperators(format, ['%', '~', '&', '|', '<<', '>>', '==', '->', '->>', '||']); - supportsParams(format, { positional: true, numbered: ['?'], named: [':', '$', '@'] }); + supportsParams(format, { positional: true, numbered: ['?'], named: [':', '@'] }); + + // SQLite has its own Tcl-style $-parameter syntax that is handled as a custom + // param type rather than the default named-param prefix. See sqlite.formatter.ts. + describe('supports SQLite $-style (Tcl) named placeholders', () => { + it('recognizes $name placeholders', () => { + expect(format('SELECT $foo, $bar, $baz;')).toBe(dedent` + SELECT + $foo, + $bar, + $baz; + `); + }); + + it('recognizes $name(...) placeholders without inserting a space', () => { + expect(format('select $p(x=y);')).toBe(dedent` + select + $p(x=y); + `); + }); + + it('recognizes $name with :: suffix and (...) trailer', () => { + expect(format('SELECT $foo::bar(extra);')).toBe(dedent` + SELECT + $foo::bar(extra); + `); + }); + + it('replaces $name placeholders with param values', () => { + expect( + format(`WHERE name = $name AND age > $current_age;`, { + params: { name: "'John'", current_age: '10' }, + }) + ).toBe(dedent` + WHERE + name = 'John' + AND age > 10; + `); + }); + }); supportsWindow(format); supportsLimiting(format, { limit: true, offset: true }); supportsDataTypeCase(format);