Clean up SourceLocation

This change makes SourceLocation public and moves some of the undesirable
functions off of the class and into the .Legacy namespace.
This commit is contained in:
Ryan Nowak 2017-01-03 09:56:26 -08:00
parent e84bc66700
commit 6b075880ce
12 changed files with 280 additions and 489 deletions

View File

@ -1142,7 +1142,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
if (!seenClose)
{
Context.ErrorSink.OnError(
SourceLocation.Advance(tag.Item2, "<"),
SourceLocationTracker.Advance(tag.Item2, "<"),
LegacyResources.FormatParseError_UnfinishedTag(tag.Item1.Content),
Math.Max(tag.Item1.Content.Length, 1));
}
@ -1277,7 +1277,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
if (!Optional(HtmlSymbolType.CloseAngle))
{
Context.ErrorSink.OnError(
SourceLocation.Advance(tagStart, "</"),
SourceLocationTracker.Advance(tagStart, "</"),
LegacyResources.FormatParseError_UnfinishedTag(ScriptTagName),
ScriptTagName.Length);
}
@ -1335,14 +1335,14 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
if (currentTag != null)
{
Context.ErrorSink.OnError(
SourceLocation.Advance(currentTag.Item2, "<"),
SourceLocationTracker.Advance(currentTag.Item2, "<"),
LegacyResources.FormatParseError_MissingEndTag(currentTag.Item1.Content),
currentTag.Item1.Content.Length);
}
else
{
Context.ErrorSink.OnError(
SourceLocation.Advance(tagStart, "</"),
SourceLocationTracker.Advance(tagStart, "</"),
LegacyResources.FormatParseError_UnexpectedEndTag(tagName),
tagName.Length);
}
@ -1360,7 +1360,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
}
var tag = tags.Pop();
Context.ErrorSink.OnError(
SourceLocation.Advance(tag.Item2, "<"),
SourceLocationTracker.Advance(tag.Item2, "<"),
LegacyResources.FormatParseError_MissingEndTag(tag.Item1.Content),
tag.Item1.Content.Length);
}

View File

@ -1,194 +0,0 @@
// 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.Globalization;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
/// <summary>
/// A location in a Razor file.
/// </summary>
internal struct SourceLocation : IEquatable<SourceLocation>
{
/// <summary>
/// An undefined <see cref="SourceLocation"/>.
/// </summary>
public static readonly SourceLocation Undefined =
new SourceLocation(absoluteIndex: -1, lineIndex: -1, characterIndex: -1);
/// <summary>
/// A <see cref="SourceLocation"/> with <see cref="AbsoluteIndex"/>, <see cref="LineIndex"/>, and
/// <see cref="CharacterIndex"/> initialized to 0.
/// </summary>
public static readonly SourceLocation Zero =
new SourceLocation(absoluteIndex: 0, lineIndex: 0, characterIndex: 0);
/// <summary>
/// Initializes a new instance of <see cref="SourceLocation"/>.
/// </summary>
/// <param name="absoluteIndex">The absolute index.</param>
/// <param name="lineIndex">The line index.</param>
/// <param name="characterIndex">The character index.</param>
public SourceLocation(int absoluteIndex, int lineIndex, int characterIndex)
: this(filePath: null, absoluteIndex: absoluteIndex, lineIndex: lineIndex, characterIndex: characterIndex)
{
}
/// <summary>
/// Initializes a new instance of <see cref="SourceLocation"/>.
/// </summary>
/// <param name="filePath">The file path.</param>
/// <param name="absoluteIndex">The absolute index.</param>
/// <param name="lineIndex">The line index.</param>
/// <param name="characterIndex">The character index.</param>
public SourceLocation(string filePath, int absoluteIndex, int lineIndex, int characterIndex)
{
FilePath = filePath;
AbsoluteIndex = absoluteIndex;
LineIndex = lineIndex;
CharacterIndex = characterIndex;
}
/// <summary>
/// Path of the file.
/// </summary>
/// <remarks>When <c>null</c>, the parser assumes the location is in the file currently being processed.
/// </remarks>
public string FilePath { get; set; }
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public int AbsoluteIndex { get; set; }
/// <summary>
/// Gets the 1-based index of the line referred to by this Source Location.
/// </summary>
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public int LineIndex { get; set; }
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public int CharacterIndex { get; set; }
/// <inheritdoc />
public override string ToString()
{
return string.Format(
CultureInfo.CurrentCulture,
"({0}:{1},{2})",
AbsoluteIndex,
LineIndex,
CharacterIndex);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is SourceLocation &&
Equals((SourceLocation)obj);
}
/// <inheritdoc />
public override int GetHashCode()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(FilePath, StringComparer.Ordinal);
hashCodeCombiner.Add(AbsoluteIndex);
return hashCodeCombiner;
}
/// <inheritdoc />
public bool Equals(SourceLocation other)
{
// LineIndex and CharacterIndex can be calculated from AbsoluteIndex and the document content.
return string.Equals(FilePath, other.FilePath, StringComparison.Ordinal) &&
AbsoluteIndex == other.AbsoluteIndex;
}
/// <summary>
/// Advances the <see cref="SourceLocation"/> by the length of the <paramref name="text" />.
/// </summary>
/// <param name="left">The <see cref="SourceLocation"/> to advance.</param>
/// <param name="text">The <see cref="string"/> to advance <paramref name="left"/> by.</param>
/// <returns>The advanced <see cref="SourceLocation"/>.</returns>
public static SourceLocation Advance(SourceLocation left, string text)
{
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
var tracker = new SourceLocationTracker(left);
tracker.UpdateLocation(text);
return tracker.CurrentLocation;
}
/// <summary>
/// Adds two <see cref="SourceLocation"/>s.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns>A <see cref="SourceLocation"/> that is the sum of the left and right operands.</returns>
/// <exception cref="ArgumentException">if the <see cref="FilePath"/> of the left and right operands
/// are different, and neither is null.</exception>
public static SourceLocation operator +(SourceLocation left, SourceLocation right)
{
if (!string.Equals(left.FilePath, right.FilePath, StringComparison.Ordinal) &&
left.FilePath != null &&
right.FilePath != null)
{
// Throw if FilePath for left and right are different, and neither is null.
throw new ArgumentException(
LegacyResources.FormatSourceLocationFilePathDoesNotMatch(nameof(SourceLocation), "+"),
nameof(right));
}
var resultFilePath = left.FilePath ?? right.FilePath;
if (right.LineIndex > 0)
{
// Column index doesn't matter
return new SourceLocation(
resultFilePath,
left.AbsoluteIndex + right.AbsoluteIndex,
left.LineIndex + right.LineIndex,
right.CharacterIndex);
}
else
{
return new SourceLocation(
resultFilePath,
left.AbsoluteIndex + right.AbsoluteIndex,
left.LineIndex + right.LineIndex,
left.CharacterIndex + right.CharacterIndex);
}
}
/// <summary>
/// Subtracts two <see cref="SourceLocation"/>s.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns>A <see cref="SourceLocation"/> that is the difference of the left and right operands.</returns>
/// <exception cref="ArgumentException">if the <see cref="FilePath"/> of the left and right operands
/// are different.</exception>
public static SourceLocation operator -(SourceLocation left, SourceLocation right)
{
if (!string.Equals(left.FilePath, right.FilePath, StringComparison.Ordinal))
{
throw new ArgumentException(
LegacyResources.FormatSourceLocationFilePathDoesNotMatch(nameof(SourceLocation), "-"),
nameof(right));
}
var characterIndex = left.LineIndex != right.LineIndex ?
left.CharacterIndex : left.CharacterIndex - right.CharacterIndex;
return new SourceLocation(
filePath: null,
absoluteIndex: left.AbsoluteIndex - right.AbsoluteIndex,
lineIndex: left.LineIndex - right.LineIndex,
characterIndex: characterIndex);
}
}
}

View File

@ -40,6 +40,18 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
}
}
public static SourceLocation Advance(SourceLocation location, string text)
{
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
var tracker = new SourceLocationTracker(location);
tracker.UpdateLocation(text);
return tracker.CurrentLocation;
}
public void UpdateLocation(char characterRead, char nextCharacter)
{
UpdateCharacterCore(characterRead, nextCharacter);

View File

@ -218,7 +218,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
i--;
name = nameBuilder.ToString();
attributeValueStartLocation = SourceLocation.Advance(attributeValueStartLocation, name);
attributeValueStartLocation = SourceLocationTracker.Advance(attributeValueStartLocation, name);
}
else if (symbol.Type == HtmlSymbolType.Equals)
{
@ -259,9 +259,11 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
symbolStartLocation = symbol.Start;
}
attributeValueStartLocation =
symbolStartLocation +
new SourceLocation(absoluteIndex: 1, lineIndex: 0, characterIndex: 1);
attributeValueStartLocation = new SourceLocation(
symbolStartLocation.FilePath,
symbolStartLocation.AbsoluteIndex + 1,
symbolStartLocation.LineIndex,
symbolStartLocation.CharacterIndex + 1);
afterEquals = true;
}
@ -274,7 +276,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
// parser currently does not know how to handle attributes in that format. This will be addressed
// by https://github.com/aspnet/Razor/issues/123.
attributeValueStartLocation = SourceLocation.Advance(attributeValueStartLocation, symbol.Content);
attributeValueStartLocation = SourceLocationTracker.Advance(attributeValueStartLocation, symbol.Content);
}
}

View File

@ -270,7 +270,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
// End tag TagHelper that states it shouldn't have an end tag.
errorSink.OnError(
SourceLocation.Advance(tagBlock.Start, "</"),
SourceLocationTracker.Advance(tagBlock.Start, "</"),
LegacyResources.FormatTagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag(
tagName,
invalidDescriptor.TypeName,
@ -295,7 +295,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
// Could not recover, the end tag helper has no corresponding start tag, create
// an error based on the current childBlock.
errorSink.OnError(
SourceLocation.Advance(tagBlock.Start, "</"),
SourceLocationTracker.Advance(tagBlock.Start, "</"),
LegacyResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(tagName),
tagName.Length);
@ -475,7 +475,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
var trimmedStart = content.TrimStart();
var whitespace = content.Substring(0, content.Length - trimmedStart.Length);
var errorStart = SourceLocation.Advance(child.Start, whitespace);
var errorStart = SourceLocationTracker.Advance(child.Start, whitespace);
var length = trimmedStart.TrimEnd().Length;
var allowedChildren = _currentTagHelperTracker.AllowedChildren;
var allowedChildrenString = string.Join(", ", allowedChildren);
@ -589,7 +589,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
var advanceBy = IsEndTag(tagBlock) ? "</" : "<";
return SourceLocation.Advance(tagBlock.Start, advanceBy);
return SourceLocationTracker.Advance(tagBlock.Start, advanceBy);
}
private static bool IsPartialTag(Block tagBlock)
@ -733,7 +733,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
var malformedTagHelper = ((TagHelperBlockTracker)tracker).Builder;
errorSink.OnError(
SourceLocation.Advance(malformedTagHelper.Start, "<"),
SourceLocationTracker.Advance(malformedTagHelper.Start, "<"),
LegacyResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(
malformedTagHelper.TagName),
malformedTagHelper.TagName.Length);

View File

@ -0,0 +1,120 @@
// 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.Globalization;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Evolution
{
/// <summary>
/// A location in a Razor file.
/// </summary>
public struct SourceLocation : IEquatable<SourceLocation>
{
/// <summary>
/// An undefined <see cref="SourceLocation"/>.
/// </summary>
public static readonly SourceLocation Undefined =
new SourceLocation(absoluteIndex: -1, lineIndex: -1, characterIndex: -1);
/// <summary>
/// A <see cref="SourceLocation"/> with <see cref="AbsoluteIndex"/>, <see cref="LineIndex"/>, and
/// <see cref="CharacterIndex"/> initialized to 0.
/// </summary>
public static readonly SourceLocation Zero =
new SourceLocation(absoluteIndex: 0, lineIndex: 0, characterIndex: 0);
/// <summary>
/// Initializes a new instance of <see cref="SourceLocation"/>.
/// </summary>
/// <param name="absoluteIndex">The absolute index.</param>
/// <param name="lineIndex">The line index.</param>
/// <param name="characterIndex">The character index.</param>
public SourceLocation(int absoluteIndex, int lineIndex, int characterIndex)
: this(filePath: null, absoluteIndex: absoluteIndex, lineIndex: lineIndex, characterIndex: characterIndex)
{
}
/// <summary>
/// Initializes a new instance of <see cref="SourceLocation"/>.
/// </summary>
/// <param name="filePath">The file path.</param>
/// <param name="absoluteIndex">The absolute index.</param>
/// <param name="lineIndex">The line index.</param>
/// <param name="characterIndex">The character index.</param>
public SourceLocation(string filePath, int absoluteIndex, int lineIndex, int characterIndex)
{
FilePath = filePath;
AbsoluteIndex = absoluteIndex;
LineIndex = lineIndex;
CharacterIndex = characterIndex;
}
/// <summary>
/// Path of the file.
/// </summary>
/// <remarks>
/// <para>
/// When <c>null</c>, the parser assumes the location is in the file currently being processed.
/// </para>
/// <para>Set property is only accessible for deserialization purposes.</para>
/// </remarks>
public string FilePath { get; set; }
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public int AbsoluteIndex { get; set; }
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public int LineIndex { get; set; }
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public int CharacterIndex { get; set; }
/// <inheritdoc />
public override string ToString()
{
return string.Format(
CultureInfo.CurrentCulture,
"({0}:{1},{2})",
AbsoluteIndex,
LineIndex,
CharacterIndex);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is SourceLocation &&
Equals((SourceLocation)obj);
}
/// <inheritdoc />
public override int GetHashCode()
{
var hashCodeCombiner = HashCodeCombiner.Start();
hashCodeCombiner.Add(FilePath, StringComparer.Ordinal);
hashCodeCombiner.Add(AbsoluteIndex);
return hashCodeCombiner;
}
/// <inheritdoc />
public bool Equals(SourceLocation other)
{
// LineIndex and CharacterIndex can be calculated from AbsoluteIndex and the document content.
return string.Equals(FilePath, other.FilePath, StringComparison.Ordinal) &&
AbsoluteIndex == other.AbsoluteIndex;
}
public static bool operator==(SourceLocation left, SourceLocation right)
{
return left.Equals(right);
}
public static bool operator !=(SourceLocation left, SourceLocation right)
{
return !left.Equals(right);
}
}
}

View File

@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
absoluteIndex: 5 + attributeNameLength + newlineLength,
lineIndex: 1,
characterIndex: 2 + attributeNameLength);
var prefixLocation2 = SourceLocation.Advance(suffixLocation1, "'");
var prefixLocation2 = SourceLocationTracker.Advance(suffixLocation1, "'");
var suffixLocation2 = new SourceLocation(
absoluteIndex: 15 + attributeNameLength * 2 + newlineLength * 2,
lineIndex: 2,
@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
absoluteIndex: 7 + attributeNameLength + newlineLength,
lineIndex: 1,
characterIndex: 4 + attributeNameLength);
var prefixLocation2 = SourceLocation.Advance(suffixLocation1, "'");
var prefixLocation2 = SourceLocationTracker.Advance(suffixLocation1, "'");
var suffixLocation2 = new SourceLocation(
absoluteIndex: 17 + attributeNameLength * 2 + newlineLength * 2,
lineIndex: 2,

View File

@ -17,7 +17,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
public SourceLocation Start { get; private set; }
public string Content { get; }
public Span Parent { get; set; }
public int Offset { get; set; }
public override bool Equals(object obj)
{
@ -36,16 +35,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
return Content == null ? 0 : Content.GetHashCode();
}
public void OffsetStart(SourceLocation documentStart)
{
Start = documentStart + Start;
}
public void ChangeStart(SourceLocation newStart)
{
Start = newStart;
}
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0} RAW - [{1}]", Start, Content);

View File

@ -1,266 +0,0 @@
// 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 Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
{
public class SourceLocationTest
{
[Fact]
public void ConstructorWithLineAndCharacterIndexSetsAssociatedProperties()
{
// Act
var loc = new SourceLocation(0, 42, 24);
// Assert
Assert.Null(loc.FilePath);
Assert.Equal(0, loc.AbsoluteIndex);
Assert.Equal(42, loc.LineIndex);
Assert.Equal(24, loc.CharacterIndex);
}
[Fact]
public void Constructor_SetsFilePathAndIndexes()
{
// Arrange
var filePath = "some-file-path";
var absoluteIndex = 133;
var lineIndex = 23;
var characterIndex = 12;
// Act
var sourceLocation = new SourceLocation(filePath, absoluteIndex, lineIndex, characterIndex);
// Assert
Assert.Equal(filePath, sourceLocation.FilePath);
Assert.Equal(absoluteIndex, sourceLocation.AbsoluteIndex);
Assert.Equal(lineIndex, sourceLocation.LineIndex);
Assert.Equal(characterIndex, sourceLocation.CharacterIndex);
}
[Theory]
[InlineData(null)]
[InlineData("some-file")]
public void GetHashCode_ReturnsSameValue_WhenEqual(string path)
{
// Arrange
var sourceLocationA = new SourceLocation(path, 10, 3, 4);
var sourceLocationB = new SourceLocation(path, 10, 3, 4);
var sourceLocationC = new SourceLocation(path, 10, 45, 8754);
// Act
var hashCodeA = sourceLocationA.GetHashCode();
var hashCodeB = sourceLocationB.GetHashCode();
var hashCodeC = sourceLocationC.GetHashCode();
// Assert
Assert.Equal(hashCodeA, hashCodeB);
Assert.Equal(hashCodeA, hashCodeC);
}
[Fact]
public void Equals_ReturnsTrue_FilePathsNullAndAbsoluteIndicesMatch()
{
// Arrange
var sourceLocationA = new SourceLocation(10, 3, 4);
var sourceLocationB = new SourceLocation(10, 45, 8754);
// Act
var result = sourceLocationA.Equals(sourceLocationB);
// Assert
Assert.True(result);
}
[Fact]
public void Equals_ReturnsFalse_IfFilePathIsDifferent()
{
// Arrange
var sourceLocationA = new SourceLocation(10, 3, 4);
var sourceLocationB = new SourceLocation("different-file", 10, 3, 4);
// Act
var result = sourceLocationA.Equals(sourceLocationB);
// Assert
Assert.False(result);
}
[Theory]
[InlineData(null)]
[InlineData("some-file")]
public void Equals_ReturnsTrue_IfFilePathAndIndexesAreSame(string path)
{
// Arrange
var sourceLocationA = new SourceLocation(path, 10, 3, 4);
var sourceLocationB = new SourceLocation(path, 10, 3, 4);
var sourceLocationC = new SourceLocation("different-path", 10, 3, 4);
// Act
var result1 = sourceLocationA.Equals(sourceLocationB);
var result2 = sourceLocationA.Equals(sourceLocationC);
// Assert
Assert.True(result1);
Assert.False(result2);
}
[Fact]
public void Add_Throws_IfFilePathsDoNotMatch()
{
// Arrange
var sourceLocationA = new SourceLocation("a-path", 1, 1, 1);
var sourceLocationB = new SourceLocation("b-path", 1, 1, 1);
// Act and Assert
ExceptionAssert.ThrowsArgument(
() => { var result = sourceLocationA + sourceLocationB; },
"right",
$"Cannot perform '+' operations on 'SourceLocation' instances with different file paths.");
}
[Theory]
[InlineData(null)]
[InlineData("same-path")]
public void Add_IgnoresCharacterIndexIfRightLineIndexIsNonZero(string path)
{
// Arrange
var sourceLocationA = new SourceLocation(path, 1, 2, 3);
var sourceLocationB = new SourceLocation(path, 4, 5, 6);
// Act
var result = sourceLocationA + sourceLocationB;
// Assert
Assert.Equal(path, result.FilePath);
Assert.Equal(5, result.AbsoluteIndex);
Assert.Equal(7, result.LineIndex);
Assert.Equal(6, result.CharacterIndex);
}
[Theory]
[InlineData(null)]
[InlineData("same-path")]
public void Add_UsesCharacterIndexIfRightLineIndexIsZero(string path)
{
// Arrange
var sourceLocationA = new SourceLocation(path, 2, 5, 3);
var sourceLocationB = new SourceLocation(path, 4, 0, 6);
// Act
var result = sourceLocationA + sourceLocationB;
// Assert
Assert.Equal(path, result.FilePath);
Assert.Equal(6, result.AbsoluteIndex);
Assert.Equal(5, result.LineIndex);
Assert.Equal(9, result.CharacterIndex);
}
[Fact]
public void Add_AllowsRightFilePathToBeNull_WhenLeftFilePathIsNonNull()
{
// Arrange
var left = new SourceLocation("left-path", 7, 1, 7);
var right = new SourceLocation(13, 1, 4);
// Act
var result = left + right;
// Assert
Assert.Equal(left.FilePath, result.FilePath);
Assert.Equal(20, result.AbsoluteIndex);
Assert.Equal(2, result.LineIndex);
Assert.Equal(4, result.CharacterIndex);
}
[Fact]
public void Add_AllowsLeftFilePathToBeNull_WhenRightFilePathIsNonNull()
{
// Arrange
var left = new SourceLocation(4, 5, 6);
var right = new SourceLocation("right-path", 7, 8, 9);
// Act
var result = left + right;
// Assert
Assert.Equal(right.FilePath, result.FilePath);
Assert.Equal(11, result.AbsoluteIndex);
Assert.Equal(13, result.LineIndex);
Assert.Equal(9, result.CharacterIndex);
}
[Fact]
public void Subtract_Throws_IfFilePathsDoNotMatch()
{
// Arrange
var sourceLocationA = new SourceLocation("a-path", 1, 1, 1);
var sourceLocationB = new SourceLocation("b-path", 1, 1, 1);
// Act and Assert
ExceptionAssert.ThrowsArgument(
() => { var result = sourceLocationA - sourceLocationB; },
"right",
"Cannot perform '-' operations on 'SourceLocation' instances with different file paths.");
}
[Theory]
[InlineData(null)]
[InlineData("same-path")]
public void Subtract_UsesDifferenceOfCharacterIndexesIfLineIndexesAreSame(string path)
{
// Arrange
var sourceLocationA = new SourceLocation(path, 1, 5, 3);
var sourceLocationB = new SourceLocation(path, 5, 5, 6);
// Act
var result = sourceLocationB - sourceLocationA;
// Assert
Assert.Null(result.FilePath);
Assert.Equal(4, result.AbsoluteIndex);
Assert.Equal(0, result.LineIndex);
Assert.Equal(3, result.CharacterIndex);
}
[Theory]
[InlineData(null)]
[InlineData("same-path")]
public void Subtract_UsesLeftCharacterIndexIfLineIndexesAreDifferent(string path)
{
// Arrange
var sourceLocationA = new SourceLocation(path, 2, 0, 3);
var sourceLocationB = new SourceLocation(path, 4, 5, 6);
// Act
var result = sourceLocationB - sourceLocationA;
// Assert
Assert.Null(result.FilePath);
Assert.Equal(2, result.AbsoluteIndex);
Assert.Equal(5, result.LineIndex);
Assert.Equal(6, result.CharacterIndex);
}
[Theory]
[InlineData(null)]
[InlineData("path-to-file")]
public void Advance_PreservesSourceLocationFilePath(string path)
{
// Arrange
var sourceLocation = new SourceLocation(path, 15, 2, 8);
// Act
var result = SourceLocation.Advance(sourceLocation, "Hello world");
// Assert
Assert.Equal(path, result.FilePath);
Assert.Equal(26, result.AbsoluteIndex);
Assert.Equal(2, result.LineIndex);
Assert.Equal(19, result.CharacterIndex);
}
}
}

View File

@ -22,6 +22,24 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
Assert.Equal(loc, new SourceLocationTracker(loc).CurrentLocation);
}
[Theory]
[InlineData(null)]
[InlineData("path-to-file")]
public void Advance_PreservesSourceLocationFilePath(string path)
{
// Arrange
var sourceLocation = new SourceLocation(path, 15, 2, 8);
// Act
var result = SourceLocationTracker.Advance(sourceLocation, "Hello world");
// Assert
Assert.Equal(path, result.FilePath);
Assert.Equal(26, result.AbsoluteIndex);
Assert.Equal(2, result.LineIndex);
Assert.Equal(19, result.CharacterIndex);
}
[Fact]
public void UpdateLocationAdvancesCorrectlyForMultiLineString()
{

View File

@ -3629,7 +3629,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
for (var i = 0; i < errors.Length; i++)
{
var error = errors[i];
error.Location = SourceLocation.Advance(error.Location, "@{");
error.Location = SourceLocationTracker.Advance(error.Location, "@{");
}
}

View File

@ -0,0 +1,110 @@
// 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 Microsoft.AspNetCore.Testing;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution
{
public class SourceLocationTest
{
[Fact]
public void ConstructorWithLineAndCharacterIndexSetsAssociatedProperties()
{
// Act
var loc = new SourceLocation(0, 42, 24);
// Assert
Assert.Null(loc.FilePath);
Assert.Equal(0, loc.AbsoluteIndex);
Assert.Equal(42, loc.LineIndex);
Assert.Equal(24, loc.CharacterIndex);
}
[Fact]
public void Constructor_SetsFilePathAndIndexes()
{
// Arrange
var filePath = "some-file-path";
var absoluteIndex = 133;
var lineIndex = 23;
var characterIndex = 12;
// Act
var sourceLocation = new SourceLocation(filePath, absoluteIndex, lineIndex, characterIndex);
// Assert
Assert.Equal(filePath, sourceLocation.FilePath);
Assert.Equal(absoluteIndex, sourceLocation.AbsoluteIndex);
Assert.Equal(lineIndex, sourceLocation.LineIndex);
Assert.Equal(characterIndex, sourceLocation.CharacterIndex);
}
[Theory]
[InlineData(null)]
[InlineData("some-file")]
public void GetHashCode_ReturnsSameValue_WhenEqual(string path)
{
// Arrange
var sourceLocationA = new SourceLocation(path, 10, 3, 4);
var sourceLocationB = new SourceLocation(path, 10, 3, 4);
var sourceLocationC = new SourceLocation(path, 10, 45, 8754);
// Act
var hashCodeA = sourceLocationA.GetHashCode();
var hashCodeB = sourceLocationB.GetHashCode();
var hashCodeC = sourceLocationC.GetHashCode();
// Assert
Assert.Equal(hashCodeA, hashCodeB);
Assert.Equal(hashCodeA, hashCodeC);
}
[Fact]
public void Equals_ReturnsTrue_FilePathsNullAndAbsoluteIndicesMatch()
{
// Arrange
var sourceLocationA = new SourceLocation(10, 3, 4);
var sourceLocationB = new SourceLocation(10, 45, 8754);
// Act
var result = sourceLocationA.Equals(sourceLocationB);
// Assert
Assert.True(result);
}
[Fact]
public void Equals_ReturnsFalse_IfFilePathIsDifferent()
{
// Arrange
var sourceLocationA = new SourceLocation(10, 3, 4);
var sourceLocationB = new SourceLocation("different-file", 10, 3, 4);
// Act
var result = sourceLocationA.Equals(sourceLocationB);
// Assert
Assert.False(result);
}
[Theory]
[InlineData(null)]
[InlineData("some-file")]
public void Equals_ReturnsTrue_IfFilePathAndIndexesAreSame(string path)
{
// Arrange
var sourceLocationA = new SourceLocation(path, 10, 3, 4);
var sourceLocationB = new SourceLocation(path, 10, 3, 4);
var sourceLocationC = new SourceLocation("different-path", 10, 3, 4);
// Act
var result1 = sourceLocationA.Equals(sourceLocationB);
var result2 = sourceLocationA.Equals(sourceLocationC);
// Assert
Assert.True(result1);
Assert.False(result2);
}
}
}