Merge in 'release/5.0-preview8' changes

This commit is contained in:
dotnet-bot 2020-07-24 21:55:52 +00:00
commit 4d23ec028e
3 changed files with 119 additions and 33 deletions

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -74,26 +74,27 @@ namespace Microsoft.AspNetCore.Razor.Tools
var resultBuilder = new StringBuilder();
var previousInsertionPosition = 0;
var scopeInsertionPositionsVisitor = new FindScopeInsertionPositionsVisitor(stylesheet);
var scopeInsertionPositionsVisitor = new FindScopeInsertionEdits(stylesheet);
scopeInsertionPositionsVisitor.Visit();
foreach (var (currentInsertionPosition, insertionType) in scopeInsertionPositionsVisitor.InsertionPositions)
foreach (var edit in scopeInsertionPositionsVisitor.Edits)
{
resultBuilder.Append(inputText.Substring(previousInsertionPosition, currentInsertionPosition - previousInsertionPosition));
switch (insertionType)
resultBuilder.Append(inputText.Substring(previousInsertionPosition, edit.Position - previousInsertionPosition));
previousInsertionPosition = edit.Position;
switch (edit)
{
case ScopeInsertionType.Selector:
case InsertSelectorScopeEdit _:
resultBuilder.AppendFormat("[{0}]", cssScope);
break;
case ScopeInsertionType.KeyframesName:
case InsertKeyframesNameScopeEdit _:
resultBuilder.AppendFormat("-{0}", cssScope);
break;
case DeleteContentEdit deleteContentEdit:
previousInsertionPosition += deleteContentEdit.DeleteLength;
break;
default:
throw new NotImplementedException($"Unknown insertion type: '{insertionType}'");
throw new NotImplementedException($"Unknown edit type: '{edit}'");
}
previousInsertionPosition = currentInsertionPosition;
}
resultBuilder.Append(inputText.Substring(previousInsertionPosition));
@ -118,19 +119,13 @@ namespace Microsoft.AspNetCore.Razor.Tools
return false;
}
private enum ScopeInsertionType
private class FindScopeInsertionEdits : Visitor
{
Selector,
KeyframesName,
}
private class FindScopeInsertionPositionsVisitor : Visitor
{
public List<(int, ScopeInsertionType)> InsertionPositions { get; } = new List<(int, ScopeInsertionType)>();
public List<CssEdit> Edits { get; } = new List<CssEdit>();
private readonly HashSet<string> _keyframeIdentifiers;
public FindScopeInsertionPositionsVisitor(ComplexItem root) : base(root)
public FindScopeInsertionEdits(ComplexItem root) : base(root)
{
// Before we start, we need to know the full set of keyframe names declared in this document
var keyframesIdentifiersVisitor = new FindKeyframesIdentifiersVisitor(root);
@ -146,11 +141,34 @@ namespace Microsoft.AspNetCore.Razor.Tools
// ".first child," containing two simple selectors: ".first" and "child"
// ".second", containing one simple selector: ".second"
// Our goal is to insert immediately after the final simple selector within each selector
var lastSimpleSelector = selector.Children.OfType<SimpleSelector>().LastOrDefault();
// 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 lastSimpleSelector = allSimpleSelectors.TakeWhile(s => s != firstDeepCombinator).LastOrDefault();
if (lastSimpleSelector != null)
{
InsertionPositions.Add((lastSimpleSelector.AfterEnd, ScopeInsertionType.Selector));
Edits.Add(new InsertSelectorScopeEdit { Position = lastSimpleSelector.AfterEnd });
}
else if (firstDeepCombinator != null)
{
// For a leading deep combinator, we want to insert the scope attribute at the start
// Otherwise the result would be a CSS rule that isn't scoped at all
Edits.Add(new InsertSelectorScopeEdit { Position = firstDeepCombinator.Start });
}
// Also remove the deep combinator if we matched one
if (firstDeepCombinator != null)
{
Edits.Add(new DeleteContentEdit { Position = firstDeepCombinator.Start, DeleteLength = firstDeepCombinator.Length });
}
}
private static bool IsDeepCombinator(string simpleSelectorText)
{
return string.Equals(simpleSelectorText, "::deep", StringComparison.Ordinal);
}
protected override void VisitAtDirective(AtDirective item)
@ -158,7 +176,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
// Whenever we see "@keyframes something { ... }", we want to insert right after "something"
if (TryFindKeyframesIdentifier(item, out var identifier))
{
InsertionPositions.Add((identifier.AfterEnd, ScopeInsertionType.KeyframesName));
Edits.Add(new InsertKeyframesNameScopeEdit { Position = identifier.AfterEnd });
}
else
{
@ -183,7 +201,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
.Where(x => x.TokenType == CssTokenType.Identifier && _keyframeIdentifiers.Contains(x.Text));
foreach (var token in animationNameTokens)
{
InsertionPositions.Add((token.AfterEnd, ScopeInsertionType.KeyframesName));
Edits.Add(new InsertKeyframesNameScopeEdit { Position = token.AfterEnd });
}
break;
default:
@ -273,5 +291,23 @@ namespace Microsoft.AspNetCore.Razor.Tools
}
}
}
private abstract class CssEdit
{
public int Position { get; set; }
}
private class InsertSelectorScopeEdit : CssEdit
{
}
private class InsertKeyframesNameScopeEdit : CssEdit
{
}
private class DeleteContentEdit : CssEdit
{
public int DeleteLength { get; set; }
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
@ -75,6 +75,54 @@ namespace Microsoft.AspNetCore.Razor.Tools
", result);
}
[Fact]
public void RespectsDeepCombinator()
{
// Arrange/act
var result = RewriteCssCommand.AddScopeToSelectors(@"
.first ::deep .second { color: red; }
a ::deep b, c ::deep d { color: blue; }
", "TestScope");
// Assert
Assert.Equal(@"
.first[TestScope] .second { color: red; }
a[TestScope] b, c[TestScope] d { color: blue; }
", result);
}
[Fact]
public void IgnoresMultipleDeepCombinators()
{
// Arrange/act
var result = RewriteCssCommand.AddScopeToSelectors(@"
.first ::deep .second ::deep .third { color:red; }
", "TestScope");
// Assert
Assert.Equal(@"
.first[TestScope] .second ::deep .third { color:red; }
", result);
}
[Fact]
public void RespectsDeepCombinatorWithSpacesAndComments()
{
// Arrange/act
var result = RewriteCssCommand.AddScopeToSelectors(@"
.a .b /* comment ::deep 1 */ ::deep /* comment ::deep 2 */ .c /* ::deep */ .d { color: red; }
::deep * { color: blue; } /* Leading deep combinator */
another ::deep { color: green } /* Trailing deep combinator */
", "TestScope");
// Assert
Assert.Equal(@"
.a .b[TestScope] /* comment ::deep 1 */ /* comment ::deep 2 */ .c /* ::deep */ .d { color: red; }
[TestScope] * { color: blue; } /* Leading deep combinator */
another[TestScope] { color: green } /* Trailing deep combinator */
", result);
}
[Fact]
public void HandlesAtBlocks()
{

View File

@ -55,17 +55,19 @@ namespace Microsoft.AspNetCore.Razor.Tasks
return builder.ToString();
}
private string ToBase36(byte[] hash)
private static string ToBase36(byte[] hash)
{
var builder = new StringBuilder();
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
var dividend = new BigInteger(hash.AsSpan().Slice(0,8).ToArray());
while (dividend > 36)
const string chars = "0123456789abcdefghijklmnopqrstuvwxyz";
var result = new char[10];
var dividend = BigInteger.Abs(new BigInteger(hash.AsSpan().Slice(0, 9).ToArray()));
for (var i = 0; i < 10; i++)
{
dividend = BigInteger.DivRem(dividend, 36, out var remainder);
builder.Insert(0, chars[Math.Abs(((int)remainder))]);
result[i] = chars[(int)remainder];
}
return builder.ToString();
return new string(result);
}
}
}