// 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 Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Razor.Language.Syntax { /// /// Immutable abstract representation of a span of text. For example, in an error diagnostic that reports a /// location, it could come from a parsed string, text from a tool editor buffer, etc. /// internal readonly struct TextSpan : IEquatable, IComparable { /// /// Creates a TextSpan instance beginning with the position Start and having the Length /// specified with . /// public TextSpan(int start, int length) { if (start < 0) { throw new ArgumentOutOfRangeException(nameof(start)); } if (start + length < start) { throw new ArgumentOutOfRangeException(nameof(length)); } Start = start; Length = length; } /// /// Start point of the span. /// public int Start { get; } /// /// End of the span. /// public int End => Start + Length; /// /// Length of the span. /// public int Length { get; } /// /// Determines whether or not the span is empty. /// public bool IsEmpty => Length == 0; /// /// Determines whether the position lies within the span. /// /// /// The position to check. /// /// /// true if the position is greater than or equal to Start and strictly less /// than End, otherwise false. /// public bool Contains(int position) { return unchecked((uint)(position - Start) < (uint)Length); } /// /// Determines whether falls completely within this span. /// /// /// The span to check. /// /// /// true if the specified span falls completely within this span, otherwise false. /// public bool Contains(TextSpan span) { return span.Start >= Start && span.End <= End; } /// /// Determines whether overlaps this span. Two spans are considered to overlap /// if they have positions in common and neither is empty. Empty spans do not overlap with any /// other span. /// /// /// The span to check. /// /// /// true if the spans overlap, otherwise false. /// public bool OverlapsWith(TextSpan span) { var overlapStart = Math.Max(Start, span.Start); var overlapEnd = Math.Min(End, span.End); return overlapStart < overlapEnd; } /// /// Returns the overlap with the given span, or null if there is no overlap. /// /// /// The span to check. /// /// /// The overlap of the spans, or null if the overlap is empty. /// public TextSpan? Overlap(TextSpan span) { var overlapStart = Math.Max(Start, span.Start); var overlapEnd = Math.Min(End, span.End); return overlapStart < overlapEnd ? FromBounds(overlapStart, overlapEnd) : (TextSpan?)null; } /// /// Determines whether intersects this span. Two spans are considered to /// intersect if they have positions in common or the end of one span /// coincides with the start of the other span. /// /// /// The span to check. /// /// /// true if the spans intersect, otherwise false. /// public bool IntersectsWith(TextSpan span) { return span.Start <= End && span.End >= Start; } /// /// Determines whether intersects this span. /// A position is considered to intersect if it is between the start and /// end positions (inclusive) of this span. /// /// /// The position to check. /// /// /// true if the position intersects, otherwise false. /// public bool IntersectsWith(int position) { return unchecked((uint)(position - Start) <= (uint)Length); } /// /// Returns the intersection with the given span, or null if there is no intersection. /// /// /// The span to check. /// /// /// The intersection of the spans, or null if the intersection is empty. /// public TextSpan? Intersection(TextSpan span) { var intersectStart = Math.Max(Start, span.Start); var intersectEnd = Math.Min(End, span.End); return intersectStart <= intersectEnd ? FromBounds(intersectStart, intersectEnd) : (TextSpan?)null; } /// /// Creates a new from and positions as opposed to a position and length. /// /// The returned TextSpan contains the range with inclusive, /// and exclusive. /// public static TextSpan FromBounds(int start, int end) { if (start < 0) { throw new ArgumentOutOfRangeException(nameof(start), "start must not be negative"); } if (end < start) { throw new ArgumentOutOfRangeException(nameof(end), "end must not be less than start"); } return new TextSpan(start, end - start); } /// /// Determines if two instances of are the same. /// public static bool operator ==(TextSpan left, TextSpan right) { return left.Equals(right); } /// /// Determines if two instances of are different. /// public static bool operator !=(TextSpan left, TextSpan right) { return !left.Equals(right); } /// /// Determines if current instance of is equal to another. /// public bool Equals(TextSpan other) { return Start == other.Start && Length == other.Length; } /// /// Determines if current instance of is equal to another. /// public override bool Equals(object obj) { return obj is TextSpan && Equals((TextSpan)obj); } /// /// Produces a hash code for . /// public override int GetHashCode() { var combiner = new HashCodeCombiner(); combiner.Add(Start); combiner.Add(Length); return combiner.CombinedHash; } /// /// Provides a string representation for . /// public override string ToString() { return $"[{Start}..{End})"; } /// /// Compares current instance of with another. /// public int CompareTo(TextSpan other) { var diff = Start - other.Start; if (diff != 0) { return diff; } return Length - other.Length; } } }