diff options
| author | Philipp Tanlak <philipp.tanlak@gmail.com> | 2025-11-24 20:54:57 +0100 |
|---|---|---|
| committer | Philipp Tanlak <philipp.tanlak@gmail.com> | 2025-11-24 20:57:48 +0100 |
| commit | b1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c (patch) | |
| tree | 49d360fd6cbc6a2754efe93524ac47ff0fbe0f7d /node_modules/tailwindcss/src/lib/defaultExtractor.js | |
Docs
Diffstat (limited to 'node_modules/tailwindcss/src/lib/defaultExtractor.js')
| -rw-r--r-- | node_modules/tailwindcss/src/lib/defaultExtractor.js | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/node_modules/tailwindcss/src/lib/defaultExtractor.js b/node_modules/tailwindcss/src/lib/defaultExtractor.js new file mode 100644 index 0000000..ae546e9 --- /dev/null +++ b/node_modules/tailwindcss/src/lib/defaultExtractor.js @@ -0,0 +1,216 @@ +import { flagEnabled } from '../featureFlags' +import * as regex from './regex' + +export function defaultExtractor(context) { + let patterns = Array.from(buildRegExps(context)) + + /** + * @param {string} content + */ + return (content) => { + /** @type {(string|string)[]} */ + let results = [] + + for (let pattern of patterns) { + for (let result of content.match(pattern) ?? []) { + results.push(clipAtBalancedParens(result)) + } + } + + return results + } +} + +function* buildRegExps(context) { + let separator = context.tailwindConfig.separator + let prefix = + context.tailwindConfig.prefix !== '' + ? regex.optional(regex.pattern([/-?/, regex.escape(context.tailwindConfig.prefix)])) + : '' + + let utility = regex.any([ + // Arbitrary properties (without square brackets) + /\[[^\s:'"`]+:[^\s\[\]]+\]/, + + // Arbitrary properties with balanced square brackets + // This is a targeted fix to continue to allow theme() + // with square brackets to work in arbitrary properties + // while fixing a problem with the regex matching too much + /\[[^\s:'"`\]]+:[^\s]+?\[[^\s]+\][^\s]+?\]/, + + // Utilities + regex.pattern([ + // Utility Name / Group Name + /-?(?:\w+)/, + + // Normal/Arbitrary values + regex.optional( + regex.any([ + regex.pattern([ + // Arbitrary values + /-(?:\w+-)*\[[^\s:]+\]/, + + // Not immediately followed by an `{[(` + /(?![{([]])/, + + // optionally followed by an opacity modifier + /(?:\/[^\s'"`\\><$]*)?/, + ]), + + regex.pattern([ + // Arbitrary values + /-(?:\w+-)*\[[^\s]+\]/, + + // Not immediately followed by an `{[(` + /(?![{([]])/, + + // optionally followed by an opacity modifier + /(?:\/[^\s'"`\\$]*)?/, + ]), + + // Normal values w/o quotes — may include an opacity modifier + /[-\/][^\s'"`\\$={><]*/, + ]) + ), + ]), + ]) + + let variantPatterns = [ + // Without quotes + regex.any([ + // This is here to provide special support for the `@` variant + regex.pattern([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/, separator]), + + // With variant modifier (e.g.: group-[..]/modifier) + regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]\/\w+/, separator]), + + regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]), + regex.pattern([/[^\s"'`\[\\]+/, separator]), + ]), + + // With quotes allowed + regex.any([ + // With variant modifier (e.g.: group-[..]/modifier) + regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]\/\w+/, separator]), + + regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/, separator]), + regex.pattern([/[^\s`\[\\]+/, separator]), + ]), + ] + + for (const variantPattern of variantPatterns) { + yield regex.pattern([ + // Variants + '((?=((', + variantPattern, + ')+))\\2)?', + + // Important (optional) + /!?/, + + prefix, + + utility, + ]) + } + + // 5. Inner matches + yield /[^<>"'`\s.(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g +} + +// We want to capture any "special" characters +// AND the characters immediately following them (if there is one) +let SPECIALS = /([\[\]'"`])([^\[\]'"`])?/g +let ALLOWED_CLASS_CHARACTERS = /[^"'`\s<>\]]+/ + +/** + * Clips a string ensuring that parentheses, quotes, etc… are balanced + * Used for arbitrary values only + * + * We will go past the end of the balanced parens until we find a non-class character + * + * Depth matching behavior: + * w-[calc(100%-theme('spacing[some_key][1.5]'))]'] + * ┬ ┬ ┬┬ ┬ ┬┬ ┬┬┬┬┬┬┬ + * 1 2 3 4 34 3 210 END + * ╰────┴──────────┴────────┴────────┴┴───┴─┴┴┴ + * + * @param {string} input + */ +function clipAtBalancedParens(input) { + // We are care about this for arbitrary values + if (!input.includes('-[')) { + return input + } + + let depth = 0 + let openStringTypes = [] + + // Find all parens, brackets, quotes, etc + // Stop when we end at a balanced pair + // This is naive and will treat mismatched parens as balanced + // This shouldn't be a problem in practice though + let matches = input.matchAll(SPECIALS) + + // We can't use lookbehind assertions because we have to support Safari + // So, instead, we've emulated it using capture groups and we'll re-work the matches to accommodate + matches = Array.from(matches).flatMap((match) => { + const [, ...groups] = match + + return groups.map((group, idx) => + Object.assign([], match, { + index: match.index + idx, + 0: group, + }) + ) + }) + + for (let match of matches) { + let char = match[0] + let inStringType = openStringTypes[openStringTypes.length - 1] + + if (char === inStringType) { + openStringTypes.pop() + } else if (char === "'" || char === '"' || char === '`') { + openStringTypes.push(char) + } + + if (inStringType) { + continue + } else if (char === '[') { + depth++ + continue + } else if (char === ']') { + depth-- + continue + } + + // We've gone one character past the point where we should stop + // This means that there was an extra closing `]` + // We'll clip to just before it + if (depth < 0) { + return input.substring(0, match.index - 1) + } + + // We've finished balancing the brackets but there still may be characters that can be included + // For example in the class `text-[#336699]/[.35]` + // The depth goes to `0` at the closing `]` but goes up again at the `[` + + // If we're at zero and encounter a non-class character then we clip the class there + if (depth === 0 && !ALLOWED_CLASS_CHARACTERS.test(char)) { + return input.substring(0, match.index) + } + } + + return input +} + +// Regular utilities +// {{modifier}:}*{namespace}{-{suffix}}*{/{opacityModifier}}? + +// Arbitrary values +// {{modifier}:}*{namespace}-[{arbitraryValue}]{/{opacityModifier}}? +// arbitraryValue: no whitespace, balanced quotes unless within quotes, balanced brackets unless within quotes + +// Arbitrary properties +// {{modifier}:}*[{validCssPropertyName}:{arbitraryValue}] |