| File: Microsoft.NetCore.Analyzers\Performance\CSharpCollapseMultiplePathOperations.Fixer.cs | Web Access |
| Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.CSharp.NetAnalyzers\Microsoft.CodeAnalysis.CSharp.NetAnalyzers.csproj (Microsoft.CodeAnalysis.CSharp.NetAnalyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; using System.Composition; using System.Linq; using Analyzer.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.NetAnalyzers; using Microsoft.NetCore.Analyzers; using Microsoft.NetCore.Analyzers.Performance; namespace Microsoft.NetCore.CSharp.Analyzers.Performance { [ExportCodeFixProvider(LanguageNames.CSharp), Shared] public sealed class CSharpCollapseMultiplePathOperationsFixer : SyntaxEditorBasedCodeFixProvider { public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(CollapseMultiplePathOperationsAnalyzer.RuleId); public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var document = context.Document; var diagnostic = context.Diagnostics[0]; var root = await document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var node = root.FindNode(context.Span, getInnermostNodeForTie: true); if (node is not InvocationExpressionSyntax || await document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false) is not { } semanticModel || WellKnownTypeProvider.GetOrCreate(semanticModel.Compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIOPath) is null) { return; } // Get the method name from diagnostic properties if (!diagnostic.Properties.TryGetValue(CollapseMultiplePathOperationsAnalyzer.MethodNameKey, out var methodName)) { methodName = "Path"; } RegisterCodeFix( context, string.Format(MicrosoftNetCoreAnalyzersResources.CollapseMultiplePathOperationsCodeFixTitle, methodName), nameof(MicrosoftNetCoreAnalyzersResources.CollapseMultiplePathOperationsCodeFixTitle)); } protected sealed override async Task ApplyFixAsync(Document document, Diagnostic diagnostic, SyntaxEditor editor, CancellationToken cancellationToken) { if (editor.OriginalRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true) is not InvocationExpressionSyntax invocation || await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false) is not { } semanticModel || WellKnownTypeProvider.GetOrCreate(semanticModel.Compilation).GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemIOPath) is not { } pathType) { return; } // Collect all arguments by recursively unwrapping nested Path.Combine/Join calls var allArguments = CollectAllArguments(invocation, pathType, semanticModel); foreach (var argument in allArguments) { editor.TrackNode(argument); } editor.ReplaceNode(invocation, (currentNode, _) => { var current = (InvocationExpressionSyntax)currentNode; // Create new argument list with all collected arguments, as any fix inside them left them var newArgumentList = SyntaxFactory.ArgumentList( SyntaxFactory.SeparatedList(allArguments.Select(argument => current.GetCurrentNode(argument) ?? argument))); return current.WithArgumentList(newArgumentList) .WithTriviaFrom(current); }); } private static ArgumentSyntax[] CollectAllArguments(InvocationExpressionSyntax invocation, INamedTypeSymbol pathType, SemanticModel semanticModel) { var arguments = ImmutableArray.CreateBuilder<ArgumentSyntax>(); foreach (var argument in invocation.ArgumentList.Arguments) { if (argument.Expression is InvocationExpressionSyntax nestedInvocation && IsPathCombineOrJoin(nestedInvocation, pathType, semanticModel, out var methodName) && IsPathCombineOrJoin(invocation, pathType, semanticModel, out var outerMethodName) && methodName == outerMethodName) { // Recursively collect arguments from nested invocation arguments.AddRange(CollectAllArguments(nestedInvocation, pathType, semanticModel)); } else { arguments.Add(argument); } } return arguments.ToArray(); } private static bool IsPathCombineOrJoin(InvocationExpressionSyntax invocation, INamedTypeSymbol pathType, SemanticModel semanticModel, out string methodName) { if (semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, pathType) && methodSymbol.Name is "Combine" or "Join") { methodName = methodSymbol.Name; return true; } methodName = string.Empty; return false; } } }