Skip to content

Commit 0dcdcc3

Browse files
committed
refactor: improve text sanitization and alias generation in ER diagram builder
1 parent d2d09a8 commit 0dcdcc3

1 file changed

Lines changed: 39 additions & 9 deletions

File tree

backend/src/entities/connection/utils/build-mermaid-er-diagram.util.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,17 @@ export function buildMermaidErDiagram(
3232
const fkColumnNames = new Set(table.foreignKeys.map((fk) => fk.column_name));
3333

3434
const aliasDiffersFromOriginal = alias !== table.tableName;
35-
const header = aliasDiffersFromOriginal ? ` ${alias}["${escapeQuotes(table.tableName)}"] {` : ` ${alias} {`;
35+
const header = aliasDiffersFromOriginal
36+
? ` ${alias}["${sanitizeQuotedText(table.tableName)}"] {`
37+
: ` ${alias} {`;
3638
lines.push(header);
3739

3840
if (table.structure.length === 0) {
3941
lines.push(' string _empty_ "no columns"');
4042
} else {
4143
for (const column of table.structure) {
42-
const dataType = sanitizeIdentifier(column.data_type || column.udt_name || 'unknown');
43-
const colName = sanitizeIdentifier(column.column_name);
44+
const dataType = toAttributeWord(column.data_type || column.udt_name || 'unknown');
45+
const colName = toAttributeWord(column.column_name);
4446
const markers: Array<string> = [];
4547
if (pkColumnNames.has(column.column_name)) markers.push('PK');
4648
if (fkColumnNames.has(column.column_name)) markers.push('FK');
@@ -58,7 +60,7 @@ export function buildMermaidErDiagram(
5860
for (const fk of table.foreignKeys) {
5961
const targetAlias = aliasByTable.get(fk.referenced_table_name);
6062
if (!targetAlias) continue;
61-
const label = `"${escapeQuotes(fk.column_name)} -> ${escapeQuotes(fk.referenced_column_name)}"`;
63+
const label = `"${sanitizeQuotedText(fk.column_name)} -> ${sanitizeQuotedText(fk.referenced_column_name)}"`;
6264
lines.push(` ${sourceAlias} }o--|| ${targetAlias} : ${label}`);
6365
relationshipCount++;
6466
}
@@ -110,12 +112,24 @@ function buildColumnComment(column: TableStructureDS): string {
110112
parts.push(`max length: ${column.character_maximum_length}`);
111113
}
112114
const text = parts.join('; ');
113-
return text ? `"${escapeQuotes(text)}"` : '';
115+
return text ? `"${sanitizeQuotedText(text)}"` : '';
114116
}
115117

118+
const MERMAID_ENTITY_RESERVED_WORDS = new Set<string>([
119+
'erDiagram',
120+
'style',
121+
'class',
122+
'classDef',
123+
'one',
124+
'many',
125+
'to',
126+
'zero',
127+
]);
128+
129+
const MERMAID_ATTRIBUTE_KEY_WORDS = new Set<string>(['PK', 'FK', 'UK']);
130+
116131
function makeUniqueAlias(name: string, used: Set<string>): string {
117-
let base = sanitizeIdentifier(name);
118-
if (base.length === 0 || /^[0-9]/.test(base)) base = `t_${base}`;
132+
const base = toEntityAlias(name);
119133
let candidate = base;
120134
let suffix = 1;
121135
while (used.has(candidate)) {
@@ -125,10 +139,26 @@ function makeUniqueAlias(name: string, used: Set<string>): string {
125139
return candidate;
126140
}
127141

142+
function toEntityAlias(value: string): string {
143+
const sanitized = sanitizeIdentifier(value);
144+
if (sanitized.length === 0 || /^[0-9]/.test(sanitized) || MERMAID_ENTITY_RESERVED_WORDS.has(sanitized)) {
145+
return `t_${sanitized}`;
146+
}
147+
return sanitized;
148+
}
149+
150+
function toAttributeWord(value: string): string {
151+
const sanitized = sanitizeIdentifier(value);
152+
if (sanitized.length === 0 || /^[0-9]/.test(sanitized) || MERMAID_ATTRIBUTE_KEY_WORDS.has(sanitized)) {
153+
return `_${sanitized}`;
154+
}
155+
return sanitized;
156+
}
157+
128158
function sanitizeIdentifier(value: string): string {
129159
return value.replace(/[^A-Za-z0-9_]/g, '_');
130160
}
131161

132-
function escapeQuotes(value: string): string {
133-
return value.replace(/"/g, "'");
162+
function sanitizeQuotedText(value: string): string {
163+
return value.replace(/"/g, "'").replace(/[\r\n\t]+/g, ' ');
134164
}

0 commit comments

Comments
 (0)