Add MarkupOptions.TreatPositionIndicatorsAsCode

Fixes #1067
This commit is contained in:
Sam Harwell 2023-03-13 11:38:14 -05:00
Родитель be10de009d
Коммит e448f97b55
6 изменённых файлов: 100 добавлений и 11 удалений

Просмотреть файл

@ -23,5 +23,13 @@ namespace Microsoft.CodeAnalysis.Testing
/// descriptor.
/// </summary>
UseFirstDescriptor = 0x0001,
/// <summary>
/// Ignore position indicators (<c>$$</c>) in markup processing. Spans and named spans are still supported in markup.
/// </summary>
/// <remarks>
/// This flag makes it easier to write tests for code containing interpolated raw strings (<see href="https://github.com/dotnet/roslyn-sdk/issues/1067">dotnet/roslyn-sdk#1067</see>).
/// </remarks>
TreatPositionIndicatorsAsCode = 0x0002,
}
}

Просмотреть файл

@ -118,6 +118,7 @@ Microsoft.CodeAnalysis.Testing.MarkupMode.IgnoreFixable = 2 -> Microsoft.CodeAna
Microsoft.CodeAnalysis.Testing.MarkupMode.None = 0 -> Microsoft.CodeAnalysis.Testing.MarkupMode
Microsoft.CodeAnalysis.Testing.MarkupOptions
Microsoft.CodeAnalysis.Testing.MarkupOptions.None = 0 -> Microsoft.CodeAnalysis.Testing.MarkupOptions
Microsoft.CodeAnalysis.Testing.MarkupOptions.TreatPositionIndicatorsAsCode = 2 -> Microsoft.CodeAnalysis.Testing.MarkupOptions
Microsoft.CodeAnalysis.Testing.MarkupOptions.UseFirstDescriptor = 1 -> Microsoft.CodeAnalysis.Testing.MarkupOptions
Microsoft.CodeAnalysis.Testing.MetadataReferenceCollection
Microsoft.CodeAnalysis.Testing.MetadataReferenceCollection.Add(System.Reflection.Assembly assembly) -> void
@ -337,6 +338,7 @@ static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetPositionAndSpans(s
static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetPositionAndSpans(string input, out string output, out int? cursorPosition, out System.Collections.Immutable.ImmutableDictionary<string, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Text.TextSpan>> spans) -> void
static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetPositionsAndSpans(string input, out string output, out System.Collections.Immutable.ImmutableArray<int> positions, out System.Collections.Immutable.ImmutableDictionary<string, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Text.TextSpan>> spans) -> void
static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetSpan(string input, out string output, out Microsoft.CodeAnalysis.Text.TextSpan span) -> void
static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetSpans(string input, bool treatPositionIndicatorsAsCode, out string output, out System.Collections.Immutable.ImmutableDictionary<string, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Text.TextSpan>> spans) -> void
static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetSpans(string input, out string output, out System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Text.TextSpan> spans) -> void
static Microsoft.CodeAnalysis.Testing.TestFileMarkupParser.GetSpans(string input, out string output, out System.Collections.Immutable.ImmutableDictionary<string, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Text.TextSpan>> spans) -> void
static readonly Microsoft.CodeAnalysis.Testing.DiagnosticResult.EmptyDiagnosticResults -> Microsoft.CodeAnalysis.Testing.DiagnosticResult[]

Просмотреть файл

@ -360,7 +360,19 @@ namespace Microsoft.CodeAnalysis.Testing
var diagnostics = new List<DiagnosticResult>(explicitDiagnostics.Select(diagnostic => diagnostic.WithDefaultPath(defaultPath)));
foreach ((var filename, var content) in sources)
{
TestFileMarkupParser.GetPositionsAndSpans(content.ToString(), out var output, out var positions, out var namedSpans);
string output;
ImmutableArray<int> positions;
ImmutableDictionary<string, ImmutableArray<TextSpan>> namedSpans;
if (markupOptions.HasFlag(MarkupOptions.TreatPositionIndicatorsAsCode))
{
positions = ImmutableArray<int>.Empty;
TestFileMarkupParser.GetSpans(content.ToString(), treatPositionIndicatorsAsCode: true, out output, out namedSpans);
}
else
{
TestFileMarkupParser.GetPositionsAndSpans(content.ToString(), out output, out positions, out namedSpans);
}
sourceFiles.Add((filename, content.Replace(new TextSpan(0, content.Length), output)));
if (positions.IsEmpty && namedSpans.IsEmpty)
{

Просмотреть файл

@ -61,9 +61,9 @@ namespace Microsoft.CodeAnalysis.Testing
@"\| (\#\d+) \}",
RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
private static void Parse(string input, out string output, out ImmutableArray<int> positions, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans)
private static void Parse(string input, bool treatPositionIndicatorsAsCode, out string output, out ImmutableArray<int> positions, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans)
{
Parse(input, out output, out positions, out var startPositions, out var endPositions);
Parse(input, treatPositionIndicatorsAsCode, out output, out positions, out var startPositions, out var endPositions);
if (startPositions.Length != endPositions.Length)
{
throw new ArgumentException($"The input contained '{startPositions.Length}' starting spans and '{endPositions.Length}' ending spans.");
@ -162,6 +162,8 @@ namespace Microsoft.CodeAnalysis.Testing
/// Parses the input markup to find standalone positions and the start and end positions of text spans.
/// </summary>
/// <param name="input">The input markup.</param>
/// <param name="treatPositionIndicatorsAsCode"><see langword="true"/> to treat <c>$$</c> as literal code;
/// otherwise, <see langword="false"/> to treat <c>$$</c> as a position in markup.</param>
/// <param name="output">The output content with markup syntax removed from <paramref name="input"/>.</param>
/// <param name="positions">A list of positions defined in markup (<c>$$</c>).</param>
/// <param name="startPositions">A list of starting positions of spans in markup. The key of the element is a
@ -172,7 +174,7 @@ namespace Microsoft.CodeAnalysis.Testing
/// position (the location of the <c>|]</c> or <c>|}</c>). The value of the element is the <c>#id</c> content of
/// a <c>|#id}</c> ending syntax, or <see langword="null"/> if the <c>|]</c> or <c>|}</c> syntax was used. This
/// list preserves the original order of the ending markup tags in the input.</param>
private static void Parse(string input, out string output, out ImmutableArray<int> positions, out ImmutableArray<(int inputPosition, int outputPosition, string key)> startPositions, out ImmutableArray<(int inputPosition, int outputPosition, string key)> endPositions)
private static void Parse(string input, bool treatPositionIndicatorsAsCode, out string output, out ImmutableArray<int> positions, out ImmutableArray<(int inputPosition, int outputPosition, string key)> startPositions, out ImmutableArray<(int inputPosition, int outputPosition, string key)> endPositions)
{
var positionsBuilder = ImmutableArray.CreateBuilder<int>();
var startPositionsBuilder = ImmutableArray.CreateBuilder<(int inputPosition, int outputPosition, string key)>();
@ -188,7 +190,11 @@ namespace Microsoft.CodeAnalysis.Testing
{
matches.Clear();
AddMatch(input, PositionString, currentIndexInInput, matches);
if (!treatPositionIndicatorsAsCode)
{
AddMatch(input, PositionString, currentIndexInInput, matches);
}
AddMatch(input, SpanStartString, currentIndexInInput, matches);
AddMatch(input, SpanEndString, currentIndexInInput, matches);
AddMatch(input, NamedSpanEndString, currentIndexInInput, matches);
@ -245,7 +251,7 @@ namespace Microsoft.CodeAnalysis.Testing
switch (matchString.Substring(0, 2))
{
case PositionString:
case PositionString when !treatPositionIndicatorsAsCode:
positionsBuilder.Add(matchIndexInOutput);
break;
@ -335,12 +341,12 @@ namespace Microsoft.CodeAnalysis.Testing
public static void GetPositionsAndSpans(string input, out string output, out ImmutableArray<int> positions, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans)
{
Parse(input, out output, out positions, out spans);
Parse(input, treatPositionIndicatorsAsCode: false, out output, out positions, out spans);
}
public static void GetPositionAndSpans(string input, out string output, out int? cursorPosition, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans)
{
Parse(input, out output, out var positions, out spans);
Parse(input, treatPositionIndicatorsAsCode: false, out output, out var positions, out spans);
cursorPosition = positions.SingleOrNull();
}
@ -357,12 +363,17 @@ namespace Microsoft.CodeAnalysis.Testing
public static void GetSpans(string input, out string output, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans)
{
GetPositionAndSpans(input, out output, out int? _, out spans);
GetSpans(input, treatPositionIndicatorsAsCode: false, out output, out spans);
}
public static void GetSpans(string input, bool treatPositionIndicatorsAsCode, out string output, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans)
{
Parse(input, treatPositionIndicatorsAsCode, out output, out _, out spans);
}
public static void GetPositionAndSpans(string input, out string output, out int? cursorPosition, out ImmutableArray<TextSpan> spans)
{
Parse(input, out output, out var positions, out var dictionary);
Parse(input, treatPositionIndicatorsAsCode: false, out output, out var positions, out var dictionary);
cursorPosition = positions.SingleOrNull();
spans = dictionary.GetValueOrDefault(string.Empty, ImmutableArray<TextSpan>.Empty);

Просмотреть файл

@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Text;
using Xunit;
@ -96,6 +95,45 @@ namespace Microsoft.CodeAnalysis.Testing
Assert.Equal(markup, TestFileMarkupParser.CreateTestFile(output, cursorPosition, spans));
}
[Fact]
public void SinglePosition7()
{
var markup = "first$$second";
var expected = "firstsecond";
TestFileMarkupParser.GetSpans(markup, out var output, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans);
Assert.Equal(expected, output);
// Test round-trip
Assert.Equal(expected, TestFileMarkupParser.CreateTestFile(output, ImmutableArray<int>.Empty, spans));
}
[Fact]
public void SinglePosition8()
{
var markup = "first$$second";
var expected = "firstsecond";
TestFileMarkupParser.GetSpans(markup, treatPositionIndicatorsAsCode: false, out var output, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans);
Assert.Equal(expected, output);
// Test round-trip
Assert.Equal(expected, TestFileMarkupParser.CreateTestFile(output, ImmutableArray<int>.Empty, spans));
}
[Fact]
public void SinglePosition9()
{
var markup = "first$$second";
var expected = "first$$second";
TestFileMarkupParser.GetSpans(markup, treatPositionIndicatorsAsCode: true, out var output, out ImmutableDictionary<string, ImmutableArray<TextSpan>> spans);
Assert.Equal(expected, output);
// Test round-trip
Assert.Equal(expected, TestFileMarkupParser.CreateTestFile(output, ImmutableArray<int>.Empty, spans));
}
[Fact]
public void MissingRequiredPosition1()
{

Просмотреть файл

@ -43,6 +43,24 @@ class TestClass $${
await new CSharpTest(nestedDiagnostics: false, hiddenDescriptors: false, reportAdditionalLocations: reportAdditionalLocations) { TestCode = testCode }.RunAsync();
}
[Theory(Skip = "Raw strings are not supported for the version of Roslyn currently used in tests.")]
[CombinatorialData]
[WorkItem(1067, "https://github.com/dotnet/roslyn-sdk/issues/1067")]
public async Task TestCSharpMarkupBraceSpansWithRawInterpolatedString(bool reportAdditionalLocations)
{
var testCode = @"
class TestClass [|{|]
string value = $$"""""" [|{{|]0}} """""";
}
";
await new CSharpTest(nestedDiagnostics: false, hiddenDescriptors: false, reportAdditionalLocations: reportAdditionalLocations)
{
TestCode = testCode,
MarkupOptions = MarkupOptions.TreatPositionIndicatorsAsCode,
}.RunAsync();
}
[Theory]
[CombinatorialData]
[WorkItem(181, "https://github.com/dotnet/roslyn-sdk/issues/181")]