/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Analyzers/CSharp/CodeFixes/DisambiguateSameVariable/CSharpDisambiguateSameVariableCodeFixProvider.cs
160 строк
7 KB
Cyrus Najmabadi
Use primary constructors
11 июл 2024, 22:07
11 июл 2024, 22:07
da96f4c
Код
Авторство
О чём код?
// 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; using System.Collections.Immutable; using System.Composition; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.LanguageService; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.DisambiguateSameVariable; using static SyntaxFactory; [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.DisambiguateSameVariable), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class CSharpDisambiguateSameVariableCodeFixProvider() : SyntaxEditorBasedCodeFixProvider { private const string CS1717 = nameof(CS1717); // Assignment made to same variable; did you mean to assign something else? private const string CS1718 = nameof(CS1718); // Comparison made to same variable; did you mean to compare something else? public override ImmutableArray<string> FixableDiagnosticIds { get; } = [CS1717, CS1718]; public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var document = context.Document; var diagnostic = context.Diagnostics.First(); var cancellationToken = context.CancellationToken; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); if (CanFix(semanticModel, diagnostic, cancellationToken, out _, out _, out var title)) { RegisterCodeFix(context, title, nameof(CSharpDisambiguateSameVariableCodeFixProvider)); } } private static bool CanFix( SemanticModel semanticModel, Diagnostic diagnostic, CancellationToken cancellationToken, [NotNullWhen(true)] out SimpleNameSyntax? leftName, [NotNullWhen(true)] out ISymbol? matchingMember, [NotNullWhen(true)] out string? title) { leftName = null; matchingMember = null; title = null; var span = diagnostic.Location.SourceSpan; var node = diagnostic.Location.FindNode(getInnermostNodeForTie: true, cancellationToken); var (left, right, titleFormat) = node switch { BinaryExpressionSyntax binary => (binary.Left, binary.Right, CSharpCodeFixesResources.Compare_to_0), AssignmentExpressionSyntax assignment => (assignment.Left, assignment.Right, CSharpCodeFixesResources.Assign_to_0), _ => default, }; if (left == null || right == null) return false; if (left is not IdentifierNameSyntax and not MemberAccessExpressionSyntax) return false; var leftSymbol = semanticModel.GetSymbolInfo(left, cancellationToken).GetAnySymbol(); var rightSymbol = semanticModel.GetSymbolInfo(right, cancellationToken).GetAnySymbol(); if (leftSymbol == null || rightSymbol == null) return false; // Since this is a self assignment/compare, these symbols should be the same. Debug.Assert(leftSymbol.Equals(rightSymbol)); if (leftSymbol.Kind is not SymbolKind.Local and not SymbolKind.Parameter and not SymbolKind.Field and not SymbolKind.Property) { return false; } var localOrParamName = leftSymbol.Name; if (string.IsNullOrWhiteSpace(localOrParamName)) return false; var enclosingType = semanticModel.GetEnclosingNamedType(span.Start, cancellationToken); if (enclosingType == null) return false; // Given a local/param called 'x' See if we have an instance field or prop in the containing type // called 'x' or 'X' or '_x'. var pascalName = localOrParamName.ToPascalCase(); var camelName = localOrParamName.ToCamelCase(); var underscoreName = "_" + localOrParamName; var members = from t in enclosingType.GetBaseTypesAndThis() from m in t.GetMembers() where !m.IsStatic where m is IFieldSymbol or IPropertySymbol where !m.Equals(leftSymbol) where m.Name == localOrParamName || m.Name == pascalName || m.Name == camelName || m.Name == underscoreName where m.IsAccessibleWithin(enclosingType) select m; matchingMember = members.FirstOrDefault(); if (matchingMember == null) return false; var memberContainer = matchingMember.ContainingType.ToMinimalDisplayString(semanticModel, span.Start); title = string.Format(titleFormat, $"{memberContainer}.{matchingMember.Name}"); leftName = left is MemberAccessExpressionSyntax memberAccess ? memberAccess.Name : (IdentifierNameSyntax)left; return true; } protected override async Task FixAllAsync( Document document, ImmutableArray<Diagnostic> diagnostics, SyntaxEditor editor, CancellationToken cancellationToken) { var syntaxFacts = CSharpSyntaxFacts.Instance; var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); foreach (var diagnostic in diagnostics) { if (!CanFix(semanticModel, diagnostic, cancellationToken, out var nameNode, out var matchingMember, out _)) { continue; } var newNameNode = matchingMember.Name.ToIdentifierName(); var newExpr = (ExpressionSyntax)newNameNode; if (!syntaxFacts.IsNameOfSimpleMemberAccessExpression(nameNode) && !syntaxFacts.IsNameOfMemberBindingExpression(nameNode)) { newExpr = MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), newNameNode).WithAdditionalAnnotations(Simplifier.Annotation); } newExpr = newExpr.WithTriviaFrom(nameNode); editor.ReplaceNode(nameNode, newExpr); } } }