1
- // Copyright (c) .NET Foundation. All rights reserved.
1
+ // Copyright (c) .NET Foundation. All rights reserved.
2
2
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
3
4
4
using System ;
@@ -74,26 +74,27 @@ public static string AddScopeToSelectors(string inputText, string cssScope)
74
74
var resultBuilder = new StringBuilder ( ) ;
75
75
var previousInsertionPosition = 0 ;
76
76
77
- var scopeInsertionPositionsVisitor = new FindScopeInsertionPositionsVisitor ( stylesheet ) ;
77
+ var scopeInsertionPositionsVisitor = new FindScopeInsertionEdits ( stylesheet ) ;
78
78
scopeInsertionPositionsVisitor . Visit ( ) ;
79
- foreach ( var ( currentInsertionPosition , insertionType ) in scopeInsertionPositionsVisitor . InsertionPositions )
79
+ foreach ( var edit in scopeInsertionPositionsVisitor . Edits )
80
80
{
81
- resultBuilder . Append ( inputText . Substring ( previousInsertionPosition , currentInsertionPosition - previousInsertionPosition ) ) ;
82
-
83
- switch ( insertionType )
81
+ resultBuilder . Append ( inputText . Substring ( previousInsertionPosition , edit . Position - previousInsertionPosition ) ) ;
82
+ previousInsertionPosition = edit . Position ;
83
+
84
+ switch ( edit )
84
85
{
85
- case ScopeInsertionType . Selector :
86
+ case InsertSelectorScopeEdit _ :
86
87
resultBuilder . AppendFormat ( "[{0}]" , cssScope ) ;
87
88
break ;
88
- case ScopeInsertionType . KeyframesName :
89
+ case InsertKeyframesNameScopeEdit _ :
89
90
resultBuilder . AppendFormat ( "-{0}" , cssScope ) ;
90
91
break ;
92
+ case DeleteContentEdit deleteContentEdit :
93
+ previousInsertionPosition += deleteContentEdit . DeleteLength ;
94
+ break ;
91
95
default :
92
- throw new NotImplementedException ( $ "Unknown insertion type: '{ insertionType } '") ;
96
+ throw new NotImplementedException ( $ "Unknown edit type: '{ edit } '") ;
93
97
}
94
-
95
-
96
- previousInsertionPosition = currentInsertionPosition ;
97
98
}
98
99
99
100
resultBuilder . Append ( inputText . Substring ( previousInsertionPosition ) ) ;
@@ -118,19 +119,13 @@ private static bool TryFindKeyframesIdentifier(AtDirective atDirective, out Pars
118
119
return false ;
119
120
}
120
121
121
- private enum ScopeInsertionType
122
+ private class FindScopeInsertionEdits : Visitor
122
123
{
123
- Selector ,
124
- KeyframesName ,
125
- }
126
-
127
- private class FindScopeInsertionPositionsVisitor : Visitor
128
- {
129
- public List < ( int , ScopeInsertionType ) > InsertionPositions { get ; } = new List < ( int , ScopeInsertionType ) > ( ) ;
124
+ public List < CssEdit > Edits { get ; } = new List < CssEdit > ( ) ;
130
125
131
126
private readonly HashSet < string > _keyframeIdentifiers ;
132
127
133
- public FindScopeInsertionPositionsVisitor ( ComplexItem root ) : base ( root )
128
+ public FindScopeInsertionEdits ( ComplexItem root ) : base ( root )
134
129
{
135
130
// Before we start, we need to know the full set of keyframe names declared in this document
136
131
var keyframesIdentifiersVisitor = new FindKeyframesIdentifiersVisitor ( root ) ;
@@ -146,19 +141,42 @@ protected override void VisitSelector(Selector selector)
146
141
// ".first child," containing two simple selectors: ".first" and "child"
147
142
// ".second", containing one simple selector: ".second"
148
143
// Our goal is to insert immediately after the final simple selector within each selector
149
- var lastSimpleSelector = selector . Children . OfType < SimpleSelector > ( ) . LastOrDefault ( ) ;
144
+
145
+ // If there's a deep combinator among the sequence of simple selectors, we consider that to signal
146
+ // the end of the set of simple selectors for us to look at, plus we strip it out
147
+ var allSimpleSelectors = selector . Children . OfType < SimpleSelector > ( ) ;
148
+ var firstDeepCombinator = allSimpleSelectors . FirstOrDefault ( s => IsDeepCombinator ( s . Text ) ) ;
149
+
150
+ var lastSimpleSelector = allSimpleSelectors . TakeWhile ( s => s != firstDeepCombinator ) . LastOrDefault ( ) ;
150
151
if ( lastSimpleSelector != null )
151
152
{
152
- InsertionPositions . Add ( ( lastSimpleSelector . AfterEnd , ScopeInsertionType . Selector ) ) ;
153
+ Edits . Add ( new InsertSelectorScopeEdit { Position = lastSimpleSelector . AfterEnd } ) ;
154
+ }
155
+ else if ( firstDeepCombinator != null )
156
+ {
157
+ // For a leading deep combinator, we want to insert the scope attribute at the start
158
+ // Otherwise the result would be a CSS rule that isn't scoped at all
159
+ Edits . Add ( new InsertSelectorScopeEdit { Position = firstDeepCombinator . Start } ) ;
153
160
}
161
+
162
+ // Also remove the deep combinator if we matched one
163
+ if ( firstDeepCombinator != null )
164
+ {
165
+ Edits . Add ( new DeleteContentEdit { Position = firstDeepCombinator . Start , DeleteLength = firstDeepCombinator . Length } ) ;
166
+ }
167
+ }
168
+
169
+ private static bool IsDeepCombinator ( string simpleSelectorText )
170
+ {
171
+ return string . Equals ( simpleSelectorText , "::deep" , StringComparison . Ordinal ) ;
154
172
}
155
173
156
174
protected override void VisitAtDirective ( AtDirective item )
157
175
{
158
176
// Whenever we see "@keyframes something { ... }", we want to insert right after "something"
159
177
if ( TryFindKeyframesIdentifier ( item , out var identifier ) )
160
178
{
161
- InsertionPositions . Add ( ( identifier . AfterEnd , ScopeInsertionType . KeyframesName ) ) ;
179
+ Edits . Add ( new InsertKeyframesNameScopeEdit { Position = identifier . AfterEnd } ) ;
162
180
}
163
181
else
164
182
{
@@ -183,7 +201,7 @@ protected override void VisitDeclaration(Declaration item)
183
201
. Where ( x => x . TokenType == CssTokenType . Identifier && _keyframeIdentifiers . Contains ( x . Text ) ) ;
184
202
foreach ( var token in animationNameTokens )
185
203
{
186
- InsertionPositions . Add ( ( token . AfterEnd , ScopeInsertionType . KeyframesName ) ) ;
204
+ Edits . Add ( new InsertKeyframesNameScopeEdit { Position = token . AfterEnd } ) ;
187
205
}
188
206
break ;
189
207
default :
@@ -273,5 +291,23 @@ private void VisitDescendants(ComplexItem container)
273
291
}
274
292
}
275
293
}
294
+
295
+ private abstract class CssEdit
296
+ {
297
+ public int Position { get ; set ; }
298
+ }
299
+
300
+ private class InsertSelectorScopeEdit : CssEdit
301
+ {
302
+ }
303
+
304
+ private class InsertKeyframesNameScopeEdit : CssEdit
305
+ {
306
+ }
307
+
308
+ private class DeleteContentEdit : CssEdit
309
+ {
310
+ public int DeleteLength { get ; set ; }
311
+ }
276
312
}
277
313
}
0 commit comments