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.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Css.Parser.Parser;
|
using Microsoft.Css.Parser.Parser;
|
||||||
using Microsoft.Css.Parser.Tokens;
|
using Microsoft.Css.Parser.Tokens;
|
||||||
|
|
@ -18,6 +19,11 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
{
|
{
|
||||||
internal class RewriteCssCommand : CommandBase
|
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)
|
public RewriteCssCommand(Application parent)
|
||||||
: base(parent, "rewritecss")
|
: 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
|
// 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
|
// 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 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();
|
var lastSimpleSelector = allSimpleSelectors.TakeWhile(s => s != firstDeepCombinator).LastOrDefault();
|
||||||
if (lastSimpleSelector != null)
|
if (lastSimpleSelector != null)
|
||||||
{
|
{
|
||||||
Edits.Add(new InsertSelectorScopeEdit { Position = lastSimpleSelector.AfterEnd });
|
Edits.Add(new InsertSelectorScopeEdit { Position = FindPositionBeforeTrailingCombinator(lastSimpleSelector) });
|
||||||
}
|
}
|
||||||
else if (firstDeepCombinator != null)
|
else if (firstDeepCombinator != null)
|
||||||
{
|
{
|
||||||
|
|
@ -162,13 +168,32 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
// Also remove the deep combinator if we matched one
|
// Also remove the deep combinator if we matched one
|
||||||
if (firstDeepCombinator != null)
|
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)
|
protected override void VisitAtDirective(AtDirective item)
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,54 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
", result);
|
", 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]
|
[Fact]
|
||||||
public void IgnoresMultipleDeepCombinators()
|
public void IgnoresMultipleDeepCombinators()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue