| File: Language\Legacy\LegacySyntaxNodeExtensions.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.Collections.Frozen; using System.Collections.Generic; using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Razor.Language.Syntax; namespace Microsoft.AspNetCore.Razor.Language.Legacy; internal static partial class LegacySyntaxNodeExtensions { private class SpanData { public SyntaxNodeOrToken? Previous; public bool PreviousComputed; public SyntaxNodeOrToken? Next; public bool NextComputed; } /// <summary> /// Caches previous/next span result for a particular node. A conditional weak table /// is used to avoid adding fields to all syntax nodes. /// </summary> private static readonly ConditionalWeakTable<SyntaxNode, SpanData> s_spanDataTable = new(); private static readonly FrozenSet<SyntaxKind> s_transitionSpanKinds = FrozenSet.Create( SyntaxKind.CSharpTransition, SyntaxKind.MarkupTransition); private static readonly FrozenSet<SyntaxKind> s_commentSpanKinds = FrozenSet.Create( SyntaxKind.RazorCommentTransition, SyntaxKind.RazorCommentStar, SyntaxKind.RazorCommentLiteral); private static readonly FrozenSet<SyntaxKind> s_codeSpanKinds = FrozenSet.Create( SyntaxKind.CSharpStatementLiteral, SyntaxKind.CSharpExpressionLiteral, SyntaxKind.CSharpEphemeralTextLiteral); private static readonly FrozenSet<SyntaxKind> s_markupSpanKinds = FrozenSet.Create( SyntaxKind.MarkupTextLiteral, SyntaxKind.MarkupEphemeralTextLiteral); private static readonly FrozenSet<SyntaxKind> s_allSpanKinds = CreateAllSpanKindsSet(); private static FrozenSet<SyntaxKind> CreateAllSpanKindsSet() { var set = new HashSet<SyntaxKind>(); set.UnionWith(s_transitionSpanKinds); set.Add(SyntaxKind.RazorMetaCode); set.UnionWith(s_commentSpanKinds); set.UnionWith(s_codeSpanKinds); set.UnionWith(s_markupSpanKinds); set.Add(SyntaxKind.UnclassifiedTextLiteral); return set.ToFrozenSet(); } internal static ISpanChunkGenerator? GetChunkGenerator(this SyntaxNode node) => (node as ILegacySyntax)?.ChunkGenerator; public static SpanEditHandler? GetEditHandler(this SyntaxNode node) => (node as ILegacySyntax)?.EditHandler; [Obsolete("Use FindToken or FindInnermostNode instead", error: false)] public static SyntaxNode? LocateOwner(this SyntaxNode node, SourceChange change) { ArgHelper.ThrowIfNull(node); if (change.Span.AbsoluteIndex < node.Position) { // Early escape for cases where changes overlap multiple spans // In those cases, the span will return false, and we don't want to search the whole tree // So if the current span starts after the change, we know we've searched as far as we need to return null; } if (node.EndPosition < change.Span.AbsoluteIndex) { // no need to look into this node as it completely precedes the change return null; } if (node.IsSpanKind()) { var editHandler = node.GetEditHandler() ?? SpanEditHandler.GetDefault(AcceptedCharactersInternal.Any); return editHandler.OwnsChange(node, change) ? node : null; } return node switch { MarkupStartTagSyntax startTag => LocateOwnerForSyntaxList(startTag.LegacyChildren, change), MarkupEndTagSyntax endTag => LocateOwnerForSyntaxList(endTag.LegacyChildren, change), MarkupTagHelperStartTagSyntax startTagHelper => LocateOwnerForSyntaxList(startTagHelper.LegacyChildren, change), MarkupTagHelperEndTagSyntax endTagHelper => LocateOwnerForSyntaxList(endTagHelper.LegacyChildren, change), _ => LocateOwnerForChildSyntaxList(node.ChildNodesAndTokens(), change) }; static SyntaxNode? LocateOwnerForSyntaxList(in SyntaxList<RazorSyntaxNode> list, SourceChange change) { foreach (var child in list) { if (child.LocateOwner(change) is { } owner) { return owner; } } return null; } static SyntaxNode? LocateOwnerForChildSyntaxList(in ChildSyntaxList list, SourceChange change) { foreach (var child in list) { if (child.AsNode(out var node) && node.LocateOwner(change) is { } owner) { return owner; } } return null; } } public static bool IsTransitionSpanKind(this SyntaxNode node) { ArgHelper.ThrowIfNull(node); return s_transitionSpanKinds.Contains(node.Kind); } public static bool IsMetaCodeSpanKind(this SyntaxNodeOrToken nodeOrToken) { return nodeOrToken.AsNode(out var node) && node.IsMetaCodeSpanKind(); } public static bool IsMetaCodeSpanKind(this SyntaxNode node) { ArgHelper.ThrowIfNull(node); return node.Kind is SyntaxKind.RazorMetaCode; } public static bool IsCommentSpanKind(this SyntaxNode node) { ArgHelper.ThrowIfNull(node); return s_commentSpanKinds.Contains(node.Kind); } public static bool IsCodeSpanKind(this SyntaxNode node) { ArgHelper.ThrowIfNull(node); return s_codeSpanKinds.Contains(node.Kind); } public static bool IsMarkupSpanKind(this SyntaxNode node) { ArgHelper.ThrowIfNull(node); return s_markupSpanKinds.Contains(node.Kind); } public static bool IsNoneSpanKind(this SyntaxNode node) { ArgHelper.ThrowIfNull(node); return node.Kind is SyntaxKind.UnclassifiedTextLiteral; } public static bool IsSpanKind(this SyntaxNode node) => s_allSpanKinds.Contains(node.Kind); public static bool IsSpanKind(this SyntaxNodeOrToken nodeOrToken) => s_allSpanKinds.Contains(nodeOrToken.Kind); private static IEnumerable<SyntaxNodeOrToken> FlattenSpansInReverse(this SyntaxNode node) { using var stack = new ChildSyntaxListReversedEnumeratorStack(node); while (stack.TryGetNextNodeOrToken(out var nextNode)) { if (nextNode.AsNode() is MarkupStartTagSyntax startTag) { var children = startTag.LegacyChildren; for (var i = children.Count - 1; i >= 0; i--) { var tagChild = children[i]; if (tagChild.IsSpanKind()) { yield return tagChild; } } } else if (nextNode.AsNode() is MarkupEndTagSyntax endTag) { var children = endTag.LegacyChildren; for (var i = children.Count - 1; i >= 0; i--) { var tagChild = children[i]; if (tagChild.IsSpanKind()) { yield return tagChild; } } } else if (nextNode.IsSpanKind()) { yield return nextNode; } } } public static IEnumerable<SyntaxNode> FlattenSpans(this SyntaxNode node) { ArgHelper.ThrowIfNull(node); #pragma warning disable CS0618 // Iterator method requires IEnumerable (ref struct cannot span yield points) foreach (var child in node.DescendantNodesAsIEnumerable()) #pragma warning restore CS0618 { if (child is MarkupStartTagSyntax startTag) { foreach (var tagChild in startTag.LegacyChildren) { if (tagChild.IsSpanKind()) { yield return tagChild; } } } else if (child is MarkupEndTagSyntax endTag) { foreach (var tagChild in endTag.LegacyChildren) { if (tagChild.IsSpanKind()) { yield return tagChild; } } } else if (child.IsSpanKind()) { yield return child; } } } public static SyntaxNodeOrToken? PreviousSpan(this SyntaxNode node) { ArgHelper.ThrowIfNull(node); var spanData = s_spanDataTable.GetOrCreateValue(node); lock (spanData) { if (spanData.PreviousComputed) { return spanData.Previous; } var parent = node.Parent; while (parent is not null) { foreach (var span in parent.FlattenSpansInReverse()) { if (span.EndPosition <= node.Position && span != node) { spanData.PreviousComputed = true; spanData.Previous = span; return span; } } parent = parent.Parent; } spanData.PreviousComputed = true; spanData.Previous = default; return default; } } public static SyntaxNodeOrToken? NextSpan(this SyntaxNode node) { ArgHelper.ThrowIfNull(node); var spanData = s_spanDataTable.GetOrCreateValue(node); lock (spanData) { if (spanData.NextComputed) { return spanData.Next; } var parent = node.Parent; while (parent is not null) { foreach (var span in parent.FlattenSpans()) { if (span.Position >= node.Position && span != node) { spanData.NextComputed = true; spanData.Next = span; return span; } } parent = parent.Parent; } spanData.NextComputed = true; spanData.Next = default; return null; } } }