| File: Language\Extensions\ViewCssScopePass.cs | Web Access |
| Project: src\roslyn\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Threading; using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Razor.Language.Extensions; internal sealed class ViewCssScopePass : IntermediateNodePassBase, IRazorOptimizationPass { // Runs after taghelpers are bound public override int Order => 110; protected override void ExecuteCore( RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode, CancellationToken cancellationToken) { var cssScope = codeDocument.CodeGenerationOptions.CssScope; if (string.IsNullOrEmpty(cssScope)) { return; } if (!string.Equals(documentNode.DocumentKind, "mvc.1.0.view", StringComparison.Ordinal) && !string.Equals(documentNode.DocumentKind, "mvc.1.0.razor-page", StringComparison.Ordinal)) { return; } var scopeWithSeparator = " " + cssScope; IntermediateToken? previousToken = null; foreach (var node in documentNode.FindDescendantNodes<HtmlContentIntermediateNode>()) { ProcessElement(node, scopeWithSeparator, ref previousToken); } } private void ProcessElement(HtmlContentIntermediateNode node, string cssScope, ref IntermediateToken? previousToken) { // Add a minimized attribute whose name is simply the CSS scope for (var i = 0; i < node.Children.Count; i++) { var child = node.Children[i]; if (child is HtmlIntermediateToken token) { if (IsValidElement(token, previousToken)) { node.Children.Insert(i + 1, IntermediateNodeFactory.HtmlToken(cssScope)); i++; } previousToken = token; } } static bool IsValidElement(IntermediateToken token, IntermediateToken? previousToken) { var content = token.Content.AsSpan(); // `<!tag` is lowered into separate nodes `<` and `tag`, we process the latter. if (previousToken?.Content == "<" && content is [not '<', ..]) { // There is no leading `<` to trim. } // Otherwise process the token if it starts with `<` but ignore if it is *only* `<`. else if (content is ['<', _, ..]) { // Trim the leading `<`. content = content[1..]; } else { return false; } /// <remarks> /// We want to avoid adding the CSS scope to elements that do not appear /// within the body element of the document. When this pass executes over the /// nodes, we don't have the ability to store whether we are a descendant of a /// `head` or `body` element so it is not possible to discern whether the tag /// is valid this way. Instead, we go for a straight-forward check on the tag /// name that we are currently inspecting. /// </remarks> return !content.StartsWith("/".AsSpan(), StringComparison.Ordinal) && !content.StartsWith("!".AsSpan(), StringComparison.Ordinal) && !content.Equals("head".AsSpan(), StringComparison.OrdinalIgnoreCase) && !content.Equals("meta".AsSpan(), StringComparison.OrdinalIgnoreCase) && !content.Equals("title".AsSpan(), StringComparison.OrdinalIgnoreCase) && !content.Equals("link".AsSpan(), StringComparison.OrdinalIgnoreCase) && !content.Equals("base".AsSpan(), StringComparison.OrdinalIgnoreCase) && !content.Equals("script".AsSpan(), StringComparison.OrdinalIgnoreCase) && !content.Equals("style".AsSpan(), StringComparison.OrdinalIgnoreCase) && !content.Equals("html".AsSpan(), StringComparison.OrdinalIgnoreCase); } } }