aspnetcore/src/Microsoft.AspNetCore.Razor..../Legacy/Block.cs

247 lines
7.5 KiB
C#

// 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;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
internal class Block : SyntaxTreeNode
{
public Block(BlockBuilder source)
: this(source.Type, source.Children, source.ChunkGenerator)
{
source.Reset();
}
protected Block(BlockKindInternal? type, IReadOnlyList<SyntaxTreeNode> children, IParentChunkGenerator generator)
{
if (type == null)
{
throw new InvalidOperationException(LegacyResources.Block_Type_Not_Specified);
}
Type = type.Value;
Children = children;
ChunkGenerator = generator;
// Perf: Avoid allocating an enumerator.
for (var i = 0; i < Children.Count; i++)
{
Children[i].Parent = this;
}
}
public IParentChunkGenerator ChunkGenerator { get; }
public BlockKindInternal Type { get; }
public IReadOnlyList<SyntaxTreeNode> Children { get; }
public override bool IsBlock => true;
public override SourceLocation Start
{
get
{
var child = Children.FirstOrDefault();
if (child == null)
{
return SourceLocation.Zero;
}
else
{
return child.Start;
}
}
}
public override int Length => Children.Sum(child => child.Length);
public virtual IEnumerable<Span> Flatten()
{
// Perf: Avoid allocating an enumerator.
for (var i = 0; i < Children.Count; i++)
{
var element = Children[i];
var span = element as Span;
if (span != null)
{
yield return span;
}
else
{
var block = element as Block;
foreach (Span childSpan in block.Flatten())
{
yield return childSpan;
}
}
}
}
public Span FindFirstDescendentSpan()
{
SyntaxTreeNode current = this;
while (current != null && current.IsBlock)
{
current = ((Block)current).Children.FirstOrDefault();
}
return current as Span;
}
public Span FindLastDescendentSpan()
{
SyntaxTreeNode current = this;
while (current != null && current.IsBlock)
{
current = ((Block)current).Children.LastOrDefault();
}
return current as Span;
}
public virtual Span LocateOwner(SourceChange change) => LocateOwner(change, Children);
protected static Span LocateOwner(SourceChange change, IEnumerable<SyntaxTreeNode> elements)
{
// Ask each child recursively
Span owner = null;
foreach (var element in elements)
{
var span = element as Span;
if (span == null)
{
owner = ((Block)element).LocateOwner(change);
}
else
{
if (change.Span.AbsoluteIndex < span.Start.AbsoluteIndex)
{
// 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
break;
}
owner = span.EditHandler.OwnsChange(span, change) ? span : owner;
}
if (owner != null)
{
break;
}
}
return owner;
}
public override string ToString()
{
return string.Format(
CultureInfo.CurrentCulture,
"{0} Block at {1}::{2} (Gen:{3})",
Type,
Start,
Length,
ChunkGenerator);
}
public override bool Equals(object obj)
{
var other = obj as Block;
return other != null &&
Type == other.Type &&
Equals(ChunkGenerator, other.ChunkGenerator) &&
ChildrenEqual(Children, other.Children);
}
public override int GetHashCode()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(Type);
hashCodeCombiner.Add(ChunkGenerator);
hashCodeCombiner.Add(Children);
return hashCodeCombiner;
}
private static bool ChildrenEqual(IEnumerable<SyntaxTreeNode> left, IEnumerable<SyntaxTreeNode> right)
{
IEnumerator<SyntaxTreeNode> leftEnum = left.GetEnumerator();
IEnumerator<SyntaxTreeNode> rightEnum = right.GetEnumerator();
while (leftEnum.MoveNext())
{
if (!rightEnum.MoveNext() || // More items in left than in right
!Equals(leftEnum.Current, rightEnum.Current))
{
// Nodes are not equal
return false;
}
}
if (rightEnum.MoveNext())
{
// More items in right than left
return false;
}
return true;
}
public override bool EquivalentTo(SyntaxTreeNode node)
{
var other = node as Block;
if (other == null || other.Type != Type)
{
return false;
}
return Enumerable.SequenceEqual(Children, other.Children, EquivalenceComparer.Default);
}
public override int GetEquivalenceHash()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(Type);
foreach (var child in Children)
{
hashCodeCombiner.Add(child.GetEquivalenceHash());
}
return hashCodeCombiner.CombinedHash;
}
public override void Accept(ParserVisitor visitor)
{
visitor.VisitBlock(this);
}
private class EquivalenceComparer : IEqualityComparer<SyntaxTreeNode>
{
public static readonly EquivalenceComparer Default = new EquivalenceComparer();
private EquivalenceComparer()
{
}
public bool Equals(SyntaxTreeNode nodeX, SyntaxTreeNode nodeY)
{
if (nodeX == nodeY)
{
return true;
}
return nodeX != null && nodeX.EquivalentTo(nodeY);
}
public int GetHashCode(SyntaxTreeNode node)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
return node.GetEquivalenceHash();
}
}
}
}