Repository: sql-formatter-org/sql-formatter (ts)

Issue:
Allow defining custom quote types
**Describe the Feature**
Allow defining a custom `quote` type for a dialect.


**Why do you want this feature?**
As I explained in #306, I would like to define a custom grammar of Trino SQL queries + `{{Handlebars templates}}` so that users can write (and format with sql-formatter) queries like this

```sql
select
  {{ country_code }}
from
  my_table
```

and we could render the query for other users with a dropdown in place of `{{ country_code }}`. The easiest way to do this is to make make sql-formatter treat `{{}}` like a type of string. In order to do this, we need to have a regex for this type of quote in `quotePatterns`. It makes no sense to add this to sql-formatter itself, so we have to fork it to add this custom quote type. It would be nice if I could pass the regex somehow through `DialectOptions`' `tokenizerOptions.stringTypes` or some other way.

Pull request description:
Add support for custom quote types
Similarly to how variable types can be specified with a regex string, a string (and identifier) can now also be specified the same way.

Fixes #503 

Patch:
diff --git a/src/lexer/TokenizerOptions.ts b/src/lexer/TokenizerOptions.ts
index 8bfc2dd6..46908dea 100644
--- a/src/lexer/TokenizerOptions.ts
+++ b/src/lexer/TokenizerOptions.ts
@@ -22,13 +22,13 @@ export interface PrefixedQuoteType {
   requirePrefix?: boolean; // True when prefix is required
 }
 
-export type QuoteType = PlainQuoteType | PrefixedQuoteType;
-
-export interface VariableRegex {
+export interface RegexPattern {
   regex: string;
 }
 
-export type VariableType = VariableRegex | PrefixedQuoteType;
+export type QuoteType = PlainQuoteType | PrefixedQuoteType | RegexPattern;
+
+export type VariableType = RegexPattern | PrefixedQuoteType;
 
 export interface ParamTypes {
   // True to allow for positional "?" parameter placeholders
diff --git a/src/lexer/regexFactory.ts b/src/lexer/regexFactory.ts
index 3d627ad8..a466e0ce 100644
--- a/src/lexer/regexFactory.ts
+++ b/src/lexer/regexFactory.ts
@@ -127,6 +127,8 @@ export const quotePatterns = {
 const singleQuotePattern = (quoteTypes: QuoteType): string => {
   if (typeof quoteTypes === 'string') {
     return quotePatterns[quoteTypes];
+  } else if ('regex' in quoteTypes) {
+    return quoteTypes.regex;
   } else {
     return prefixesPattern(quoteTypes) + quotePatterns[quoteTypes.quote];
   }


Test patch:
diff --git a/test/sqlFormatter.test.ts b/test/sqlFormatter.test.ts
index dabafa2e..3efd3b06 100644
--- a/test/sqlFormatter.test.ts
+++ b/test/sqlFormatter.test.ts
@@ -1,6 +1,6 @@
 import dedent from 'dedent-js';
 
-import { format, formatDialect, SqlLanguage, sqlite } from '../src/index.js';
+import { format, formatDialect, SqlLanguage, sqlite, DialectOptions } from '../src/index.js';
 
 describe('sqlFormatter', () => {
   it('throws error when unsupported language parameter specified', () => {
@@ -63,5 +63,28 @@ describe('sqlFormatter', () => {
           \`bar\`;
       `);
     });
+
+    it('allows use of regex-based custom string type', () => {
+      // Extend SQLite dialect with additional string type
+      const sqliteWithTemplates: DialectOptions = {
+        tokenizerOptions: {
+          ...sqlite.tokenizerOptions,
+          stringTypes: [...sqlite.tokenizerOptions.stringTypes, { regex: String.raw`\{\{.*?\}\}` }],
+        },
+        formatOptions: sqlite.formatOptions,
+      };
+
+      expect(
+        formatDialect(`SELECT {{template item}}, 'normal string' FROM {{tbl}};`, {
+          dialect: sqliteWithTemplates,
+        })
+      ).toBe(dedent`
+        SELECT
+          {{template item}},
+          'normal string'
+        FROM
+          {{tbl}};
+      `);
+    });
   });
 });