source: frontend/node_modules/tailwindcss/src/lib/defaultExtractor.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 7.4 KB
Line 
1import * as regex from './regex'
2import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly'
3
4export function defaultExtractor(context) {
5 let patterns = Array.from(buildRegExps(context))
6
7 /**
8 * @param {string} content
9 */
10 return (content) => {
11 /** @type {(string|string)[]} */
12 let results = []
13
14 for (let pattern of patterns) {
15 for (let result of content.match(pattern) ?? []) {
16 results.push(clipAtBalancedParens(result))
17 }
18 }
19
20 // Extract any subclasses from languages like Slim and Pug, eg:
21 // div.flex.px-5.underline
22 for (let result of results.slice()) {
23 let segments = splitAtTopLevelOnly(result, '.')
24
25 for (let idx = 0; idx < segments.length; idx++) {
26 let segment = segments[idx]
27 if (idx >= segments.length - 1) {
28 results.push(segment)
29 continue
30 }
31
32 // If the next segment is a number, discard both, for example seeing
33 // `px-1` and `5` means the real candidate was `px-1.5` which is already
34 // captured.
35 let next = Number(segments[idx + 1])
36 if (isNaN(next)) {
37 results.push(segment)
38 } else {
39 idx++
40 }
41 }
42 }
43
44 return results
45 }
46}
47
48function* buildRegExps(context) {
49 let separator = context.tailwindConfig.separator
50 let prefix =
51 context.tailwindConfig.prefix !== ''
52 ? regex.optional(regex.pattern([/-?/, regex.escape(context.tailwindConfig.prefix)]))
53 : ''
54
55 let utility = regex.any([
56 // Arbitrary properties (without square brackets)
57 /\[[^\s:'"`]+:[^\s\[\]]+\]/,
58
59 // Arbitrary properties with balanced square brackets
60 // This is a targeted fix to continue to allow theme()
61 // with square brackets to work in arbitrary properties
62 // while fixing a problem with the regex matching too much
63 /\[[^\s:'"`\]]+:[^\s]+?\[[^\s]+\][^\s]+?\]/,
64
65 // Utilities
66 regex.pattern([
67 // Utility Name / Group Name
68 regex.any([
69 /-?(?:\w+)/,
70
71 // This is here to make sure @container supports everything that other utilities do
72 /@(?:\w+)/,
73 ]),
74
75 // Normal/Arbitrary values
76 regex.optional(
77 regex.any([
78 regex.pattern([
79 // Arbitrary values
80 regex.any([
81 /-(?:\w+-)*\['[^\s]+'\]/,
82 /-(?:\w+-)*\["[^\s]+"\]/,
83 /-(?:\w+-)*\[`[^\s]+`\]/,
84 /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/,
85 ]),
86
87 // Not immediately followed by an `{[(`
88 /(?![{([]])/,
89
90 // optionally followed by an opacity modifier
91 /(?:\/[^\s'"`\\><$]*)?/,
92 ]),
93
94 regex.pattern([
95 // Arbitrary values
96 regex.any([
97 /-(?:\w+-)*\['[^\s]+'\]/,
98 /-(?:\w+-)*\["[^\s]+"\]/,
99 /-(?:\w+-)*\[`[^\s]+`\]/,
100 /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s\[\]]+\]/,
101 ]),
102
103 // Not immediately followed by an `{[(`
104 /(?![{([]])/,
105
106 // optionally followed by an opacity modifier
107 /(?:\/[^\s'"`\\$]*)?/,
108 ]),
109
110 // Normal values w/o quotes — may include an opacity modifier
111 /[-\/][^\s'"`\\$={><]*/,
112 ])
113 ),
114 ]),
115 ])
116
117 let variantPatterns = [
118 // Without quotes
119 regex.any([
120 // This is here to provide special support for the `@` variant
121 regex.pattern([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/, separator]),
122
123 // With variant modifier (e.g.: group-[..]/modifier)
124 regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]\/[\w_-]+/, separator]),
125
126 regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]),
127 regex.pattern([/[^\s"'`\[\\]+/, separator]),
128 ]),
129
130 // With quotes allowed
131 regex.any([
132 // With variant modifier (e.g.: group-[..]/modifier)
133 regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]\/[\w_-]+/, separator]),
134
135 regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/, separator]),
136 regex.pattern([/[^\s`\[\\]+/, separator]),
137 ]),
138 ]
139
140 for (const variantPattern of variantPatterns) {
141 yield regex.pattern([
142 // Variants
143 '((?=((',
144 variantPattern,
145 ')+))\\2)?',
146
147 // Important (optional)
148 /!?/,
149
150 prefix,
151
152 utility,
153 ])
154 }
155
156 // 5. Inner matches
157 yield /[^<>"'`\s.(){}[\]#=%$][^<>"'`\s(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g
158}
159
160// We want to capture any "special" characters
161// AND the characters immediately following them (if there is one)
162let SPECIALS = /([\[\]'"`])([^\[\]'"`])?/g
163let ALLOWED_CLASS_CHARACTERS = /[^"'`\s<>\]]+/
164
165/**
166 * Clips a string ensuring that parentheses, quotes, etc… are balanced
167 * Used for arbitrary values only
168 *
169 * We will go past the end of the balanced parens until we find a non-class character
170 *
171 * Depth matching behavior:
172 * w-[calc(100%-theme('spacing[some_key][1.5]'))]']
173 * ┬ ┬ ┬┬ ┬ ┬┬ ┬┬┬┬┬┬┬
174 * 1 2 3 4 34 3 210 END
175 * ╰────┴──────────┴────────┴────────┴┴───┴─┴┴┴
176 *
177 * @param {string} input
178 */
179function clipAtBalancedParens(input) {
180 // We are care about this for arbitrary values
181 if (!input.includes('-[')) {
182 return input
183 }
184
185 let depth = 0
186 let openStringTypes = []
187
188 // Find all parens, brackets, quotes, etc
189 // Stop when we end at a balanced pair
190 // This is naive and will treat mismatched parens as balanced
191 // This shouldn't be a problem in practice though
192 let matches = input.matchAll(SPECIALS)
193
194 // We can't use lookbehind assertions because we have to support Safari
195 // So, instead, we've emulated it using capture groups and we'll re-work the matches to accommodate
196 matches = Array.from(matches).flatMap((match) => {
197 const [, ...groups] = match
198
199 return groups.map((group, idx) =>
200 Object.assign([], match, {
201 index: match.index + idx,
202 0: group,
203 })
204 )
205 })
206
207 for (let match of matches) {
208 let char = match[0]
209 let inStringType = openStringTypes[openStringTypes.length - 1]
210
211 if (char === inStringType) {
212 openStringTypes.pop()
213 } else if (char === "'" || char === '"' || char === '`') {
214 openStringTypes.push(char)
215 }
216
217 if (inStringType) {
218 continue
219 } else if (char === '[') {
220 depth++
221 continue
222 } else if (char === ']') {
223 depth--
224 continue
225 }
226
227 // We've gone one character past the point where we should stop
228 // This means that there was an extra closing `]`
229 // We'll clip to just before it
230 if (depth < 0) {
231 return input.substring(0, match.index - 1)
232 }
233
234 // We've finished balancing the brackets but there still may be characters that can be included
235 // For example in the class `text-[#336699]/[.35]`
236 // The depth goes to `0` at the closing `]` but goes up again at the `[`
237
238 // If we're at zero and encounter a non-class character then we clip the class there
239 if (depth === 0 && !ALLOWED_CLASS_CHARACTERS.test(char)) {
240 return input.substring(0, match.index)
241 }
242 }
243
244 return input
245}
246
247// Regular utilities
248// {{modifier}:}*{namespace}{-{suffix}}*{/{opacityModifier}}?
249
250// Arbitrary values
251// {{modifier}:}*{namespace}-[{arbitraryValue}]{/{opacityModifier}}?
252// arbitraryValue: no whitespace, balanced quotes unless within quotes, balanced brackets unless within quotes
253
254// Arbitrary properties
255// {{modifier}:}*[{validCssPropertyName}:{arbitraryValue}]
Note: See TracBrowser for help on using the repository browser.