|
import postcss from 'postcss' |
|
import selectorParser from 'postcss-selector-parser' |
|
import parseObjectStyles from '../util/parseObjectStyles' |
|
import isPlainObject from '../util/isPlainObject' |
|
import prefixSelector from '../util/prefixSelector' |
|
import { updateAllClasses, getMatchingTypes } from '../util/pluginUtils' |
|
import log from '../util/log' |
|
import * as sharedState from './sharedState' |
|
import { |
|
formatVariantSelector, |
|
finalizeSelector, |
|
eliminateIrrelevantSelectors, |
|
} from '../util/formatVariantSelector' |
|
import { asClass } from '../util/nameClass' |
|
import { normalize } from '../util/dataTypes' |
|
import { isValidVariantFormatString, parseVariant, INTERNAL_FEATURES } from './setupContextUtils' |
|
import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue' |
|
import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js' |
|
import { flagEnabled } from '../featureFlags' |
|
import { applyImportantSelector } from '../util/applyImportantSelector' |
|
|
|
let classNameParser = selectorParser((selectors) => { |
|
return selectors.first.filter(({ type }) => type === 'class').pop().value |
|
}) |
|
|
|
export function getClassNameFromSelector(selector) { |
|
return classNameParser.transformSync(selector) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function* candidatePermutations(candidate) { |
|
let lastIndex = Infinity |
|
|
|
while (lastIndex >= 0) { |
|
let dashIdx |
|
let wasSlash = false |
|
|
|
if (lastIndex === Infinity && candidate.endsWith(']')) { |
|
let bracketIdx = candidate.indexOf('[') |
|
|
|
|
|
|
|
if (candidate[bracketIdx - 1] === '-') { |
|
dashIdx = bracketIdx - 1 |
|
} else if (candidate[bracketIdx - 1] === '/') { |
|
dashIdx = bracketIdx - 1 |
|
wasSlash = true |
|
} else { |
|
dashIdx = -1 |
|
} |
|
} else if (lastIndex === Infinity && candidate.includes('/')) { |
|
dashIdx = candidate.lastIndexOf('/') |
|
wasSlash = true |
|
} else { |
|
dashIdx = candidate.lastIndexOf('-', lastIndex) |
|
} |
|
|
|
if (dashIdx < 0) { |
|
break |
|
} |
|
|
|
let prefix = candidate.slice(0, dashIdx) |
|
let modifier = candidate.slice(wasSlash ? dashIdx : dashIdx + 1) |
|
|
|
lastIndex = dashIdx - 1 |
|
|
|
|
|
if (prefix === '' || modifier === '/') { |
|
continue |
|
} |
|
|
|
yield [prefix, modifier] |
|
} |
|
} |
|
|
|
function applyPrefix(matches, context) { |
|
if (matches.length === 0 || context.tailwindConfig.prefix === '') { |
|
return matches |
|
} |
|
|
|
for (let match of matches) { |
|
let [meta] = match |
|
if (meta.options.respectPrefix) { |
|
let container = postcss.root({ nodes: [match[1].clone()] }) |
|
let classCandidate = match[1].raws.tailwind.classCandidate |
|
|
|
container.walkRules((r) => { |
|
|
|
|
|
|
|
|
|
|
|
let shouldPrependNegative = classCandidate.startsWith('-') |
|
|
|
r.selector = prefixSelector( |
|
context.tailwindConfig.prefix, |
|
r.selector, |
|
shouldPrependNegative |
|
) |
|
}) |
|
|
|
match[1] = container.nodes[0] |
|
} |
|
} |
|
|
|
return matches |
|
} |
|
|
|
function applyImportant(matches, classCandidate) { |
|
if (matches.length === 0) { |
|
return matches |
|
} |
|
|
|
let result = [] |
|
|
|
function isInKeyframes(rule) { |
|
return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes' |
|
} |
|
|
|
for (let [meta, rule] of matches) { |
|
let container = postcss.root({ nodes: [rule.clone()] }) |
|
|
|
container.walkRules((r) => { |
|
|
|
|
|
if (isInKeyframes(r)) { |
|
return |
|
} |
|
|
|
let ast = selectorParser().astSync(r.selector) |
|
|
|
|
|
ast.each((sel) => eliminateIrrelevantSelectors(sel, classCandidate)) |
|
|
|
|
|
updateAllClasses(ast, (className) => |
|
className === classCandidate ? `!${className}` : className |
|
) |
|
|
|
r.selector = ast.toString() |
|
|
|
r.walkDecls((d) => (d.important = true)) |
|
}) |
|
|
|
result.push([{ ...meta, important: true }, container.nodes[0]]) |
|
} |
|
|
|
return result |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function applyVariant(variant, matches, context) { |
|
if (matches.length === 0) { |
|
return matches |
|
} |
|
|
|
|
|
let args = { modifier: null, value: sharedState.NONE } |
|
|
|
|
|
{ |
|
let [baseVariant, ...modifiers] = splitAtTopLevelOnly(variant, '/') |
|
|
|
|
|
|
|
if (modifiers.length > 1) { |
|
baseVariant = baseVariant + '/' + modifiers.slice(0, -1).join('/') |
|
modifiers = modifiers.slice(-1) |
|
} |
|
|
|
if (modifiers.length && !context.variantMap.has(variant)) { |
|
variant = baseVariant |
|
args.modifier = modifiers[0] |
|
|
|
if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) { |
|
return [] |
|
} |
|
} |
|
} |
|
|
|
|
|
if (variant.endsWith(']') && !variant.startsWith('[')) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let match = /(.)(-?)\[(.*)\]/g.exec(variant) |
|
if (match) { |
|
let [, char, separator, value] = match |
|
|
|
if (char === '@' && separator === '-') return [] |
|
|
|
if (char !== '@' && separator === '') return [] |
|
|
|
variant = variant.replace(`${separator}[${value}]`, '') |
|
args.value = value |
|
} |
|
} |
|
|
|
|
|
if (isArbitraryValue(variant) && !context.variantMap.has(variant)) { |
|
let sort = context.offsets.recordVariant(variant) |
|
|
|
let selector = normalize(variant.slice(1, -1)) |
|
let selectors = splitAtTopLevelOnly(selector, ',') |
|
|
|
|
|
if (selectors.length > 1) { |
|
return [] |
|
} |
|
|
|
if (!selectors.every(isValidVariantFormatString)) { |
|
return [] |
|
} |
|
|
|
let records = selectors.map((sel, idx) => [ |
|
context.offsets.applyParallelOffset(sort, idx), |
|
parseVariant(sel.trim()), |
|
]) |
|
|
|
context.variantMap.set(variant, records) |
|
} |
|
|
|
if (context.variantMap.has(variant)) { |
|
let isArbitraryVariant = isArbitraryValue(variant) |
|
let internalFeatures = context.variantOptions.get(variant)?.[INTERNAL_FEATURES] ?? {} |
|
let variantFunctionTuples = context.variantMap.get(variant).slice() |
|
let result = [] |
|
|
|
let respectPrefix = (() => { |
|
if (isArbitraryVariant) return false |
|
if (internalFeatures.respectPrefix === false) return false |
|
return true |
|
})() |
|
|
|
for (let [meta, rule] of matches) { |
|
|
|
if (meta.layer === 'user') { |
|
continue |
|
} |
|
|
|
let container = postcss.root({ nodes: [rule.clone()] }) |
|
|
|
for (let [variantSort, variantFunction, containerFromArray] of variantFunctionTuples) { |
|
let clone = (containerFromArray ?? container).clone() |
|
let collectedFormats = [] |
|
|
|
function prepareBackup() { |
|
|
|
if (clone.raws.neededBackup) { |
|
return |
|
} |
|
clone.raws.neededBackup = true |
|
clone.walkRules((rule) => (rule.raws.originalSelector = rule.selector)) |
|
} |
|
|
|
function modifySelectors(modifierFunction) { |
|
prepareBackup() |
|
clone.each((rule) => { |
|
if (rule.type !== 'rule') { |
|
return |
|
} |
|
|
|
rule.selectors = rule.selectors.map((selector) => { |
|
return modifierFunction({ |
|
get className() { |
|
return getClassNameFromSelector(selector) |
|
}, |
|
selector, |
|
}) |
|
}) |
|
}) |
|
|
|
return clone |
|
} |
|
|
|
let ruleWithVariant = variantFunction({ |
|
|
|
get container() { |
|
prepareBackup() |
|
return clone |
|
}, |
|
separator: context.tailwindConfig.separator, |
|
modifySelectors, |
|
|
|
|
|
wrap(wrapper) { |
|
let nodes = clone.nodes |
|
clone.removeAll() |
|
wrapper.append(nodes) |
|
clone.append(wrapper) |
|
}, |
|
format(selectorFormat) { |
|
collectedFormats.push({ |
|
format: selectorFormat, |
|
respectPrefix, |
|
}) |
|
}, |
|
args, |
|
}) |
|
|
|
|
|
|
|
if (Array.isArray(ruleWithVariant)) { |
|
for (let [idx, variantFunction] of ruleWithVariant.entries()) { |
|
|
|
|
|
|
|
|
|
variantFunctionTuples.push([ |
|
context.offsets.applyParallelOffset(variantSort, idx), |
|
variantFunction, |
|
|
|
|
|
|
|
clone.clone(), |
|
]) |
|
} |
|
continue |
|
} |
|
|
|
if (typeof ruleWithVariant === 'string') { |
|
collectedFormats.push({ |
|
format: ruleWithVariant, |
|
respectPrefix, |
|
}) |
|
} |
|
|
|
if (ruleWithVariant === null) { |
|
continue |
|
} |
|
|
|
|
|
|
|
|
|
if (clone.raws.neededBackup) { |
|
delete clone.raws.neededBackup |
|
clone.walkRules((rule) => { |
|
let before = rule.raws.originalSelector |
|
if (!before) return |
|
delete rule.raws.originalSelector |
|
if (before === rule.selector) return |
|
|
|
let modified = rule.selector |
|
|
|
|
|
|
|
|
|
|
|
let rebuiltBase = selectorParser((selectors) => { |
|
selectors.walkClasses((classNode) => { |
|
classNode.value = `${variant}${context.tailwindConfig.separator}${classNode.value}` |
|
}) |
|
}).processSync(before) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
collectedFormats.push({ |
|
format: modified.replace(rebuiltBase, '&'), |
|
respectPrefix, |
|
}) |
|
rule.selector = before |
|
}) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
clone.nodes[0].raws.tailwind = { ...clone.nodes[0].raws.tailwind, parentLayer: meta.layer } |
|
|
|
let withOffset = [ |
|
{ |
|
...meta, |
|
sort: context.offsets.applyVariantOffset( |
|
meta.sort, |
|
variantSort, |
|
Object.assign(args, context.variantOptions.get(variant)) |
|
), |
|
collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats), |
|
}, |
|
clone.nodes[0], |
|
] |
|
result.push(withOffset) |
|
} |
|
} |
|
|
|
return result |
|
} |
|
|
|
return [] |
|
} |
|
|
|
function parseRules(rule, cache, options = {}) { |
|
|
|
if (!isPlainObject(rule) && !Array.isArray(rule)) { |
|
return [[rule], options] |
|
} |
|
|
|
|
|
if (Array.isArray(rule)) { |
|
return parseRules(rule[0], cache, rule[1]) |
|
} |
|
|
|
|
|
if (!cache.has(rule)) { |
|
cache.set(rule, parseObjectStyles(rule)) |
|
} |
|
|
|
return [cache.get(rule), options] |
|
} |
|
|
|
const IS_VALID_PROPERTY_NAME = /^[a-z_-]/ |
|
|
|
function isValidPropName(name) { |
|
return IS_VALID_PROPERTY_NAME.test(name) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function looksLikeUri(declaration) { |
|
|
|
|
|
if (!declaration.includes('://')) { |
|
return false |
|
} |
|
|
|
try { |
|
const url = new URL(declaration) |
|
return url.scheme !== '' && url.host !== '' |
|
} catch (err) { |
|
|
|
return false |
|
} |
|
} |
|
|
|
function isParsableNode(node) { |
|
let isParsable = true |
|
|
|
node.walkDecls((decl) => { |
|
if (!isParsableCssValue(decl.prop, decl.value)) { |
|
isParsable = false |
|
return false |
|
} |
|
}) |
|
|
|
return isParsable |
|
} |
|
|
|
function isParsableCssValue(property, value) { |
|
|
|
|
|
|
|
if (looksLikeUri(`${property}:${value}`)) { |
|
return false |
|
} |
|
|
|
try { |
|
postcss.parse(`a{${property}:${value}}`).toResult() |
|
return true |
|
} catch (err) { |
|
return false |
|
} |
|
} |
|
|
|
function extractArbitraryProperty(classCandidate, context) { |
|
let [, property, value] = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/) ?? [] |
|
|
|
if (value === undefined) { |
|
return null |
|
} |
|
|
|
if (!isValidPropName(property)) { |
|
return null |
|
} |
|
|
|
if (!isValidArbitraryValue(value)) { |
|
return null |
|
} |
|
|
|
let normalized = normalize(value, { property }) |
|
|
|
if (!isParsableCssValue(property, normalized)) { |
|
return null |
|
} |
|
|
|
let sort = context.offsets.arbitraryProperty() |
|
|
|
return [ |
|
[ |
|
{ sort, layer: 'utilities' }, |
|
() => ({ |
|
[asClass(classCandidate)]: { |
|
[property]: normalized, |
|
}, |
|
}), |
|
], |
|
] |
|
} |
|
|
|
function* resolveMatchedPlugins(classCandidate, context) { |
|
if (context.candidateRuleMap.has(classCandidate)) { |
|
yield [context.candidateRuleMap.get(classCandidate), 'DEFAULT'] |
|
} |
|
|
|
yield* (function* (arbitraryPropertyRule) { |
|
if (arbitraryPropertyRule !== null) { |
|
yield [arbitraryPropertyRule, 'DEFAULT'] |
|
} |
|
})(extractArbitraryProperty(classCandidate, context)) |
|
|
|
let candidatePrefix = classCandidate |
|
let negative = false |
|
|
|
const twConfigPrefix = context.tailwindConfig.prefix |
|
|
|
const twConfigPrefixLen = twConfigPrefix.length |
|
|
|
const hasMatchingPrefix = |
|
candidatePrefix.startsWith(twConfigPrefix) || candidatePrefix.startsWith(`-${twConfigPrefix}`) |
|
|
|
if (candidatePrefix[twConfigPrefixLen] === '-' && hasMatchingPrefix) { |
|
negative = true |
|
candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1) |
|
} |
|
|
|
if (negative && context.candidateRuleMap.has(candidatePrefix)) { |
|
yield [context.candidateRuleMap.get(candidatePrefix), '-DEFAULT'] |
|
} |
|
|
|
for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) { |
|
if (context.candidateRuleMap.has(prefix)) { |
|
yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier] |
|
} |
|
} |
|
} |
|
|
|
function splitWithSeparator(input, separator) { |
|
if (input === sharedState.NOT_ON_DEMAND) { |
|
return [sharedState.NOT_ON_DEMAND] |
|
} |
|
|
|
return splitAtTopLevelOnly(input, separator) |
|
} |
|
|
|
function* recordCandidates(matches, classCandidate) { |
|
for (const match of matches) { |
|
match[1].raws.tailwind = { |
|
...match[1].raws.tailwind, |
|
classCandidate, |
|
preserveSource: match[0].options?.preserveSource ?? false, |
|
} |
|
|
|
yield match |
|
} |
|
} |
|
|
|
function* resolveMatches(candidate, context) { |
|
let separator = context.tailwindConfig.separator |
|
let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse() |
|
let important = false |
|
|
|
if (classCandidate.startsWith('!')) { |
|
important = true |
|
classCandidate = classCandidate.slice(1) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (let matchedPlugins of resolveMatchedPlugins(classCandidate, context)) { |
|
let matches = [] |
|
let typesByMatches = new Map() |
|
|
|
let [plugins, modifier] = matchedPlugins |
|
let isOnlyPlugin = plugins.length === 1 |
|
|
|
for (let [sort, plugin] of plugins) { |
|
let matchesPerPlugin = [] |
|
|
|
if (typeof plugin === 'function') { |
|
for (let ruleSet of [].concat(plugin(modifier, { isOnlyPlugin }))) { |
|
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache) |
|
for (let rule of rules) { |
|
matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule]) |
|
} |
|
} |
|
} |
|
|
|
else if (modifier === 'DEFAULT' || modifier === '-DEFAULT') { |
|
let ruleSet = plugin |
|
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache) |
|
for (let rule of rules) { |
|
matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule]) |
|
} |
|
} |
|
|
|
if (matchesPerPlugin.length > 0) { |
|
let matchingTypes = Array.from( |
|
getMatchingTypes( |
|
sort.options?.types ?? [], |
|
modifier, |
|
sort.options ?? {}, |
|
context.tailwindConfig |
|
) |
|
).map(([_, type]) => type) |
|
|
|
if (matchingTypes.length > 0) { |
|
typesByMatches.set(matchesPerPlugin, matchingTypes) |
|
} |
|
|
|
matches.push(matchesPerPlugin) |
|
} |
|
} |
|
|
|
if (isArbitraryValue(modifier)) { |
|
if (matches.length > 1) { |
|
|
|
|
|
let [withAny, withoutAny] = matches.reduce( |
|
(group, plugin) => { |
|
let hasAnyType = plugin.some(([{ options }]) => |
|
options.types.some(({ type }) => type === 'any') |
|
) |
|
|
|
if (hasAnyType) { |
|
group[0].push(plugin) |
|
} else { |
|
group[1].push(plugin) |
|
} |
|
return group |
|
}, |
|
[[], []] |
|
) |
|
|
|
function findFallback(matches) { |
|
|
|
if (matches.length === 1) { |
|
return matches[0] |
|
} |
|
|
|
|
|
|
|
return matches.find((rules) => { |
|
let matchingTypes = typesByMatches.get(rules) |
|
return rules.some(([{ options }, rule]) => { |
|
if (!isParsableNode(rule)) { |
|
return false |
|
} |
|
|
|
return options.types.some( |
|
({ type, preferOnConflict }) => matchingTypes.includes(type) && preferOnConflict |
|
) |
|
}) |
|
}) |
|
} |
|
|
|
|
|
|
|
let fallback = findFallback(withoutAny) ?? findFallback(withAny) |
|
if (fallback) { |
|
matches = [fallback] |
|
} |
|
|
|
|
|
|
|
|
|
|
|
else { |
|
let typesPerPlugin = matches.map( |
|
(match) => new Set([...(typesByMatches.get(match) ?? [])]) |
|
) |
|
|
|
|
|
for (let pluginTypes of typesPerPlugin) { |
|
for (let type of pluginTypes) { |
|
let removeFromOwnGroup = false |
|
|
|
for (let otherGroup of typesPerPlugin) { |
|
if (pluginTypes === otherGroup) continue |
|
|
|
if (otherGroup.has(type)) { |
|
otherGroup.delete(type) |
|
removeFromOwnGroup = true |
|
} |
|
} |
|
|
|
if (removeFromOwnGroup) pluginTypes.delete(type) |
|
} |
|
} |
|
|
|
let messages = [] |
|
|
|
for (let [idx, group] of typesPerPlugin.entries()) { |
|
for (let type of group) { |
|
let rules = matches[idx] |
|
.map(([, rule]) => rule) |
|
.flat() |
|
.map((rule) => |
|
rule |
|
.toString() |
|
.split('\n') |
|
.slice(1, -1) |
|
.map((line) => line.trim()) |
|
.map((x) => ` ${x}`) |
|
.join('\n') |
|
) |
|
.join('\n\n') |
|
|
|
messages.push( |
|
` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\`` |
|
) |
|
break |
|
} |
|
} |
|
|
|
log.warn([ |
|
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`, |
|
...messages, |
|
`If this is content and not a class, replace it with \`${candidate |
|
.replace('[', '[') |
|
.replace(']', ']')}\` to silence this warning.`, |
|
]) |
|
continue |
|
} |
|
} |
|
|
|
matches = matches.map((list) => list.filter((match) => isParsableNode(match[1]))) |
|
} |
|
|
|
matches = matches.flat() |
|
matches = Array.from(recordCandidates(matches, classCandidate)) |
|
matches = applyPrefix(matches, context) |
|
|
|
if (important) { |
|
matches = applyImportant(matches, classCandidate) |
|
} |
|
|
|
for (let variant of variants) { |
|
matches = applyVariant(variant, matches, context) |
|
} |
|
|
|
for (let match of matches) { |
|
match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate } |
|
|
|
|
|
match = applyFinalFormat(match, { context, candidate }) |
|
|
|
|
|
|
|
|
|
if (match === null) { |
|
continue |
|
} |
|
|
|
yield match |
|
} |
|
} |
|
} |
|
|
|
function applyFinalFormat(match, { context, candidate }) { |
|
if (!match[0].collectedFormats) { |
|
return match |
|
} |
|
|
|
let isValid = true |
|
let finalFormat |
|
|
|
try { |
|
finalFormat = formatVariantSelector(match[0].collectedFormats, { |
|
context, |
|
candidate, |
|
}) |
|
} catch { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null |
|
} |
|
|
|
let container = postcss.root({ nodes: [match[1].clone()] }) |
|
|
|
container.walkRules((rule) => { |
|
if (inKeyframes(rule)) { |
|
return |
|
} |
|
|
|
try { |
|
let selector = finalizeSelector(rule.selector, finalFormat, { |
|
candidate, |
|
context, |
|
}) |
|
|
|
|
|
|
|
if (selector === null) { |
|
rule.remove() |
|
return |
|
} |
|
|
|
rule.selector = selector |
|
} catch { |
|
|
|
|
|
isValid = false |
|
return false |
|
} |
|
}) |
|
|
|
if (!isValid) { |
|
return null |
|
} |
|
|
|
|
|
if (container.nodes.length === 0) { |
|
return null |
|
} |
|
|
|
match[1] = container.nodes[0] |
|
|
|
return match |
|
} |
|
|
|
function inKeyframes(rule) { |
|
return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes' |
|
} |
|
|
|
function getImportantStrategy(important) { |
|
if (important === true) { |
|
return (rule) => { |
|
if (inKeyframes(rule)) { |
|
return |
|
} |
|
|
|
rule.walkDecls((d) => { |
|
if (d.parent.type === 'rule' && !inKeyframes(d.parent)) { |
|
d.important = true |
|
} |
|
}) |
|
} |
|
} |
|
|
|
if (typeof important === 'string') { |
|
return (rule) => { |
|
if (inKeyframes(rule)) { |
|
return |
|
} |
|
|
|
rule.selectors = rule.selectors.map((selector) => { |
|
return applyImportantSelector(selector, important) |
|
}) |
|
} |
|
} |
|
} |
|
|
|
function generateRules(candidates, context, isSorting = false) { |
|
let allRules = [] |
|
let strategy = getImportantStrategy(context.tailwindConfig.important) |
|
|
|
for (let candidate of candidates) { |
|
if (context.notClassCache.has(candidate)) { |
|
continue |
|
} |
|
|
|
if (context.candidateRuleCache.has(candidate)) { |
|
allRules = allRules.concat(Array.from(context.candidateRuleCache.get(candidate))) |
|
continue |
|
} |
|
|
|
let matches = Array.from(resolveMatches(candidate, context)) |
|
|
|
if (matches.length === 0) { |
|
context.notClassCache.add(candidate) |
|
continue |
|
} |
|
|
|
context.classCache.set(candidate, matches) |
|
|
|
let rules = context.candidateRuleCache.get(candidate) ?? new Set() |
|
context.candidateRuleCache.set(candidate, rules) |
|
|
|
for (const match of matches) { |
|
let [{ sort, options }, rule] = match |
|
|
|
if (options.respectImportant && strategy) { |
|
let container = postcss.root({ nodes: [rule.clone()] }) |
|
container.walkRules(strategy) |
|
rule = container.nodes[0] |
|
} |
|
|
|
|
|
|
|
let newEntry = [sort, isSorting ? rule.clone() : rule] |
|
rules.add(newEntry) |
|
context.ruleCache.add(newEntry) |
|
allRules.push(newEntry) |
|
} |
|
} |
|
|
|
return allRules |
|
} |
|
|
|
function isArbitraryValue(input) { |
|
return input.startsWith('[') && input.endsWith(']') |
|
} |
|
|
|
export { resolveMatches, generateRules } |
|
|