In CSS scoping, support ::deep alongside other combinators (#24376)
This commit is contained in:
parent
01e05359d6
commit
5e49fc336e
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Css.Parser.Parser;
|
||||
using Microsoft.Css.Parser.Tokens;
|
||||
|
|
@ -18,6 +19,11 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
|||
{
|
||||
internal class RewriteCssCommand : CommandBase
|
||||
{
|
||||
private const string DeepCombinatorText = "::deep";
|
||||
private readonly static TimeSpan _regexTimeout = TimeSpan.FromSeconds(1);
|
||||
private readonly static Regex _deepCombinatorRegex = new Regex($@"^{DeepCombinatorText}\s*", RegexOptions.None, _regexTimeout);
|
||||
private readonly static Regex _trailingCombinatorRegex = new Regex(@"\s+[\>\+\~]$", RegexOptions.None, _regexTimeout);
|
||||
|
||||
public RewriteCssCommand(Application parent)
|
||||
: base(parent, "rewritecss")
|
||||
{
|
||||
|
|
@ -145,12 +151,12 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
|||
// If there's a deep combinator among the sequence of simple selectors, we consider that to signal
|
||||
// the end of the set of simple selectors for us to look at, plus we strip it out
|
||||
var allSimpleSelectors = selector.Children.OfType<SimpleSelector>();
|
||||
var firstDeepCombinator = allSimpleSelectors.FirstOrDefault(s => IsDeepCombinator(s.Text));
|
||||
var firstDeepCombinator = allSimpleSelectors.FirstOrDefault(s => _deepCombinatorRegex.IsMatch(s.Text));
|
||||
|
||||
var lastSimpleSelector = allSimpleSelectors.TakeWhile(s => s != firstDeepCombinator).LastOrDefault();
|
||||
if (lastSimpleSelector != null)
|
||||
{
|
||||
Edits.Add(new InsertSelectorScopeEdit { Position = lastSimpleSelector.AfterEnd });
|
||||
Edits.Add(new InsertSelectorScopeEdit { Position = FindPositionBeforeTrailingCombinator(lastSimpleSelector) });
|
||||
}
|
||||
else if (firstDeepCombinator != null)
|
||||
{
|
||||
|
|
@ -162,13 +168,32 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
|||
// Also remove the deep combinator if we matched one
|
||||
if (firstDeepCombinator != null)
|
||||
{
|
||||
Edits.Add(new DeleteContentEdit { Position = firstDeepCombinator.Start, DeleteLength = firstDeepCombinator.Length });
|
||||
Edits.Add(new DeleteContentEdit { Position = firstDeepCombinator.Start, DeleteLength = DeepCombinatorText.Length });
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsDeepCombinator(string simpleSelectorText)
|
||||
private int FindPositionBeforeTrailingCombinator(SimpleSelector lastSimpleSelector)
|
||||
{
|
||||
return string.Equals(simpleSelectorText, "::deep", StringComparison.Ordinal);
|
||||
// For a selector like "a > ::deep b", the parser splits it as "a >", "::deep", "b".
|
||||
// The place we want to insert the scope is right after "a", hence we need to detect
|
||||
// if the simple selector ends with " >" or similar, and if so, insert before that.
|
||||
var text = lastSimpleSelector.Text;
|
||||
var lastChar = text.Length > 0 ? text[^1] : default;
|
||||
switch (lastChar)
|
||||
{
|
||||
case '>':
|
||||
case '+':
|
||||
case '~':
|
||||
var trailingCombinatorMatch = _trailingCombinatorRegex.Match(text);
|
||||
if (trailingCombinatorMatch.Success)
|
||||
{
|
||||
var trailingCombinatorLength = trailingCombinatorMatch.Length;
|
||||
return lastSimpleSelector.AfterEnd - trailingCombinatorLength;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return lastSimpleSelector.AfterEnd;
|
||||
}
|
||||
|
||||
protected override void VisitAtDirective(AtDirective item)
|
||||
|
|
|
|||
|
|
@ -91,6 +91,54 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
|||
", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RespectsDeepCombinatorWithDirectDescendant()
|
||||
{
|
||||
// Arrange/act
|
||||
var result = RewriteCssCommand.AddScopeToSelectors(@"
|
||||
a > ::deep b { color: red; }
|
||||
c ::deep > d { color: blue; }
|
||||
", "TestScope");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(@"
|
||||
a[TestScope] > b { color: red; }
|
||||
c[TestScope] > d { color: blue; }
|
||||
", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RespectsDeepCombinatorWithAdjacentSibling()
|
||||
{
|
||||
// Arrange/act
|
||||
var result = RewriteCssCommand.AddScopeToSelectors(@"
|
||||
a + ::deep b { color: red; }
|
||||
c ::deep + d { color: blue; }
|
||||
", "TestScope");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(@"
|
||||
a[TestScope] + b { color: red; }
|
||||
c[TestScope] + d { color: blue; }
|
||||
", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RespectsDeepCombinatorWithGeneralSibling()
|
||||
{
|
||||
// Arrange/act
|
||||
var result = RewriteCssCommand.AddScopeToSelectors(@"
|
||||
a ~ ::deep b { color: red; }
|
||||
c ::deep ~ d { color: blue; }
|
||||
", "TestScope");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(@"
|
||||
a[TestScope] ~ b { color: red; }
|
||||
c[TestScope] ~ d { color: blue; }
|
||||
", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IgnoresMultipleDeepCombinators()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue