Merge in 'release/5.0-preview8' changes
This commit is contained in:
commit
4d23ec028e
|
|
@ -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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -74,26 +74,27 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
var resultBuilder = new StringBuilder();
|
var resultBuilder = new StringBuilder();
|
||||||
var previousInsertionPosition = 0;
|
var previousInsertionPosition = 0;
|
||||||
|
|
||||||
var scopeInsertionPositionsVisitor = new FindScopeInsertionPositionsVisitor(stylesheet);
|
var scopeInsertionPositionsVisitor = new FindScopeInsertionEdits(stylesheet);
|
||||||
scopeInsertionPositionsVisitor.Visit();
|
scopeInsertionPositionsVisitor.Visit();
|
||||||
foreach (var (currentInsertionPosition, insertionType) in scopeInsertionPositionsVisitor.InsertionPositions)
|
foreach (var edit in scopeInsertionPositionsVisitor.Edits)
|
||||||
{
|
{
|
||||||
resultBuilder.Append(inputText.Substring(previousInsertionPosition, currentInsertionPosition - previousInsertionPosition));
|
resultBuilder.Append(inputText.Substring(previousInsertionPosition, edit.Position - previousInsertionPosition));
|
||||||
|
previousInsertionPosition = edit.Position;
|
||||||
switch (insertionType)
|
|
||||||
|
switch (edit)
|
||||||
{
|
{
|
||||||
case ScopeInsertionType.Selector:
|
case InsertSelectorScopeEdit _:
|
||||||
resultBuilder.AppendFormat("[{0}]", cssScope);
|
resultBuilder.AppendFormat("[{0}]", cssScope);
|
||||||
break;
|
break;
|
||||||
case ScopeInsertionType.KeyframesName:
|
case InsertKeyframesNameScopeEdit _:
|
||||||
resultBuilder.AppendFormat("-{0}", cssScope);
|
resultBuilder.AppendFormat("-{0}", cssScope);
|
||||||
break;
|
break;
|
||||||
|
case DeleteContentEdit deleteContentEdit:
|
||||||
|
previousInsertionPosition += deleteContentEdit.DeleteLength;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unknown insertion type: '{insertionType}'");
|
throw new NotImplementedException($"Unknown edit type: '{edit}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
previousInsertionPosition = currentInsertionPosition;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resultBuilder.Append(inputText.Substring(previousInsertionPosition));
|
resultBuilder.Append(inputText.Substring(previousInsertionPosition));
|
||||||
|
|
@ -118,19 +119,13 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ScopeInsertionType
|
private class FindScopeInsertionEdits : Visitor
|
||||||
{
|
{
|
||||||
Selector,
|
public List<CssEdit> Edits { get; } = new List<CssEdit>();
|
||||||
KeyframesName,
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FindScopeInsertionPositionsVisitor : Visitor
|
|
||||||
{
|
|
||||||
public List<(int, ScopeInsertionType)> InsertionPositions { get; } = new List<(int, ScopeInsertionType)>();
|
|
||||||
|
|
||||||
private readonly HashSet<string> _keyframeIdentifiers;
|
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
|
// Before we start, we need to know the full set of keyframe names declared in this document
|
||||||
var keyframesIdentifiersVisitor = new FindKeyframesIdentifiersVisitor(root);
|
var keyframesIdentifiersVisitor = new FindKeyframesIdentifiersVisitor(root);
|
||||||
|
|
@ -146,11 +141,34 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
// ".first child," containing two simple selectors: ".first" and "child"
|
// ".first child," containing two simple selectors: ".first" and "child"
|
||||||
// ".second", containing one simple selector: ".second"
|
// ".second", containing one simple selector: ".second"
|
||||||
// Our goal is to insert immediately after the final simple selector within each selector
|
// 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)
|
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)
|
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"
|
// Whenever we see "@keyframes something { ... }", we want to insert right after "something"
|
||||||
if (TryFindKeyframesIdentifier(item, out var identifier))
|
if (TryFindKeyframesIdentifier(item, out var identifier))
|
||||||
{
|
{
|
||||||
InsertionPositions.Add((identifier.AfterEnd, ScopeInsertionType.KeyframesName));
|
Edits.Add(new InsertKeyframesNameScopeEdit { Position = identifier.AfterEnd });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -183,7 +201,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
.Where(x => x.TokenType == CssTokenType.Identifier && _keyframeIdentifiers.Contains(x.Text));
|
.Where(x => x.TokenType == CssTokenType.Identifier && _keyframeIdentifiers.Contains(x.Text));
|
||||||
foreach (var token in animationNameTokens)
|
foreach (var token in animationNameTokens)
|
||||||
{
|
{
|
||||||
InsertionPositions.Add((token.AfterEnd, ScopeInsertionType.KeyframesName));
|
Edits.Add(new InsertKeyframesNameScopeEdit { Position = token.AfterEnd });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -75,6 +75,54 @@ namespace Microsoft.AspNetCore.Razor.Tools
|
||||||
", result);
|
", 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]
|
[Fact]
|
||||||
public void HandlesAtBlocks()
|
public void HandlesAtBlocks()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -55,17 +55,19 @@ namespace Microsoft.AspNetCore.Razor.Tasks
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ToBase36(byte[] hash)
|
private static string ToBase36(byte[] hash)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
const string chars = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||||
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
var dividend = new BigInteger(hash.AsSpan().Slice(0,8).ToArray());
|
var result = new char[10];
|
||||||
while (dividend > 36)
|
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);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue