2021-05-03 17:00:04 +00:00
|
|
|
import { State } from './state'
|
2022-05-26 10:31:22 +00:00
|
|
|
import * as jit from './jit'
|
2021-05-03 17:00:04 +00:00
|
|
|
|
|
|
|
export function getVariantsFromClassName(
|
|
|
|
state: State,
|
|
|
|
className: string
|
|
|
|
): { variants: string[]; offset: number } {
|
|
|
|
let allVariants = Object.keys(state.variants)
|
2022-05-26 10:31:22 +00:00
|
|
|
let parts = Array.from(splitAtTopLevelOnly(className, state.separator)).filter(Boolean)
|
2021-05-03 17:00:04 +00:00
|
|
|
let variants = new Set<string>()
|
|
|
|
let offset = 0
|
|
|
|
|
2022-05-26 10:31:22 +00:00
|
|
|
for (let part of parts) {
|
|
|
|
if (
|
|
|
|
allVariants.includes(part) ||
|
|
|
|
(state.jit &&
|
|
|
|
part.startsWith('[') &&
|
|
|
|
part.endsWith(']') &&
|
|
|
|
jit.generateRules(state, [`${part}${state.separator}[color:red]`]).rules.length > 0)
|
|
|
|
) {
|
|
|
|
variants.add(part)
|
|
|
|
offset += part.length + state.separator.length
|
|
|
|
continue
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
2022-05-26 10:31:22 +00:00
|
|
|
|
|
|
|
break
|
2021-05-03 17:00:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return { variants: Array.from(variants), offset }
|
|
|
|
}
|
2022-05-26 10:31:22 +00:00
|
|
|
|
|
|
|
const REGEX_SPECIAL = /[\\^$.*+?()[\]{}|]/g
|
|
|
|
const REGEX_HAS_SPECIAL = RegExp(REGEX_SPECIAL.source)
|
|
|
|
|
|
|
|
function regexEscape(string: string): string {
|
|
|
|
return string && REGEX_HAS_SPECIAL.test(string)
|
|
|
|
? string.replace(REGEX_SPECIAL, '\\$&')
|
|
|
|
: string || ''
|
|
|
|
}
|
|
|
|
|
|
|
|
function* splitAtTopLevelOnly(input: string, separator: string): Generator<string> {
|
|
|
|
let SPECIALS = new RegExp(`[(){}\\[\\]${regexEscape(separator)}]`, 'g')
|
|
|
|
|
|
|
|
let depth = 0
|
|
|
|
let lastIndex = 0
|
|
|
|
let found = false
|
|
|
|
let separatorIndex = 0
|
|
|
|
let separatorStart = 0
|
|
|
|
let separatorLength = separator.length
|
|
|
|
|
|
|
|
// Find all paren-like things & character
|
|
|
|
// And only split on commas if they're top-level
|
|
|
|
for (let match of input.matchAll(SPECIALS)) {
|
|
|
|
let matchesSeparator = match[0] === separator[separatorIndex]
|
|
|
|
let atEndOfSeparator = separatorIndex === separatorLength - 1
|
|
|
|
let matchesFullSeparator = matchesSeparator && atEndOfSeparator
|
|
|
|
|
|
|
|
if (match[0] === '(') depth++
|
|
|
|
if (match[0] === ')') depth--
|
|
|
|
if (match[0] === '[') depth++
|
|
|
|
if (match[0] === ']') depth--
|
|
|
|
if (match[0] === '{') depth++
|
|
|
|
if (match[0] === '}') depth--
|
|
|
|
|
|
|
|
if (matchesSeparator && depth === 0) {
|
|
|
|
if (separatorStart === 0) {
|
|
|
|
separatorStart = match.index
|
|
|
|
}
|
|
|
|
|
|
|
|
separatorIndex++
|
|
|
|
}
|
|
|
|
|
|
|
|
if (matchesFullSeparator && depth === 0) {
|
|
|
|
found = true
|
|
|
|
|
|
|
|
yield input.substring(lastIndex, separatorStart)
|
|
|
|
lastIndex = separatorStart + separatorLength
|
|
|
|
}
|
|
|
|
|
|
|
|
if (separatorIndex === separatorLength) {
|
|
|
|
separatorIndex = 0
|
|
|
|
separatorStart = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Provide the last segment of the string if available
|
|
|
|
// Otherwise the whole string since no `char`s were found
|
|
|
|
// This mirrors the behavior of string.split()
|
|
|
|
if (found) {
|
|
|
|
yield input.substring(lastIndex)
|
|
|
|
} else {
|
|
|
|
yield input
|
|
|
|
}
|
|
|
|
}
|