Skip to content

Commit

Permalink
[GH-11] - Adding tests for namespace and class suppressions
Browse files Browse the repository at this point in the history
  • Loading branch information
tpodolak committed Jun 24, 2018
1 parent eaa7d63 commit 3757f09
Show file tree
Hide file tree
Showing 19 changed files with 817 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,57 +10,82 @@
using Newtonsoft.Json;
using NSubstitute.Analyzers.Shared.Extensions;
using NSubstitute.Analyzers.Shared.Settings;
using NSubstitute.Analyzers.Shared.Threading;

namespace NSubstitute.Analyzers.Shared.CodeFixProviders
{
internal abstract class AbstractSuppressDiagnosticsCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticIdentifiers.NonVirtualSetupSpecification);

public override Task RegisterCodeFixesAsync(CodeFixContext context)
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var project = context.Document.Project;
var workspace = project.Solution.Workspace;

// check if we are allowed to add it
if (!workspace.CanApplyChange(ApplyChangesKind.AddAdditionalDocument))
{
return SpecializedTasks.CompletedTask;
return;
}

foreach (var diagnostic in context.Diagnostics.Where(diagnostic => FixableDiagnosticIds.Contains(diagnostic.Id)))
{
context.RegisterCodeFix(
CodeAction.Create(
$"Suppress in {AnalyzersSettings.AnalyzerFileName}",
cancellationToken => GetTransformedSolutionAsync(context, diagnostic),
nameof(AbstractSuppressDiagnosticsCodeFixProvider)),
diagnostic);
}

return SpecializedTasks.CompletedTask;
}

private async Task<Solution> GetTransformedSolutionAsync(CodeFixContext context, Diagnostic diagnostic)
{
var project = context.Document.Project;
var solution = project.Solution;

var settingsFile = GetSettingsFile(project);

// creating additional document from Roslyn is broken (https://github.com/dotnet/roslyn/issues/4655) the nsubstitute.json file have to be created by users manually
// if there is no settings file do not provide refactorings
if (settingsFile == null)
{
return solution;
return;
}

var root = await context.Document.GetSyntaxRootAsync();
var model = await context.Document.GetSemanticModelAsync();
var i = 1;
foreach (var diagnostic in context.Diagnostics.Where(diagnostic => FixableDiagnosticIds.Contains(diagnostic.Id)))
{
var syntaxNode = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
var symbolInfo = model.GetSymbolInfo(syntaxNode);

foreach (var innerSymbol in GetSuppressibleSymbol(symbolInfo.Symbol))
{
context.RegisterCodeFix(
CodeAction.Create(
CreateCodeFixTitle(diagnostic, innerSymbol),
cancellationToken => GetTransformedSolutionAsync(context, diagnostic, settingsFile, innerSymbol),
(i++).ToString()),
diagnostic);
}
}
}

private static string CreateCodeFixTitle(Diagnostic diagnostic, ISymbol innerSymbol)
{
var prefix = GetSymbolTitlePrefix(innerSymbol);
return $"Suppress {diagnostic.Id} for {prefix} {innerSymbol.Name} in {AnalyzersSettings.AnalyzerFileName}";
}

private static string GetSymbolTitlePrefix(ISymbol innerSymbol)
{
switch (innerSymbol)
{
case IMethodSymbol methodSymbol:
return "method";
case IPropertySymbol propertySymbol when propertySymbol.IsIndexer:
return "indexer";
case IPropertySymbol propertySymbol:
return "property";
case ITypeSymbol typeSymbol:
return "class";
case INamespaceSymbol namespaceSymbol:
return "namespace";
default:
return string.Empty;
}
}

var syntaxNode = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
var symbol = model.GetSymbolInfo(syntaxNode);
private Task<Solution> GetTransformedSolutionAsync(CodeFixContext context, Diagnostic diagnostic, TextDocument settingsFile, ISymbol symbol)
{
var project = context.Document.Project;
var solution = project.Solution;

var options = GetUpdatedAnalyzersOptions(context, diagnostic, symbol);

Expand All @@ -69,16 +94,18 @@ private async Task<Solution> GetTransformedSolutionAsync(CodeFixContext context,

var newDocumentId = settingsFile.Id ?? DocumentId.CreateNewId(project.Id);

return solution.AddAdditionalDocument(
solution = solution.AddAdditionalDocument(
newDocumentId,
AnalyzersSettings.AnalyzerFileName,
JsonConvert.SerializeObject(options, Formatting.Indented));

return Task.FromResult(solution);
}

private static AnalyzersSettings GetUpdatedAnalyzersOptions(CodeFixContext context, Diagnostic diagnostic, SymbolInfo symbol)
private static AnalyzersSettings GetUpdatedAnalyzersOptions(CodeFixContext context, Diagnostic diagnostic, ISymbol symbol)
{
var options = context.Document.Project.AnalyzerOptions.GetSettings(default(CancellationToken));
var target = DocumentationCommentId.CreateDeclarationId(symbol.Symbol);
var target = DocumentationCommentId.CreateDeclarationId(symbol);
options.Suppressions = options.Suppressions ?? new List<Suppression>();

var existingSuppression = options.Suppressions.FirstOrDefault(suppression => suppression.Target == target);
Expand All @@ -92,7 +119,7 @@ private static AnalyzersSettings GetUpdatedAnalyzersOptions(CodeFixContext conte
{
options.Suppressions.Add(new Suppression
{
Target = DocumentationCommentId.CreateDeclarationId(symbol.Symbol),
Target = DocumentationCommentId.CreateDeclarationId(symbol),
Rules = new List<string>
{
diagnostic.Id
Expand All @@ -108,5 +135,26 @@ private static TextDocument GetSettingsFile(Project project)
return project.AdditionalDocuments.SingleOrDefault(document =>
document.Name.Equals(AnalyzersSettings.AnalyzerFileName, StringComparison.CurrentCultureIgnoreCase));
}

private IEnumerable<ISymbol> GetSuppressibleSymbol(ISymbol symbol)
{
if (symbol == null)
{
yield break;
}

yield return symbol;

if (!(symbol is ITypeSymbol))
{
yield return symbol.ContainingType;
yield return symbol.ContainingType.ContainingNamespace;
}

if (symbol is ITypeSymbol)
{
yield return symbol.ContainingNamespace;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ public abstract class DefaultSuppressDiagnosticsCodeFixProviderVerifier : CSharp
[Fact]
public abstract Task SuppressesDiagnosticsInSettings_WhenSettingValueForNonVirtualIndexer();

[Fact]
public abstract Task SuppressesDiagnosticsInSettingsForClass_WhenSettingsValueForNonVirtualMember_AndSelectingClassSuppression();

[Fact]
public abstract Task SuppressesDiagnosticsInSettingsForNamespace_WhenSettingsValueForNonVirtualMember_AndSelectingNamespaceSuppression();

protected override DiagnosticAnalyzer GetDiagnosticAnalyzer()
{
return new NonVirtualSetupAnalyzer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,53 @@ public void Test()

await VerifySuppressionSettings(source, "P:MyNamespace.Foo.Item(System.Int32)", DiagnosticIdentifiers.NonVirtualSetupSpecification);
}

public override async Task SuppressesDiagnosticsInSettingsForClass_WhenSettingsValueForNonVirtualMember_AndSelectingClassSuppression()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public int this[int x] => 0;
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>();
substitute[1].Returns(1);
}
}
}";

await VerifySuppressionSettings(source, "T:MyNamespace.Foo", DiagnosticIdentifiers.NonVirtualSetupSpecification, 1);
}

public override async Task SuppressesDiagnosticsInSettingsForNamespace_WhenSettingsValueForNonVirtualMember_AndSelectingNamespaceSuppression()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public int this[int x] => 0;
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>();
substitute[1].Returns(1);
}
}
}";

await VerifySuppressionSettings(source, "N:MyNamespace", DiagnosticIdentifiers.NonVirtualSetupSpecification, 2);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,53 @@ public void Test()

await VerifySuppressionSettings(source, "P:MyNamespace.Foo.Item(System.Int32)", DiagnosticIdentifiers.NonVirtualSetupSpecification);
}

public override async Task SuppressesDiagnosticsInSettingsForClass_WhenSettingsValueForNonVirtualMember_AndSelectingClassSuppression()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public int this[int x] => 0;
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>();
substitute[1].Returns<int>(1);
}
}
}";

await VerifySuppressionSettings(source, "T:MyNamespace.Foo", DiagnosticIdentifiers.NonVirtualSetupSpecification, 1);
}

public override async Task SuppressesDiagnosticsInSettingsForNamespace_WhenSettingsValueForNonVirtualMember_AndSelectingNamespaceSuppression()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public int this[int x] => 0;
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>();
substitute[1].Returns<int>(1);
}
}
}";

await VerifySuppressionSettings(source, "N:MyNamespace", DiagnosticIdentifiers.NonVirtualSetupSpecification, 2);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,53 @@ public void Test()

await VerifySuppressionSettings(source, "P:MyNamespace.Foo.Item(System.Int32)", DiagnosticIdentifiers.NonVirtualSetupSpecification);
}

public override async Task SuppressesDiagnosticsInSettingsForClass_WhenSettingsValueForNonVirtualMember_AndSelectingClassSuppression()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public int this[int x] => 0;
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>();
SubstituteExtensions.Returns(substitute[1], 1);
}
}
}";

await VerifySuppressionSettings(source, "T:MyNamespace.Foo", DiagnosticIdentifiers.NonVirtualSetupSpecification, 1);
}

public override async Task SuppressesDiagnosticsInSettingsForNamespace_WhenSettingsValueForNonVirtualMember_AndSelectingNamespaceSuppression()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public int this[int x] => 0;
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>();
SubstituteExtensions.Returns(substitute[1], 1);
}
}
}";

await VerifySuppressionSettings(source, "N:MyNamespace", DiagnosticIdentifiers.NonVirtualSetupSpecification, 2);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,53 @@ public void Test()

await VerifySuppressionSettings(source, "P:MyNamespace.Foo.Item(System.Int32)", DiagnosticIdentifiers.NonVirtualSetupSpecification);
}

public override async Task SuppressesDiagnosticsInSettingsForClass_WhenSettingsValueForNonVirtualMember_AndSelectingClassSuppression()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public int this[int x] => 0;
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>();
substitute[1].ReturnsForAnyArgs<int>(1);
}
}
}";

await VerifySuppressionSettings(source, "T:MyNamespace.Foo", DiagnosticIdentifiers.NonVirtualSetupSpecification, 1);
}

public override async Task SuppressesDiagnosticsInSettingsForNamespace_WhenSettingsValueForNonVirtualMember_AndSelectingNamespaceSuppression()
{
var source = @"using NSubstitute;
namespace MyNamespace
{
public class Foo
{
public int this[int x] => 0;
}
public class FooTests
{
public void Test()
{
var substitute = NSubstitute.Substitute.For<Foo>();
substitute[1].ReturnsForAnyArgs<int>(1);
}
}
}";

await VerifySuppressionSettings(source, "N:MyNamespace", DiagnosticIdentifiers.NonVirtualSetupSpecification, 2);
}
}
}
Loading

0 comments on commit 3757f09

Please sign in to comment.