"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveMatches = resolveMatches; exports.generateRules = generateRules; var _postcss = _interopRequireDefault(require("postcss")); var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser")); var _parseObjectStyles = _interopRequireDefault(require("../../util/parseObjectStyles")); var _isPlainObject = _interopRequireDefault(require("../../util/isPlainObject")); var _prefixSelector = _interopRequireDefault(require("../../util/prefixSelector")); var _pluginUtils = require("../../util/pluginUtils"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } let classNameParser = (0, _postcssSelectorParser.default)(selectors => { return selectors.first.filter(({ type }) => type === 'class').pop().value; }); function getClassNameFromSelector(selector) { return classNameParser.transformSync(selector); } // Generate match permutations for a class candidate, like: // ['ring-offset-blue', '100'] // ['ring-offset', 'blue-100'] // ['ring', 'offset-blue-100'] // Example with dynamic classes: // ['grid-cols', '[[linename],1fr,auto]'] // ['grid', 'cols-[[linename],1fr,auto]'] function* candidatePermutations(candidate, lastIndex = Infinity) { if (lastIndex < 0) { return; } let dashIdx; if (lastIndex === Infinity && candidate.endsWith(']')) { let bracketIdx = candidate.indexOf('['); // If character before `[` isn't a dash or a slash, this isn't a dynamic class // eg. string[] dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1; } else { dashIdx = candidate.lastIndexOf('-', lastIndex); } if (dashIdx < 0) { return; } let prefix = candidate.slice(0, dashIdx); let modifier = candidate.slice(dashIdx + 1); yield [prefix, modifier]; yield* candidatePermutations(candidate, dashIdx - 1); } 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.default.root({ nodes: [match[1].clone()] }); container.walkRules(r => { r.selector = (0, _prefixSelector.default)(context.tailwindConfig.prefix, r.selector); }); match[1] = container.nodes[0]; } } return matches; } function applyImportant(matches) { if (matches.length === 0) { return matches; } let result = []; for (let [meta, rule] of matches) { let container = _postcss.default.root({ nodes: [rule.clone()] }); container.walkRules(r => { r.selector = (0, _pluginUtils.updateAllClasses)(r.selector, className => { return `!${className}`; }); r.walkDecls(d => d.important = true); }); result.push([{ ...meta, important: true }, container.nodes[0]]); } return result; } // Takes a list of rule tuples and applies a variant like `hover`, sm`, // whatever to it. We used to do some extra caching here to avoid generating // a variant of the same rule more than once, but this was never hit because // we cache at the entire selector level further up the tree. // // Technically you can get a cache hit if you have `hover:focus:text-center` // and `focus:hover:text-center` in the same project, but it doesn't feel // worth the complexity for that case. function applyVariant(variant, matches, context) { if (matches.length === 0) { return matches; } if (context.variantMap.has(variant)) { let variantFunctionTuples = context.variantMap.get(variant); let result = []; for (let [meta, rule] of matches) { if (meta.options.respectVariants === false) { result.push([meta, rule]); continue; } let container = _postcss.default.root({ nodes: [rule.clone()] }); for (let [variantSort, variantFunction] of variantFunctionTuples) { let clone = container.clone(); function modifySelectors(modifierFunction) { 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({ container: clone, separator: context.tailwindConfig.separator, modifySelectors }); if (ruleWithVariant === null) { continue; } let withOffset = [{ ...meta, sort: variantSort | meta.sort }, clone.nodes[0]]; result.push(withOffset); } } return result; } return []; } function parseRules(rule, cache, options = {}) { // PostCSS node if (!(0, _isPlainObject.default)(rule) && !Array.isArray(rule)) { return [[rule], options]; } // Tuple if (Array.isArray(rule)) { return parseRules(rule[0], cache, rule[1]); } // Simple object if (!cache.has(rule)) { cache.set(rule, (0, _parseObjectStyles.default)(rule)); } return [cache.get(rule), options]; } function* resolveMatchedPlugins(classCandidate, context) { if (context.candidateRuleMap.has(classCandidate)) { yield [context.candidateRuleMap.get(classCandidate), 'DEFAULT']; } let candidatePrefix = classCandidate; let negative = false; const twConfigPrefix = context.tailwindConfig.prefix || ''; const twConfigPrefixLen = twConfigPrefix.length; if (candidatePrefix[twConfigPrefixLen] === '-') { negative = true; candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1); } for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) { if (context.candidateRuleMap.has(prefix)) { yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier]; return; } } } function splitWithSeparator(input, separator) { return input.split(new RegExp(`\\${separator}(?![^[]*\\])`, 'g')); } 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); } // TODO: Reintroduce this in ways that doesn't break on false positives // function sortAgainst(toSort, against) { // return toSort.slice().sort((a, z) => { // return bigSign(against.get(a)[0] - against.get(z)[0]) // }) // } // let sorted = sortAgainst(variants, context.variantMap) // if (sorted.toString() !== variants.toString()) { // let corrected = sorted.reverse().concat(classCandidate).join(':') // throw new Error(`Class ${candidate} should be written as ${corrected}`) // } for (let matchedPlugins of resolveMatchedPlugins(classCandidate, context)) { let matches = []; let [plugins, modifier] = matchedPlugins; for (let [sort, plugin] of plugins) { if (typeof plugin === 'function') { for (let ruleSet of [].concat(plugin(modifier))) { let [rules, options] = parseRules(ruleSet, context.postCssNodeCache); for (let rule of rules) { matches.push([{ ...sort, options: { ...sort.options, ...options } }, rule]); } } } // Only process static plugins on exact matches else if (modifier === 'DEFAULT') { let ruleSet = plugin; let [rules, options] = parseRules(ruleSet, context.postCssNodeCache); for (let rule of rules) { matches.push([{ ...sort, options: { ...sort.options, ...options } }, rule]); } } } matches = applyPrefix(matches, context); if (important) { matches = applyImportant(matches, context); } for (let variant of variants) { matches = applyVariant(variant, matches, context); } for (let match of matches) { yield match; } } } function inKeyframes(rule) { return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'; } function generateRules(candidates, context) { let allRules = []; for (let candidate of candidates) { if (context.notClassCache.has(candidate)) { continue; } if (context.classCache.has(candidate)) { allRules.push(context.classCache.get(candidate)); continue; } let matches = Array.from(resolveMatches(candidate, context)); if (matches.length === 0) { context.notClassCache.add(candidate); continue; } context.classCache.set(candidate, matches); allRules.push(matches); } return allRules.flat(1).map(([{ sort, layer, options }, rule]) => { if (options.respectImportant) { if (context.tailwindConfig.important === true) { rule.walkDecls(d => { if (d.parent.type === 'rule' && !inKeyframes(d.parent)) { d.important = true; } }); } else if (typeof context.tailwindConfig.important === 'string') { let container = _postcss.default.root({ nodes: [rule.clone()] }); container.walkRules(r => { if (inKeyframes(r)) { return; } r.selectors = r.selectors.map(selector => { return `${context.tailwindConfig.important} ${selector}`; }); }); rule = container.nodes[0]; } } return [sort | context.layerOrder[layer], rule]; }); }