Skip to content

Commit af705ae

Browse files
refactor: unify reserved words and move Deparser.needsQuotes to QuoteUtils
- Remove Deparser.RESERVED_WORDS and Deparser.needsQuotes from deparser.ts - Add QuoteUtils.needsQuotesForString() and QuoteUtils.quoteString() methods - Update all call sites to use QuoteUtils instead of Deparser methods - Both QuoteUtils.needsQuotes and QuoteUtils.needsQuotesForString now use RESERVED_KEYWORDS from kwlist.ts as the single source of truth - Add Set<string> type annotations to kwlist.ts exports for TypeScript compatibility Co-Authored-By: Dan Lynch <[email protected]>
1 parent 5b34b15 commit af705ae

File tree

3 files changed

+42
-57
lines changed

3 files changed

+42
-57
lines changed

packages/deparser/src/deparser.ts

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2457,36 +2457,8 @@ export class Deparser implements DeparserVisitor {
24572457
return output.join(' ');
24582458
}
24592459

2460-
private static readonly RESERVED_WORDS = new Set([
2461-
'all', 'analyse', 'analyze', 'and', 'any', 'array', 'as', 'asc', 'asymmetric', 'both',
2462-
'case', 'cast', 'check', 'collate', 'column', 'constraint', 'create', 'current_catalog',
2463-
'current_date', 'current_role', 'current_time', 'current_timestamp', 'current_user',
2464-
'default', 'deferrable', 'desc', 'distinct', 'do', 'else', 'end', 'except', 'false',
2465-
'fetch', 'for', 'foreign', 'from', 'grant', 'group', 'having', 'in', 'initially',
2466-
'intersect', 'into', 'lateral', 'leading', 'limit', 'localtime', 'localtimestamp',
2467-
'not', 'null', 'offset', 'on', 'only', 'or', 'order', 'placing', 'primary',
2468-
'references', 'returning', 'select', 'session_user', 'some', 'symmetric', 'table',
2469-
'then', 'to', 'trailing', 'true', 'union', 'unique', 'user', 'using', 'variadic',
2470-
'when', 'where', 'window', 'with'
2471-
]);
2472-
2473-
private static needsQuotes(value: string): boolean {
2474-
if (!value) return false;
2475-
2476-
const needsQuotesRegex = /[a-z]+[\W\w]*[A-Z]+|[A-Z]+[\W\w]*[a-z]+|\W/;
2477-
2478-
const isAllUppercase = /^[A-Z]+$/.test(value);
2479-
2480-
return needsQuotesRegex.test(value) ||
2481-
Deparser.RESERVED_WORDS.has(value.toLowerCase()) ||
2482-
isAllUppercase;
2483-
}
2484-
24852460
quoteIfNeeded(value: string): string {
2486-
if (Deparser.needsQuotes(value)) {
2487-
return `"${value}"`;
2488-
}
2489-
return value;
2461+
return QuoteUtils.quoteString(value);
24902462
}
24912463

24922464
preserveOperatorDefElemCase(defName: string): string {
@@ -2528,7 +2500,7 @@ export class Deparser implements DeparserVisitor {
25282500
}
25292501
}
25302502

2531-
return Deparser.needsQuotes(value) ? `"${value}"` : value;
2503+
return QuoteUtils.quoteString(value);
25322504
}
25332505

25342506
Integer(node: t.Integer, context: DeparserContext): string {
@@ -5715,7 +5687,7 @@ export class Deparser implements DeparserVisitor {
57155687
}
57165688

57175689
if (node.role) {
5718-
const roleName = Deparser.needsQuotes(node.role) ? `"${node.role}"` : node.role;
5690+
const roleName = QuoteUtils.quoteString(node.role);
57195691
output.push(roleName);
57205692
}
57215693

@@ -5788,7 +5760,7 @@ export class Deparser implements DeparserVisitor {
57885760
? `'${argValue}'`
57895761
: argValue;
57905762

5791-
const quotedDefname = node.defname.includes(' ') || node.defname.includes('-') || Deparser.needsQuotes(node.defname)
5763+
const quotedDefname = node.defname.includes(' ') || node.defname.includes('-') || QuoteUtils.needsQuotesForString(node.defname)
57925764
? `"${node.defname}"`
57935765
: node.defname;
57945766

@@ -5968,7 +5940,7 @@ export class Deparser implements DeparserVisitor {
59685940
if (this.getNodeType(item) === 'String') {
59695941
// Check if this identifier needs quotes to preserve case
59705942
const value = itemData.sval;
5971-
if (Deparser.needsQuotes(value)) {
5943+
if (QuoteUtils.needsQuotesForString(value)) {
59725944
return `"${value}"`;
59735945
}
59745946
return value;
@@ -6245,7 +6217,7 @@ export class Deparser implements DeparserVisitor {
62456217
}
62466218

62476219
// Handle CREATE AGGREGATE quoted identifiers - preserve quotes when needed
6248-
if (Deparser.needsQuotes(node.defname)) {
6220+
if (QuoteUtils.needsQuotesForString(node.defname)) {
62496221
const quotedDefname = `"${node.defname}"`;
62506222
if (node.arg) {
62516223
if (this.getNodeType(node.arg) === 'String') {
@@ -9848,7 +9820,7 @@ export class Deparser implements DeparserVisitor {
98489820

98499821
if (defName && defValue) {
98509822
let preservedDefName;
9851-
if (Deparser.needsQuotes(defName)) {
9823+
if (QuoteUtils.needsQuotesForString(defName)) {
98529824
preservedDefName = `"${defName}"`;
98539825
} else {
98549826
preservedDefName = this.preserveOperatorDefElemCase(defName);
@@ -10012,7 +9984,7 @@ export class Deparser implements DeparserVisitor {
100129984

100139985
if (defName && defValue) {
100149986
let preservedDefName;
10015-
if (Deparser.needsQuotes(defName)) {
9987+
if (QuoteUtils.needsQuotesForString(defName)) {
100169988
preservedDefName = `"${defName}"`;
100179989
} else {
100189990
preservedDefName = defName;

packages/deparser/src/kwlist.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -522,10 +522,10 @@ export const kwlist = {
522522
]
523523
} as const;
524524

525-
export const RESERVED_KEYWORDS = new Set(kwlist.RESERVED_KEYWORD ?? []);
526-
export const UNRESERVED_KEYWORDS = new Set(kwlist.UNRESERVED_KEYWORD ?? []);
527-
export const COL_NAME_KEYWORDS = new Set(kwlist.COL_NAME_KEYWORD ?? []);
528-
export const TYPE_FUNC_NAME_KEYWORDS = new Set(kwlist.TYPE_FUNC_NAME_KEYWORD ?? []);
525+
export const RESERVED_KEYWORDS: Set<string> = new Set(kwlist.RESERVED_KEYWORD ?? []);
526+
export const UNRESERVED_KEYWORDS: Set<string> = new Set(kwlist.UNRESERVED_KEYWORD ?? []);
527+
export const COL_NAME_KEYWORDS: Set<string> = new Set(kwlist.COL_NAME_KEYWORD ?? []);
528+
export const TYPE_FUNC_NAME_KEYWORDS: Set<string> = new Set(kwlist.TYPE_FUNC_NAME_KEYWORD ?? []);
529529

530530
export function keywordKindOf(word: string): KeywordKind {
531531
const w = word.toLowerCase();

packages/deparser/src/utils/quote-utils.ts

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,41 @@
1-
const RESERVED_WORDS = new Set([
2-
'all', 'analyse', 'analyze', 'and', 'any', 'array', 'as', 'asc', 'asymmetric',
3-
'authorization', 'binary', 'both', 'case', 'cast', 'check', 'collate', 'collation',
4-
'column', 'concurrently', 'constraint', 'create', 'cross', 'current_catalog',
5-
'current_date', 'current_role', 'current_schema', 'current_time', 'current_timestamp',
6-
'current_user', 'default', 'deferrable', 'desc', 'distinct', 'do', 'else', 'end',
7-
'except', 'false', 'fetch', 'for', 'foreign', 'freeze', 'from', 'full', 'grant',
8-
'group', 'having', 'ilike', 'in', 'initially', 'inner', 'intersect', 'into', 'is',
9-
'isnull', 'join', 'lateral', 'leading', 'left', 'like', 'limit', 'localtime',
10-
'localtimestamp', 'natural', 'not', 'notnull', 'null', 'offset', 'on', 'only',
11-
'or', 'order', 'outer', 'overlaps', 'placing', 'primary', 'references', 'returning',
12-
'right', 'select', 'session_user', 'similar', 'some', 'symmetric', 'table', 'tablesample',
13-
'then', 'to', 'trailing', 'true', 'union', 'unique', 'user', 'using', 'variadic',
14-
'verbose', 'when', 'where', 'window', 'with'
15-
]);
1+
import { RESERVED_KEYWORDS, TYPE_FUNC_NAME_KEYWORDS } from '../kwlist';
162

173
export class QuoteUtils {
4+
/**
5+
* Checks if a value needs quoting for use in String nodes, DefElem, role names, etc.
6+
* Uses a different algorithm than needsQuotes - checks for mixed case and special characters.
7+
* This was previously Deparser.needsQuotes.
8+
*/
9+
static needsQuotesForString(value: string): boolean {
10+
if (!value) return false;
11+
12+
const needsQuotesRegex = /[a-z]+[\W\w]*[A-Z]+|[A-Z]+[\W\w]*[a-z]+|\W/;
13+
const isAllUppercase = /^[A-Z]+$/.test(value);
14+
15+
return needsQuotesRegex.test(value) ||
16+
RESERVED_KEYWORDS.has(value.toLowerCase()) ||
17+
isAllUppercase;
18+
}
19+
20+
/**
21+
* Quotes a string value if it needs quoting for String nodes.
22+
* Uses needsQuotesForString logic.
23+
*/
24+
static quoteString(value: string): string {
25+
if (QuoteUtils.needsQuotesForString(value)) {
26+
return `"${value}"`;
27+
}
28+
return value;
29+
}
30+
1831
static needsQuotes(value: string): boolean {
1932
if (!value || typeof value !== 'string') {
2033
return false;
2134
}
2235

2336
const lowerValue = value.toLowerCase();
2437

25-
if (RESERVED_WORDS.has(lowerValue)) {
38+
if (RESERVED_KEYWORDS.has(lowerValue) || TYPE_FUNC_NAME_KEYWORDS.has(lowerValue)) {
2639
return true;
2740
}
2841

@@ -93,4 +106,4 @@ export class QuoteUtils {
93106
return !/^\\x[0-9a-fA-F]+$/i.test(value) && value.includes('\\');
94107
}
95108

96-
}
109+
}

0 commit comments

Comments
 (0)