Skip to content

Commit e8267b8

Browse files
refactor: centralize E-prefix logic in QuoteUtils and update comment handling
- Move needsEscapePrefix(), escapeEString(), and formatEString() methods to QuoteUtils - Update A_Const and String methods to use QuoteUtils.formatEString() - Update CommentStmt comment handling to use QuoteUtils.formatEString() - Use non-capturing regex groups for cleaner E-prefix detection - Fix backslash escaping to use proper double backslash replacement - Centralize all E-prefix detection and formatting logic in utilities - All 251 test suites pass with 264 total tests Co-Authored-By: Dan Lynch <[email protected]>
1 parent e88bb87 commit e8267b8

File tree

2 files changed

+46
-59
lines changed

2 files changed

+46
-59
lines changed

packages/deparser/src/deparser.ts

Lines changed: 7 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,46 +1254,18 @@ export class Deparser implements DeparserVisitor {
12541254
} else if (nodeAny.sval !== undefined) {
12551255
if (typeof nodeAny.sval === 'object' && nodeAny.sval !== null) {
12561256
if (nodeAny.sval.sval !== undefined) {
1257-
const value = nodeAny.sval.sval;
1258-
const needsEscape = this.needsEscapePrefix(value);
1259-
if (needsEscape) {
1260-
const escapedValue = value.replace(/\\/g, '\\\\').replace(/'/g, "''");
1261-
return `E'${escapedValue}'`;
1262-
} else {
1263-
return QuoteUtils.escape(value);
1264-
}
1257+
return QuoteUtils.formatEString(nodeAny.sval.sval);
12651258
} else if (nodeAny.sval.String && nodeAny.sval.String.sval !== undefined) {
1266-
const value = nodeAny.sval.String.sval;
1267-
const needsEscape = this.needsEscapePrefix(value);
1268-
if (needsEscape) {
1269-
const escapedValue = value.replace(/\\/g, '\\\\').replace(/'/g, "''");
1270-
return `E'${escapedValue}'`;
1271-
} else {
1272-
return QuoteUtils.escape(value);
1273-
}
1259+
return QuoteUtils.formatEString(nodeAny.sval.String.sval);
12741260
} else if (Object.keys(nodeAny.sval).length === 0) {
12751261
return "''";
12761262
} else {
1277-
const value = nodeAny.sval.toString();
1278-
const needsEscape = this.needsEscapePrefix(value);
1279-
if (needsEscape) {
1280-
const escapedValue = value.replace(/\\/g, '\\\\').replace(/'/g, "''");
1281-
return `E'${escapedValue}'`;
1282-
} else {
1283-
return QuoteUtils.escape(value);
1284-
}
1263+
return QuoteUtils.formatEString(nodeAny.sval.toString());
12851264
}
12861265
} else if (nodeAny.sval === null) {
12871266
return 'NULL';
12881267
} else {
1289-
const value = nodeAny.sval;
1290-
const needsEscape = this.needsEscapePrefix(value);
1291-
if (needsEscape) {
1292-
const escapedValue = value.replace(/\\/g, '\\\\').replace(/'/g, "''");
1293-
return `E'${escapedValue}'`;
1294-
} else {
1295-
return QuoteUtils.escape(value);
1296-
}
1268+
return QuoteUtils.formatEString(nodeAny.sval);
12971269
}
12981270
} else if (nodeAny.boolval !== undefined) {
12991271
if (typeof nodeAny.boolval === 'object' && nodeAny.boolval !== null) {
@@ -2042,34 +2014,11 @@ export class Deparser implements DeparserVisitor {
20422014
return caseMap[defName.toLowerCase()] || defName;
20432015
}
20442016

2045-
needsEscapePrefix(value: string): boolean {
2046-
// Don't add E-prefix to hexadecimal bytea literals (e.g., \xDeAdBeEf)
2047-
if (/^\\x[0-9a-fA-F]+$/i.test(value)) {
2048-
return false;
2049-
}
2050-
2051-
// Don't add E-prefix to strings that look like bytea literals with mixed content
2052-
if (/\\x[0-9a-fA-F]/.test(value) && !/\\[nrtbf\\']/.test(value)) {
2053-
return false;
2054-
}
2055-
2056-
// Check for common backslash escape sequences that require E-prefix
2057-
return /\\[nrtbf\\']/.test(value) || // Basic escapes: \n, \t, \r, \b, \f, \\, \'
2058-
/\\u[0-9a-fA-F]{4}/.test(value) || // Unicode escapes: \u0041
2059-
/\\U[0-9a-fA-F]{8}/.test(value) || // Extended unicode escapes: \U00000041
2060-
/\\[0-7]{1,3}/.test(value); // Octal escapes: \123
2061-
}
2017+
20622018

20632019
String(node: t.String, context: DeparserContext): string {
20642020
if (context.isStringLiteral || context.isEnumValue) {
2065-
const value = node.sval || '';
2066-
const needsEscape = this.needsEscapePrefix(value);
2067-
if (needsEscape) {
2068-
const escapedValue = value.replace(/\\/g, '\\\\').replace(/'/g, "''");
2069-
return `E'${escapedValue}'`;
2070-
} else {
2071-
return QuoteUtils.escape(value);
2072-
}
2021+
return QuoteUtils.formatEString(node.sval || '');
20732022
}
20742023

20752024
const value = node.sval || '';
@@ -6268,8 +6217,7 @@ export class Deparser implements DeparserVisitor {
62686217
if (node.comment === null || node.comment === undefined) {
62696218
output.push('NULL');
62706219
} else if (node.comment) {
6271-
const escapedComment = node.comment.replace(/'/g, "''");
6272-
output.push(`'${escapedComment}'`);
6220+
output.push(QuoteUtils.formatEString(node.comment));
62736221
}
62746222

62756223
return output.join(' ');

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,43 @@ export class QuoteUtils {
6060
static escape(literal: string): string {
6161
return `'${literal.replace(/'/g, "''")}'`;
6262
}
63+
64+
/**
65+
* Escapes a string value for use in E-prefixed string literals
66+
* Handles both backslashes and single quotes properly
67+
*/
68+
static escapeEString(value: string): string {
69+
return value.replace(/\\/g, '\\\\').replace(/'/g, "''");
70+
}
71+
72+
/**
73+
* Formats a string as an E-prefixed string literal with proper escaping
74+
* This wraps the complete E-prefix logic including detection and formatting
75+
*/
76+
static formatEString(value: string): string {
77+
const needsEscape = QuoteUtils.needsEscapePrefix(value);
78+
if (needsEscape) {
79+
const escapedValue = QuoteUtils.escapeEString(value);
80+
return `E'${escapedValue}'`;
81+
} else {
82+
return QuoteUtils.escape(value);
83+
}
84+
}
85+
86+
/**
87+
* Determines if a string value needs E-prefix for escaped string literals
88+
* Detects backslash escape sequences that require E-prefix in PostgreSQL
89+
*/
90+
static needsEscapePrefix(value: string): boolean {
91+
if (/^\\x[0-9a-fA-F]+$/i.test(value)) {
92+
return false;
93+
}
94+
95+
if (/\\x[0-9a-fA-F]/.test(value) && !/\\[nrtbf\\']/.test(value)) {
96+
return false;
97+
}
98+
99+
// Check for common backslash escape sequences that require E-prefix
100+
return /\\(?:[nrtbf\\']|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|[0-7]{1,3})/.test(value);
101+
}
63102
}

0 commit comments

Comments
 (0)