|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes.Suppression;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CodeActions.CodeAction;
namespace Microsoft.CodeAnalysis.CodeFixes.Configuration.ConfigureSeverity;
[ExportConfigurationFixProvider(PredefinedConfigurationFixProviderNames.ConfigureSeverity, LanguageNames.CSharp, LanguageNames.VisualBasic), Shared]
[ExtensionOrder(After = PredefinedConfigurationFixProviderNames.Suppression)]
internal sealed partial class ConfigureSeverityLevelCodeFixProvider : IConfigurationFixProvider
{
private static readonly ImmutableArray<(string value, string title)> s_editorConfigSeverityStrings =
[
(EditorConfigSeverityStrings.None, WorkspacesResources.None),
(EditorConfigSeverityStrings.Silent, FeaturesResources.Silent),
(EditorConfigSeverityStrings.Suggestion, WorkspacesResources.Suggestion),
(EditorConfigSeverityStrings.Warning, WorkspacesResources.Warning),
(EditorConfigSeverityStrings.Error, WorkspacesResources.Error),
];
[ImportingConstructor]
[SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
public ConfigureSeverityLevelCodeFixProvider()
{
}
// We only offer fix for configurable diagnostics.
// Also skip suppressed diagnostics defensively, though the code fix engine should ideally never call us for suppressed diagnostics.
public bool IsFixableDiagnostic(Diagnostic diagnostic)
=> !diagnostic.IsSuppressed && !SuppressionHelpers.IsNotConfigurableDiagnostic(diagnostic);
public FixAllProvider? GetFixAllProvider()
=> null;
public Task<ImmutableArray<CodeFix>> GetFixesAsync(TextDocument document, TextSpan span, IEnumerable<Diagnostic> diagnostics, CancellationToken cancellationToken)
=> Task.FromResult(GetConfigurations(document.Project, diagnostics, cancellationToken));
public Task<ImmutableArray<CodeFix>> GetFixesAsync(Project project, IEnumerable<Diagnostic> diagnostics, CancellationToken cancellationToken)
=> Task.FromResult(GetConfigurations(project, diagnostics, cancellationToken));
private static ImmutableArray<CodeFix> GetConfigurations(Project project, IEnumerable<Diagnostic> diagnostics, CancellationToken cancellationToken)
{
var result = ArrayBuilder<CodeFix>.GetInstance();
var analyzerDiagnosticsByCategory = new SortedDictionary<string, ArrayBuilder<Diagnostic>>();
using var disposer = ArrayBuilder<Diagnostic>.GetInstance(out var analyzerDiagnostics);
foreach (var diagnostic in diagnostics)
{
var nestedActions = ArrayBuilder<CodeAction>.GetInstance();
foreach (var (value, title) in s_editorConfigSeverityStrings)
{
nestedActions.Add(
SolutionChangeAction.Create(
title,
cancellationToken => ConfigurationUpdater.ConfigureSeverityAsync(value, diagnostic, project, cancellationToken),
value));
}
var codeAction = new TopLevelConfigureSeverityCodeAction(diagnostic, nestedActions.ToImmutableAndFree());
result.Add(new CodeFix(project, codeAction, diagnostic));
// Bulk configuration is only supported for analyzer diagnostics.
if (!SuppressionHelpers.IsCompilerDiagnostic(diagnostic))
{
// Ensure diagnostic has a valid non-empty 'Category' for category based configuration.
if (!string.IsNullOrEmpty(diagnostic.Descriptor.Category))
{
var diagnosticsForCategory = analyzerDiagnosticsByCategory.GetOrAdd(diagnostic.Descriptor.Category, _ => ArrayBuilder<Diagnostic>.GetInstance());
diagnosticsForCategory.Add(diagnostic);
}
analyzerDiagnostics.Add(diagnostic);
}
}
foreach (var (category, diagnosticsWithCategory) in analyzerDiagnosticsByCategory)
{
AddBulkConfigurationCodeFixes(diagnosticsWithCategory.ToImmutableAndFree(), category);
}
if (analyzerDiagnostics.Count > 0)
{
AddBulkConfigurationCodeFixes(analyzerDiagnostics.ToImmutable(), category: null);
}
return result.ToImmutableAndFree();
void AddBulkConfigurationCodeFixes(ImmutableArray<Diagnostic> diagnostics, string? category)
{
var nestedActions = ArrayBuilder<CodeAction>.GetInstance();
foreach (var (value, title) in s_editorConfigSeverityStrings)
{
nestedActions.Add(
SolutionChangeAction.Create(
title,
solution => category != null
? ConfigurationUpdater.BulkConfigureSeverityAsync(value, category, project, cancellationToken)
: ConfigurationUpdater.BulkConfigureSeverityAsync(value, project, cancellationToken),
value));
}
var codeAction = new TopLevelBulkConfigureSeverityCodeAction(nestedActions.ToImmutableAndFree(), category);
result.Add(new CodeFix(project, codeAction, diagnostics));
}
}
}
|