Repository: novuhq/novu
File: apps/api/src/app/shared/utils/template-parser/new-liquid-parser.ts (TypeScript)

Code before:
      variables.push(...endVars);
    }
  }

  return variables;
}

function extractVariablesFromCondition(condition: string): string[] {
  const variables: string[] = [];

  // First, temporarily replace string literals with placeholders to avoid matching their contents
  let processedCondition = condition;
  const stringLiterals: string[] = [];

  // Replace all string literals (both single and double quoted) with placeholders
  processedCondition = processedCondition.replace(/(["'])(?:(?=(\\?))\2.)*?\1/g, (match) => {
    stringLiterals.push(match);

    return `__STRING_LITERAL_${stringLiterals.length - 1}__`;
  });

  // Strip filter segments to avoid treating filter names as variables
  processedCondition = processedCondition.replace(/\|\s*[a-zA-Z_][a-zA-Z0-9_]*(?:\s*:[^|%}]*)?/g, '');

  // Now match variable patterns from the processed condition
  const variableMatches = processedCondition.match(/[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*(?:\[\d+\])*/g);

  if (variableMatches) {
    variables.push(
      ...variableMatches.filter(
        (variable) =>
          // Filter out common keywords/operators
          ![
            'true',
            'false',
            'null',
            'nil',
            'and',
            'or',
            'not',
            'contains',
            'eq',
            'ne',
            'lt',
            'le',
            'gt',
            'ge',
          ].includes(variable.toLowerCase()) &&
          // Filter out our placeholder patterns
          !variable.startsWith('__STRING_LITERAL_')
      )

Review comment (refactor):
More permissive regex, since we later have a validation of extracted variables against schema, its safer approach to rather extract even potentially invalid ones like "steps.events..[abc]" and then disallow them later as opposed to do complicated regex and risk missing extraction of even valid ones.

Code after:
      variables.push(...endVars);
    }
  }

  return variables;
}

function extractVariablesFromCondition(condition: string): string[] {
  const variables: string[] = [];

  // First, temporarily replace string literals with placeholders to avoid matching their contents
  let processedCondition = condition;
  const stringLiterals: string[] = [];

  // Replace all string literals (both single and double quoted) with placeholders
  processedCondition = processedCondition.replace(/(["'])(?:(?=(\\?))\2.)*?\1/g, (match) => {
    stringLiterals.push(match);

    return `__STRING_LITERAL_${stringLiterals.length - 1}__`;
  });

  // Strip filter segments to avoid treating filter names as variables
  processedCondition = processedCondition.replace(/\|\s*[a-zA-Z_][a-zA-Z0-9_]*(?:\s*:[^|%}]*)?/g, '');

  // Now match variable patterns from the processed condition
  const variableMatches = processedCondition.match(/[a-zA-Z_][a-zA-Z0-9_[\].-]+/g);

  if (variableMatches) {
    variables.push(
      ...variableMatches.filter(
        (variable) =>
          // Filter out common keywords/operators
          ![
            'true',
            'false',
            'null',
            'nil',
            'and',
            'or',
            'not',
            'contains',
            'eq',
            'ne',
            'lt',
            'le',
            'gt',
            'ge',
          ].includes(variable.toLowerCase()) &&
          // Filter out our placeholder patterns
          !variable.startsWith('__STRING_LITERAL_')
      )