66using System . IO ;
77using System . Linq ;
88using System . Text ;
9+ using System . Text . RegularExpressions ;
910using System . Threading . Tasks ;
1011using Microsoft . Css . Parser . Parser ;
1112using Microsoft . Css . Parser . Tokens ;
@@ -18,6 +19,11 @@ namespace Microsoft.AspNetCore.Razor.Tools
1819{
1920 internal class RewriteCssCommand : CommandBase
2021 {
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+
2127 public RewriteCssCommand ( Application parent )
2228 : base ( parent , "rewritecss" )
2329 {
@@ -145,12 +151,12 @@ protected override void VisitSelector(Selector selector)
145151 // If there's a deep combinator among the sequence of simple selectors, we consider that to signal
146152 // the end of the set of simple selectors for us to look at, plus we strip it out
147153 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 ) ) ;
149155
150156 var lastSimpleSelector = allSimpleSelectors . TakeWhile ( s => s != firstDeepCombinator ) . LastOrDefault ( ) ;
151157 if ( lastSimpleSelector != null )
152158 {
153- Edits . Add ( new InsertSelectorScopeEdit { Position = lastSimpleSelector . AfterEnd } ) ;
159+ Edits . Add ( new InsertSelectorScopeEdit { Position = FindPositionBeforeTrailingCombinator ( lastSimpleSelector ) } ) ;
154160 }
155161 else if ( firstDeepCombinator != null )
156162 {
@@ -162,13 +168,32 @@ protected override void VisitSelector(Selector selector)
162168 // Also remove the deep combinator if we matched one
163169 if ( firstDeepCombinator != null )
164170 {
165- Edits . Add ( new DeleteContentEdit { Position = firstDeepCombinator . Start , DeleteLength = firstDeepCombinator . Length } ) ;
171+ Edits . Add ( new DeleteContentEdit { Position = firstDeepCombinator . Start , DeleteLength = DeepCombinatorText . Length } ) ;
166172 }
167173 }
168174
169- private static bool IsDeepCombinator ( string simpleSelectorText )
175+ private int FindPositionBeforeTrailingCombinator ( SimpleSelector lastSimpleSelector )
170176 {
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 ;
172197 }
173198
174199 protected override void VisitAtDirective ( AtDirective item )
0 commit comments