'use strict'; class CleanPasswords { constructor({ substr, limit, trieRoot }) { this.buffer = []; this.finalPasswords = []; this.substr = substr; this.limit = limit; this.trieRoot = trieRoot; } getAllPossibleSubsAtIndex(index) { const nodes = []; let cur = this.trieRoot; for (let i = index; i < this.substr.length; i += 1) { const character = this.substr.charAt(i); cur = cur.getChild(character); if (!cur) { break; } nodes.push(cur); } return nodes; } // eslint-disable-next-line complexity,max-statements helper({ onlyFullSub, isFullSub, index, subIndex, changes, lastSubLetter, consecutiveSubCount }) { if (this.finalPasswords.length >= this.limit) { return; } if (index === this.substr.length) { if (onlyFullSub === isFullSub) { this.finalPasswords.push({ password: this.buffer.join(''), changes, isFullSubstitution: onlyFullSub }); } return; } // first, exhaust all possible substitutions at this index const nodes = [...this.getAllPossibleSubsAtIndex(index)]; let hasSubs = false; // iterate backward to get wider substitutions first for (let i = index + nodes.length - 1; i >= index; i -= 1) { const cur = nodes[i - index]; const sub = cur.parents.join(''); if (cur.isTerminal()) { // Skip if this would be a 4th or more consecutive substitution of the same letter // this should work in all language as there shouldn't be the same letter more than four times in a row // So we can ignore the rest to save calculation time if (lastSubLetter === sub && consecutiveSubCount >= 3) { continue; } hasSubs = true; const letters = cur.subs; for (const letter of letters) { this.buffer.push(letter); const newSubs = changes.concat({ i: subIndex, letter, substitution: sub }); // recursively build the rest of the string this.helper({ onlyFullSub, isFullSub, index: index + sub.length, subIndex: subIndex + letter.length, changes: newSubs, lastSubLetter: sub, consecutiveSubCount: lastSubLetter === sub ? consecutiveSubCount + 1 : 1 }); // backtrack by ignoring the added postfix this.buffer.pop(); if (this.finalPasswords.length >= this.limit) { return; } } } } // next, generate all combos without doing a substitution at this index // if a partial substitution is requested or there are no substitutions at this index if (!onlyFullSub || !hasSubs) { const firstChar = this.substr.charAt(index); this.buffer.push(firstChar); this.helper({ onlyFullSub, isFullSub: isFullSub && !hasSubs, index: index + 1, subIndex: subIndex + 1, changes, lastSubLetter, consecutiveSubCount }); this.buffer.pop(); } } getAll() { // only full substitution this.helper({ onlyFullSub: true, isFullSub: true, index: 0, subIndex: 0, changes: [], lastSubLetter: undefined, consecutiveSubCount: 0 }); // only partial substitution this.helper({ onlyFullSub: false, isFullSub: true, index: 0, subIndex: 0, changes: [], lastSubLetter: undefined, consecutiveSubCount: 0 }); return this.finalPasswords; } } const getCleanPasswords = (password, limit, trieRoot) => { const helper = new CleanPasswords({ substr: password, limit, trieRoot }); return helper.getAll(); }; module.exports = getCleanPasswords; //# sourceMappingURL=getCleanPasswords.cjs.map