/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Analyzers/Core/CodeFixes/PopulateSwitch/AbstractPopulateSwitchCodeFixProvider.cs
224 строки
9 KB
Cyrus Najmabadi
Reapply "Update methods to be `async`." (#81808)
31 дек 2025, 02:04
31 дек 2025, 02:04
ba729ad
Код
Авторство
О чём код?
// 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.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; namespace Microsoft.CodeAnalysis.PopulateSwitch; internal abstract class AbstractPopulateSwitchCodeFixProvider< TSwitchOperation, TSwitchSyntax, TSwitchArmSyntax, TMemberAccessExpression>(string diagnosticId) : SyntaxEditorBasedCodeFixProvider where TSwitchOperation : IOperation where TSwitchSyntax : SyntaxNode where TSwitchArmSyntax : SyntaxNode where TMemberAccessExpression : SyntaxNode { public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = [diagnosticId]; protected abstract ITypeSymbol GetSwitchType(TSwitchOperation switchStatement); protected abstract ICollection<ISymbol> GetMissingEnumMembers(TSwitchOperation switchOperation); protected abstract bool HasNullSwitchArm(TSwitchOperation switchOperation); protected abstract TSwitchArmSyntax CreateSwitchArm(SyntaxGenerator generator, Compilation compilation, TMemberAccessExpression caseLabel); protected abstract TSwitchArmSyntax CreateNullSwitchArm(SyntaxGenerator generator, Compilation compilation); protected abstract TSwitchArmSyntax CreateDefaultSwitchArm(SyntaxGenerator generator, Compilation compilation); protected abstract int InsertPosition(TSwitchOperation switchOperation); protected abstract TSwitchSyntax InsertSwitchArms(SyntaxGenerator generator, TSwitchSyntax switchNode, int insertLocation, List<TSwitchArmSyntax> newArms); protected abstract void FixOneDiagnostic( Document document, SyntaxEditor editor, SemanticModel semanticModel, bool addCases, bool addDefaultCase, bool onlyOneDiagnostic, bool hasMissingCases, bool hasMissingDefaultCase, TSwitchSyntax switchNode, TSwitchOperation switchOperation); public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var diagnostic = context.Diagnostics.First(); var properties = diagnostic.Properties; var missingCases = bool.Parse(properties[PopulateSwitchStatementHelpers.MissingCases]!); var missingDefaultCase = bool.Parse(properties[PopulateSwitchStatementHelpers.MissingDefaultCase]!); Debug.Assert(missingCases || missingDefaultCase); var document = context.Document; if (missingCases) { context.RegisterCodeFix( CodeAction.Create( AnalyzersResources.Add_missing_cases, c => FixAsync(document, diagnostic, addCases: true, addDefaultCase: false, cancellationToken: c), nameof(AnalyzersResources.Add_missing_cases)), context.Diagnostics); } if (missingDefaultCase) { context.RegisterCodeFix( CodeAction.Create( CodeFixesResources.Add_default_case, c => FixAsync(document, diagnostic, addCases: false, addDefaultCase: true, cancellationToken: c), nameof(CodeFixesResources.Add_default_case)), context.Diagnostics); } if (missingCases && missingDefaultCase) { context.RegisterCodeFix( CodeAction.Create( CodeFixesResources.Add_both, c => FixAsync(document, diagnostic, addCases: true, addDefaultCase: true, cancellationToken: c), nameof(CodeFixesResources.Add_both)), context.Diagnostics); } } private Task<Document> FixAsync( Document document, Diagnostic diagnostic, bool addCases, bool addDefaultCase, CancellationToken cancellationToken) { return FixAllAsync(document, [diagnostic], addCases, addDefaultCase, cancellationToken); } private Task<Document> FixAllAsync( Document document, ImmutableArray<Diagnostic> diagnostics, bool addCases, bool addDefaultCase, CancellationToken cancellationToken) { return FixAllWithEditorAsync(document, editor => FixWithEditorAsync(document, editor, diagnostics, addCases, addDefaultCase, cancellationToken), cancellationToken); } private async Task FixWithEditorAsync( Document document, SyntaxEditor editor, ImmutableArray<Diagnostic> diagnostics, bool addCases, bool addDefaultCase, CancellationToken cancellationToken) { foreach (var diagnostic in diagnostics) { await FixOneDiagnosticAsync( document, editor, diagnostic, addCases, addDefaultCase, diagnostics.Length == 1, cancellationToken).ConfigureAwait(false); } } private async Task FixOneDiagnosticAsync( Document document, SyntaxEditor editor, Diagnostic diagnostic, bool addCases, bool addDefaultCase, bool onlyOneDiagnostic, CancellationToken cancellationToken) { var hasMissingCases = bool.Parse(diagnostic.Properties[PopulateSwitchStatementHelpers.MissingCases]!); var hasMissingDefaultCase = bool.Parse(diagnostic.Properties[PopulateSwitchStatementHelpers.MissingDefaultCase]!); var switchLocation = diagnostic.AdditionalLocations[0]; if (switchLocation.FindNode(getInnermostNodeForTie: true, cancellationToken) is not TSwitchSyntax switchNode) return; var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); // https://github.com/dotnet/roslyn/issues/40505 var switchStatement = (TSwitchOperation)model.GetOperation(switchNode, cancellationToken)!; FixOneDiagnostic( document, editor, model, addCases, addDefaultCase, onlyOneDiagnostic, hasMissingCases, hasMissingDefaultCase, switchNode, switchStatement); } protected TSwitchSyntax UpdateSwitchNode( SyntaxEditor editor, SemanticModel semanticModel, bool addCases, bool addDefaultCase, bool hasMissingCases, bool hasMissingDefaultCase, TSwitchSyntax switchNode, TSwitchOperation switchOperation) { var enumType = GetSwitchType(switchOperation); var isNullable = false; if (enumType.IsNullable(out var underlyingType)) { isNullable = true; enumType = underlyingType; } var generator = editor.Generator; var newArms = new List<TSwitchArmSyntax>(); if (hasMissingCases && addCases) { var missingArms = from e in GetMissingEnumMembers(switchOperation) let caseLabel = (TMemberAccessExpression)generator.MemberAccessExpression(generator.TypeExpression(enumType), e.Name).WithAdditionalAnnotations(Simplifier.Annotation) select CreateSwitchArm(generator, semanticModel.Compilation, caseLabel); newArms.AddRange(missingArms); if (isNullable && !HasNullSwitchArm(switchOperation)) newArms.Add(CreateNullSwitchArm(generator, semanticModel.Compilation)); } if (hasMissingDefaultCase && addDefaultCase) { // Always add the default clause at the end. newArms.Add(CreateDefaultSwitchArm(generator, semanticModel.Compilation)); } var insertLocation = InsertPosition(switchOperation); var newSwitchNode = InsertSwitchArms(generator, switchNode, insertLocation, newArms) .WithAdditionalAnnotations(Formatter.Annotation); return newSwitchNode; } protected static void AddMissingBraces( Document document, ref SyntaxNode root, ref TSwitchSyntax switchNode) { // Parsing of the switch may have caused imbalanced braces. i.e. the switch // may have consumed a brace that was intended for a higher level construct. // So balance the tree first, then do the switch replacement. var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>(); syntaxFacts.AddFirstMissingCloseBrace( root, switchNode, out var newRoot, out var newSwitchNode); root = newRoot; switchNode = newSwitchNode; } protected override Task FixAllAsync( Document document, ImmutableArray<Diagnostic> diagnostics, SyntaxEditor editor, CancellationToken cancellationToken) { // If the user is performing a fix-all, then fix up all the issues we see. i.e. // add missing cases and missing 'default' cases for any switches we reported an // issue on. return FixWithEditorAsync(document, editor, diagnostics, addCases: true, addDefaultCase: true, cancellationToken: cancellationToken); } }