1 | #ifndef SASS_EXTENDER_H |
2 | #define SASS_EXTENDER_H |
3 | |
4 | #include <set> |
5 | #include <map> |
6 | #include <string> |
7 | |
8 | #include "ast_helpers.hpp" |
9 | #include "ast_fwd_decl.hpp" |
10 | #include "operation.hpp" |
11 | #include "extension.hpp" |
12 | #include "backtrace.hpp" |
13 | #include "ordered_map.hpp" |
14 | |
15 | namespace Sass { |
16 | |
17 | // ########################################################################## |
18 | // Different hash map types used by extender |
19 | // ########################################################################## |
20 | |
21 | // This is special (ptrs!) |
22 | typedef std::unordered_set< |
23 | ComplexSelectorObj, |
24 | ObjPtrHash, |
25 | ObjPtrEquality |
26 | > ExtCplxSelSet; |
27 | |
28 | typedef std::unordered_set< |
29 | SimpleSelectorObj, |
30 | ObjHash, |
31 | ObjEquality |
32 | > ExtSmplSelSet; |
33 | |
34 | typedef std::unordered_set< |
35 | SelectorListObj, |
36 | ObjPtrHash, |
37 | ObjPtrEquality |
38 | > ExtListSelSet; |
39 | |
40 | typedef std::unordered_map< |
41 | SimpleSelectorObj, |
42 | ExtListSelSet, |
43 | ObjHash, |
44 | ObjEquality |
45 | > ExtSelMap; |
46 | |
47 | typedef ordered_map< |
48 | ComplexSelectorObj, |
49 | Extension, |
50 | ObjHash, |
51 | ObjEquality |
52 | > ExtSelExtMapEntry; |
53 | |
54 | typedef std::unordered_map< |
55 | SimpleSelectorObj, |
56 | ExtSelExtMapEntry, |
57 | ObjHash, |
58 | ObjEquality |
59 | > ExtSelExtMap; |
60 | |
61 | typedef std::unordered_map < |
62 | SimpleSelectorObj, |
63 | sass::vector< |
64 | Extension |
65 | >, |
66 | ObjHash, |
67 | ObjEquality |
68 | > ExtByExtMap; |
69 | |
70 | class Extender : public Operation_CRTP<void, Extender> { |
71 | |
72 | public: |
73 | |
74 | enum ExtendMode { TARGETS, REPLACE, NORMAL, }; |
75 | |
76 | private: |
77 | |
78 | // ########################################################################## |
79 | // The mode that controls this extender's behavior. |
80 | // ########################################################################## |
81 | ExtendMode mode; |
82 | |
83 | // ########################################################################## |
84 | // Shared backtraces with context and expander. Needed the throw |
85 | // errors when e.g. extending across media query boundaries. |
86 | // ########################################################################## |
87 | Backtraces& traces; |
88 | |
89 | // ########################################################################## |
90 | // A map from all simple selectors in the stylesheet to the rules that |
91 | // contain them.This is used to find which rules an `@extend` applies to. |
92 | // ########################################################################## |
93 | ExtSelMap selectors; |
94 | |
95 | // ########################################################################## |
96 | // A map from all extended simple selectors |
97 | // to the sources of those extensions. |
98 | // ########################################################################## |
99 | ExtSelExtMap extensions; |
100 | |
101 | // ########################################################################## |
102 | // A map from all simple selectors in extenders to |
103 | // the extensions that those extenders define. |
104 | // ########################################################################## |
105 | ExtByExtMap extensionsByExtender; |
106 | |
107 | // ########################################################################## |
108 | // A map from CSS rules to the media query contexts they're defined in. |
109 | // This tracks the contexts in which each style rule is defined. |
110 | // If a rule is defined at the top level, it doesn't have an entry. |
111 | // ########################################################################## |
112 | ordered_map< |
113 | SelectorListObj, |
114 | CssMediaRuleObj, |
115 | ObjPtrHash, |
116 | ObjPtrEquality |
117 | > mediaContexts; |
118 | |
119 | // ########################################################################## |
120 | // A map from [SimpleSelector]s to the specificity of their source selectors. |
121 | // This tracks the maximum specificity of the [ComplexSelector] that originally |
122 | // contained each [SimpleSelector]. This allows us to ensure we don't trim any |
123 | // selectors that need to exist to satisfy the [second law that of extend][]. |
124 | // [second law of extend]: https://github.com/sass/sass/issues/324#issuecomment-4607184 |
125 | // ########################################################################## |
126 | std::unordered_map< |
127 | SimpleSelectorObj, |
128 | size_t, |
129 | ObjPtrHash, |
130 | ObjPtrEquality |
131 | > sourceSpecificity; |
132 | |
133 | // ########################################################################## |
134 | // A set of [ComplexSelector]s that were originally part of their |
135 | // component [SelectorList]s, as opposed to being added by `@extend`. |
136 | // This allows us to ensure that we don't trim any selectors |
137 | // that need to exist to satisfy the [first law of extend][]. |
138 | // ########################################################################## |
139 | ExtCplxSelSet originals; |
140 | |
141 | public: |
142 | |
143 | // Constructor without default [mode]. |
144 | // [traces] are needed to throw errors. |
145 | Extender(Backtraces& traces); |
146 | |
147 | // ########################################################################## |
148 | // Constructor with specific [mode]. |
149 | // [traces] are needed to throw errors. |
150 | // ########################################################################## |
151 | Extender(ExtendMode mode, Backtraces& traces); |
152 | |
153 | // ########################################################################## |
154 | // Empty desctructor |
155 | // ########################################################################## |
156 | ~Extender() {}; |
157 | |
158 | // ########################################################################## |
159 | // Extends [selector] with [source] extender and [targets] extendees. |
160 | // This works as though `source {@extend target}` were written in the |
161 | // stylesheet, with the exception that [target] can contain compound |
162 | // selectors which must be extended as a unit. |
163 | // ########################################################################## |
164 | static SelectorListObj extend( |
165 | SelectorListObj& selector, |
166 | const SelectorListObj& source, |
167 | const SelectorListObj& target, |
168 | Backtraces& traces); |
169 | |
170 | // ########################################################################## |
171 | // Returns a copy of [selector] with [targets] replaced by [source]. |
172 | // ########################################################################## |
173 | static SelectorListObj replace( |
174 | SelectorListObj& selector, |
175 | const SelectorListObj& source, |
176 | const SelectorListObj& target, |
177 | Backtraces& traces); |
178 | |
179 | // ########################################################################## |
180 | // Adds [selector] to this extender, with [selectorSpan] as the span covering |
181 | // the selector and [ruleSpan] as the span covering the entire style rule. |
182 | // Extends [selector] using any registered extensions, then returns an empty |
183 | // [ModifiableCssStyleRule] with the resulting selector. If any more relevant |
184 | // extensions are added, the returned rule is automatically updated. |
185 | // The [mediaContext] is the media query context in which the selector was |
186 | // defined, or `null` if it was defined at the top level of the document. |
187 | // ########################################################################## |
188 | void addSelector( |
189 | const SelectorListObj& selector, |
190 | const CssMediaRuleObj& mediaContext); |
191 | |
192 | // ########################################################################## |
193 | // Registers the [SimpleSelector]s in [list] |
194 | // to point to [rule] in [selectors]. |
195 | // ########################################################################## |
196 | void registerSelector( |
197 | const SelectorListObj& list, |
198 | const SelectorListObj& rule); |
199 | |
200 | // ########################################################################## |
201 | // Adds an extension to this extender. The [extender] is the selector for the |
202 | // style rule in which the extension is defined, and [target] is the selector |
203 | // passed to `@extend`. The [extend] provides the extend span and indicates |
204 | // whether the extension is optional. The [mediaContext] defines the media query |
205 | // context in which the extension is defined. It can only extend selectors |
206 | // within the same context. A `null` context indicates no media queries. |
207 | // ########################################################################## |
208 | void addExtension( |
209 | const SelectorListObj& extender, |
210 | const SimpleSelectorObj& target, |
211 | const CssMediaRuleObj& mediaQueryContext, |
212 | bool is_optional = false); |
213 | |
214 | // ########################################################################## |
215 | // The set of all simple selectors in style rules handled |
216 | // by this extender. This includes simple selectors that |
217 | // were added because of downstream extensions. |
218 | // ########################################################################## |
219 | ExtSmplSelSet getSimpleSelectors() const; |
220 | |
221 | // ########################################################################## |
222 | // Check for extends that have not been satisfied. |
223 | // Returns true if any non-optional extension did not |
224 | // extend any selector. Updates the passed reference |
225 | // to point to that Extension for further analysis. |
226 | // ########################################################################## |
227 | bool checkForUnsatisfiedExtends( |
228 | Extension& unsatisfied) const; |
229 | |
230 | private: |
231 | |
232 | // ########################################################################## |
233 | // A helper function for [extend] and [replace]. |
234 | // ########################################################################## |
235 | static SelectorListObj extendOrReplace( |
236 | SelectorListObj& selector, |
237 | const SelectorListObj& source, |
238 | const SelectorListObj& target, |
239 | const ExtendMode mode, |
240 | Backtraces& traces); |
241 | |
242 | // ########################################################################## |
243 | // Returns an extension that combines [left] and [right]. Throws |
244 | // a [SassException] if [left] and [right] have incompatible |
245 | // media contexts. Throws an [ArgumentError] if [left] |
246 | // and [right] don't have the same extender and target. |
247 | // ########################################################################## |
248 | static Extension mergeExtension( |
249 | const Extension& lhs, |
250 | const Extension& rhs); |
251 | |
252 | // ########################################################################## |
253 | // Extend [extensions] using [newExtensions]. |
254 | // ########################################################################## |
255 | // Note: dart-sass throws an error in here |
256 | // ########################################################################## |
257 | void extendExistingStyleRules( |
258 | const ExtListSelSet& rules, |
259 | const ExtSelExtMap& newExtensions); |
260 | |
261 | // ########################################################################## |
262 | // Extend [extensions] using [newExtensions]. Note that this does duplicate |
263 | // some work done by [_extendExistingStyleRules], but it's necessary to |
264 | // expand each extension's extender separately without reference to the full |
265 | // selector list, so that relevant results don't get trimmed too early. |
266 | // Returns `null` (Note: empty map) if there are no extensions to add. |
267 | // ########################################################################## |
268 | ExtSelExtMap extendExistingExtensions( |
269 | // Taking in a reference here makes MSVC debug stuck!? |
270 | const sass::vector<Extension>& extensions, |
271 | const ExtSelExtMap& newExtensions); |
272 | |
273 | // ########################################################################## |
274 | // Extends [list] using [extensions]. |
275 | // ########################################################################## |
276 | SelectorListObj extendList( |
277 | const SelectorListObj& list, |
278 | const ExtSelExtMap& extensions, |
279 | const CssMediaRuleObj& mediaContext); |
280 | |
281 | // ########################################################################## |
282 | // Extends [complex] using [extensions], and |
283 | // returns the contents of a [SelectorList]. |
284 | // ########################################################################## |
285 | sass::vector<ComplexSelectorObj> extendComplex( |
286 | // Taking in a reference here makes MSVC debug stuck!? |
287 | const ComplexSelectorObj& list, |
288 | const ExtSelExtMap& extensions, |
289 | const CssMediaRuleObj& mediaQueryContext); |
290 | |
291 | // ########################################################################## |
292 | // Returns a one-off [Extension] whose |
293 | // extender is composed solely of [simple]. |
294 | // ########################################################################## |
295 | Extension extensionForSimple( |
296 | const SimpleSelectorObj& simple) const; |
297 | |
298 | // ########################################################################## |
299 | // Returns a one-off [Extension] whose extender is composed |
300 | // solely of a compound selector containing [simples]. |
301 | // ########################################################################## |
302 | Extension extensionForCompound( |
303 | // Taking in a reference here makes MSVC debug stuck!? |
304 | const sass::vector<SimpleSelectorObj>& simples) const; |
305 | |
306 | // ########################################################################## |
307 | // Extends [compound] using [extensions], and returns the |
308 | // contents of a [SelectorList]. The [inOriginal] parameter |
309 | // indicates whether this is in an original complex selector, |
310 | // meaning that [compound] should not be trimmed out. |
311 | // ########################################################################## |
312 | sass::vector<ComplexSelectorObj> extendCompound( |
313 | const CompoundSelectorObj& compound, |
314 | const ExtSelExtMap& extensions, |
315 | const CssMediaRuleObj& mediaQueryContext, |
316 | bool inOriginal = false); |
317 | |
318 | // ########################################################################## |
319 | // Extends [simple] without extending the |
320 | // contents of any selector pseudos it contains. |
321 | // ########################################################################## |
322 | sass::vector<Extension> extendWithoutPseudo( |
323 | const SimpleSelectorObj& simple, |
324 | const ExtSelExtMap& extensions, |
325 | ExtSmplSelSet* targetsUsed) const; |
326 | |
327 | // ########################################################################## |
328 | // Extends [simple] and also extending the |
329 | // contents of any selector pseudos it contains. |
330 | // ########################################################################## |
331 | sass::vector<sass::vector<Extension>> extendSimple( |
332 | const SimpleSelectorObj& simple, |
333 | const ExtSelExtMap& extensions, |
334 | const CssMediaRuleObj& mediaQueryContext, |
335 | ExtSmplSelSet* targetsUsed); |
336 | |
337 | // ########################################################################## |
338 | // Inner loop helper for [extendPseudo] function |
339 | // ########################################################################## |
340 | static sass::vector<ComplexSelectorObj> extendPseudoComplex( |
341 | const ComplexSelectorObj& complex, |
342 | const PseudoSelectorObj& pseudo, |
343 | const CssMediaRuleObj& mediaQueryContext); |
344 | |
345 | // ########################################################################## |
346 | // Extends [pseudo] using [extensions], and returns |
347 | // a list of resulting pseudo selectors. |
348 | // ########################################################################## |
349 | sass::vector<PseudoSelectorObj> extendPseudo( |
350 | const PseudoSelectorObj& pseudo, |
351 | const ExtSelExtMap& extensions, |
352 | const CssMediaRuleObj& mediaQueryContext); |
353 | |
354 | // ########################################################################## |
355 | // Rotates the element in list from [start] (inclusive) to [end] (exclusive) |
356 | // one index higher, looping the final element back to [start]. |
357 | // ########################################################################## |
358 | static void rotateSlice( |
359 | sass::vector<ComplexSelectorObj>& list, |
360 | size_t start, size_t end); |
361 | |
362 | // ########################################################################## |
363 | // Removes elements from [selectors] if they're subselectors of other |
364 | // elements. The [isOriginal] callback indicates which selectors are |
365 | // original to the document, and thus should never be trimmed. |
366 | // ########################################################################## |
367 | sass::vector<ComplexSelectorObj> trim( |
368 | const sass::vector<ComplexSelectorObj>& selectors, |
369 | const ExtCplxSelSet& set) const; |
370 | |
371 | // ########################################################################## |
372 | // Returns the maximum specificity of the given [simple] source selector. |
373 | // ########################################################################## |
374 | size_t maxSourceSpecificity(const SimpleSelectorObj& simple) const; |
375 | |
376 | // ########################################################################## |
377 | // Returns the maximum specificity for sources that went into producing [compound]. |
378 | // ########################################################################## |
379 | size_t maxSourceSpecificity(const CompoundSelectorObj& compound) const; |
380 | |
381 | // ########################################################################## |
382 | // Helper function used as callbacks on lists |
383 | // ########################################################################## |
384 | static bool dontTrimComplex( |
385 | const ComplexSelector* complex2, |
386 | const ComplexSelector* complex1, |
387 | const size_t maxSpecificity); |
388 | |
389 | // ########################################################################## |
390 | // Helper function used as callbacks on lists |
391 | // ########################################################################## |
392 | static bool hasExactlyOne(const ComplexSelectorObj& vec); |
393 | static bool hasMoreThanOne(const ComplexSelectorObj& vec); |
394 | |
395 | }; |
396 | |
397 | } |
398 | |
399 | #endif |
400 | |