6
6
using System . IO ;
7
7
using System . Linq ;
8
8
using System . Text ;
9
+ using System . Text . RegularExpressions ;
9
10
using System . Threading . Tasks ;
10
11
using Microsoft . Css . Parser . Parser ;
11
12
using Microsoft . Css . Parser . Tokens ;
@@ -18,6 +19,11 @@ namespace Microsoft.AspNetCore.Razor.Tools
18
19
{
19
20
internal class RewriteCssCommand : CommandBase
20
21
{
22
+ private const string DeepCombinatorText = "::deep" ;
23
+ private readonly static TimeSpan _regexTimeout = TimeSpan . FromSeconds ( 1 ) ;
24
+ private readonly static Regex _deepCombinatorRegex = new Regex ( $@ "^{ DeepCombinatorText } \s*", RegexOptions . None , _regexTimeout ) ;
25
+ private readonly static Regex _trailingCombinatorRegex = new Regex ( @"\s+[\>\+\~]$" , RegexOptions . None , _regexTimeout ) ;
26
+
21
27
public RewriteCssCommand ( Application parent )
22
28
: base ( parent , "rewritecss" )
23
29
{
@@ -145,12 +151,12 @@ protected override void VisitSelector(Selector selector)
145
151
// If there's a deep combinator among the sequence of simple selectors, we consider that to signal
146
152
// the end of the set of simple selectors for us to look at, plus we strip it out
147
153
var allSimpleSelectors = selector . Children . OfType < SimpleSelector > ( ) ;
148
- var firstDeepCombinator = allSimpleSelectors . FirstOrDefault ( s => IsDeepCombinator ( s . Text ) ) ;
154
+ var firstDeepCombinator = allSimpleSelectors . FirstOrDefault ( s => _deepCombinatorRegex . IsMatch ( s . Text ) ) ;
149
155
150
156
var lastSimpleSelector = allSimpleSelectors . TakeWhile ( s => s != firstDeepCombinator ) . LastOrDefault ( ) ;
151
157
if ( lastSimpleSelector != null )
152
158
{
153
- Edits . Add ( new InsertSelectorScopeEdit { Position = lastSimpleSelector . AfterEnd } ) ;
159
+ Edits . Add ( new InsertSelectorScopeEdit { Position = FindPositionBeforeTrailingCombinator ( lastSimpleSelector ) } ) ;
154
160
}
155
161
else if ( firstDeepCombinator != null )
156
162
{
@@ -162,13 +168,32 @@ protected override void VisitSelector(Selector selector)
162
168
// Also remove the deep combinator if we matched one
163
169
if ( firstDeepCombinator != null )
164
170
{
165
- Edits . Add ( new DeleteContentEdit { Position = firstDeepCombinator . Start , DeleteLength = firstDeepCombinator . Length } ) ;
171
+ Edits . Add ( new DeleteContentEdit { Position = firstDeepCombinator . Start , DeleteLength = DeepCombinatorText . Length } ) ;
166
172
}
167
173
}
168
174
169
- private static bool IsDeepCombinator ( string simpleSelectorText )
175
+ private int FindPositionBeforeTrailingCombinator ( SimpleSelector lastSimpleSelector )
170
176
{
171
- return string . Equals ( simpleSelectorText , "::deep" , StringComparison . Ordinal ) ;
177
+ // For a selector like "a > ::deep b", the parser splits it as "a >", "::deep", "b".
178
+ // The place we want to insert the scope is right after "a", hence we need to detect
179
+ // if the simple selector ends with " >" or similar, and if so, insert before that.
180
+ var text = lastSimpleSelector . Text ;
181
+ var lastChar = text . Length > 0 ? text [ ^ 1 ] : default ;
182
+ switch ( lastChar )
183
+ {
184
+ case '>' :
185
+ case '+' :
186
+ case '~' :
187
+ var trailingCombinatorMatch = _trailingCombinatorRegex . Match ( text ) ;
188
+ if ( trailingCombinatorMatch . Success )
189
+ {
190
+ var trailingCombinatorLength = trailingCombinatorMatch . Length ;
191
+ return lastSimpleSelector . AfterEnd - trailingCombinatorLength ;
192
+ }
193
+ break ;
194
+ }
195
+
196
+ return lastSimpleSelector . AfterEnd ;
172
197
}
173
198
174
199
protected override void VisitAtDirective ( AtDirective item )
0 commit comments