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     // ##########################################################################
~Extender()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