提交 02b4007b 编写于 作者: J jnm2

Check for spans tagged as unnecessary using {|Unnecessary:|}

上级 0c072633
......@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
......@@ -37,6 +38,7 @@ public struct TestParameters
internal readonly int index;
internal readonly CodeActionPriority? priority;
internal readonly bool retainNonFixableDiagnostics;
internal readonly bool includeDiagnosticsOutsideSelection;
internal readonly string title;
internal TestParameters(
......@@ -47,6 +49,7 @@ public struct TestParameters
int index = 0,
CodeActionPriority? priority = null,
bool retainNonFixableDiagnostics = false,
bool includeDiagnosticsOutsideSelection = false,
string title = null)
{
this.parseOptions = parseOptions;
......@@ -56,23 +59,27 @@ public struct TestParameters
this.index = index;
this.priority = priority;
this.retainNonFixableDiagnostics = retainNonFixableDiagnostics;
this.includeDiagnosticsOutsideSelection = includeDiagnosticsOutsideSelection;
this.title = title;
}
public TestParameters WithParseOptions(ParseOptions parseOptions)
=> new TestParameters(parseOptions, compilationOptions, options, fixProviderData, index, priority, title: title);
=> new TestParameters(parseOptions, compilationOptions, options, fixProviderData, index, priority, retainNonFixableDiagnostics, includeDiagnosticsOutsideSelection, title);
public TestParameters WithOptions(IDictionary<OptionKey, object> options)
=> new TestParameters(parseOptions, compilationOptions, options, fixProviderData, index, priority, title: title);
=> new TestParameters(parseOptions, compilationOptions, options, fixProviderData, index, priority, retainNonFixableDiagnostics, includeDiagnosticsOutsideSelection, title);
public TestParameters WithFixProviderData(object fixProviderData)
=> new TestParameters(parseOptions, compilationOptions, options, fixProviderData, index, priority, title: title);
=> new TestParameters(parseOptions, compilationOptions, options, fixProviderData, index, priority, retainNonFixableDiagnostics, includeDiagnosticsOutsideSelection, title);
public TestParameters WithIndex(int index)
=> new TestParameters(parseOptions, compilationOptions, options, fixProviderData, index, priority, title: title);
=> new TestParameters(parseOptions, compilationOptions, options, fixProviderData, index, priority, retainNonFixableDiagnostics, includeDiagnosticsOutsideSelection, title);
public TestParameters WithRetainNonFixableDiagnostics(bool retainNonFixableDiagnostics)
=> new TestParameters(parseOptions, compilationOptions, options, fixProviderData, index, priority, title: title, retainNonFixableDiagnostics: retainNonFixableDiagnostics);
=> new TestParameters(parseOptions, compilationOptions, options, fixProviderData, index, priority, retainNonFixableDiagnostics, includeDiagnosticsOutsideSelection, title);
public TestParameters WithIncludeDiagnosticsOutsideSelection(bool includeDiagnosticsOutsideSelection)
=> new TestParameters(parseOptions, compilationOptions, options, fixProviderData, index, priority, retainNonFixableDiagnostics, includeDiagnosticsOutsideSelection, title);
}
protected abstract string GetLanguage();
......@@ -373,17 +380,35 @@ protected Task TestSmartTagGlyphTagsAsync(string initialMarkup, ImmutableArray<s
CodeActionPriority? priority,
TestParameters parameters)
{
MarkupTestFile.GetSpans(
initialMarkup.NormalizeLineEndings(),
out var initialMarkupWithoutSpans, out IDictionary<string, ImmutableArray<TextSpan>> initialSpanMap);
const string UnnecessaryMarkupKey = "Unnecessary";
var unnecessarySpans = initialSpanMap.GetOrAdd(UnnecessaryMarkupKey, _ => ImmutableArray<TextSpan>.Empty);
MarkupTestFile.GetSpans(
expectedMarkup.NormalizeLineEndings(),
out var expected, out IDictionary<string, ImmutableArray<TextSpan>> spanMap);
out var expected, out IDictionary<string, ImmutableArray<TextSpan>> expectedSpanMap);
var conflictSpans = spanMap.GetOrAdd("Conflict", _ => ImmutableArray<TextSpan>.Empty);
var renameSpans = spanMap.GetOrAdd("Rename", _ => ImmutableArray<TextSpan>.Empty);
var warningSpans = spanMap.GetOrAdd("Warning", _ => ImmutableArray<TextSpan>.Empty);
var navigationSpans = spanMap.GetOrAdd("Navigation", _ => ImmutableArray<TextSpan>.Empty);
var conflictSpans = expectedSpanMap.GetOrAdd("Conflict", _ => ImmutableArray<TextSpan>.Empty);
var renameSpans = expectedSpanMap.GetOrAdd("Rename", _ => ImmutableArray<TextSpan>.Empty);
var warningSpans = expectedSpanMap.GetOrAdd("Warning", _ => ImmutableArray<TextSpan>.Empty);
var navigationSpans = expectedSpanMap.GetOrAdd("Navigation", _ => ImmutableArray<TextSpan>.Empty);
using (var workspace = CreateWorkspaceFromOptions(initialMarkup, parameters))
{
// Ideally this check would always run, but there are several hundred tests that would need to be
// updated with {|Unnecessary:|} spans.
if (unnecessarySpans.Any())
{
var allDiagnostics = await GetDiagnosticsWorkerAsync(workspace, parameters
.WithRetainNonFixableDiagnostics(true)
.WithIncludeDiagnosticsOutsideSelection(true));
TestDiagnosticTags(allDiagnostics, unnecessarySpans, WellKnownDiagnosticTags.Unnecessary, UnnecessaryMarkupKey, initialMarkupWithoutSpans);
}
var (_, action) = await GetCodeActionsAsync(workspace, parameters);
await TestActionAsync(
workspace, expected, action,
......@@ -392,6 +417,85 @@ protected Task TestSmartTagGlyphTagsAsync(string initialMarkup, ImmutableArray<s
}
}
private static void TestDiagnosticTags(
ImmutableArray<Diagnostic> diagnostics,
ImmutableArray<TextSpan> expectedSpans,
string diagnosticTag,
string markupKey,
string initialMarkupWithoutSpans)
{
var diagnosticsWithTag = diagnostics
.Where(d => d.Descriptor.CustomTags.Contains(diagnosticTag))
.OrderBy(s => s.Location.SourceSpan.Start)
.ToList();
if (expectedSpans.Length != diagnosticsWithTag.Count)
{
AssertEx.Fail(BuildFailureMessage(expectedSpans, diagnosticTag, markupKey, initialMarkupWithoutSpans, diagnosticsWithTag));
}
for (var i = 0; i < Math.Min(expectedSpans.Length, diagnosticsWithTag.Count); i++)
{
var actual = diagnosticsWithTag[i].Location.SourceSpan;
var expected = expectedSpans[i];
Assert.Equal(expected, actual);
}
}
private static string BuildFailureMessage(
ImmutableArray<TextSpan> expectedSpans,
string diagnosticTag,
string markupKey,
string initialMarkupWithoutSpans,
List<Diagnostic> diagnosticsWithTag)
{
var message = $"Expected {expectedSpans.Length} diagnostic spans with custom tag '{diagnosticTag}', but there were {diagnosticsWithTag.Count}.";
if (expectedSpans.Length == 0)
{
message += $" If a diagnostic span tagged '{diagnosticTag}' is expected, surround the span in the test markup with the following syntax: {{|Unnecessary:...}}";
var segments = new List<(int originalStringIndex, string segment)>();
foreach (var diagnostic in diagnosticsWithTag)
{
var documentOffset = initialMarkupWithoutSpans.IndexOf(diagnosticsWithTag.First().Location.SourceTree.ToString());
if (documentOffset == -1) continue;
segments.Add((documentOffset + diagnostic.Location.SourceSpan.Start, "{|" + markupKey + ":"));
segments.Add((documentOffset + diagnostic.Location.SourceSpan.End, "|}"));
}
if (segments.Any())
{
message += Environment.NewLine
+ "Example:" + Environment.NewLine
+ Environment.NewLine
+ InsertSegments(initialMarkupWithoutSpans, segments);
}
}
return message;
}
private static string InsertSegments(string originalString, IEnumerable<(int originalStringIndex, string segment)> segments)
{
var builder = new StringBuilder();
var positionInOriginalString = 0;
foreach (var (originalStringIndex, segment) in segments.OrderBy(s => s.originalStringIndex))
{
builder.Append(originalString, positionInOriginalString, originalStringIndex - positionInOriginalString);
builder.Append(segment);
positionInOriginalString = originalStringIndex;
}
builder.Append(originalString, positionInOriginalString, originalString.Length - positionInOriginalString);
return builder.ToString();
}
internal async Task<Tuple<Solution, Solution>> TestActionAsync(
TestWorkspace workspace, string expected,
CodeAction action,
......@@ -659,7 +763,7 @@ protected static (OptionKey, object) SingleOption<T>(PerLanguageOption<CodeStyle
/// <summary>
/// Tests all the code actions for the given <paramref name="input"/> string. Each code
/// action must produce the corresponding output in the <paramref name="outputs"/> array.
///
///
/// Will throw if there are more outputs than code actions or more code actions than outputs.
/// </summary>
protected Task TestAllInRegularAndScriptAsync(
......
......@@ -141,7 +141,8 @@ public void TestSupportedDiagnosticsMessageHelpLinkUri()
}
var testDriver = new TestDiagnosticAnalyzerDriver(document.Project, provider);
var diagnostics = (await testDriver.GetAllDiagnosticsAsync(document, span)).ToImmutableArray();
var filterSpan = parameters.includeDiagnosticsOutsideSelection ? (TextSpan?)null : span;
var diagnostics = (await testDriver.GetAllDiagnosticsAsync(document, filterSpan)).ToImmutableArray();
AssertNoAnalyzerExceptionDiagnostics(diagnostics);
var fixer = providerAndFixer.Item2;
......
......@@ -54,7 +54,7 @@ private TestDiagnosticAnalyzerService CreateDiagnosticAnalyzerService(Project pr
private async Task<IEnumerable<Diagnostic>> GetDiagnosticsAsync(
Project project,
Document document,
TextSpan span,
TextSpan? filterSpan,
bool getDocumentDiagnostics,
bool getProjectDiagnostics)
{
......@@ -66,7 +66,12 @@ private TestDiagnosticAnalyzerService CreateDiagnosticAnalyzerService(Project pr
if (getDocumentDiagnostics)
{
var dxs = await _diagnosticAnalyzerService.GetDiagnosticsAsync(project.Solution, project.Id, document.Id, _includeSuppressedDiagnostics);
documentDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync(dxs.Where(d => d.HasTextSpan && d.TextSpan.IntersectsWith(span)), project, CancellationToken.None);
documentDiagnostics = await CodeAnalysis.Diagnostics.Extensions.ToDiagnosticsAsync(
filterSpan is null
? dxs.Where(d => d.HasTextSpan)
: dxs.Where(d => d.HasTextSpan && d.TextSpan.IntersectsWith(filterSpan.Value)),
project,
CancellationToken.None);
}
if (getProjectDiagnostics)
......@@ -86,9 +91,9 @@ private TestDiagnosticAnalyzerService CreateDiagnosticAnalyzerService(Project pr
return allDiagnostics;
}
public Task<IEnumerable<Diagnostic>> GetAllDiagnosticsAsync(Document document, TextSpan span)
public Task<IEnumerable<Diagnostic>> GetAllDiagnosticsAsync(Document document, TextSpan? filterSpan)
{
return GetDiagnosticsAsync(document.Project, document, span, getDocumentDiagnostics: true, getProjectDiagnostics: true);
return GetDiagnosticsAsync(document.Project, document, filterSpan, getDocumentDiagnostics: true, getProjectDiagnostics: true);
}
public async Task<IEnumerable<Diagnostic>> GetAllDiagnosticsAsync(Project project)
......@@ -125,7 +130,7 @@ public Task<IEnumerable<Diagnostic>> GetDocumentDiagnosticsAsync(Document docume
public Task<IEnumerable<Diagnostic>> GetProjectDiagnosticsAsync(Project project)
{
return GetDiagnosticsAsync(project, document: null, span: default, getDocumentDiagnostics: false, getProjectDiagnostics: true);
return GetDiagnosticsAsync(project, document: null, filterSpan: default, getDocumentDiagnostics: false, getProjectDiagnostics: true);
}
private async Task SynchronizeGlobalAssetToRemoteHostIfNeededAsync(Workspace workspace)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册