Files
SnowLuma/.pnpm-store/v11/files/80/ac46d24889813ca1f6ff91f42ad73e80e6ef864f5255fa7bba7cac7346389e9309570ccc795f7ccc080fda5b4bc66e08a15b2d657a12fcf6fbc226cba09dce
T
TanTan 2a69b23b47
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

148 lines
4.0 KiB
Plaintext

import { MatcherBaseClass, MatchOptions, RepeatMatch } from '../../types'
import Scoring from '../../scoring'
import Matching from '../../Matching'
import Options from '../../Options'
import { MaybePromise } from 'rollup'
/*
*-------------------------------------------------------------------------------
* repeats (aaa, abcabcabc) ------------------------------
*-------------------------------------------------------------------------------
*/
class MatchRepeat extends MatcherBaseClass {
private scoring: Scoring
constructor(options: Options) {
super(options)
this.scoring = new Scoring(options)
}
// eslint-disable-next-line max-statements
match({ password, omniMatch }: MatchOptions) {
const matches: (RepeatMatch | Promise<RepeatMatch>)[] = []
let lastIndex = 0
while (lastIndex < password.length) {
const greedyMatch = this.getGreedyMatch(password, lastIndex)
const lazyMatch = this.getLazyMatch(password, lastIndex)
if (greedyMatch == null) {
break
}
const { match, baseToken } = this.setMatchToken(greedyMatch, lazyMatch)
if (match) {
const j = match.index + match[0].length - 1
const baseGuesses = this.getBaseGuesses(baseToken, omniMatch)
matches.push(this.normalizeMatch(baseToken, j, match, baseGuesses))
lastIndex = j + 1
}
}
const hasPromises = matches.some((match) => {
return match instanceof Promise
})
if (hasPromises) {
// eslint-disable-next-line @typescript-eslint/await-thenable
return Promise.all(matches)
}
return matches as RepeatMatch[]
}
// eslint-disable-next-line max-params
normalizeMatch(
baseToken: string,
j: number,
match: RegExpExecArray,
baseGuesses: number | Promise<number>,
): MaybePromise<RepeatMatch> {
const baseMatch: RepeatMatch = {
pattern: 'repeat',
i: match.index,
j,
token: match[0],
baseToken,
baseGuesses: 0,
repeatCount: match[0].length / baseToken.length,
}
if (baseGuesses instanceof Promise) {
return baseGuesses.then((resolvedBaseGuesses) => {
return {
...baseMatch,
baseGuesses: resolvedBaseGuesses,
}
})
}
return {
...baseMatch,
baseGuesses,
}
}
getGreedyMatch(password: string, lastIndex: number) {
const greedy = /(.+)\1+/g
greedy.lastIndex = lastIndex
return greedy.exec(password)
}
getLazyMatch(password: string, lastIndex: number) {
const lazy = /(.+?)\1+/g
lazy.lastIndex = lastIndex
return lazy.exec(password)
}
setMatchToken(
greedyMatch: RegExpExecArray,
lazyMatch: RegExpExecArray | null,
) {
const lazyAnchored = /^(.+?)\1+$/
let match
let baseToken = ''
if (lazyMatch && greedyMatch[0].length > lazyMatch[0].length) {
// greedy beats lazy for 'aabaab'
// greedy: [aabaab, aab]
// lazy: [aa, a]
match = greedyMatch
// greedy's repeated string might itself be repeated, eg.
// aabaab in aabaabaabaab.
// run an anchored lazy match on greedy's repeated string
// to find the shortest repeated string
const temp = lazyAnchored.exec(match[0])
if (temp) {
baseToken = temp[1]
}
} else {
// lazy beats greedy for 'aaaaa'
// greedy: [aaaa, aa]
// lazy: [aaaaa, a]
match = lazyMatch
if (match) {
baseToken = match[1]
}
}
return {
match,
baseToken,
}
}
getBaseGuesses(baseToken: string, omniMatch: Matching) {
const matches = omniMatch.match(baseToken)
if (matches instanceof Promise) {
return matches.then((resolvedMatches) => {
const baseAnalysis = this.scoring.mostGuessableMatchSequence(
baseToken,
resolvedMatches,
)
return baseAnalysis.guesses
})
}
const baseAnalysis = this.scoring.mostGuessableMatchSequence(
baseToken,
matches,
)
return baseAnalysis.guesses
}
}
export default MatchRepeat