diff --git a/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/DefaultSuppressDiagnosticsCodeFixProvider.cs b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/DefaultSuppressDiagnosticsCodeFixProvider.cs new file mode 100644 index 00000000..132dd3fd --- /dev/null +++ b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/DefaultSuppressDiagnosticsCodeFixProvider.cs @@ -0,0 +1,11 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using NSubstitute.Analyzers.Shared.CodeFixProviders; + +namespace NSubstitute.Analyzers.CSharp.CodeFixProviders +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + public class DefaultSuppressDiagnosticsCodeFixProvider : AbstractSuppressDiagnosticsCodeFixProvider + { + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/SupressDiagnosticCodeFixProvider.cs b/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/SupressDiagnosticCodeFixProvider.cs deleted file mode 100644 index cbb3a5e4..00000000 --- a/src/NSubstitute.Analyzers.CSharp/CodeFixProviders/SupressDiagnosticCodeFixProvider.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Linq.Expressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Newtonsoft.Json; -using NSubstitute.Analyzers.Shared; -using NSubstitute.Analyzers.Shared.Extensions; -using NSubstitute.Analyzers.Shared.Settings; -using NSubstitute.Analyzers.Shared.Threading; - -namespace NSubstitute.Analyzers.CSharp.CodeFixProviders -{ - [ExportCodeFixProvider(LanguageNames.CSharp)] - public class SupressDiagnosticCodeFixProvider : CodeFixProvider - { - public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticIdentifiers.NonVirtualSetupSpecification); - - public override 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; - } - - foreach (var diagnostic in context.Diagnostics) - { - context.RegisterCodeFix( - CodeAction.Create( - "Suppress in nsubstitute.json", // TODO proper message - cancellationToken => GetTransformedSolutionAsync(context, diagnostic), - nameof(SupressDiagnosticCodeFixProvider)), - diagnostic); - } - - return SpecializedTasks.CompletedTask; - } - - private async Task GetTransformedSolutionAsync(CodeFixContext context, Diagnostic diagnostic) - { - var options = context.Document.Project.AnalyzerOptions.GetSettings(default(CancellationToken)); - var project = context.Document.Project; - var solution = project.Solution; - var root = await context.Document.GetSyntaxRootAsync().ConfigureAwait(false); - var model = await context.Document.GetSemanticModelAsync(); - - var forPartsOfNode = (InvocationExpressionSyntax)root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); - var symbol = model.GetSymbolInfo(forPartsOfNode); - - var newDocumentId = DocumentId.CreateNewId(project.Id); - - options.Suppressions.Add(new Suppression - { - Target = DocumentationCommentId.CreateDeclarationId(symbol.Symbol), - Rules = new List - { - DiagnosticIdentifiers.NonVirtualSetupSpecification - } - }); - - var newSolution = solution.AddAdditionalDocument(newDocumentId, AnalyzersSettings.AnalyzerFileName, JsonConvert.SerializeObject(options)); - - return newSolution; - } - } -} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.CSharp/NSubstitute.Analyzers.CSharp.csproj b/src/NSubstitute.Analyzers.CSharp/NSubstitute.Analyzers.CSharp.csproj index 4b84151a..0b6160b2 100644 --- a/src/NSubstitute.Analyzers.CSharp/NSubstitute.Analyzers.CSharp.csproj +++ b/src/NSubstitute.Analyzers.CSharp/NSubstitute.Analyzers.CSharp.csproj @@ -10,7 +10,7 @@ NSubstitute.Analyzers.CSharp - 7.0.0 + 8.0.0 Tomasz Podolak, NSubstitute.Analyzers contributors https://github.com/nsubstitute/NSubstitute.Analyzers/blob/master/LICENSE.md https://github.com/nsubstitute/NSubstitute.Analyzers diff --git a/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractSuppressDiagnosticsCodeFixProvider.cs b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractSuppressDiagnosticsCodeFixProvider.cs new file mode 100644 index 00000000..21841716 --- /dev/null +++ b/src/NSubstitute.Analyzers.Shared/CodeFixProviders/AbstractSuppressDiagnosticsCodeFixProvider.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Newtonsoft.Json; +using NSubstitute.Analyzers.Shared.Extensions; +using NSubstitute.Analyzers.Shared.Settings; +using NSubstitute.Analyzers.Shared.Threading; + +namespace NSubstitute.Analyzers.Shared.CodeFixProviders +{ + public class AbstractSuppressDiagnosticsCodeFixProvider : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticIdentifiers.NonVirtualSetupSpecification); + + public override 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; + } + + foreach (var diagnostic in context.Diagnostics.Where(diagnostic => FixableDiagnosticIds.Contains(diagnostic.Id))) + { + context.RegisterCodeFix( + CodeAction.Create( + "Suppress in nsubstitute.json", + cancellationToken => GetTransformedSolutionAsync(context, diagnostic), + nameof(AbstractSuppressDiagnosticsCodeFixProvider)), + diagnostic); + } + + return SpecializedTasks.CompletedTask; + } + + private async Task 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; + } + + var root = await context.Document.GetSyntaxRootAsync(); + var model = await context.Document.GetSemanticModelAsync(); + + var syntaxNode = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + var symbol = model.GetSymbolInfo(syntaxNode); + + var options = GetUpdatedAnalyzersOptions(context, diagnostic, symbol); + + project = project.RemoveAdditionalDocument(settingsFile.Id); + solution = project.Solution; + + var newDocumentId = settingsFile.Id ?? DocumentId.CreateNewId(project.Id); + + return solution.AddAdditionalDocument( + newDocumentId, + AnalyzersSettings.AnalyzerFileName, + JsonConvert.SerializeObject(options, Formatting.Indented)); + } + + private static AnalyzersSettings GetUpdatedAnalyzersOptions(CodeFixContext context, Diagnostic diagnostic, SymbolInfo symbol) + { + var options = context.Document.Project.AnalyzerOptions.GetSettings(default(CancellationToken)); + var target = DocumentationCommentId.CreateDeclarationId(symbol.Symbol); + options.Suppressions = options.Suppressions ?? new List(); + + var existingSuppression = options.Suppressions.FirstOrDefault(suppression => suppression.Target == target); + + if (existingSuppression != null) + { + existingSuppression.Rules = existingSuppression.Rules ?? new List(); + existingSuppression.Rules.Add(diagnostic.Id); + } + else + { + options.Suppressions.Add(new Suppression + { + Target = DocumentationCommentId.CreateDeclarationId(symbol.Symbol), + Rules = new List + { + diagnostic.Id + } + }); + } + + return options; + } + + private static TextDocument GetSettingsFile(Project project) + { + return project.AdditionalDocuments.SingleOrDefault(document => + document.Name.Equals(AnalyzersSettings.AnalyzerFileName, StringComparison.CurrentCultureIgnoreCase)); + } + } +} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/Extensions/AdditionalTextExtensions.cs b/src/NSubstitute.Analyzers.Shared/Extensions/AdditionalTextExtensions.cs deleted file mode 100644 index 53c76e40..00000000 --- a/src/NSubstitute.Analyzers.Shared/Extensions/AdditionalTextExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.IO; -using Microsoft.CodeAnalysis; -using NSubstitute.Analyzers.Shared.Settings; - -namespace NSubstitute.Analyzers.Shared.Extensions -{ - public static class AdditionalTextExtensions - { - public static bool IsSettingsFile(this AdditionalText additionalText) - { - return Path.GetFileName(additionalText.Path).Equals(AnalyzersSettings.AnalyzerFileName, StringComparison.CurrentCultureIgnoreCase); - } - } -} \ No newline at end of file diff --git a/src/NSubstitute.Analyzers.Shared/Extensions/AnalyzerOptionsExtensions.cs b/src/NSubstitute.Analyzers.Shared/Extensions/AnalyzerOptionsExtensions.cs index 11121606..11793278 100644 --- a/src/NSubstitute.Analyzers.Shared/Extensions/AnalyzerOptionsExtensions.cs +++ b/src/NSubstitute.Analyzers.Shared/Extensions/AnalyzerOptionsExtensions.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.Diagnostics; +using Newtonsoft.Json; using NSubstitute.Analyzers.Shared.Settings; namespace NSubstitute.Analyzers.Shared.Extensions @@ -22,9 +23,7 @@ public static AnalyzersSettings GetSettings(this AnalyzerOptions options, Cancel { var sourceText = settingsText.GetText(cancellationToken); - return AnalyzersSettings.Default; - - // return JsonConvert.DeserializeObject(sourceText.ToString()); + return JsonConvert.DeserializeObject(sourceText.ToString()); } catch (Exception) { diff --git a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SupressDiagnosticCodeFixProviderTests.cs b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SupressDiagnosticCodeFixProviderTests.cs index 0ed904c3..ef0fd872 100644 --- a/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SupressDiagnosticCodeFixProviderTests.cs +++ b/tests/NSubstitute.Analyzers.Tests.CSharp/CodeFixProviderTests/SupressDiagnosticCodeFixProviderTests.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using NSubstitute.Analyzers.CSharp.CodeFixProviders; using NSubstitute.Analyzers.CSharp.DiagnosticAnalyzers; +using NSubstitute.Analyzers.Shared.CodeFixProviders; using NSubstitute.Analyzers.Tests.CSharp.DiagnosticAnalyzerTests.NonVirtualSetupAnalyzerTests; using NSubstitute.Analyzers.Tests.Shared.CodeFixProviders; using Xunit; @@ -46,7 +47,7 @@ protected override DiagnosticAnalyzer GetDiagnosticAnalyzer() protected override CodeFixProvider GetCodeFixProvider() { - return new SupressDiagnosticCodeFixProvider(); + return new AbstractSuppressDiagnosticsCodeFixProvider(); } } } \ No newline at end of file