src/optimizer/transforms/combine-repeating-patterns-transform.js
function combineRepeatingPatternLeft(alternative, child, index) {
  const {node} = alternative;

  const nbPossibleLengths = Math.ceil(index / 2);
  let i = 0;

  while (i < nbPossibleLengths) {
    const startIndex = index - 2 * i - 1;
    let right, left;

    if (i === 0) {
      right = child;
      left = alternative.getChild(startIndex);
    } else {
      right = NodePath.getForNode({
        type: 'Alternative',
        expressions: [...node.expressions.slice(index - i, index), child.node]
      });

      left = NodePath.getForNode({
        type: 'Alternative',
        expressions: [...node.expressions.slice(startIndex, index - i)]
      });
    }

    if (right.hasEqualSource(left)) {
      for (let j = 0; j < 2 * i + 1; j++) {
        alternative.getChild(startIndex).remove();
      }

      child.replace({
        type: 'Repetition',
        expression: i === 0 ? right.node : {
          type: 'Group',
          capturing: false,
          expression: right.node
        },
        quantifier: {
          type: 'Quantifier',
          kind: 'Range',
          from: 2,
          to: 2,
          greedy: true
        }
      });
      return startIndex;
    }

    i++;
  }

  return index;
}
function combineRepeatingPatternLeft(alternative, child, index) {
  const {node} = alternative;

  const nbPossibleLengths = Math.ceil(index / 2);
  let i = 0;

  while (i < nbPossibleLengths) {
    const startIndex = index - 2 * i - 1;
    let right, left;

    if (i === 0) {
      right = child;
      left = alternative.getChild(startIndex);
    } else {
      right = NodePath.getForNode({
        type: 'Alternative',
        expressions: [...node.expressions.slice(index - i, index), child.node]
      });

      left = NodePath.getForNode({
        type: 'Alternative',
        expressions: [...node.expressions.slice(startIndex, index - i)]
      });
    }

    if (right.hasEqualSource(left)) {
      for (let j = 0; j < 2 * i + 1; j++) {
        alternative.getChild(startIndex).remove();
      }

      child.replace({
        type: 'Repetition',
        expression: i === 0 ? right.node : {
          type: 'Group',
          capturing: false,
          expression: right.node
        },
        quantifier: {
          type: 'Quantifier',
          kind: 'Range',
          from: 2,
          to: 2,
          greedy: true
        }
      });
      return startIndex;
    }

    i++;
  }

  return index;
}
abcabc -> (?:abc){2}