* Adding FilePath to SourceLocation

* Using SourceLocation.FilePath when printing line pragmas, if available.
This commit is contained in:
Pranav K 2015-04-08 12:30:14 -07:00
parent fedd53aab8
commit dc4ee8b915
10 changed files with 531 additions and 38 deletions

View File

@ -49,7 +49,8 @@ namespace Microsoft.Internal.Web.Utils
public HashCodeCombiner Add<TValue>(TValue value, IEqualityComparer<TValue> comparer)
{
return Add(comparer.GetHashCode(value));
var hashCode = value != null ? comparer.GetHashCode(value) : 0;
return Add(hashCode);
}
public static HashCodeCombiner Start()

View File

@ -190,18 +190,18 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
/// <returns>The current instance of <see cref="CSharpCodeWriter"/>.</returns>
public CSharpCodeWriter WriteLineNumberDirective(SourceLocation location, string file)
{
return WriteLineNumberDirective(location.LineIndex + 1, file);
}
if (location.FilePath != null)
{
file = location.FilePath;
}
public CSharpCodeWriter WriteLineNumberDirective(int lineNumber, string file)
{
if (!string.IsNullOrEmpty(LastWrite) &&
!LastWrite.EndsWith(NewLine, StringComparison.Ordinal))
{
WriteLine();
}
var lineNumberAsString = lineNumber.ToString(CultureInfo.InvariantCulture);
var lineNumberAsString = (location.LineIndex + 1).ToString(CultureInfo.InvariantCulture);
return Write("#line ").Write(lineNumberAsString).Write(" \"").Write(file).WriteLine("\"");
}

View File

@ -371,7 +371,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
var documentLocation = firstChild.Association.Start;
// This is only here to enable accurate formatting by the C# editor.
Writer.WriteLineNumberDirective(documentLocation.LineIndex + 1, Context.SourceFile);
Writer.WriteLineNumberDirective(documentLocation, Context.SourceFile);
// We build the padding with an offset of the design time assignment statement.
Writer.Write(_paddingBuilder.BuildExpressionPadding((Span)firstChild.Association, designTimeAssignment.Length))

View File

@ -1466,6 +1466,22 @@ namespace Microsoft.AspNet.Razor
return string.Format(CultureInfo.CurrentCulture, GetString("RewriterError_EmptyTagHelperBoundAttribute"), p0, p1, p2);
}
/// <summary>
/// Cannot perform '{1}' operations on '{0}' instances with different file paths.
/// </summary>
internal static string SourceLocationFilePathDoesNotMatch
{
get { return GetString("SourceLocationFilePathDoesNotMatch"); }
}
/// <summary>
/// Cannot perform '{1}' operations on '{0}' instances with different file paths.
/// </summary>
internal static string FormatSourceLocationFilePathDoesNotMatch(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("SourceLocationFilePathDoesNotMatch"), p0, p1);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -410,4 +410,7 @@ Instead, wrap the contents of the block in "{{}}":
<data name="RewriterError_EmptyTagHelperBoundAttribute" xml:space="preserve">
<value>Attribute '{0}' on tag helper element '{1}' requires a value. Tag helper bound attributes of type '{2}' cannot be empty or contain only whitespace.</value>
</data>
<data name="SourceLocationFilePathDoesNotMatch" xml:space="preserve">
<value>Cannot perform '{1}' operations on '{0}' instances with different file paths.</value>
</data>
</root>

View File

@ -4,25 +4,65 @@
using System;
using System.Globalization;
using Microsoft.AspNet.Razor.Text;
using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor
{
/// <summary>
/// A location in a Razor file.
/// </summary>
#if NET45
// No Serializable attribute in CoreCLR (no need for it anymore?)
[Serializable]
#endif
public struct SourceLocation : IEquatable<SourceLocation>, IComparable<SourceLocation>
{
public static readonly SourceLocation Undefined = CreateUndefined();
public static readonly SourceLocation Zero = new SourceLocation(0, 0, 0);
/// <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; }
@ -35,6 +75,7 @@ namespace Microsoft.AspNet.Razor
/// <remarks>Set property is only accessible for deserialization purposes.</remarks>
public int CharacterIndex { get; set; }
/// <inheritdoc />
public override string ToString()
{
return string.Format(
@ -45,43 +86,82 @@ namespace Microsoft.AspNet.Razor
CharacterIndex);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return (obj is SourceLocation) && Equals((SourceLocation)obj);
}
/// <inheritdoc />
public override int GetHashCode()
{
// LineIndex and CharacterIndex can be calculated from AbsoluteIndex and the document content.
return AbsoluteIndex;
return HashCodeCombiner.Start()
.Add(FilePath, StringComparer.Ordinal)
.Add(AbsoluteIndex)
.CombinedHash;
}
/// <inheritdoc />
public bool Equals(SourceLocation other)
{
return
return string.Equals(FilePath, other.FilePath, StringComparison.Ordinal) &&
AbsoluteIndex == other.AbsoluteIndex &&
LineIndex == other.LineIndex &&
CharacterIndex == other.CharacterIndex;
}
/// <inheritdoc />
public int CompareTo(SourceLocation other)
{
var filePathOrdinal = string.Compare(FilePath, other.FilePath, StringComparison.Ordinal);
if (filePathOrdinal != 0)
{
return filePathOrdinal;
}
return AbsoluteIndex.CompareTo(other.AbsoluteIndex);
}
public static SourceLocation Advance(SourceLocation left, string text)
/// <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, [NotNull] string text)
{
var tracker = new SourceLocationTracker(left);
tracker.UpdateLocation(text);
return tracker.CurrentLocation;
}
public static SourceLocation Add(SourceLocation left, SourceLocation right)
/// <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(
RazorResources.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);
@ -89,57 +169,82 @@ namespace Microsoft.AspNet.Razor
else
{
return new SourceLocation(
resultFilePath,
left.AbsoluteIndex + right.AbsoluteIndex,
left.LineIndex + right.LineIndex,
left.CharacterIndex + right.CharacterIndex);
}
}
public static SourceLocation Subtract(SourceLocation left, SourceLocation right)
/// <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(
RazorResources.FormatSourceLocationFilePathDoesNotMatch(nameof(SourceLocation), "-"),
nameof(right));
}
var characterIndex = left.LineIndex != right.LineIndex ?
left.CharacterIndex : left.CharacterIndex - right.CharacterIndex;
return new SourceLocation(
left.AbsoluteIndex - right.AbsoluteIndex,
left.LineIndex - right.LineIndex,
left.LineIndex != right.LineIndex ? left.CharacterIndex : left.CharacterIndex - right.CharacterIndex);
}
private static SourceLocation CreateUndefined()
{
var sl = new SourceLocation();
sl.AbsoluteIndex = -1;
sl.LineIndex = -1;
sl.CharacterIndex = -1;
return sl;
filePath: null,
absoluteIndex: left.AbsoluteIndex - right.AbsoluteIndex,
lineIndex: left.LineIndex - right.LineIndex,
characterIndex: characterIndex);
}
/// <summary>
/// Determines whether the first operand is less than the second operand.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns><c>true</c> if <paramref name="left"/> is less than <paramref name="right"/>.</returns>
public static bool operator <(SourceLocation left, SourceLocation right)
{
return left.CompareTo(right) < 0;
}
/// <summary>
/// Determines whether the first operand is greater than the second operand.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns><c>true</c> if <paramref name="left"/> is greater than <paramref name="right"/>.</returns>
public static bool operator >(SourceLocation left, SourceLocation right)
{
return left.CompareTo(right) > 0;
}
/// <summary>
/// Determines whether the operands are equal.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns><c>true</c> if <paramref name="left"/> and <paramref name="right"/> are equal.</returns>
public static bool operator ==(SourceLocation left, SourceLocation right)
{
return left.Equals(right);
}
/// <summary>
/// Determines whether the operands are not equal.
/// </summary>
/// <param name="left">The left operand.</param>
/// <param name="right">The right operand.</param>
/// <returns><c>true</c> if <paramref name="left"/> and <paramref name="right"/> are not equal.</returns>
public static bool operator !=(SourceLocation left, SourceLocation right)
{
return !left.Equals(right);
}
public static SourceLocation operator +(SourceLocation left, SourceLocation right)
{
return Add(left, right);
}
public static SourceLocation operator -(SourceLocation left, SourceLocation right)
{
return Subtract(left, right);
}
}
}

View File

@ -85,7 +85,11 @@ namespace Microsoft.AspNet.Razor.Text
private void RecalculateSourceLocation()
{
_currentLocation = new SourceLocation(_absoluteIndex, _lineIndex, _characterIndex);
_currentLocation = new SourceLocation(
_currentLocation.FilePath,
_absoluteIndex,
_lineIndex,
_characterIndex);
}
public static SourceLocation CalculateNewLocation(SourceLocation lastPosition, string newContent)

View File

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp
{
public class CSharpCodeWriterTest
{
[Fact]
public void WriteLineNumberDirective_UsesFilePath_WhenFileInSourceLocationIsNull()
{
// Arrange
var filePath = "some-path";
var writer = new CSharpCodeWriter();
var expected = $"#line 5 \"{filePath}\"" + writer.NewLine;
var sourceLocation = new SourceLocation(10, 4, 3);
// Act
writer.WriteLineNumberDirective(sourceLocation, filePath);
var code = writer.GenerateCode();
// Assert
Assert.Equal(expected, code);
}
[Theory]
[InlineData("")]
[InlineData("source-location-file-path")]
public void WriteLineNumberDirective_UsesSourceLocationFilePath_IfAvailable(
string sourceLocationFilePath)
{
// Arrange
var filePath = "some-path";
var writer = new CSharpCodeWriter();
var expected = $"#line 5 \"{sourceLocationFilePath}\"" + writer.NewLine;
var sourceLocation = new SourceLocation(sourceLocationFilePath, 10, 4, 3);
// Act
writer.WriteLineNumberDirective(sourceLocation, filePath);
var code = writer.GenerateCode();
// Assert
Assert.Equal(expected, code);
}
}
}

View File

@ -19,6 +19,7 @@ namespace Microsoft.AspNet.Razor
length: 456);
var expectedSerializedError =
$"{{\"{nameof(RazorError.Message)}\":\"Testing\",\"{nameof(RazorError.Location)}\":{{\"" +
$"{nameof(SourceLocation.FilePath)}\":null,\"" +
$"{nameof(SourceLocation.AbsoluteIndex)}\":1,\"{nameof(SourceLocation.LineIndex)}\":2,\"" +
$"{nameof(SourceLocation.CharacterIndex)}\":3}},\"{nameof(RazorError.Length)}\":456}}";
@ -29,13 +30,34 @@ namespace Microsoft.AspNet.Razor
Assert.Equal(expectedSerializedError, serializedError, StringComparer.Ordinal);
}
[Fact]
public void RazorError_WithFilePath_CanBeSerialized()
{
// Arrange
var error = new RazorError(
message: "Testing",
location: new SourceLocation("some-path", absoluteIndex: 1, lineIndex: 2, characterIndex: 56),
length: 3);
var expectedSerializedError =
$"{{\"{nameof(RazorError.Message)}\":\"Testing\",\"{nameof(RazorError.Location)}\":{{\"" +
$"{nameof(SourceLocation.FilePath)}\":\"some-path\",\"" +
$"{nameof(SourceLocation.AbsoluteIndex)}\":1,\"{nameof(SourceLocation.LineIndex)}\":2,\"" +
$"{nameof(SourceLocation.CharacterIndex)}\":56}},\"{nameof(RazorError.Length)}\":3}}";
// Act
var serializedError = JsonConvert.SerializeObject(error);
// Assert
Assert.Equal(expectedSerializedError, serializedError, StringComparer.Ordinal);
}
[Fact]
public void RazorError_CanBeDeserialized()
{
// Arrange
var error = new RazorError(
message: "Testing",
location: new SourceLocation(absoluteIndex: 1, lineIndex: 2, characterIndex: 3),
location: new SourceLocation("somepath", absoluteIndex: 1, lineIndex: 2, characterIndex: 3),
length: 456);
var serializedError = JsonConvert.SerializeObject(error);

View File

@ -1,6 +1,8 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Testing;
using Xunit;
namespace Microsoft.AspNet.Razor
@ -14,9 +16,302 @@ namespace Microsoft.AspNet.Razor
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);
}
[Fact]
public void GetHashCode_ReturnsHashCode_UsingAbsoluteIndex()
{
// Arrange
var sourceLocationA = new SourceLocation(10, 3, 4);
var sourceLocationB = new SourceLocation(10, 45, 8754);
var sourceLocationC = new SourceLocation(12, 45, 8754);
// Act
var hashCodeA = sourceLocationA.GetHashCode();
var hashCodeB = sourceLocationB.GetHashCode();
var hashCodeC = sourceLocationC.GetHashCode();
// Assert
Assert.Equal(hashCodeA, hashCodeB);
Assert.NotEqual(hashCodeA, hashCodeC);
}
[Fact]
public void GetHashCode_ReturnsHashCode_UsingFilePathAndAbsoluteIndex_WhenFilePathIsNonNull()
{
// Arrange
var sourceLocationA = new SourceLocation("some-path", 3, 53, 94);
var sourceLocationB = new SourceLocation("some-path", 3, 43, 87);
var sourceLocationC = new SourceLocation(3, 53, 94);
// Act
var hashCodeA = sourceLocationA.GetHashCode();
var hashCodeB = sourceLocationB.GetHashCode();
var hashCodeC = sourceLocationC.GetHashCode();
// Assert
Assert.Equal(hashCodeA, hashCodeB);
Assert.NotEqual(hashCodeA, hashCodeC);
}
[Fact]
public void Equal_ReturnsFalse_IfIndexesDiffer()
{
// Arrange
var sourceLocationA = new SourceLocation(10, 3, 4);
var sourceLocationB = new SourceLocation(10, 45, 8754);
// Act
var result = sourceLocationA.Equals(sourceLocationB);
// Assert
Assert.False(result);
}
[Fact]
public void Equal_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 Equal_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 CompareTo_ReturnsResultOfFilePathComparisons_WhenFilePathsAreDifferent()
{
// Arrange
var sourceLocationA = new SourceLocation("a-path", 1, 1, 1);
var sourceLocationB = new SourceLocation("b-path", 1, 1, 1);
// Act
var result = sourceLocationA.CompareTo(sourceLocationB);
// Assert
Assert.Equal(string.Compare(sourceLocationA.FilePath, sourceLocationB.FilePath, StringComparison.Ordinal),
result);
}
[Theory]
[InlineData(null, 1, 2)]
[InlineData(null, 32, 32)]
[InlineData("same-path", 34, 32)]
[InlineData("same-path-b", 18, 32)]
public void CompareTo_ReturnsResultOfAbsoluteIndexComparisons_IfFilePathsMatch(
string path, int indexA, int indexB)
{
// Arrange
var sourceLocationA = new SourceLocation(path, indexA, 1, 1);
var sourceLocationB = new SourceLocation(path, indexB, 1, 1);
// Act
var result = sourceLocationA.CompareTo(sourceLocationB);
// Assert
Assert.Equal(indexA.CompareTo(indexB), result);
}
[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);
}
}
}