Files
initiative/packages/domain/src/auto-number.ts
Lukas 8efba288f7 Use String.raw and RegExp.exec, add prefer-regexp-exec oxlint rule
Replace escaped backslash in template literal with String.raw in
auto-number.ts. Use RegExp#exec() instead of String#match() in
bestiary-adapter.ts. Enable typescript/prefer-regexp-exec in oxlint
for automated enforcement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 14:55:54 +01:00

58 lines
1.7 KiB
TypeScript

/**
* Resolves a creature name against existing combatant names,
* handling auto-numbering for duplicates.
*
* - No conflict: returns name as-is, no renames.
* - First conflict (one existing match): renames existing to "Name 1",
* new becomes "Name 2".
* - Subsequent conflicts: new gets next number suffix.
*/
export function resolveCreatureName(
baseName: string,
existingNames: readonly string[],
): {
newName: string;
renames: ReadonlyArray<{ from: string; to: string }>;
} {
// Find exact matches and numbered matches (e.g., "Goblin 1", "Goblin 2")
const exactMatches: number[] = [];
let maxNumber = 0;
for (let i = 0; i < existingNames.length; i++) {
const name = existingNames[i];
if (name === baseName) {
exactMatches.push(i);
} else {
const match = new RegExp(
String.raw`^${escapeRegExp(baseName)} (\d+)$`,
).exec(name);
// biome-ignore lint/nursery/noUnnecessaryConditions: RegExp.exec() returns null on no match — false positive
if (match) {
const num = Number.parseInt(match[1], 10);
if (num > maxNumber) maxNumber = num;
}
}
}
// No conflict at all
if (exactMatches.length === 0 && maxNumber === 0) {
return { newName: baseName, renames: [] };
}
// First conflict: one exact match, no numbered ones yet
if (exactMatches.length === 1 && maxNumber === 0) {
return {
newName: `${baseName} 2`,
renames: [{ from: baseName, to: `${baseName} 1` }],
};
}
// Subsequent conflicts: append next number
const nextNumber = Math.max(maxNumber, exactMatches.length) + 1;
return { newName: `${baseName} ${nextNumber}`, renames: [] };
}
function escapeRegExp(s: string): string {
return s.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
}