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

Issue:
[FORMATTING] postgresql timestamp datatype on create table adds unnecessary newline
**Input data**
Which SQL and options did you provide as input?

```sql
create table test (
	a int,
	b int,
	c TIMESTAMP with time zone,
	d int
);
```

**Expected Output**

```sql
CREATE    TABLE test (
                    a INT,
                    b INT,
                    c TIMESTAMP WITH TIME ZONE,
                    d INT
                    );
```

**Actual Output**

```sql
CREATE    TABLE test (
                    a INT,
                    b INT,
                    c TIMESTAMP
                    WITH      TIME ZONE,
                              d INT
                    );
```

**Usage**

- How are you calling / using the library? By coc-sql
- What SQL language(s) does this apply to? postgresql
- Which SQL Formatter version are you using? latest with coc-sql




Pull request description:
Fix TIMESTAMP WITH TIME ZONE formatting
Changed the priority of RESERVED_PHRASE token type. Ended up not actually needing it, but it's something that will likely be needed in the future for solving similar issues.

Fixes #391 

Patch:
diff --git a/src/languages/postgresql/postgresql.formatter.ts b/src/languages/postgresql/postgresql.formatter.ts
index b77583eb..dc6fe7b7 100644
--- a/src/languages/postgresql/postgresql.formatter.ts
+++ b/src/languages/postgresql/postgresql.formatter.ts
@@ -241,6 +241,8 @@ const reservedPhrases = expandPhrases([
   'ON DELETE',
   'ON UPDATE',
   '{ROWS | RANGE | GROUPS} BETWEEN',
+  // https://www.postgresql.org/docs/current/datatype-datetime.html
+  '{TIMESTAMP | TIME} {WITH | WITHOUT} TIME ZONE',
 ]);
 
 const binaryOperators = [
diff --git a/src/lexer/Tokenizer.ts b/src/lexer/Tokenizer.ts
index c17cc6e8..be01c773 100644
--- a/src/lexer/Tokenizer.ts
+++ b/src/lexer/Tokenizer.ts
@@ -1,7 +1,7 @@
 import { Token, TokenType } from 'src/lexer/token';
 import * as regex from 'src/lexer/regexFactory';
 import { ParamTypes, TokenizerOptions } from 'src/lexer/TokenizerOptions';
-import TokenizerEngine, { type TokenRule } from 'src/lexer/TokenizerEngine';
+import TokenizerEngine, { TokenRule } from 'src/lexer/TokenizerEngine';
 import { escapeRegExp } from 'src/lexer/regexUtil';
 import { equalizeWhitespace, Optional } from 'src/utils';
 
@@ -47,6 +47,13 @@ export default class Tokenizer {
         regex:
           /(?:0x[0-9a-fA-F]+|0b[01]+|(?:-\s*)?[0-9]+(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+(?:\.[0-9]+)?)?)(?!\w)/uy,
       },
+      // RESERVED_PHRASE is matched before all other keyword tokens
+      // to e.g. prioritize matching "TIMESTAMP WITH TIME ZONE" phrase over "WITH" command.
+      {
+        type: TokenType.RESERVED_PHRASE,
+        regex: regex.reservedWord(cfg.reservedPhrases ?? [], cfg.identChars),
+        text: toCanonical,
+      },
       {
         type: TokenType.CASE,
         regex: /CASE\b/iuy,
@@ -92,11 +99,6 @@ export default class Tokenizer {
         regex: regex.reservedWord(cfg.reservedJoins, cfg.identChars),
         text: toCanonical,
       },
-      {
-        type: TokenType.RESERVED_PHRASE,
-        regex: regex.reservedWord(cfg.reservedPhrases ?? [], cfg.identChars),
-        text: toCanonical,
-      },
       {
         type: TokenType.AND,
         regex: /AND\b/iuy,
diff --git a/src/lexer/TokenizerOptions.ts b/src/lexer/TokenizerOptions.ts
index 2cce0e73..3948f398 100644
--- a/src/lexer/TokenizerOptions.ts
+++ b/src/lexer/TokenizerOptions.ts
@@ -57,7 +57,7 @@ export interface TokenizerOptions {
   // Various joins like LEFT OUTER JOIN
   reservedJoins: string[];
   // These are essentially multi-word sequences of keywords,
-  // that we prioritize over normal keywords
+  // that we prioritize over all other keywords (RESERVED_* tokens)
   reservedPhrases?: string[];
   // built in function names
   reservedFunctionNames: string[];


Test patch:
diff --git a/test/postgresql.test.ts b/test/postgresql.test.ts
index bfddae57..4302bdba 100644
--- a/test/postgresql.test.ts
+++ b/test/postgresql.test.ts
@@ -117,6 +117,22 @@ describe('PostgreSqlFormatter', () => {
     `);
   });
 
+  // Regression test for issue #391
+  it('formats TIMESTAMP WITH TIME ZONE syntax', () => {
+    expect(
+      format(
+        'CREATE TABLE time_table (id INT, created_at TIMESTAMP WITH TIME ZONE, deleted_at TIME WITH TIME ZONE);'
+      )
+    ).toBe(dedent`
+      CREATE TABLE
+        time_table (
+          id INT,
+          created_at TIMESTAMP WITH TIME ZONE,
+          deleted_at TIME WITH TIME ZONE
+        );
+    `);
+  });
+
   it('formats ALTER TABLE ... ALTER COLUMN', () => {
     expect(
       format(