| File: src\roslyn\src\Analyzers\Core\Analyzers\SimplifyInterpolation\AbstractSimplifyInterpolationDiagnosticAnalyzer.cs | Web Access |
| Project: src\roslyn\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle) |
// 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 Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.CodeAnalysis.SimplifyInterpolation; internal abstract class AbstractSimplifyInterpolationDiagnosticAnalyzer< TInterpolationSyntax, TExpressionSyntax>() : AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer(IDEDiagnosticIds.SimplifyInterpolationId, EnforceOnBuildValues.SimplifyInterpolation, CodeStyleOptions2.PreferSimplifiedInterpolation, new LocalizableResourceString(nameof(AnalyzersResources.Simplify_interpolation), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)), new LocalizableResourceString(nameof(AnalyzersResources.Interpolation_can_be_simplified), AnalyzersResources.ResourceManager, typeof(AnalyzersResources))) where TInterpolationSyntax : SyntaxNode where TExpressionSyntax : SyntaxNode { protected abstract ISyntaxFacts SyntaxFacts { get; } protected abstract IVirtualCharService VirtualCharService { get; } protected abstract AbstractSimplifyInterpolationHelpers<TInterpolationSyntax, TExpressionSyntax> Helpers { get; } public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; protected override void InitializeWorker(AnalysisContext context) => context.RegisterCompilationStartAction( context => { var compilation = context.Compilation; var knownToStringFormats = Helpers.BuildKnownToStringFormatsLookupTable(compilation); var readOnlySpanOfCharType = compilation.ReadOnlySpanOfTType()?.Construct(compilation.GetSpecialType(SpecialType.System_Char)); var handlersAvailable = compilation.InterpolatedStringHandlerAttributeType() != null; context.RegisterOperationAction(context => AnalyzeInterpolation(context, compilation.FormattableStringType(), compilation.IFormattableType(), readOnlySpanOfCharType, knownToStringFormats, handlersAvailable), OperationKind.Interpolation); }); private void AnalyzeInterpolation( OperationAnalysisContext context, INamedTypeSymbol? formattableStringType, INamedTypeSymbol? iFormattableType, INamedTypeSymbol? readOnlySpanOfCharType, ImmutableDictionary<IMethodSymbol, string> knownToStringFormats, bool handlersAvailable) { var option = context.GetAnalyzerOptions().PreferSimplifiedInterpolation; // No point in analyzing if the option is off. if (!option.Value || ShouldSkipAnalysis(context, option.Notification)) return; var interpolation = (IInterpolationOperation)context.Operation; // Formattable strings can observe the inner types of the arguments passed to them. So we can't safely change // to drop ToString in that. if (interpolation.Parent is IInterpolatedStringOperation { Parent: IConversionOperation { Type: { } convertedType } conversion } && (convertedType.Equals(formattableStringType) || convertedType.Equals(iFormattableType))) { // One exception to this is calling directly into FormattableString.Invariant. That method has known good // behavior that is fine to continue calling into. var isInvariantInvocation = conversion.Parent is IArgumentOperation { Parent: IInvocationOperation invocation } && invocation.TargetMethod.Name == nameof(FormattableString.Invariant) && invocation.TargetMethod.ContainingType.Equals(formattableStringType); if (!isInvariantInvocation) return; } this.Helpers.UnwrapInterpolation( this.VirtualCharService, this.SyntaxFacts, interpolation, knownToStringFormats, readOnlySpanOfCharType, handlersAvailable, out _, out var alignment, out _, out var formatString, out var unnecessaryLocations); if (alignment == null && formatString == null) return; // The diagnostic itself fades the first unnecessary location, and the remaining locations are passed as // additional unnecessary locations. var firstUnnecessaryLocation = unnecessaryLocations[0]; var remainingUnnecessaryLocations = unnecessaryLocations.RemoveAt(0); context.ReportDiagnostic(DiagnosticHelper.CreateWithLocationTags( Descriptor, firstUnnecessaryLocation, option.Notification, context.Options, additionalLocations: [interpolation.Syntax.GetLocation()], additionalUnnecessaryLocations: remainingUnnecessaryLocations)); } }