Skip to content

Commit

Permalink
Add diagnostic and code fix verification helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
sharwell committed Aug 14, 2018
1 parent 37e2a00 commit 718fada
Show file tree
Hide file tree
Showing 11 changed files with 1,757 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<OutputPath>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)\..\..\bin\CSharp\ConvertToConditional.Test'))</OutputPath>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.6.0" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="2.6.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ protected Document CreateDocument(string code)
// find these assemblies in the running process
string[] simpleNames = { "mscorlib", "System.Core", "System" };

#if !NETSTANDARD1_5
IEnumerable<PortableExecutableReference> references = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => simpleNames.Contains(a.GetName().Name, StringComparer.OrdinalIgnoreCase))
.Select(a => MetadataReference.CreateFromFile(a.Location));
#else
IEnumerable<PortableExecutableReference> references = Enumerable.Empty<PortableExecutableReference>();
#endif

return new AdhocWorkspace().CurrentSolution
.AddProject(projectId, "TestProject", "TestProject", LanguageName)
Expand Down
61 changes: 61 additions & 0 deletions samples/Shared/UnitTestFramework/CodeFixVerifier`2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Roslyn.UnitTestFramework
{
public static class CodeFixVerifier<TAnalyzer, TCodeFix>
where TAnalyzer : DiagnosticAnalyzer, new()
where TCodeFix : CodeFixProvider, new()
{
public static DiagnosticResult[] EmptyDiagnosticResults
=> DiagnosticVerifier<TAnalyzer>.EmptyDiagnosticResults;

public static DiagnosticResult Diagnostic(string diagnosticId = null)
=> DiagnosticVerifier<TAnalyzer>.Diagnostic(diagnosticId);

public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
=> DiagnosticVerifier<TAnalyzer>.Diagnostic(descriptor);

public static DiagnosticResult CompilerError(string errorIdentifier)
=> DiagnosticVerifier<TAnalyzer>.CompilerError(errorIdentifier);

public static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult expected, CancellationToken cancellationToken)
=> DiagnosticVerifier<TAnalyzer>.VerifyCSharpDiagnosticAsync(source, expected, cancellationToken);

public static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] expected, CancellationToken cancellationToken)
=> DiagnosticVerifier<TAnalyzer>.VerifyCSharpDiagnosticAsync(source, expected, cancellationToken);

public static Task VerifyCSharpFixAsync(string source, DiagnosticResult expected, string fixedSource, CancellationToken cancellationToken)
=> VerifyCSharpFixAsync(source, new[] { expected }, fixedSource, cancellationToken);

public static Task VerifyCSharpFixAsync(string source, DiagnosticResult[] expected, string fixedSource, CancellationToken cancellationToken)
{
CSharpTest test = new CSharpTest
{
TestCode = source,
FixedCode = fixedSource,
};

test.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync(cancellationToken);
}

public class CSharpTest : DiagnosticVerifier<TAnalyzer>.CSharpTest
{
protected override IEnumerable<CodeFixProvider> GetCodeFixProviders()
=> new[] { new TCodeFix() };
}

public class VisualBasicTest : DiagnosticVerifier<TAnalyzer>.VisualBasicTest
{
protected override IEnumerable<CodeFixProvider> GetCodeFixProviders()
=> new[] { new TCodeFix() };
}
}
}
23 changes: 23 additions & 0 deletions samples/Shared/UnitTestFramework/CustomDiagnosticVerifier`1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Roslyn.UnitTestFramework
{
public static class CustomDiagnosticVerifier<TAnalyzer>
where TAnalyzer : DiagnosticAnalyzer, new()
{
public static DiagnosticResult[] EmptyDiagnosticResults
=> DiagnosticVerifier<TAnalyzer>.EmptyDiagnosticResults;

public static DiagnosticResult Diagnostic(string diagnosticId = null)
=> DiagnosticVerifier<TAnalyzer>.Diagnostic(diagnosticId);

public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
=> DiagnosticVerifier<TAnalyzer>.Diagnostic(descriptor);

public static DiagnosticResult CompilerError(string errorIdentifier)
=> DiagnosticVerifier<TAnalyzer>.CompilerError(errorIdentifier);
}
}
176 changes: 176 additions & 0 deletions samples/Shared/UnitTestFramework/DiagnosticResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace Roslyn.UnitTestFramework
{
/// <summary>
/// Structure that stores information about a <see cref="Diagnostic"/> appearing in a source.
/// </summary>
public struct DiagnosticResult
{
private const string DefaultPath = "Test0.cs";

private static readonly object[] EmptyArguments = new object[0];

private ImmutableArray<FileLinePositionSpan> _spans;
private string _message;

public DiagnosticResult(string id, DiagnosticSeverity severity)
: this()
{
Id = id;
Severity = severity;
}

public DiagnosticResult(DiagnosticDescriptor descriptor)
: this()
{
Id = descriptor.Id;
Severity = descriptor.DefaultSeverity;
MessageFormat = descriptor.MessageFormat;
}

public ImmutableArray<FileLinePositionSpan> Spans
{
get
{
return _spans.IsDefault ? ImmutableArray<FileLinePositionSpan>.Empty : _spans;
}
}

public DiagnosticSeverity Severity
{
get;
private set;
}

public string Id
{
get;
private set;
}

public string Message
{
get
{
if (_message != null)
{
return _message;
}

if (MessageFormat != null)
{
return string.Format(MessageFormat.ToString(), MessageArguments ?? EmptyArguments);
}

return null;
}
}

public LocalizableString MessageFormat
{
get;
private set;
}

public object[] MessageArguments
{
get;
private set;
}

public bool HasLocation
{
get
{
return (_spans != null) && (_spans.Length > 0);
}
}

public DiagnosticResult WithSeverity(DiagnosticSeverity severity)
{
DiagnosticResult result = this;
result.Severity = severity;
return result;
}

public DiagnosticResult WithArguments(params object[] arguments)
{
DiagnosticResult result = this;
result.MessageArguments = arguments;
return result;
}

public DiagnosticResult WithMessage(string message)
{
DiagnosticResult result = this;
result._message = message;
return result;
}

public DiagnosticResult WithMessageFormat(LocalizableString messageFormat)
{
DiagnosticResult result = this;
result.MessageFormat = messageFormat;
return result;
}

public DiagnosticResult WithLocation(int line, int column)
{
return WithLocation(DefaultPath, line, column);
}

public DiagnosticResult WithLocation(string path, int line, int column)
{
LinePosition linePosition = new LinePosition(line, column);

return AppendSpan(new FileLinePositionSpan(path, linePosition, linePosition));
}

public DiagnosticResult WithSpan(int startLine, int startColumn, int endLine, int endColumn)
{
return WithSpan(DefaultPath, startLine, startColumn, endLine, endColumn);
}

public DiagnosticResult WithSpan(string path, int startLine, int startColumn, int endLine, int endColumn)
{
return AppendSpan(new FileLinePositionSpan(path, new LinePosition(startLine, startColumn), new LinePosition(endLine, endColumn)));
}

public DiagnosticResult WithLineOffset(int offset)
{
DiagnosticResult result = this;
ImmutableArray<FileLinePositionSpan>.Builder spansBuilder = result._spans.ToBuilder();
for (int i = 0; i < result._spans.Length; i++)
{
LinePosition newStartLinePosition = new LinePosition(result._spans[i].StartLinePosition.Line + offset, result._spans[i].StartLinePosition.Character);
LinePosition newEndLinePosition = new LinePosition(result._spans[i].EndLinePosition.Line + offset, result._spans[i].EndLinePosition.Character);

spansBuilder[i] = new FileLinePositionSpan(result._spans[i].Path, newStartLinePosition, newEndLinePosition);
}

result._spans = spansBuilder.MoveToImmutable();
return result;
}

private DiagnosticResult AppendSpan(FileLinePositionSpan span)
{
ImmutableArray<FileLinePositionSpan> newSpans = _spans.Add(span);

// clone the object, so that the fluent syntax will work on immutable objects.
return new DiagnosticResult
{
Id = Id,
_message = _message,
MessageFormat = MessageFormat,
MessageArguments = MessageArguments,
Severity = Severity,
_spans = newSpans,
};
}
}
}
79 changes: 79 additions & 0 deletions samples/Shared/UnitTestFramework/DiagnosticVerifier`1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Roslyn.UnitTestFramework
{
public static class DiagnosticVerifier<TAnalyzer>
where TAnalyzer : DiagnosticAnalyzer, new()
{
public static DiagnosticResult[] EmptyDiagnosticResults { get; } = { };

public static DiagnosticResult Diagnostic(string diagnosticId = null)
{
TAnalyzer analyzer = new TAnalyzer();
ImmutableArray<DiagnosticDescriptor> supportedDiagnostics = analyzer.SupportedDiagnostics;
if (diagnosticId is null)
{
return Diagnostic(supportedDiagnostics.Single());
}
else
{
return Diagnostic(supportedDiagnostics.Single(i => i.Id == diagnosticId));
}
}

public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
{
return new DiagnosticResult(descriptor);
}

public static DiagnosticResult CompilerError(string errorIdentifier)
{
return new DiagnosticResult(errorIdentifier, DiagnosticSeverity.Error);
}

public static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult expected, CancellationToken cancellationToken)
=> VerifyCSharpDiagnosticAsync(source, new[] { expected }, cancellationToken);

public static Task VerifyCSharpDiagnosticAsync(string source, DiagnosticResult[] expected, CancellationToken cancellationToken)
{
CSharpTest test = new CSharpTest
{
TestCode = source,
};

test.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync(cancellationToken);
}

public class CSharpTest : GenericAnalyzerTest
{
public override string Language => LanguageNames.CSharp;

protected override IEnumerable<DiagnosticAnalyzer> GetDiagnosticAnalyzers()
=> new[] { new TAnalyzer() };

protected override IEnumerable<CodeFixProvider> GetCodeFixProviders()
=> Enumerable.Empty<CodeFixProvider>();
}

public class VisualBasicTest : GenericAnalyzerTest
{
public override string Language => LanguageNames.VisualBasic;

protected override IEnumerable<DiagnosticAnalyzer> GetDiagnosticAnalyzers()
=> new[] { new TAnalyzer() };

protected override IEnumerable<CodeFixProvider> GetCodeFixProviders()
=> Enumerable.Empty<CodeFixProvider>();
}
}
}
Loading

0 comments on commit 718fada

Please sign in to comment.