Macros 2.0 [Fix] - Make macro name matching case-insensitive throughout the macro system (#4942)
* Make macro name matching case-insensitive throughout the macro system - Normalize macro names and aliases to lowercase in MacroRegistry storage and lookup - Update MacroCstWalker to use case-insensitive matching for block macro pairing - Normalize dynamic macro keys to lowercase in MacroEnvBuilder - Update MacroEngine to use lowercase keys when checking dynamic macros - Preserve original casing in macro definitions for display purposes - Add comments explaining case-insensitive matching behavior * Make alias validation case-insensitive in MacroRegistry to prevent duplicate names - Prevents registering aliases that differ only in casing from the macro name
This commit is contained in:
@@ -157,8 +157,8 @@ class MacroCstWalker {
|
||||
if (!info) continue;
|
||||
|
||||
if (info.isClosing) {
|
||||
// Closing tag - pop matching opener from stack
|
||||
if (unclosedStack.length > 0 && unclosedStack[unclosedStack.length - 1].name === info.name) {
|
||||
// Closing tag - pop matching opener from stack (case-insensitive match)
|
||||
if (unclosedStack.length > 0 && unclosedStack[unclosedStack.length - 1].name.toLowerCase() === info.name.toLowerCase()) {
|
||||
unclosedStack.pop();
|
||||
}
|
||||
// If no matching opener, ignore (orphan closing tag)
|
||||
@@ -1015,8 +1015,8 @@ class MacroCstWalker {
|
||||
for (let i = openingIdx + 1; i < macroInfos.length; i++) {
|
||||
const info = macroInfos[i];
|
||||
|
||||
// Only consider macros with the same name
|
||||
if (info.name !== targetName) continue;
|
||||
// Only consider macros with the same name (case-insensitive)
|
||||
if (info.name.toLowerCase() !== targetName.toLowerCase()) continue;
|
||||
|
||||
// Skip already matched macros
|
||||
if (info.matched) continue;
|
||||
|
||||
@@ -165,10 +165,12 @@ class MacroEngine {
|
||||
if (!name) return raw;
|
||||
|
||||
// First check if this is a dynamic macro to use. If so, we will create a temporary macro definition for it and use that over any registered macro.
|
||||
// Dynamic macro keys are normalized to lowercase for case-insensitive matching.
|
||||
/** @type {MacroDefinition?} */
|
||||
let defOverride = null;
|
||||
if (Object.hasOwn(env.dynamicMacros, name)) {
|
||||
const impl = env.dynamicMacros[name];
|
||||
const nameLower = name.toLowerCase();
|
||||
if (Object.hasOwn(env.dynamicMacros, nameLower)) {
|
||||
const impl = env.dynamicMacros[nameLower];
|
||||
defOverride = {
|
||||
name,
|
||||
aliases: [],
|
||||
|
||||
@@ -143,8 +143,11 @@ class MacroEnvBuilder {
|
||||
env.functions.postProcess = typeof ctx.postProcessFn === 'function' ? ctx.postProcessFn : (x) => x;
|
||||
|
||||
// Dynamic, per-call macros that should be visible only for this evaluation run.
|
||||
// Keys are normalized to lowercase for case-insensitive matching.
|
||||
if (ctx.dynamicMacros && typeof ctx.dynamicMacros === 'object') {
|
||||
env.dynamicMacros = { ...ctx.dynamicMacros };
|
||||
for (const [key, value] of Object.entries(ctx.dynamicMacros)) {
|
||||
env.dynamicMacros[key.toLowerCase()] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Let providers augment the env, if any are registered. Apply them in order,
|
||||
|
||||
@@ -217,7 +217,7 @@ class MacroRegistry {
|
||||
if (typeof aliasDef.alias !== 'string' || !aliasDef.alias.trim()) throw new Error(`Macro "${name}" options.aliases[${i}].alias must be a non-empty string.`);
|
||||
const aliasName = aliasDef.alias.trim();
|
||||
if (!isIdentifierValid(aliasName)) throw new Error(`Macro "${name}" options.aliases[${i}].alias "${aliasName}" is invalid. Must start with a letter, followed by word chars or hyphens.`);
|
||||
if (aliasName === name) throw new Error(`Macro "${name}" options.aliases[${i}].alias cannot be the same as the macro name.`);
|
||||
if (aliasName.toLowerCase() === name.toLowerCase()) throw new Error(`Macro "${name}" options.aliases[${i}].alias cannot be the same as the macro name (insensitive).`);
|
||||
const visible = aliasDef.visible !== false; // Default to true
|
||||
aliases.push({ alias: aliasName, visible });
|
||||
}
|
||||
@@ -358,7 +358,8 @@ class MacroRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.#macros.has(name)) {
|
||||
const nameKey = name.toLowerCase();
|
||||
if (this.#macros.has(nameKey)) {
|
||||
logMacroRegisterWarning({ macroName: name, message: `Macro "${name}" is already registered and will be overwritten.` });
|
||||
}
|
||||
|
||||
@@ -390,21 +391,22 @@ class MacroRegistry {
|
||||
aliasVisible: null,
|
||||
};
|
||||
|
||||
this.#macros.set(name, definition);
|
||||
this.#macros.set(nameKey, definition);
|
||||
|
||||
// Register alias entries pointing to the same definition
|
||||
for (const { alias, visible } of aliases) {
|
||||
if (this.#macros.has(alias)) {
|
||||
const aliasKey = alias.toLowerCase();
|
||||
if (this.#macros.has(aliasKey)) {
|
||||
logMacroRegisterWarning({ macroName: name, message: `Alias "${alias}" for macro "${name}" overwrites an existing macro.` });
|
||||
}
|
||||
/** @type {MacroDefinition} */
|
||||
const aliasEntry = {
|
||||
...definition,
|
||||
name: alias, // The lookup name is the alias
|
||||
name: alias, // The lookup name is the alias (preserves original casing for display)
|
||||
aliasOf: name,
|
||||
aliasVisible: visible,
|
||||
};
|
||||
this.#macros.set(alias, aliasEntry);
|
||||
this.#macros.set(aliasKey, aliasEntry);
|
||||
}
|
||||
|
||||
return definition;
|
||||
@@ -427,7 +429,7 @@ class MacroRegistry {
|
||||
unregisterMacro(name) {
|
||||
if (typeof name !== 'string' || !name.trim()) throw new Error('Macro name must be a non-empty string');
|
||||
name = name.trim();
|
||||
return this.#macros.delete(name);
|
||||
return this.#macros.delete(name.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,7 +441,7 @@ class MacroRegistry {
|
||||
hasMacro(name) {
|
||||
if (typeof name !== 'string' || !name.trim()) return false;
|
||||
name = name.trim();
|
||||
return this.#macros.has(name);
|
||||
return this.#macros.has(name.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,7 +453,7 @@ class MacroRegistry {
|
||||
getMacro(name) {
|
||||
if (typeof name !== 'string' || !name.trim()) return undefined;
|
||||
name = name.trim();
|
||||
return this.#macros.get(name);
|
||||
return this.#macros.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user