From bf878e12902f6ec8cedbd8fae33d16d74656156a Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 15 Jul 2022 16:32:18 +0200 Subject: [PATCH 01/10] Move UseExpressionBodyForLambda to Analyzers layer --- .../Analyzers/CSharpAnalyzers.projitems | 1 + .../Analyzers/CSharpAnalyzersResources.resx | 6 + ...pressionBodyForLambdaDiagnosticAnalyzer.cs | 215 +++++++++++++++ .../xlf/CSharpAnalyzersResources.cs.xlf | 10 + .../xlf/CSharpAnalyzersResources.de.xlf | 10 + .../xlf/CSharpAnalyzersResources.es.xlf | 10 + .../xlf/CSharpAnalyzersResources.fr.xlf | 10 + .../xlf/CSharpAnalyzersResources.it.xlf | 10 + .../xlf/CSharpAnalyzersResources.ja.xlf | 10 + .../xlf/CSharpAnalyzersResources.ko.xlf | 10 + .../xlf/CSharpAnalyzersResources.pl.xlf | 10 + .../xlf/CSharpAnalyzersResources.pt-BR.xlf | 10 + .../xlf/CSharpAnalyzersResources.ru.xlf | 10 + .../xlf/CSharpAnalyzersResources.tr.xlf | 10 + .../xlf/CSharpAnalyzersResources.zh-Hans.xlf | 10 + .../xlf/CSharpAnalyzersResources.zh-Hant.xlf | 10 + .../CodeFixes/CSharpCodeFixes.projitems | 1 + ...eExpressionBodyForLambdaCodeFixProvider.cs | 179 +++++++++++++ ...xpressionBodyForLambdaCodeStyleProvider.cs | 248 ------------------ ...BodyForLambdaCodeStyleProvider_Analysis.cs | 73 ------ ...onBodyForLambdaCodeStyleProvider_Fixing.cs | 56 ---- ...yForLambdaCodeStyleProvider_Refactoring.cs | 146 ----------- .../AbstractCodeStyleProvider.Analysis.cs | 141 ---------- .../AbstractCodeStyleProvider.Fixing.cs | 52 ---- .../AbstractCodeStyleProvider.Refactoring.cs | 89 ------- .../CodeStyle/AbstractCodeStyleProvider.cs | 149 ----------- .../Core/Portable/FeaturesResources.resx | 6 - .../Portable/xlf/FeaturesResources.cs.xlf | 10 - .../Portable/xlf/FeaturesResources.de.xlf | 10 - .../Portable/xlf/FeaturesResources.es.xlf | 10 - .../Portable/xlf/FeaturesResources.fr.xlf | 10 - .../Portable/xlf/FeaturesResources.it.xlf | 10 - .../Portable/xlf/FeaturesResources.ja.xlf | 10 - .../Portable/xlf/FeaturesResources.ko.xlf | 10 - .../Portable/xlf/FeaturesResources.pl.xlf | 10 - .../Portable/xlf/FeaturesResources.pt-BR.xlf | 10 - .../Portable/xlf/FeaturesResources.ru.xlf | 10 - .../Portable/xlf/FeaturesResources.tr.xlf | 10 - .../xlf/FeaturesResources.zh-Hans.xlf | 10 - .../xlf/FeaturesResources.zh-Hant.xlf | 10 - 40 files changed, 532 insertions(+), 1090 deletions(-) create mode 100644 src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs create mode 100644 src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs delete mode 100644 src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider.cs delete mode 100644 src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider_Analysis.cs delete mode 100644 src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider_Fixing.cs delete mode 100644 src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider_Refactoring.cs delete mode 100644 src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.Analysis.cs delete mode 100644 src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.Fixing.cs delete mode 100644 src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.Refactoring.cs delete mode 100644 src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.cs diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems index 1166ed25ed1fd..b523943346420 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems @@ -81,6 +81,7 @@ + diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx index 2309710bdd916..9a16f3bcdd125 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx @@ -383,4 +383,10 @@ Remove redundant nullable directive + + Use expression body for lambda expressions + + + Use block body for lambda expressions + \ No newline at end of file diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs new file mode 100644 index 0000000000000..da9f418094543 --- /dev/null +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs @@ -0,0 +1,215 @@ +// 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.Immutable; +using System.Threading; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal sealed class UseExpressionBodyForLambdaDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer + { + private static readonly LocalizableString UseExpressionBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_expression_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); + private static readonly LocalizableString UseBlockBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_block_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); + + public UseExpressionBodyForLambdaDiagnosticAnalyzer() + : base(IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId, + EnforceOnBuildValues.UseExpressionBodyForLambdaExpressions, + CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, + LanguageNames.CSharp, + UseExpressionBodyTitle, + UseExpressionBodyTitle, + isUnnecessary: false, + configurable: true) + { + } + + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() + => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; + + protected override void InitializeWorker(AnalysisContext context) + => context.RegisterSyntaxNodeAction(AnalyzeIfEnabled, + SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression); + + private static CodeStyleOption2 GetCodeStyleOption(AnalyzerOptionsProvider provider) + => ((CSharpAnalyzerOptionsProvider)provider).PreferExpressionBodiedLambdas; + + /// + /// Helper to get the true ReportDiagnostic severity for a given option. Importantly, this + /// handle ReportDiagnostic.Default and will map that back to the appropriate value in that + /// case. + /// + private static ReportDiagnostic GetOptionSeverity(CodeStyleOption2 optionValue) + { + var severity = optionValue.Notification.Severity; + return severity == ReportDiagnostic.Default + ? severity.WithDefaultSeverity(DiagnosticSeverity.Hidden) + : severity; + } + + private static void AnalyzeIfEnabled(SyntaxNodeAnalysisContext context) + { + var analyzerOptions = context.Options; + var syntaxTree = context.SemanticModel.SyntaxTree; + var optionValue = GetCodeStyleOption(analyzerOptions.GetAnalyzerOptions(syntaxTree)); + var severity = GetOptionSeverity(optionValue); + switch (severity) + { + case ReportDiagnostic.Error: + case ReportDiagnostic.Warn: + case ReportDiagnostic.Info: + break; + default: + // don't analyze if it's any other value. + return; + } + + AnalyzeSyntax(context, optionValue); + } + + private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context, CodeStyleOption2 option) + { + var declaration = (LambdaExpressionSyntax)context.Node; + var diagnostic = AnalyzeSyntax(context.SemanticModel, option, declaration, context.CancellationToken); + if (diagnostic != null) + { + context.ReportDiagnostic(diagnostic); + } + } + + private static Diagnostic AnalyzeSyntax( + SemanticModel semanticModel, CodeStyleOption2 option, + LambdaExpressionSyntax declaration, CancellationToken cancellationToken) + { + if (CanOfferUseExpressionBody(option.Value, declaration, declaration.GetLanguageVersion())) + { + var location = GetDiagnosticLocation(declaration); + + var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); + var properties = ImmutableDictionary.Empty; + return DiagnosticHelper.Create( + CreateDescriptorWithId(UseExpressionBodyTitle, UseExpressionBodyTitle), + location, option.Notification.Severity, additionalLocations, properties); + } + + if (CanOfferUseBlockBody(semanticModel, option.Value, declaration, cancellationToken)) + { + // They have an expression body. Create a diagnostic to convert it to a block + // if they don't want expression bodies for this member. + var location = GetDiagnosticLocation(declaration); + + var properties = ImmutableDictionary.Empty; + var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); + return DiagnosticHelper.Create( + CreateDescriptorWithId(UseBlockBodyTitle, UseBlockBodyTitle), + location, option.Notification.Severity, additionalLocations, properties); + } + + return null; + } + + private static Location GetDiagnosticLocation(LambdaExpressionSyntax declaration) + => Location.Create(declaration.SyntaxTree, + TextSpan.FromBounds(declaration.SpanStart, declaration.ArrowToken.Span.End)); + + internal static ExpressionSyntax GetBodyAsExpression(LambdaExpressionSyntax declaration) + => declaration.Body as ExpressionSyntax; + + private static bool CanOfferUseExpressionBody( + ExpressionBodyPreference preference, LambdaExpressionSyntax declaration, LanguageVersion languageVersion) + { + var userPrefersExpressionBodies = preference != ExpressionBodyPreference.Never; + if (!userPrefersExpressionBodies) + { + // If the user doesn't even want expression bodies, then certainly do not offer. + return false; + } + + var expressionBody = GetBodyAsExpression(declaration); + if (expressionBody != null) + { + // they already have an expression body. so nothing to do here. + return false; + } + + // They don't have an expression body. See if we could convert the block they + // have into one. + return TryConvertToExpressionBody(declaration, languageVersion, preference, out _, out _); + } + + internal static bool TryConvertToExpressionBody( + LambdaExpressionSyntax declaration, + LanguageVersion languageVersion, + ExpressionBodyPreference conversionPreference, + out ExpressionSyntax expression, + out SyntaxToken semicolon) + { + var body = declaration.Body as BlockSyntax; + + return body.TryConvertToExpressionBody(languageVersion, conversionPreference, out expression, out semicolon); + } + + private static bool CanOfferUseBlockBody( + SemanticModel semanticModel, ExpressionBodyPreference preference, + LambdaExpressionSyntax declaration, CancellationToken cancellationToken) + { + var userPrefersBlockBodies = preference == ExpressionBodyPreference.Never; + if (!userPrefersBlockBodies) + { + // If the user doesn't even want block bodies, then certainly do not offer. + return false; + } + + var expressionBodyOpt = GetBodyAsExpression(declaration); + if (expressionBodyOpt == null) + { + // they already have a block body. + return false; + } + + // We need to know what sort of lambda this is (void returning or not) in order to be + // able to create the right sort of block body (i.e. with a return-statement or + // expr-statement). So, if we can't figure out what lambda type this is, we should not + // proceed. + if (semanticModel.GetTypeInfo(declaration, cancellationToken).ConvertedType is not INamedTypeSymbol lambdaType || lambdaType.DelegateInvokeMethod == null) + { + return false; + } + + var canOffer = expressionBodyOpt.TryConvertToStatement( + semicolonTokenOpt: null, createReturnStatementForExpression: false, out _); + if (!canOffer) + { + // Couldn't even convert the expression into statement form. + return false; + } + + var languageVersion = declaration.SyntaxTree.Options.LanguageVersion(); + if (expressionBodyOpt.IsKind(SyntaxKind.ThrowExpression) && + languageVersion < LanguageVersion.CSharp7) + { + // Can't convert this prior to C# 7 because ```a => throw ...``` isn't allowed. + return false; + } + + return true; + } + + private static DiagnosticDescriptor CreateDescriptorWithId( + LocalizableString title, LocalizableString message) + { + return new DiagnosticDescriptor( + IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId, title, message, + DiagnosticCategory.Style, + DiagnosticSeverity.Hidden, + isEnabledByDefault: true); + } + } +} diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf index af5366121bfdb..b03be7e062f49 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf @@ -227,6 +227,11 @@ Pro indexery používat text bloku + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions Pro místní funkce používat text bloku @@ -277,6 +282,11 @@ Pro indexery používat text výrazu + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions Pro místní funkce používat text výrazu diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf index 339186f197b90..1e969738c775d 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf @@ -227,6 +227,11 @@ Blocktextkörper für Indexer verwenden + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions Blocktextkörper für lokale Funktionen verwenden @@ -277,6 +282,11 @@ Ausdruckskörper für Indexer verwenden + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions Ausdruckstext für lokale Funktionen verwenden diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf index f7c549b87d8f2..c1d6c9911105d 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf @@ -227,6 +227,11 @@ Usar cuerpo del bloque para los indizadores + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions Usar cuerpo del bloque para las funciones locales @@ -277,6 +282,11 @@ Usar cuerpo de expresiones para los indizadores + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions Usar el cuerpo de la expresión para las funciones locales diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf index b3453f2d14ed9..a02b1d2c5a73d 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf @@ -227,6 +227,11 @@ Utiliser un corps de bloc pour les indexeurs + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions Utiliser le corps de bloc pour les fonctions locales @@ -277,6 +282,11 @@ Utiliser un corps d'expression pour les indexeurs + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions Utiliser le corps d'expression pour des fonctions locales diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf index 1e39571ccaeb6..29e33441cc65a 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf @@ -227,6 +227,11 @@ Usa il corpo del blocco per gli indicizzatori + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions Usa il corpo del blocco per le funzioni locali @@ -277,6 +282,11 @@ Usa il corpo dell'espressione per gli indicizzatori + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions Usa corpo dell'espressione per funzioni locali diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf index 813c1e6262195..dafcd07d3312a 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf @@ -227,6 +227,11 @@ インデクサーにブロック本体を使用する + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions ローカル関数にブロック本体を使用します @@ -277,6 +282,11 @@ インデクサーに式本体を使用する + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions ローカル関数に式本体を使用します diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf index ade8ca96c9929..54fb2c3e204cf 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf @@ -227,6 +227,11 @@ 인덱서에 블록 본문 사용 + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions 로컬 함수에 블록 본문 사용 @@ -277,6 +282,11 @@ 인덱서에 식 본문 사용 + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions 로컬 함수의 식 본문 사용 diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf index 5220a76631555..26ea1bc9d4f77 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf @@ -227,6 +227,11 @@ Użyj treści bloku dla indeksatorów + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions Użyj treści bloku dla funkcji lokalnych @@ -277,6 +282,11 @@ Użyj treści wyrażenia dla indeksatorów + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions Użyj treści wyrażenia dla funkcji lokalnych diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf index 05a9c9fdc54bb..b9a541bc85b89 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf @@ -227,6 +227,11 @@ Usar o corpo do bloco para indexadores + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions Usar o corpo do bloco para funções locais @@ -277,6 +282,11 @@ Usar o corpo da expressão para indexadores + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions Usar o corpo da expressão para funções locais diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf index 7ebe8349efe2d..10035fafc6990 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf @@ -227,6 +227,11 @@ Использовать тело блока для индексаторов + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions Использовать тело блока для локальных функций @@ -277,6 +282,11 @@ Использовать тело выражения для индексаторов + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions Использовать тело выражения для локальных функций diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf index 5abba358c15a5..cecb8076b9fa6 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf @@ -227,6 +227,11 @@ Dizin oluşturucular için blok gövdesi kullan + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions Yerel işlevler için blok gövdesi kullan @@ -277,6 +282,11 @@ Dizin oluşturucular için ifade gövdesi kullan + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions Yerel işlevler için ifade gövdesi kullan diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf index 03643d9e6145f..85cd168bf900e 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf @@ -227,6 +227,11 @@ 使用索引器的程序块主体 + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions 对本地函数使用块主体 @@ -277,6 +282,11 @@ 使用索引器的表达式主体 + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions 将表达式主体用于本地函数 diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf index 5b08f437ce5b6..fa0191b130cd1 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf @@ -227,6 +227,11 @@ 使用索引子的區塊主體 + + Use block body for lambda expressions + Use block body for lambda expressions + + Use block body for local functions 為區域函式使用區塊主體 @@ -277,6 +282,11 @@ 使用索引子的運算式主體 + + Use expression body for lambda expressions + Use expression body for lambda expressions + + Use expression body for local functions 為區域函式使用運算式主體 diff --git a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems index 1f71528cf56de..448d4da00813d 100644 --- a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems +++ b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems @@ -40,6 +40,7 @@ + diff --git a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs new file mode 100644 index 0000000000000..05713c185305b --- /dev/null +++ b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs @@ -0,0 +1,179 @@ +// 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.Immutable; +using System.Composition; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseExpressionBodyForLambda), Shared] + internal sealed class UseExpressionBodyForLambdaCodeFixProvider : SyntaxEditorBasedCodeFixProvider + { + [ImportingConstructor] + [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] + public UseExpressionBodyForLambdaCodeFixProvider() + { + } + + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId); + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + var document = context.Document; + var diagnostic = context.Diagnostics[0]; + + var title = diagnostic.GetMessage(); + var codeAction = CodeAction.Create( + title, + c => FixWithSyntaxEditorAsync(document, diagnostic, c), + title); + + context.RegisterCodeFix(codeAction, context.Diagnostics); + return Task.CompletedTask; + } + + protected override Task FixAllAsync(Document document, ImmutableArray diagnostics, SyntaxEditor editor, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) + => FixAllAsync(document, diagnostics, editor, cancellationToken); + + private static async Task FixAllAsync(Document document, ImmutableArray diagnostics, SyntaxEditor editor, CancellationToken cancellationToken) + { + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + foreach (var diagnostic in diagnostics) + { + cancellationToken.ThrowIfCancellationRequested(); + AddEdits(editor, semanticModel, diagnostic, cancellationToken); + } + } + + private static Task FixWithSyntaxEditorAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) + => FixAllWithEditorAsync( + document, editor => FixAllAsync(document, ImmutableArray.Create(diagnostic), editor, cancellationToken), cancellationToken); + + private static void AddEdits( + SyntaxEditor editor, SemanticModel semanticModel, + Diagnostic diagnostic, CancellationToken cancellationToken) + { + var declarationLocation = diagnostic.AdditionalLocations[0]; + var originalDeclaration = (LambdaExpressionSyntax)declarationLocation.FindNode(getInnermostNodeForTie: true, cancellationToken); + + editor.ReplaceNode( + originalDeclaration, + (current, _) => Update(semanticModel, originalDeclaration, (LambdaExpressionSyntax)current)); + } + + private static LambdaExpressionSyntax Update(SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) + => UpdateWorker(semanticModel, originalDeclaration, currentDeclaration).WithAdditionalAnnotations(Formatter.Annotation); + + private static LambdaExpressionSyntax UpdateWorker( + SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) + { + var expressionBody = UseExpressionBodyForLambdaDiagnosticAnalyzer.GetBodyAsExpression(currentDeclaration); + return expressionBody == null + ? WithExpressionBody(currentDeclaration, originalDeclaration.GetLanguageVersion()) + : WithBlockBody(semanticModel, originalDeclaration, currentDeclaration); + } + + private static LambdaExpressionSyntax WithExpressionBody(LambdaExpressionSyntax declaration, LanguageVersion languageVersion) + { + if (!UseExpressionBodyForLambdaDiagnosticAnalyzer.TryConvertToExpressionBody(declaration, languageVersion, ExpressionBodyPreference.WhenPossible, out var expressionBody, out _)) + { + return declaration; + } + + var updatedDecl = declaration.WithBody(expressionBody); + + // If there will only be whitespace between the arrow and the body, then replace that + // with a single space so that the lambda doesn't have superfluous newlines in it. + if (declaration.ArrowToken.TrailingTrivia.All(t => t.IsWhitespaceOrEndOfLine()) && + expressionBody.GetLeadingTrivia().All(t => t.IsWhitespaceOrEndOfLine())) + { + updatedDecl = updatedDecl.WithArrowToken(updatedDecl.ArrowToken.WithTrailingTrivia(SyntaxFactory.ElasticSpace)); + } + + return updatedDecl; + } + + private static LambdaExpressionSyntax WithBlockBody( + SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) + { + var expressionBody = UseExpressionBodyForLambdaDiagnosticAnalyzer.GetBodyAsExpression(currentDeclaration); + var createReturnStatementForExpression = CreateReturnStatementForExpression( + semanticModel, originalDeclaration); + + if (!expressionBody.TryConvertToStatement( + semicolonTokenOpt: null, + createReturnStatementForExpression, + out var statement)) + { + return currentDeclaration; + } + + // If the user is converting to a block, it's likely they intend to add multiple + // statements to it. So make a multi-line block so that things are formatted properly + // for them to do so. + return currentDeclaration.WithBody(SyntaxFactory.Block( + SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed), + SyntaxFactory.SingletonList(statement), + SyntaxFactory.Token(SyntaxKind.CloseBraceToken))); + } + + private static bool CreateReturnStatementForExpression( + SemanticModel semanticModel, LambdaExpressionSyntax declaration) + { + var lambdaType = (INamedTypeSymbol)semanticModel.GetTypeInfo(declaration).ConvertedType!; + if (lambdaType.DelegateInvokeMethod!.ReturnsVoid) + { + return false; + } + + // 'async Task' is effectively a void-returning lambda. we do not want to create + // 'return statements' when converting. + if (declaration.AsyncKeyword != default) + { + var returnType = lambdaType.DelegateInvokeMethod.ReturnType; + if (returnType.IsErrorType()) + { + // "async Goo" where 'Goo' failed to bind. If 'Goo' is 'Task' then it's + // reasonable to assume this is just a missing 'using' and that this is a true + // "async Task" lambda. If the name isn't 'Task', then this looks like a + // real return type, and we should use return statements. + return returnType.Name != nameof(Task); + } + + var taskType = semanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName); + if (returnType.Equals(taskType)) + { + // 'async Task'. definitely do not create a 'return' statement; + return false; + } + } + + return true; + } + } + + // PROTOTYPE: TODO + //[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.UseExpressionBodyForLambda), Shared] + //internal sealed class UseExpressionBodyForLambdaCodeRefactoringProvider : UseExpressionBodyForLambdaCodeStyleProvider.CodeRefactoringProvider + //{ + // [ImportingConstructor] + // [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] + // public UseExpressionBodyForLambdaCodeRefactoringProvider() + // { + // } + //} +} diff --git a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider.cs b/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider.cs deleted file mode 100644 index ac7591f83de49..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider.cs +++ /dev/null @@ -1,248 +0,0 @@ -// 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. - -#nullable disable - -using System; -using System.Composition; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda -{ - internal partial class UseExpressionBodyForLambdaCodeStyleProvider - : AbstractCodeStyleProvider - { - private static readonly LocalizableString UseExpressionBodyTitle = new LocalizableResourceString(nameof(FeaturesResources.Use_expression_body_for_lambda_expressions), FeaturesResources.ResourceManager, typeof(FeaturesResources)); - private static readonly LocalizableString UseBlockBodyTitle = new LocalizableResourceString(nameof(FeaturesResources.Use_block_body_for_lambda_expressions), FeaturesResources.ResourceManager, typeof(FeaturesResources)); - - public UseExpressionBodyForLambdaCodeStyleProvider() - : base(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, - LanguageNames.CSharp, - IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId, - EnforceOnBuildValues.UseExpressionBodyForLambdaExpressions, - UseExpressionBodyTitle, - UseExpressionBodyTitle) - { - } - - // Shared code needed by all parts of the style provider for this feature. - - protected override CodeStyleOption2 GetCodeStyleOption(AnalyzerOptionsProvider provider) - => ((CSharpAnalyzerOptionsProvider)provider).PreferExpressionBodiedLambdas; - - private static ExpressionSyntax GetBodyAsExpression(LambdaExpressionSyntax declaration) - => declaration.Body as ExpressionSyntax; - - private static bool CanOfferUseExpressionBody( - ExpressionBodyPreference preference, LambdaExpressionSyntax declaration, LanguageVersion languageVersion) - { - var userPrefersExpressionBodies = preference != ExpressionBodyPreference.Never; - if (!userPrefersExpressionBodies) - { - // If the user doesn't even want expression bodies, then certainly do not offer. - return false; - } - - var expressionBody = GetBodyAsExpression(declaration); - if (expressionBody != null) - { - // they already have an expression body. so nothing to do here. - return false; - } - - // They don't have an expression body. See if we could convert the block they - // have into one. - return TryConvertToExpressionBody(declaration, languageVersion, preference, out _, out _); - } - - private static bool TryConvertToExpressionBody( - LambdaExpressionSyntax declaration, - LanguageVersion languageVersion, - ExpressionBodyPreference conversionPreference, - out ExpressionSyntax expression, - out SyntaxToken semicolon) - { - var body = declaration.Body as BlockSyntax; - - return body.TryConvertToExpressionBody(languageVersion, conversionPreference, out expression, out semicolon); - } - - private static bool CanOfferUseBlockBody( - SemanticModel semanticModel, ExpressionBodyPreference preference, - LambdaExpressionSyntax declaration, CancellationToken cancellationToken) - { - var userPrefersBlockBodies = preference == ExpressionBodyPreference.Never; - if (!userPrefersBlockBodies) - { - // If the user doesn't even want block bodies, then certainly do not offer. - return false; - } - - var expressionBodyOpt = GetBodyAsExpression(declaration); - if (expressionBodyOpt == null) - { - // they already have a block body. - return false; - } - - // We need to know what sort of lambda this is (void returning or not) in order to be - // able to create the right sort of block body (i.e. with a return-statement or - // expr-statement). So, if we can't figure out what lambda type this is, we should not - // proceed. - if (semanticModel.GetTypeInfo(declaration, cancellationToken).ConvertedType is not INamedTypeSymbol lambdaType || lambdaType.DelegateInvokeMethod == null) - { - return false; - } - - var canOffer = expressionBodyOpt.TryConvertToStatement( - semicolonTokenOpt: null, createReturnStatementForExpression: false, out _); - if (!canOffer) - { - // Couldn't even convert the expression into statement form. - return false; - } - - var languageVersion = declaration.SyntaxTree.Options.LanguageVersion(); - if (expressionBodyOpt.IsKind(SyntaxKind.ThrowExpression) && - languageVersion < LanguageVersion.CSharp7) - { - // Can't convert this prior to C# 7 because ```a => throw ...``` isn't allowed. - return false; - } - - return true; - } - - private static LambdaExpressionSyntax Update(SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) - => UpdateWorker(semanticModel, originalDeclaration, currentDeclaration).WithAdditionalAnnotations(Formatter.Annotation); - - private static LambdaExpressionSyntax UpdateWorker( - SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) - { - var expressionBody = GetBodyAsExpression(currentDeclaration); - return expressionBody == null - ? WithExpressionBody(currentDeclaration, originalDeclaration.GetLanguageVersion()) - : WithBlockBody(semanticModel, originalDeclaration, currentDeclaration); - } - - private static LambdaExpressionSyntax WithExpressionBody(LambdaExpressionSyntax declaration, LanguageVersion languageVersion) - { - if (!TryConvertToExpressionBody(declaration, languageVersion, ExpressionBodyPreference.WhenPossible, out var expressionBody, out _)) - { - return declaration; - } - - var updatedDecl = declaration.WithBody(expressionBody); - - // If there will only be whitespace between the arrow and the body, then replace that - // with a single space so that the lambda doesn't have superfluous newlines in it. - if (declaration.ArrowToken.TrailingTrivia.All(t => t.IsWhitespaceOrEndOfLine()) && - expressionBody.GetLeadingTrivia().All(t => t.IsWhitespaceOrEndOfLine())) - { - updatedDecl = updatedDecl.WithArrowToken(updatedDecl.ArrowToken.WithTrailingTrivia(SyntaxFactory.ElasticSpace)); - } - - return updatedDecl; - } - - private static LambdaExpressionSyntax WithBlockBody( - SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) - { - var expressionBody = GetBodyAsExpression(currentDeclaration); - var createReturnStatementForExpression = CreateReturnStatementForExpression( - semanticModel, originalDeclaration); - - if (!expressionBody.TryConvertToStatement( - semicolonTokenOpt: null, - createReturnStatementForExpression, - out var statement)) - { - return currentDeclaration; - } - - // If the user is converting to a block, it's likely they intend to add multiple - // statements to it. So make a multi-line block so that things are formatted properly - // for them to do so. - return currentDeclaration.WithBody(SyntaxFactory.Block( - SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed), - SyntaxFactory.SingletonList(statement), - SyntaxFactory.Token(SyntaxKind.CloseBraceToken))); - } - - private static bool CreateReturnStatementForExpression( - SemanticModel semanticModel, LambdaExpressionSyntax declaration) - { - var lambdaType = (INamedTypeSymbol)semanticModel.GetTypeInfo(declaration).ConvertedType; - if (lambdaType.DelegateInvokeMethod.ReturnsVoid) - { - return false; - } - - // 'async Task' is effectively a void-returning lambda. we do not want to create - // 'return statements' when converting. - if (declaration.AsyncKeyword != default) - { - var returnType = lambdaType.DelegateInvokeMethod.ReturnType; - if (returnType.IsErrorType()) - { - // "async Goo" where 'Goo' failed to bind. If 'Goo' is 'Task' then it's - // reasonable to assume this is just a missing 'using' and that this is a true - // "async Task" lambda. If the name isn't 'Task', then this looks like a - // real return type, and we should use return statements. - return returnType.Name != nameof(Task); - } - - var taskType = semanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName); - if (returnType.Equals(taskType)) - { - // 'async Task'. definitely do not create a 'return' statement; - return false; - } - } - - return true; - } - } - - // Stub classes needed only for exporting purposes. - - [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseExpressionBodyForLambda), Shared] - internal sealed class UseExpressionBodyForLambdaCodeFixProvider : UseExpressionBodyForLambdaCodeStyleProvider.CodeFixProvider - { - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public UseExpressionBodyForLambdaCodeFixProvider() - { - } - } - - [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.UseExpressionBodyForLambda), Shared] - internal sealed class UseExpressionBodyForLambdaCodeRefactoringProvider : UseExpressionBodyForLambdaCodeStyleProvider.CodeRefactoringProvider - { - [ImportingConstructor] - [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - public UseExpressionBodyForLambdaCodeRefactoringProvider() - { - } - } - - [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal sealed class UseExpressionBodyForLambdaDiagnosticAnalyzer : UseExpressionBodyForLambdaCodeStyleProvider.DiagnosticAnalyzer - { - } -} diff --git a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider_Analysis.cs b/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider_Analysis.cs deleted file mode 100644 index bb21394dadfff..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider_Analysis.cs +++ /dev/null @@ -1,73 +0,0 @@ -// 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. - -#nullable disable - -using System.Collections.Immutable; -using System.Threading; -using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda -{ - // Code for the DiagnosticAnalyzer ("Analysis") portion of the feature. - - internal partial class UseExpressionBodyForLambdaCodeStyleProvider - { - protected override void DiagnosticAnalyzerInitialize(AnalysisContext context) - => context.RegisterSyntaxNodeAction(AnalyzeSyntax, - SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression); - - protected override DiagnosticAnalyzerCategory GetAnalyzerCategory() - => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; - - private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, CodeStyleOption2 option) - { - var declaration = (LambdaExpressionSyntax)context.Node; - var diagnostic = AnalyzeSyntax(context.SemanticModel, option, declaration, context.CancellationToken); - if (diagnostic != null) - { - context.ReportDiagnostic(diagnostic); - } - } - - private Diagnostic AnalyzeSyntax( - SemanticModel semanticModel, CodeStyleOption2 option, - LambdaExpressionSyntax declaration, CancellationToken cancellationToken) - { - if (CanOfferUseExpressionBody(option.Value, declaration, declaration.GetLanguageVersion())) - { - var location = GetDiagnosticLocation(declaration); - - var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); - var properties = ImmutableDictionary.Empty; - return DiagnosticHelper.Create( - CreateDescriptorWithId(UseExpressionBodyTitle, UseExpressionBodyTitle), - location, option.Notification.Severity, additionalLocations, properties); - } - - if (CanOfferUseBlockBody(semanticModel, option.Value, declaration, cancellationToken)) - { - // They have an expression body. Create a diagnostic to convert it to a block - // if they don't want expression bodies for this member. - var location = GetDiagnosticLocation(declaration); - - var properties = ImmutableDictionary.Empty; - var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); - return DiagnosticHelper.Create( - CreateDescriptorWithId(UseBlockBodyTitle, UseBlockBodyTitle), - location, option.Notification.Severity, additionalLocations, properties); - } - - return null; - } - - private static Location GetDiagnosticLocation(LambdaExpressionSyntax declaration) - => Location.Create(declaration.SyntaxTree, - TextSpan.FromBounds(declaration.SpanStart, declaration.ArrowToken.Span.End)); - } -} diff --git a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider_Fixing.cs b/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider_Fixing.cs deleted file mode 100644 index 9f451b3cb3d49..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider_Fixing.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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. - -#nullable disable - -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Shared.Extensions; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda -{ - // Code for the CodeFixProvider ("Fixing") portion of the feature. - - internal partial class UseExpressionBodyForLambdaCodeStyleProvider - { - protected override Task> ComputeCodeActionsAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) - { - var title = diagnostic.GetMessage(); - var codeAction = CodeAction.Create( - title, - c => FixWithSyntaxEditorAsync(document, diagnostic, c), - title); - - return Task.FromResult(ImmutableArray.Create(codeAction)); - } - - protected override async Task FixAllAsync( - Document document, ImmutableArray diagnostics, - SyntaxEditor editor, CancellationToken cancellationToken) - { - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - foreach (var diagnostic in diagnostics) - { - cancellationToken.ThrowIfCancellationRequested(); - AddEdits(editor, semanticModel, diagnostic, cancellationToken); - } - } - - private static void AddEdits( - SyntaxEditor editor, SemanticModel semanticModel, - Diagnostic diagnostic, CancellationToken cancellationToken) - { - var declarationLocation = diagnostic.AdditionalLocations[0]; - var originalDeclaration = (LambdaExpressionSyntax)declarationLocation.FindNode(getInnermostNodeForTie: true, cancellationToken); - - editor.ReplaceNode( - originalDeclaration, - (current, _) => Update(semanticModel, originalDeclaration, (LambdaExpressionSyntax)current)); - } - } -} diff --git a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider_Refactoring.cs b/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider_Refactoring.cs deleted file mode 100644 index 1e0aabb93d3b1..0000000000000 --- a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeStyleProvider_Refactoring.cs +++ /dev/null @@ -1,146 +0,0 @@ -// 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. - -#nullable disable - -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda -{ - // Code for the CodeRefactoringProvider ("Refactoring") portion of the feature. - - internal partial class UseExpressionBodyForLambdaCodeStyleProvider - { - protected override async Task> ComputeOpposingRefactoringsWhenAnalyzerActiveAsync( - Document document, TextSpan span, ExpressionBodyPreference option, CancellationToken cancellationToken) - { - if (option == ExpressionBodyPreference.Never) - { - // the user wants block-bodies (and the analyzer will be trying to enforce that). So - // the reverse of this is that we want to offer the refactoring to convert a - // block-body to an expression-body whenever possible. - return await ComputeRefactoringsAsync( - document, span, ExpressionBodyPreference.WhenPossible, cancellationToken).ConfigureAwait(false); - } - else if (option == ExpressionBodyPreference.WhenPossible) - { - // the user likes expression-bodies whenever possible, and the analyzer will be - // trying to enforce that. So the reverse of this is that we want to offer the - // refactoring to convert an expression-body to a block-body whenever possible. - return await ComputeRefactoringsAsync( - document, span, ExpressionBodyPreference.Never, cancellationToken).ConfigureAwait(false); - } - else if (option == ExpressionBodyPreference.WhenOnSingleLine) - { - // the user likes expression-bodies *if* the body would be on a single line. this - // means if we hit an block-body with an expression on a single line, then the - // analyzer will handle it for us. - - // So we need to handle the cases of either hitting an expression-body and wanting - // to convert it to a block-body *or* hitting an block-body over *multiple* lines and - // wanting to offer to convert to an expression-body. - - // Always offer to convert an expression to a block since the analyzer will never - // offer that. For this option setting. - var useBlockRefactorings = await ComputeRefactoringsAsync( - document, span, ExpressionBodyPreference.Never, cancellationToken).ConfigureAwait(false); - - var whenOnSingleLineRefactorings = await ComputeRefactoringsAsync( - document, span, ExpressionBodyPreference.WhenOnSingleLine, cancellationToken).ConfigureAwait(false); - if (whenOnSingleLineRefactorings.Length > 0) - { - // this block lambda would be converted to an expression lambda based on the - // analyzer alone. So we don't want to offer that as a refactoring ourselves. - return useBlockRefactorings; - } - - // The lambda block statement wasn't on a single line. So the analyzer would - // not offer to convert it to an expression body. So we should can offer that - // as a refactoring if possible. - var whenPossibleRefactorings = await ComputeRefactoringsAsync( - document, span, ExpressionBodyPreference.WhenPossible, cancellationToken).ConfigureAwait(false); - return useBlockRefactorings.AddRange(whenPossibleRefactorings); - } - else - { - throw ExceptionUtilities.UnexpectedValue(option); - } - } - - protected override async Task> ComputeAllRefactoringsWhenAnalyzerInactiveAsync( - Document document, TextSpan span, CancellationToken cancellationToken) - { - // If the analyzer is inactive, then we want to offer refactorings in any viable - // direction. So we want to offer to convert expression-bodies to block-bodies, and - // vice-versa if applicable. - - var toExpressionBodyRefactorings = await ComputeRefactoringsAsync( - document, span, ExpressionBodyPreference.WhenPossible, cancellationToken).ConfigureAwait(false); - - var toBlockBodyRefactorings = await ComputeRefactoringsAsync( - document, span, ExpressionBodyPreference.Never, cancellationToken).ConfigureAwait(false); - - return toExpressionBodyRefactorings.AddRange(toBlockBodyRefactorings); - } - - private static async Task> ComputeRefactoringsAsync( - Document document, TextSpan span, ExpressionBodyPreference option, CancellationToken cancellationToken) - { - var lambdaNode = await document.TryGetRelevantNodeAsync(span, cancellationToken).ConfigureAwait(false); - if (lambdaNode == null) - { - return ImmutableArray.Empty; - } - - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - using var resultDisposer = ArrayBuilder.GetInstance(out var result); - if (CanOfferUseExpressionBody(option, lambdaNode, root.GetLanguageVersion())) - { - var title = UseExpressionBodyTitle.ToString(); - result.Add(CodeAction.Create( - title, - c => UpdateDocumentAsync( - document, root, lambdaNode, c), - title)); - } - - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - if (CanOfferUseBlockBody(semanticModel, option, lambdaNode, cancellationToken)) - { - var title = UseBlockBodyTitle.ToString(); - result.Add(CodeAction.Create( - title, - c => UpdateDocumentAsync( - document, root, lambdaNode, c), - title)); - } - - return result.ToImmutable(); - } - - private static async Task UpdateDocumentAsync( - Document document, SyntaxNode root, LambdaExpressionSyntax declaration, CancellationToken cancellationToken) - { - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - - // We're only replacing a single declaration in the refactoring. So pass 'declaration' - // as both the 'original' and 'current' declaration. - var updatedDeclaration = Update(semanticModel, declaration, declaration); - - var newRoot = root.ReplaceNode(declaration, updatedDeclaration); - return document.WithSyntaxRoot(newRoot); - } - } -} diff --git a/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.Analysis.cs b/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.Analysis.cs deleted file mode 100644 index 8c093cc1aa3f6..0000000000000 --- a/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.Analysis.cs +++ /dev/null @@ -1,141 +0,0 @@ -// 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. - -#nullable disable - -using System; -using System.Threading; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.CodeAnalysis.CodeStyle -{ - // This part contains all the logic for hooking up the DiagnosticAnalyzer to the CodeStyleProvider. - // All the code in this part is an implementation detail and is intentionally private so that - // subclasses cannot change anything. All code relevant to subclasses relating to analysis - // is contained in AbstractCodeStyleProvider.cs - - internal abstract partial class AbstractCodeStyleProvider - { - public abstract class DiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer - { - public readonly TCodeStyleProvider _codeStyleProvider; - - protected DiagnosticAnalyzer(bool isUnnecessary = false, bool configurable = true) - : this(new TCodeStyleProvider(), isUnnecessary, configurable) - { - } - - private DiagnosticAnalyzer(TCodeStyleProvider codeStyleProvider, bool isUnnecessary, bool configurable) - : base(codeStyleProvider._descriptorId, - codeStyleProvider._enforceOnBuild, - codeStyleProvider._option, - codeStyleProvider._language, - codeStyleProvider._title, - codeStyleProvider._message, - isUnnecessary, - configurable) - { - _codeStyleProvider = codeStyleProvider; - } - - protected sealed override void InitializeWorker(Diagnostics.AnalysisContext context) - => _codeStyleProvider.DiagnosticAnalyzerInitialize(new AnalysisContext(_codeStyleProvider, context)); - - public sealed override DiagnosticAnalyzerCategory GetAnalyzerCategory() - => _codeStyleProvider.GetAnalyzerCategory(); - } - - /// - /// Critically, we want to consolidate the logic about checking if the analyzer should run - /// at all. i.e. if the user has their option set to 'none' or 'refactoring only' then we - /// do not want the analyzer to run at all. - /// - /// To that end, we don't let the subclass have direct access to the real . Instead, we pass this type to the subclass for it - /// register with. We then check if the registration should proceed given the - /// and the current being processed. If not, we don't do the - /// actual registration. - /// - protected struct AnalysisContext - { - private readonly TCodeStyleProvider _codeStyleProvider; - private readonly Diagnostics.AnalysisContext _context; - - public AnalysisContext(TCodeStyleProvider codeStyleProvider, Diagnostics.AnalysisContext context) - { - _codeStyleProvider = codeStyleProvider; - _context = context; - } - - public void RegisterCompilationStartAction(Action analyze) - { - var _this = this; - _context.RegisterCompilationStartAction( - c => analyze(c.Compilation, _this)); - } - - public void RegisterCodeBlockAction(Action> analyze) - { - var provider = _codeStyleProvider; - _context.RegisterCodeBlockAction( - c => AnalyzeIfEnabled(provider, c, analyze, c.Options, c.SemanticModel.SyntaxTree)); - } - - public void RegisterSemanticModelAction(Action> analyze) - { - var provider = _codeStyleProvider; - _context.RegisterSemanticModelAction( - c => AnalyzeIfEnabled(provider, c, analyze, c.Options, c.SemanticModel.SyntaxTree)); - } - - public void RegisterSyntaxTreeAction(Action> analyze) - { - var provider = _codeStyleProvider; - _context.RegisterSyntaxTreeAction( - c => AnalyzeIfEnabled(provider, c, analyze, c.Options, c.Tree)); - } - - public void RegisterOperationAction( - Action> analyze, - params OperationKind[] operationKinds) - { - var provider = _codeStyleProvider; - _context.RegisterOperationAction( - c => AnalyzeIfEnabled(provider, c, analyze, c.Options, c.Operation.SemanticModel.SyntaxTree), - operationKinds); - } - - public void RegisterSyntaxNodeAction( - Action> analyze, - params TSyntaxKind[] syntaxKinds) where TSyntaxKind : struct - { - var provider = _codeStyleProvider; - _context.RegisterSyntaxNodeAction( - c => AnalyzeIfEnabled(provider, c, analyze, c.Options, c.SemanticModel.SyntaxTree), - syntaxKinds); - } - - private static void AnalyzeIfEnabled( - TCodeStyleProvider provider, TContext context, Action> analyze, - AnalyzerOptions analyzerOptions, SyntaxTree syntaxTree) - { - var optionValue = provider.GetCodeStyleOption(analyzerOptions.GetAnalyzerOptions(syntaxTree)); - var severity = GetOptionSeverity(optionValue); - switch (severity) - { - case ReportDiagnostic.Error: - case ReportDiagnostic.Warn: - case ReportDiagnostic.Info: - break; - default: - // don't analyze if it's any other value. - return; - } - - analyze(context, optionValue); - } - } - } -} diff --git a/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.Fixing.cs b/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.Fixing.cs deleted file mode 100644 index 6481f90b5f7bb..0000000000000 --- a/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.Fixing.cs +++ /dev/null @@ -1,52 +0,0 @@ -// 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. - -#nullable disable - -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Editing; - -namespace Microsoft.CodeAnalysis.CodeStyle -{ - // This part contains all the logic for hooking up the CodeFixProvider to the CodeStyleProvider. - // All the code in this part is an implementation detail and is intentionally private so that - // subclasses cannot change anything. All code relevant to subclasses relating to fixing is - // contained in AbstractCodeStyleProvider.cs - - internal abstract partial class AbstractCodeStyleProvider - { - private async Task RegisterCodeFixesAsync(CodeFixContext context) - { - var document = context.Document; - var diagnostic = context.Diagnostics[0]; - var cancellationToken = context.CancellationToken; - - var codeFixes = await ComputeCodeActionsAsync( - document, diagnostic, cancellationToken).ConfigureAwait(false); - context.RegisterFixes(codeFixes, context.Diagnostics); - } - - public abstract class CodeFixProvider : SyntaxEditorBasedCodeFixProvider - { - public readonly TCodeStyleProvider _codeStyleProvider = new(); - - protected CodeFixProvider() - { - FixableDiagnosticIds = ImmutableArray.Create(_codeStyleProvider._descriptorId); - } - - public sealed override ImmutableArray FixableDiagnosticIds { get; } - - public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) - => _codeStyleProvider.RegisterCodeFixesAsync(context); - - protected sealed override Task FixAllAsync(Document document, ImmutableArray diagnostics, SyntaxEditor editor, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) - => _codeStyleProvider.FixAllAsync(document, diagnostics, editor, cancellationToken); - } - } -} diff --git a/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.Refactoring.cs b/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.Refactoring.cs deleted file mode 100644 index fe2711b81fe16..0000000000000 --- a/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.Refactoring.cs +++ /dev/null @@ -1,89 +0,0 @@ -// 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. - -#nullable disable - -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.CodeAnalysis.CodeStyle -{ - // This part contains all the logic for hooking up the CodeRefactoring to the CodeStyleProvider. - // All the code in this part is an implementation detail and is intentionally private so that - // subclasses cannot change anything. All code relevant to subclasses relating to refactorings - // is contained in AbstractCodeStyleProvider.cs - - internal abstract partial class AbstractCodeStyleProvider - { - private async Task ComputeRefactoringsAsync(CodeRefactoringContext context) - { - var (document, _, cancellationToken) = context; - - var optionProvider = await document.GetAnalyzerOptionsProviderAsync(cancellationToken).ConfigureAwait(false); - var optionValue = GetCodeStyleOption(optionProvider); - - var severity = GetOptionSeverity(optionValue); - switch (severity) - { - case ReportDiagnostic.Suppress: - case ReportDiagnostic.Hidden: - // if the severity is Hidden that's equivalent to 'refactoring only', so we want - // to try to compute the refactoring here. - // - // If the severity is 'suppress', that means the user doesn't want the actual - // analyzer to run here. However, we can still check to see if we could offer - // the feature here as a refactoring. - await ComputeRefactoringsAsync(context, optionValue.Value, analyzerActive: false).ConfigureAwait(false); - return; - - case ReportDiagnostic.Error: - case ReportDiagnostic.Warn: - case ReportDiagnostic.Info: - // User has this option set at a level where we want it checked by the - // DiagnosticAnalyser and not the CodeRefactoringProvider. However, we still - // want to check if we want to offer the *reverse* refactoring here in this - // single location. - // - // For example, say this is the "use expression body" feature. If the user says - // they always prefer expression-bodies (with warning level), then we want the - // analyzer to always be checking for that. However, we still want to offer the - // refactoring to flip their code to use a block body here, just in case that - // was something they wanted to do as a one off (i.e. before adding new - // statements. - // - // TODO(cyrusn): Should we only do this for warn/info? Argument could be made - // that we shouldn't even offer to refactor in the reverse direction if it will - // just cause an error. That said, maybe this is just an intermediary step, and - // we shouldn't really be blocking the user from making it. - await ComputeRefactoringsAsync(context, optionValue.Value, analyzerActive: true).ConfigureAwait(false); - return; - } - } - - private async Task ComputeRefactoringsAsync( - CodeRefactoringContext context, TOptionValue option, bool analyzerActive) - { - var (document, span, cancellationToken) = context; - - var computationTask = analyzerActive - ? ComputeOpposingRefactoringsWhenAnalyzerActiveAsync(document, span, option, cancellationToken) - : ComputeAllRefactoringsWhenAnalyzerInactiveAsync(document, span, cancellationToken); - - var codeActions = await computationTask.ConfigureAwait(false); - context.RegisterRefactorings(codeActions); - } - - public class CodeRefactoringProvider : CodeRefactorings.CodeRefactoringProvider - { - public readonly TCodeStyleProvider _codeStyleProvider; - - protected CodeRefactoringProvider() - => _codeStyleProvider = new TCodeStyleProvider(); - - public sealed override Task ComputeRefactoringsAsync(CodeRefactoringContext context) - => _codeStyleProvider.ComputeRefactoringsAsync(context); - } - } -} diff --git a/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.cs b/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.cs deleted file mode 100644 index 2b1722f568b48..0000000000000 --- a/src/Features/Core/Portable/CodeStyle/AbstractCodeStyleProvider.cs +++ /dev/null @@ -1,149 +0,0 @@ -// 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. - -#nullable disable - -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Editing; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.CodeStyle -{ - // This file contains the "protected" surface area of the AbstractCodeStyleProvider. - // It specifically is all the extensibility surface that a subclass needs to fill in - // in order to properly expose a code style analyzer/fixer/refactoring. - - /// - /// This is the core class a code-style feature needs to derive from. All logic related to the - /// feature will then be contained in this class. This class will take care of many bit of - /// common logic that all code style providers would have to care about and can thus do that - /// logic in a consistent fashion without all providers having to do the same. For example, - /// this class will check the current value of the code style option. If it is 'refactoring - /// only', it will not bother running any of the DiagnosticAnalyzer codepaths, and will only run - /// the CodeRefactoringProvider codepaths. - /// - internal abstract partial class AbstractCodeStyleProvider - where TCodeStyleProvider : AbstractCodeStyleProvider, new() - { - private readonly Option2> _option; - private readonly string _language; - private readonly string _descriptorId; - private readonly EnforceOnBuild _enforceOnBuild; - private readonly LocalizableString _title; - private readonly LocalizableString _message; - - protected AbstractCodeStyleProvider( - Option2> option, - string language, - string descriptorId, - EnforceOnBuild enforceOnBuild, - LocalizableString title, - LocalizableString message) - { - _option = option; - _language = language; - _descriptorId = descriptorId; - _enforceOnBuild = enforceOnBuild; - _title = title; - _message = message; - } - - /// - /// Helper to get the true ReportDiagnostic severity for a given option. Importantly, this - /// handle ReportDiagnostic.Default and will map that back to the appropriate value in that - /// case. - /// - protected static ReportDiagnostic GetOptionSeverity(CodeStyleOption2 optionValue) - { - var severity = optionValue.Notification.Severity; - return severity == ReportDiagnostic.Default - ? severity.WithDefaultSeverity(DiagnosticSeverity.Hidden) - : severity; - } - - protected abstract CodeStyleOption2 GetCodeStyleOption(AnalyzerOptionsProvider provider); - - #region analysis - - protected abstract void DiagnosticAnalyzerInitialize(AnalysisContext context); - protected abstract DiagnosticAnalyzerCategory GetAnalyzerCategory(); - - protected DiagnosticDescriptor CreateDescriptorWithId( - LocalizableString title, LocalizableString message) - { - return new DiagnosticDescriptor( - _descriptorId, title, message, - DiagnosticCategory.Style, - DiagnosticSeverity.Hidden, - isEnabledByDefault: true); - } - - #endregion - - #region fixing - - /// - /// Subclasses must implement this method to provide fixes for any diagnostics that this - /// type has registered. If this subclass wants the same code to run for this single - /// diagnostic as well as for when running fix-all, then it should call - /// from its code action. This will end up calling - /// , with that single in the - /// passed to that method. - /// - protected abstract Task> ComputeCodeActionsAsync( - Document document, Diagnostic diagnostic, CancellationToken cancellationToken); - - /// - /// Subclasses should implement this to support fixing all given diagnostics efficiently. - /// - protected abstract Task FixAllAsync( - Document document, ImmutableArray diagnostics, SyntaxEditor editor, CancellationToken cancellationToken); - - protected Task FixWithSyntaxEditorAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) - => SyntaxEditorBasedCodeFixProvider.FixAllWithEditorAsync( - document, editor => FixAllAsync(document, ImmutableArray.Create(diagnostic), editor, cancellationToken), cancellationToken); - - #endregion - - #region refactoring - - /// - /// Subclasses should implement this to provide their feature as a refactoring. This will - /// be called when the user has the code style set to 'refactoring only' (or if the - /// diagnostic is suppressed). - /// - /// The implementation of this should offer all refactorings it can that are relevant at the - /// provided . Specifically, because these are just refactorings, - /// they should be offered when they would make the code match the desired user preference, - /// or even for allowing the user to quickly switch their code to *not* follow their desired - /// preference. - /// - protected abstract Task> ComputeAllRefactoringsWhenAnalyzerInactiveAsync( - Document document, TextSpan span, CancellationToken cancellationToken); - - /// - /// Subclasses should implement this to provide the refactoring that works in the opposing - /// direction of what the option preference is. This is only called if the user has the - /// code style enabled, and has it set to 'info/warning/error'. In this case it is the - /// *analyzer* responsible for making code compliant with the option. - /// - /// The refactoring then exists to allow the user to update their code to go against that - /// option on an individual case by case basis. - /// - /// For example, if the user had set that they want expression-bodies for methods (at - /// warning level), then this would offer 'use block body' on a method that had an - /// expression body already. - /// - protected abstract Task> ComputeOpposingRefactoringsWhenAnalyzerActiveAsync( - Document document, TextSpan span, TOptionValue option, CancellationToken cancellationToken); - - #endregion - } -} diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 98f8aff7c83e6..19cba4225fb58 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -1151,12 +1151,6 @@ This version used in: {2} Add member name - - Use block body for lambda expressions - - - Use expression body for lambda expressions - Convert to LINQ (call form) diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 98ca114537c0f..46d7529d021b6 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -2895,16 +2895,6 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv Aktualizace variance {0} vyžaduje restartování aplikace. - - Use block body for lambda expressions - Pro lambda výrazy používat text bloku - - - - Use expression body for lambda expressions - Používat text výrazu pro lambda výrazy - - Value: Hodnota: diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 1a18d48f7fd6f..091f033414aa0 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -2895,16 +2895,6 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg Das Aktualisieren der Abweichung von {0} erfordert einen Neustart der Anwendung. - - Use block body for lambda expressions - Blocktextkörper für Lambdaausdrücke verwenden - - - - Use expression body for lambda expressions - Ausdruckskörper für Lambdaausdrücke verwenden - - Value: Wert: diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index fd5b8253b37e0..2f62ca9618abd 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -2895,16 +2895,6 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us Al actualizar la varianza de {0} se requiere reiniciar la aplicación. - - Use block body for lambda expressions - Usar cuerpo del bloque para las expresiones lambda - - - - Use expression body for lambda expressions - Usar órgano de expresión para expresiones lambda - - Value: Valor: diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index 1060ca2e56faf..45f155df1a47a 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -2895,16 +2895,6 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée La mise à jour de la variance de {0} requiert le redémarrage de l’application. - - Use block body for lambda expressions - Utiliser le corps de bloc pour les expressions lambda - - - - Use expression body for lambda expressions - Utiliser le corps d'expression pour les expressions lambda - - Value: Valeur : diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 1e28eaffcbadd..231a8c633135d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -2895,16 +2895,6 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all' Se si aggiorna la varianza di {0}, è necessario riavviare l'applicazione. - - Use block body for lambda expressions - Usa il corpo del blocco per le espressioni lambda - - - - Use expression body for lambda expressions - Usa il corpo dell'espressione per le espressioni lambda - - Value: Valore: diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 219fe86dedb1c..884a80e5acd7f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -2895,16 +2895,6 @@ Zero-width positive lookbehind assertions are typically used at the beginning of {0} の変性を更新するには、アプリケーションを再起動する必要があります。 - - Use block body for lambda expressions - ラムダ式にブロック本体を使用する - - - - Use expression body for lambda expressions - ラムダ式に式本体を使用する - - Value: 値: diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 2c1d9e3bae111..dc0386a0745d7 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -2895,16 +2895,6 @@ Zero-width positive lookbehind assertions are typically used at the beginning of {0}의 분산을 업데이트하려면 응용 프로그램을 다시 시작해야 합니다. - - Use block body for lambda expressions - 람다 식에 블록 본문 사용 - - - - Use expression body for lambda expressions - 람다 식에 식 본문 사용 - - Value: 값: diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 19d1da19dc7c6..832a0426688a2 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -2895,16 +2895,6 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk Aktualizowanie odchylenia elementu {0} wymaga ponownego uruchomienia aplikacji. - - Use block body for lambda expressions - Użyj treści bloku dla wyrażeń lambda - - - - Use expression body for lambda expressions - Użyj treści wyrażenia dla wyrażeń lambda - - Value: Wartość: diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index f144264920ed9..a379492276de3 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -2895,16 +2895,6 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas Atualizar a variação de {0} requer a reinicialização do aplicativo. - - Use block body for lambda expressions - Usar o corpo do bloco para expressões lambda - - - - Use expression body for lambda expressions - Usar corpo da expressão para expressões lambda - - Value: Valor: diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index b8ca9cd2e41ad..b7380335f71d2 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -2895,16 +2895,6 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Для обновления расхождения {0} требуется перезапустить приложение. - - Use block body for lambda expressions - Использовать тело блока для лямбда-выражений - - - - Use expression body for lambda expressions - Использовать тело выражения для лямбда-выражений - - Value: Значение: diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index d4ff250b089bf..5bc6d594ce489 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -2895,16 +2895,6 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri {0} varyansını güncelleştirmek, uygulamanın yeniden başlatılmasını gerektirir. - - Use block body for lambda expressions - Lambda ifadeleri için blok vücut kullanımı - - - - Use expression body for lambda expressions - Lambda ifadeleri için ifade vücut kullanımı - - Value: Değer: diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 56fbae53b4f62..77302ee42ec2f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -2895,16 +2895,6 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 更新 {0} 的差异需要重新启动应用程序。 - - Use block body for lambda expressions - 对 lambda 表达式使用块主体 - - - - Use expression body for lambda expressions - 对 lambda 表达式使用表达式正文 - - Value: 值: diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index a001043cf4802..cf4befa70743f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -2895,16 +2895,6 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 更新 {0} 的差異需要重新啟動應用程式。 - - Use block body for lambda expressions - 使用 Lambda 運算式的區塊主體 - - - - Use expression body for lambda expressions - 使用 Lambda 運算式的運算式主體 - - Value: 值: From 736af295c3ab860deb53faace50537b81aed5ee7 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 15 Jul 2022 16:45:45 +0200 Subject: [PATCH 02/10] Fix null reference warnings --- ...eExpressionBodyForLambdaDiagnosticAnalyzer.cs | 16 ++++++++-------- .../UseExpressionBodyForLambdaCodeFixProvider.cs | 7 +++---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs index da9f418094543..d97ced7e81a7d 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; @@ -84,7 +85,7 @@ private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context, CodeStyleOp } } - private static Diagnostic AnalyzeSyntax( + private static Diagnostic? AnalyzeSyntax( SemanticModel semanticModel, CodeStyleOption2 option, LambdaExpressionSyntax declaration, CancellationToken cancellationToken) { @@ -93,7 +94,7 @@ private static Diagnostic AnalyzeSyntax( var location = GetDiagnosticLocation(declaration); var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); - var properties = ImmutableDictionary.Empty; + var properties = ImmutableDictionary.Empty; return DiagnosticHelper.Create( CreateDescriptorWithId(UseExpressionBodyTitle, UseExpressionBodyTitle), location, option.Notification.Severity, additionalLocations, properties); @@ -105,7 +106,7 @@ private static Diagnostic AnalyzeSyntax( // if they don't want expression bodies for this member. var location = GetDiagnosticLocation(declaration); - var properties = ImmutableDictionary.Empty; + var properties = ImmutableDictionary.Empty; var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); return DiagnosticHelper.Create( CreateDescriptorWithId(UseBlockBodyTitle, UseBlockBodyTitle), @@ -119,7 +120,7 @@ private static Location GetDiagnosticLocation(LambdaExpressionSyntax declaration => Location.Create(declaration.SyntaxTree, TextSpan.FromBounds(declaration.SpanStart, declaration.ArrowToken.Span.End)); - internal static ExpressionSyntax GetBodyAsExpression(LambdaExpressionSyntax declaration) + internal static ExpressionSyntax? GetBodyAsExpression(LambdaExpressionSyntax declaration) => declaration.Body as ExpressionSyntax; private static bool CanOfferUseExpressionBody( @@ -141,19 +142,18 @@ private static bool CanOfferUseExpressionBody( // They don't have an expression body. See if we could convert the block they // have into one. - return TryConvertToExpressionBody(declaration, languageVersion, preference, out _, out _); + return TryConvertToExpressionBody(declaration, languageVersion, preference, out _); } internal static bool TryConvertToExpressionBody( LambdaExpressionSyntax declaration, LanguageVersion languageVersion, ExpressionBodyPreference conversionPreference, - out ExpressionSyntax expression, - out SyntaxToken semicolon) + [NotNullWhen(true)] out ExpressionSyntax? expression) { var body = declaration.Body as BlockSyntax; - return body.TryConvertToExpressionBody(languageVersion, conversionPreference, out expression, out semicolon); + return body.TryConvertToExpressionBody(languageVersion, conversionPreference, out expression, out _); } private static bool CanOfferUseBlockBody( diff --git a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs index 05713c185305b..92e5d7e2e99b1 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs @@ -84,12 +84,12 @@ private static LambdaExpressionSyntax UpdateWorker( var expressionBody = UseExpressionBodyForLambdaDiagnosticAnalyzer.GetBodyAsExpression(currentDeclaration); return expressionBody == null ? WithExpressionBody(currentDeclaration, originalDeclaration.GetLanguageVersion()) - : WithBlockBody(semanticModel, originalDeclaration, currentDeclaration); + : WithBlockBody(semanticModel, originalDeclaration, currentDeclaration, expressionBody); } private static LambdaExpressionSyntax WithExpressionBody(LambdaExpressionSyntax declaration, LanguageVersion languageVersion) { - if (!UseExpressionBodyForLambdaDiagnosticAnalyzer.TryConvertToExpressionBody(declaration, languageVersion, ExpressionBodyPreference.WhenPossible, out var expressionBody, out _)) + if (!UseExpressionBodyForLambdaDiagnosticAnalyzer.TryConvertToExpressionBody(declaration, languageVersion, ExpressionBodyPreference.WhenPossible, out var expressionBody)) { return declaration; } @@ -108,9 +108,8 @@ private static LambdaExpressionSyntax WithExpressionBody(LambdaExpressionSyntax } private static LambdaExpressionSyntax WithBlockBody( - SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) + SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration, ExpressionSyntax expressionBody) { - var expressionBody = UseExpressionBodyForLambdaDiagnosticAnalyzer.GetBodyAsExpression(currentDeclaration); var createReturnStatementForExpression = CreateReturnStatementForExpression( semanticModel, originalDeclaration); From c008e1e87e462e357f2740eb80514ea7b11efd37 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 15 Jul 2022 16:47:39 +0200 Subject: [PATCH 03/10] Clean BuildActionTelemetryTable --- src/Tools/BuildActionTelemetryTable/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tools/BuildActionTelemetryTable/Program.cs b/src/Tools/BuildActionTelemetryTable/Program.cs index 6379da889d3df..a215499e3f8c8 100644 --- a/src/Tools/BuildActionTelemetryTable/Program.cs +++ b/src/Tools/BuildActionTelemetryTable/Program.cs @@ -30,7 +30,6 @@ public class Program "Microsoft.CodeAnalysis.CodeActions.CodeAction+SolutionChangeAction", "Microsoft.CodeAnalysis.CodeActions.CustomCodeActions+DocumentChangeAction", "Microsoft.CodeAnalysis.CodeActions.CustomCodeActions+SolutionChangeAction", - "Microsoft.CodeAnalysis.CodeStyle.AbstractCodeStyleProvider`2+CodeRefactoringProvider", "Microsoft.CodeAnalysis.CodeFixes.DocumentBasedFixAllProvider+PostProcessCodeAction", }.ToImmutableHashSet(); From 271e51d59a55f512e41007d7c48ca00d6dfa682c Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 15 Jul 2022 17:11:47 +0200 Subject: [PATCH 04/10] Add refactoring provider --- ...pressionBodyForLambdaDiagnosticAnalyzer.cs | 12 +- ...eExpressionBodyForLambdaCodeFixProvider.cs | 13 +- ...ionBodyForLambdaCodeRefactoringProvider.cs | 213 ++++++++++++++++++ 3 files changed, 220 insertions(+), 18 deletions(-) create mode 100644 src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs index d97ced7e81a7d..ac9a0bdd02cf4 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs @@ -17,8 +17,8 @@ namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda [DiagnosticAnalyzer(LanguageNames.CSharp)] internal sealed class UseExpressionBodyForLambdaDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer { - private static readonly LocalizableString UseExpressionBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_expression_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); - private static readonly LocalizableString UseBlockBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_block_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); + internal static readonly LocalizableString UseExpressionBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_expression_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); + internal static readonly LocalizableString UseBlockBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_block_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); public UseExpressionBodyForLambdaDiagnosticAnalyzer() : base(IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId, @@ -39,7 +39,7 @@ protected override void InitializeWorker(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeIfEnabled, SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression); - private static CodeStyleOption2 GetCodeStyleOption(AnalyzerOptionsProvider provider) + internal static CodeStyleOption2 GetCodeStyleOption(AnalyzerOptionsProvider provider) => ((CSharpAnalyzerOptionsProvider)provider).PreferExpressionBodiedLambdas; /// @@ -47,7 +47,7 @@ private static CodeStyleOption2 GetCodeStyleOption(Ana /// handle ReportDiagnostic.Default and will map that back to the appropriate value in that /// case. /// - private static ReportDiagnostic GetOptionSeverity(CodeStyleOption2 optionValue) + internal static ReportDiagnostic GetOptionSeverity(CodeStyleOption2 optionValue) { var severity = optionValue.Notification.Severity; return severity == ReportDiagnostic.Default @@ -123,7 +123,7 @@ private static Location GetDiagnosticLocation(LambdaExpressionSyntax declaration internal static ExpressionSyntax? GetBodyAsExpression(LambdaExpressionSyntax declaration) => declaration.Body as ExpressionSyntax; - private static bool CanOfferUseExpressionBody( + internal static bool CanOfferUseExpressionBody( ExpressionBodyPreference preference, LambdaExpressionSyntax declaration, LanguageVersion languageVersion) { var userPrefersExpressionBodies = preference != ExpressionBodyPreference.Never; @@ -156,7 +156,7 @@ internal static bool TryConvertToExpressionBody( return body.TryConvertToExpressionBody(languageVersion, conversionPreference, out expression, out _); } - private static bool CanOfferUseBlockBody( + internal static bool CanOfferUseBlockBody( SemanticModel semanticModel, ExpressionBodyPreference preference, LambdaExpressionSyntax declaration, CancellationToken cancellationToken) { diff --git a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs index 92e5d7e2e99b1..4fc9f22f72b4f 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs @@ -75,7 +75,7 @@ private static void AddEdits( (current, _) => Update(semanticModel, originalDeclaration, (LambdaExpressionSyntax)current)); } - private static LambdaExpressionSyntax Update(SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) + internal static LambdaExpressionSyntax Update(SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) => UpdateWorker(semanticModel, originalDeclaration, currentDeclaration).WithAdditionalAnnotations(Formatter.Annotation); private static LambdaExpressionSyntax UpdateWorker( @@ -164,15 +164,4 @@ private static bool CreateReturnStatementForExpression( return true; } } - - // PROTOTYPE: TODO - //[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.UseExpressionBodyForLambda), Shared] - //internal sealed class UseExpressionBodyForLambdaCodeRefactoringProvider : UseExpressionBodyForLambdaCodeStyleProvider.CodeRefactoringProvider - //{ - // [ImportingConstructor] - // [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] - // public UseExpressionBodyForLambdaCodeRefactoringProvider() - // { - // } - //} } diff --git a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs new file mode 100644 index 0000000000000..fedfd032e300b --- /dev/null +++ b/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs @@ -0,0 +1,213 @@ +// 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.Immutable; +using System.Composition; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda +{ + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.UseExpressionBodyForLambda), Shared] + internal sealed class UseExpressionBodyForLambdaCodeRefactoringProvider : CodeRefactoringProvider + { + [ImportingConstructor] + [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] + public UseExpressionBodyForLambdaCodeRefactoringProvider() + { + } + + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + var document = context.Document; + var cancellationToken = context.CancellationToken; + + var optionProvider = await document.GetAnalyzerOptionsProviderAsync(cancellationToken).ConfigureAwait(false); + var optionValue = UseExpressionBodyForLambdaDiagnosticAnalyzer.GetCodeStyleOption(optionProvider); + + var severity = UseExpressionBodyForLambdaDiagnosticAnalyzer.GetOptionSeverity(optionValue); + switch (severity) + { + case ReportDiagnostic.Suppress: + case ReportDiagnostic.Hidden: + // if the severity is Hidden that's equivalent to 'refactoring only', so we want + // to try to compute the refactoring here. + // + // If the severity is 'suppress', that means the user doesn't want the actual + // analyzer to run here. However, we can still check to see if we could offer + // the feature here as a refactoring. + await ComputeRefactoringsAsync(context, optionValue.Value, analyzerActive: false).ConfigureAwait(false); + return; + + case ReportDiagnostic.Error: + case ReportDiagnostic.Warn: + case ReportDiagnostic.Info: + // User has this option set at a level where we want it checked by the + // DiagnosticAnalyser and not the CodeRefactoringProvider. However, we still + // want to check if we want to offer the *reverse* refactoring here in this + // single location. + // + // For example, say this is the "use expression body" feature. If the user says + // they always prefer expression-bodies (with warning level), then we want the + // analyzer to always be checking for that. However, we still want to offer the + // refactoring to flip their code to use a block body here, just in case that + // was something they wanted to do as a one off (i.e. before adding new + // statements. + // + // TODO(cyrusn): Should we only do this for warn/info? Argument could be made + // that we shouldn't even offer to refactor in the reverse direction if it will + // just cause an error. That said, maybe this is just an intermediary step, and + // we shouldn't really be blocking the user from making it. + await ComputeRefactoringsAsync(context, optionValue.Value, analyzerActive: true).ConfigureAwait(false); + return; + } + } + + private static async Task ComputeRefactoringsAsync( + CodeRefactoringContext context, ExpressionBodyPreference option, bool analyzerActive) + { + var document = context.Document; + var span = context.Span; + var cancellationToken = context.CancellationToken; + + var computationTask = analyzerActive + ? ComputeOpposingRefactoringsWhenAnalyzerActiveAsync(document, span, option, cancellationToken) + : ComputeAllRefactoringsWhenAnalyzerInactiveAsync(document, span, cancellationToken); + + var codeActions = await computationTask.ConfigureAwait(false); + context.RegisterRefactorings(codeActions); + } + + private static async Task> ComputeOpposingRefactoringsWhenAnalyzerActiveAsync( + Document document, TextSpan span, ExpressionBodyPreference option, CancellationToken cancellationToken) + { + if (option == ExpressionBodyPreference.Never) + { + // the user wants block-bodies (and the analyzer will be trying to enforce that). So + // the reverse of this is that we want to offer the refactoring to convert a + // block-body to an expression-body whenever possible. + return await ComputeRefactoringsAsync( + document, span, ExpressionBodyPreference.WhenPossible, cancellationToken).ConfigureAwait(false); + } + else if (option == ExpressionBodyPreference.WhenPossible) + { + // the user likes expression-bodies whenever possible, and the analyzer will be + // trying to enforce that. So the reverse of this is that we want to offer the + // refactoring to convert an expression-body to a block-body whenever possible. + return await ComputeRefactoringsAsync( + document, span, ExpressionBodyPreference.Never, cancellationToken).ConfigureAwait(false); + } + else if (option == ExpressionBodyPreference.WhenOnSingleLine) + { + // the user likes expression-bodies *if* the body would be on a single line. this + // means if we hit an block-body with an expression on a single line, then the + // analyzer will handle it for us. + + // So we need to handle the cases of either hitting an expression-body and wanting + // to convert it to a block-body *or* hitting an block-body over *multiple* lines and + // wanting to offer to convert to an expression-body. + + // Always offer to convert an expression to a block since the analyzer will never + // offer that. For this option setting. + var useBlockRefactorings = await ComputeRefactoringsAsync( + document, span, ExpressionBodyPreference.Never, cancellationToken).ConfigureAwait(false); + + var whenOnSingleLineRefactorings = await ComputeRefactoringsAsync( + document, span, ExpressionBodyPreference.WhenOnSingleLine, cancellationToken).ConfigureAwait(false); + if (whenOnSingleLineRefactorings.Length > 0) + { + // this block lambda would be converted to an expression lambda based on the + // analyzer alone. So we don't want to offer that as a refactoring ourselves. + return useBlockRefactorings; + } + + // The lambda block statement wasn't on a single line. So the analyzer would + // not offer to convert it to an expression body. So we should can offer that + // as a refactoring if possible. + var whenPossibleRefactorings = await ComputeRefactoringsAsync( + document, span, ExpressionBodyPreference.WhenPossible, cancellationToken).ConfigureAwait(false); + return useBlockRefactorings.AddRange(whenPossibleRefactorings); + } + else + { + throw ExceptionUtilities.UnexpectedValue(option); + } + } + + private static async Task> ComputeAllRefactoringsWhenAnalyzerInactiveAsync( + Document document, TextSpan span, CancellationToken cancellationToken) + { + // If the analyzer is inactive, then we want to offer refactorings in any viable + // direction. So we want to offer to convert expression-bodies to block-bodies, and + // vice-versa if applicable. + + var toExpressionBodyRefactorings = await ComputeRefactoringsAsync( + document, span, ExpressionBodyPreference.WhenPossible, cancellationToken).ConfigureAwait(false); + + var toBlockBodyRefactorings = await ComputeRefactoringsAsync( + document, span, ExpressionBodyPreference.Never, cancellationToken).ConfigureAwait(false); + + return toExpressionBodyRefactorings.AddRange(toBlockBodyRefactorings); + } + + private static async Task> ComputeRefactoringsAsync( + Document document, TextSpan span, ExpressionBodyPreference option, CancellationToken cancellationToken) + { + var lambdaNode = await document.TryGetRelevantNodeAsync(span, cancellationToken).ConfigureAwait(false); + if (lambdaNode == null) + { + return ImmutableArray.Empty; + } + + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + using var resultDisposer = ArrayBuilder.GetInstance(out var result); + if (UseExpressionBodyForLambdaDiagnosticAnalyzer.CanOfferUseExpressionBody(option, lambdaNode, root.GetLanguageVersion())) + { + var title = UseExpressionBodyForLambdaDiagnosticAnalyzer.UseExpressionBodyTitle.ToString(); + result.Add(CodeAction.Create( + title, + c => UpdateDocumentAsync( + document, root, lambdaNode, c), + title)); + } + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + if (UseExpressionBodyForLambdaDiagnosticAnalyzer.CanOfferUseBlockBody(semanticModel, option, lambdaNode, cancellationToken)) + { + var title = UseExpressionBodyForLambdaDiagnosticAnalyzer.UseBlockBodyTitle.ToString(); + result.Add(CodeAction.Create( + title, + c => UpdateDocumentAsync( + document, root, lambdaNode, c), + title)); + } + + return result.ToImmutable(); + } + + private static async Task UpdateDocumentAsync( + Document document, SyntaxNode root, LambdaExpressionSyntax declaration, CancellationToken cancellationToken) + { + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + // We're only replacing a single declaration in the refactoring. So pass 'declaration' + // as both the 'original' and 'current' declaration. + var updatedDeclaration = UseExpressionBodyForLambdaCodeFixProvider.Update(semanticModel, declaration, declaration); + + var newRoot = root.ReplaceNode(declaration, updatedDeclaration); + return document.WithSyntaxRoot(newRoot); + } + } +} From 84106df20ac30aa537abd56b376b03c91ef6ff46 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 15 Jul 2022 17:38:59 +0200 Subject: [PATCH 05/10] Fix NRE --- .../UseExpressionBodyForLambdaCodeFixProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs index 4fc9f22f72b4f..4e1bdbf7aa5d1 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs @@ -153,7 +153,7 @@ private static bool CreateReturnStatementForExpression( return returnType.Name != nameof(Task); } - var taskType = semanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName); + var taskType = semanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName!); if (returnType.Equals(taskType)) { // 'async Task'. definitely do not create a 'return' statement; From 8f9d451782344a3a5641e5c3aad63f5032cd6f14 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Fri, 15 Jul 2022 19:30:14 +0200 Subject: [PATCH 06/10] Don't use banned API --- ...pressionBodyForLambdaDiagnosticAnalyzer.cs | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs index ac9a0bdd02cf4..5d4a081aaafd0 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs @@ -20,15 +20,14 @@ internal sealed class UseExpressionBodyForLambdaDiagnosticAnalyzer : AbstractBui internal static readonly LocalizableString UseExpressionBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_expression_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); internal static readonly LocalizableString UseBlockBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_block_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); - public UseExpressionBodyForLambdaDiagnosticAnalyzer() - : base(IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId, - EnforceOnBuildValues.UseExpressionBodyForLambdaExpressions, - CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, - LanguageNames.CSharp, - UseExpressionBodyTitle, - UseExpressionBodyTitle, - isUnnecessary: false, - configurable: true) + private static readonly DiagnosticDescriptor s_useExpressionBodyForLambda = CreateDescriptorWithId(UseExpressionBodyTitle, UseExpressionBodyTitle); + private static readonly DiagnosticDescriptor s_useBlockBodyForLambda = CreateDescriptorWithId(UseBlockBodyTitle, UseBlockBodyTitle); + + public UseExpressionBodyForLambdaDiagnosticAnalyzer() : base( + ImmutableDictionary.Empty + .Add(s_useExpressionBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas) + .Add(s_useBlockBodyForLambda, CSharpCodeStyleOptions.PreferExpressionBodiedLambdas), + LanguageNames.CSharp) { } @@ -96,7 +95,7 @@ private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context, CodeStyleOp var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); var properties = ImmutableDictionary.Empty; return DiagnosticHelper.Create( - CreateDescriptorWithId(UseExpressionBodyTitle, UseExpressionBodyTitle), + s_useExpressionBodyForLambda, location, option.Notification.Severity, additionalLocations, properties); } @@ -109,7 +108,7 @@ private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context, CodeStyleOp var properties = ImmutableDictionary.Empty; var additionalLocations = ImmutableArray.Create(declaration.GetLocation()); return DiagnosticHelper.Create( - CreateDescriptorWithId(UseBlockBodyTitle, UseBlockBodyTitle), + s_useBlockBodyForLambda, location, option.Notification.Severity, additionalLocations, properties); } @@ -205,11 +204,7 @@ internal static bool CanOfferUseBlockBody( private static DiagnosticDescriptor CreateDescriptorWithId( LocalizableString title, LocalizableString message) { - return new DiagnosticDescriptor( - IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId, title, message, - DiagnosticCategory.Style, - DiagnosticSeverity.Hidden, - isEnabledByDefault: true); + return CreateDescriptorWithId(IDEDiagnosticIds.UseExpressionBodyForLambdaExpressionsDiagnosticId, EnforceOnBuildValues.UseExpressionBodyForLambdaExpressions, title, message); } } } From c7b45768712e3e499c6a211f78dd6d213f9301ae Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Thu, 21 Jul 2022 11:22:49 +0200 Subject: [PATCH 07/10] Refactor --- .../Analyzers/CSharpAnalyzers.projitems | 1 + ...pressionBodyForLambdaDiagnosticAnalyzer.cs | 113 +---------------- ...nBodyForLambdaDiagnosticAnalyzerHelpers.cs | 117 ++++++++++++++++++ .../CodeFixes/CSharpCodeFixes.projitems | 1 + ...xpressionBodyForLambdaCodeActionHelpers.cs | 107 ++++++++++++++++ ...eExpressionBodyForLambdaCodeFixProvider.cs | 92 +------------- ...ionBodyForLambdaCodeRefactoringProvider.cs | 15 +-- 7 files changed, 242 insertions(+), 204 deletions(-) create mode 100644 src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzerHelpers.cs create mode 100644 src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems index b523943346420..4183c1999e53a 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems @@ -82,6 +82,7 @@ + diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs index 5d4a081aaafd0..f825a08126ceb 100644 --- a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzer.cs @@ -17,11 +17,8 @@ namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda [DiagnosticAnalyzer(LanguageNames.CSharp)] internal sealed class UseExpressionBodyForLambdaDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer { - internal static readonly LocalizableString UseExpressionBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_expression_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); - internal static readonly LocalizableString UseBlockBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_block_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); - - private static readonly DiagnosticDescriptor s_useExpressionBodyForLambda = CreateDescriptorWithId(UseExpressionBodyTitle, UseExpressionBodyTitle); - private static readonly DiagnosticDescriptor s_useBlockBodyForLambda = CreateDescriptorWithId(UseBlockBodyTitle, UseBlockBodyTitle); + private static readonly DiagnosticDescriptor s_useExpressionBodyForLambda = CreateDescriptorWithId(UseExpressionBodyForLambdaHelpers.UseExpressionBodyTitle, UseExpressionBodyForLambdaHelpers.UseExpressionBodyTitle); + private static readonly DiagnosticDescriptor s_useBlockBodyForLambda = CreateDescriptorWithId(UseExpressionBodyForLambdaHelpers.UseBlockBodyTitle, UseExpressionBodyForLambdaHelpers.UseBlockBodyTitle); public UseExpressionBodyForLambdaDiagnosticAnalyzer() : base( ImmutableDictionary.Empty @@ -38,28 +35,12 @@ protected override void InitializeWorker(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeIfEnabled, SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression); - internal static CodeStyleOption2 GetCodeStyleOption(AnalyzerOptionsProvider provider) - => ((CSharpAnalyzerOptionsProvider)provider).PreferExpressionBodiedLambdas; - - /// - /// Helper to get the true ReportDiagnostic severity for a given option. Importantly, this - /// handle ReportDiagnostic.Default and will map that back to the appropriate value in that - /// case. - /// - internal static ReportDiagnostic GetOptionSeverity(CodeStyleOption2 optionValue) - { - var severity = optionValue.Notification.Severity; - return severity == ReportDiagnostic.Default - ? severity.WithDefaultSeverity(DiagnosticSeverity.Hidden) - : severity; - } - private static void AnalyzeIfEnabled(SyntaxNodeAnalysisContext context) { var analyzerOptions = context.Options; var syntaxTree = context.SemanticModel.SyntaxTree; - var optionValue = GetCodeStyleOption(analyzerOptions.GetAnalyzerOptions(syntaxTree)); - var severity = GetOptionSeverity(optionValue); + var optionValue = UseExpressionBodyForLambdaHelpers.GetCodeStyleOption(analyzerOptions.GetAnalyzerOptions(syntaxTree)); + var severity = UseExpressionBodyForLambdaHelpers.GetOptionSeverity(optionValue); switch (severity) { case ReportDiagnostic.Error: @@ -88,7 +69,7 @@ private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context, CodeStyleOp SemanticModel semanticModel, CodeStyleOption2 option, LambdaExpressionSyntax declaration, CancellationToken cancellationToken) { - if (CanOfferUseExpressionBody(option.Value, declaration, declaration.GetLanguageVersion())) + if (UseExpressionBodyForLambdaHelpers.CanOfferUseExpressionBody(option.Value, declaration, declaration.GetLanguageVersion())) { var location = GetDiagnosticLocation(declaration); @@ -99,7 +80,7 @@ private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context, CodeStyleOp location, option.Notification.Severity, additionalLocations, properties); } - if (CanOfferUseBlockBody(semanticModel, option.Value, declaration, cancellationToken)) + if (UseExpressionBodyForLambdaHelpers.CanOfferUseBlockBody(semanticModel, option.Value, declaration, cancellationToken)) { // They have an expression body. Create a diagnostic to convert it to a block // if they don't want expression bodies for this member. @@ -119,88 +100,6 @@ private static Location GetDiagnosticLocation(LambdaExpressionSyntax declaration => Location.Create(declaration.SyntaxTree, TextSpan.FromBounds(declaration.SpanStart, declaration.ArrowToken.Span.End)); - internal static ExpressionSyntax? GetBodyAsExpression(LambdaExpressionSyntax declaration) - => declaration.Body as ExpressionSyntax; - - internal static bool CanOfferUseExpressionBody( - ExpressionBodyPreference preference, LambdaExpressionSyntax declaration, LanguageVersion languageVersion) - { - var userPrefersExpressionBodies = preference != ExpressionBodyPreference.Never; - if (!userPrefersExpressionBodies) - { - // If the user doesn't even want expression bodies, then certainly do not offer. - return false; - } - - var expressionBody = GetBodyAsExpression(declaration); - if (expressionBody != null) - { - // they already have an expression body. so nothing to do here. - return false; - } - - // They don't have an expression body. See if we could convert the block they - // have into one. - return TryConvertToExpressionBody(declaration, languageVersion, preference, out _); - } - - internal static bool TryConvertToExpressionBody( - LambdaExpressionSyntax declaration, - LanguageVersion languageVersion, - ExpressionBodyPreference conversionPreference, - [NotNullWhen(true)] out ExpressionSyntax? expression) - { - var body = declaration.Body as BlockSyntax; - - return body.TryConvertToExpressionBody(languageVersion, conversionPreference, out expression, out _); - } - - internal static bool CanOfferUseBlockBody( - SemanticModel semanticModel, ExpressionBodyPreference preference, - LambdaExpressionSyntax declaration, CancellationToken cancellationToken) - { - var userPrefersBlockBodies = preference == ExpressionBodyPreference.Never; - if (!userPrefersBlockBodies) - { - // If the user doesn't even want block bodies, then certainly do not offer. - return false; - } - - var expressionBodyOpt = GetBodyAsExpression(declaration); - if (expressionBodyOpt == null) - { - // they already have a block body. - return false; - } - - // We need to know what sort of lambda this is (void returning or not) in order to be - // able to create the right sort of block body (i.e. with a return-statement or - // expr-statement). So, if we can't figure out what lambda type this is, we should not - // proceed. - if (semanticModel.GetTypeInfo(declaration, cancellationToken).ConvertedType is not INamedTypeSymbol lambdaType || lambdaType.DelegateInvokeMethod == null) - { - return false; - } - - var canOffer = expressionBodyOpt.TryConvertToStatement( - semicolonTokenOpt: null, createReturnStatementForExpression: false, out _); - if (!canOffer) - { - // Couldn't even convert the expression into statement form. - return false; - } - - var languageVersion = declaration.SyntaxTree.Options.LanguageVersion(); - if (expressionBodyOpt.IsKind(SyntaxKind.ThrowExpression) && - languageVersion < LanguageVersion.CSharp7) - { - // Can't convert this prior to C# 7 because ```a => throw ...``` isn't allowed. - return false; - } - - return true; - } - private static DiagnosticDescriptor CreateDescriptorWithId( LocalizableString title, LocalizableString message) { diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzerHelpers.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzerHelpers.cs new file mode 100644 index 0000000000000..0c461b20fbd02 --- /dev/null +++ b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzerHelpers.cs @@ -0,0 +1,117 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Threading; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda +{ + internal static class UseExpressionBodyForLambdaHelpers + { + internal static readonly LocalizableString UseExpressionBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_expression_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); + internal static readonly LocalizableString UseBlockBodyTitle = new LocalizableResourceString(nameof(CSharpAnalyzersResources.Use_block_body_for_lambda_expressions), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); + + internal static bool CanOfferUseBlockBody( + SemanticModel semanticModel, ExpressionBodyPreference preference, + LambdaExpressionSyntax declaration, CancellationToken cancellationToken) + { + var userPrefersBlockBodies = preference == ExpressionBodyPreference.Never; + if (!userPrefersBlockBodies) + { + // If the user doesn't even want block bodies, then certainly do not offer. + return false; + } + + var expressionBodyOpt = GetBodyAsExpression(declaration); + if (expressionBodyOpt == null) + { + // they already have a block body. + return false; + } + + // We need to know what sort of lambda this is (void returning or not) in order to be + // able to create the right sort of block body (i.e. with a return-statement or + // expr-statement). So, if we can't figure out what lambda type this is, we should not + // proceed. + if (semanticModel.GetTypeInfo(declaration, cancellationToken).ConvertedType is not INamedTypeSymbol lambdaType || lambdaType.DelegateInvokeMethod == null) + { + return false; + } + + var canOffer = expressionBodyOpt.TryConvertToStatement( + semicolonTokenOpt: null, createReturnStatementForExpression: false, out _); + if (!canOffer) + { + // Couldn't even convert the expression into statement form. + return false; + } + + var languageVersion = declaration.SyntaxTree.Options.LanguageVersion(); + if (expressionBodyOpt.IsKind(SyntaxKind.ThrowExpression) && + languageVersion < LanguageVersion.CSharp7) + { + // Can't convert this prior to C# 7 because ```a => throw ...``` isn't allowed. + return false; + } + + return true; + } + + internal static bool CanOfferUseExpressionBody( + ExpressionBodyPreference preference, LambdaExpressionSyntax declaration, LanguageVersion languageVersion) + { + var userPrefersExpressionBodies = preference != ExpressionBodyPreference.Never; + if (!userPrefersExpressionBodies) + { + // If the user doesn't even want expression bodies, then certainly do not offer. + return false; + } + + var expressionBody = GetBodyAsExpression(declaration); + if (expressionBody != null) + { + // they already have an expression body. so nothing to do here. + return false; + } + + // They don't have an expression body. See if we could convert the block they + // have into one. + return TryConvertToExpressionBody(declaration, languageVersion, preference, out _); + } + + internal static ExpressionSyntax? GetBodyAsExpression(LambdaExpressionSyntax declaration) + => declaration.Body as ExpressionSyntax; + + internal static CodeStyleOption2 GetCodeStyleOption(AnalyzerOptionsProvider provider) + => ((CSharpAnalyzerOptionsProvider)provider).PreferExpressionBodiedLambdas; + + /// + /// Helper to get the true ReportDiagnostic severity for a given option. Importantly, this + /// handle ReportDiagnostic.Default and will map that back to the appropriate value in that + /// case. + /// + internal static ReportDiagnostic GetOptionSeverity(CodeStyleOption2 optionValue) + { + var severity = optionValue.Notification.Severity; + return severity == ReportDiagnostic.Default + ? severity.WithDefaultSeverity(DiagnosticSeverity.Hidden) + : severity; + } + + internal static bool TryConvertToExpressionBody( + LambdaExpressionSyntax declaration, + LanguageVersion languageVersion, + ExpressionBodyPreference conversionPreference, + [NotNullWhen(true)] out ExpressionSyntax? expression) + { + var body = declaration.Body as BlockSyntax; + + return body.TryConvertToExpressionBody(languageVersion, conversionPreference, out expression, out _); + } + } +} diff --git a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems index 448d4da00813d..413f4cef0532e 100644 --- a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems +++ b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems @@ -40,6 +40,7 @@ + diff --git a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs new file mode 100644 index 0000000000000..921a43fdf2a9f --- /dev/null +++ b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs @@ -0,0 +1,107 @@ +// 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.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.UseExpressionBodyForLambda; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.UseExpressionBodyForLambda +{ + internal class UseExpressionBodyForLambdaCodeActionHelpers + { + internal static LambdaExpressionSyntax Update(SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) + => UpdateWorker(semanticModel, originalDeclaration, currentDeclaration).WithAdditionalAnnotations(Formatter.Annotation); + + private static LambdaExpressionSyntax UpdateWorker( + SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) + { + var expressionBody = UseExpressionBodyForLambdaHelpers.GetBodyAsExpression(currentDeclaration); + return expressionBody == null + ? WithExpressionBody(currentDeclaration, originalDeclaration.GetLanguageVersion()) + : WithBlockBody(semanticModel, originalDeclaration, currentDeclaration, expressionBody); + } + + private static LambdaExpressionSyntax WithExpressionBody(LambdaExpressionSyntax declaration, LanguageVersion languageVersion) + { + if (!UseExpressionBodyForLambdaHelpers.TryConvertToExpressionBody(declaration, languageVersion, ExpressionBodyPreference.WhenPossible, out var expressionBody)) + { + return declaration; + } + + var updatedDecl = declaration.WithBody(expressionBody); + + // If there will only be whitespace between the arrow and the body, then replace that + // with a single space so that the lambda doesn't have superfluous newlines in it. + if (declaration.ArrowToken.TrailingTrivia.All(t => t.IsWhitespaceOrEndOfLine()) && + expressionBody.GetLeadingTrivia().All(t => t.IsWhitespaceOrEndOfLine())) + { + updatedDecl = updatedDecl.WithArrowToken(updatedDecl.ArrowToken.WithTrailingTrivia(SyntaxFactory.ElasticSpace)); + } + + return updatedDecl; + } + + private static LambdaExpressionSyntax WithBlockBody( + SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration, ExpressionSyntax expressionBody) + { + var createReturnStatementForExpression = CreateReturnStatementForExpression( + semanticModel, originalDeclaration); + + if (!expressionBody.TryConvertToStatement( + semicolonTokenOpt: null, + createReturnStatementForExpression, + out var statement)) + { + return currentDeclaration; + } + + // If the user is converting to a block, it's likely they intend to add multiple + // statements to it. So make a multi-line block so that things are formatted properly + // for them to do so. + return currentDeclaration.WithBody(SyntaxFactory.Block( + SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed), + SyntaxFactory.SingletonList(statement), + SyntaxFactory.Token(SyntaxKind.CloseBraceToken))); + } + + private static bool CreateReturnStatementForExpression( + SemanticModel semanticModel, LambdaExpressionSyntax declaration) + { + var lambdaType = (INamedTypeSymbol)semanticModel.GetTypeInfo(declaration).ConvertedType!; + if (lambdaType.DelegateInvokeMethod!.ReturnsVoid) + { + return false; + } + + // 'async Task' is effectively a void-returning lambda. we do not want to create + // 'return statements' when converting. + if (declaration.AsyncKeyword != default) + { + var returnType = lambdaType.DelegateInvokeMethod.ReturnType; + if (returnType.IsErrorType()) + { + // "async Goo" where 'Goo' failed to bind. If 'Goo' is 'Task' then it's + // reasonable to assume this is just a missing 'using' and that this is a true + // "async Task" lambda. If the name isn't 'Task', then this looks like a + // real return type, and we should use return statements. + return returnType.Name != nameof(Task); + } + + var taskType = semanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName!); + if (returnType.Equals(taskType)) + { + // 'async Task'. definitely do not create a 'return' statement; + return false; + } + } + + return true; + } + } +} diff --git a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs index 4e1bdbf7aa5d1..136f438eb14a4 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeFixProvider.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeFixes.UseExpressionBodyForLambda; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -72,96 +73,7 @@ private static void AddEdits( editor.ReplaceNode( originalDeclaration, - (current, _) => Update(semanticModel, originalDeclaration, (LambdaExpressionSyntax)current)); - } - - internal static LambdaExpressionSyntax Update(SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) - => UpdateWorker(semanticModel, originalDeclaration, currentDeclaration).WithAdditionalAnnotations(Formatter.Annotation); - - private static LambdaExpressionSyntax UpdateWorker( - SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) - { - var expressionBody = UseExpressionBodyForLambdaDiagnosticAnalyzer.GetBodyAsExpression(currentDeclaration); - return expressionBody == null - ? WithExpressionBody(currentDeclaration, originalDeclaration.GetLanguageVersion()) - : WithBlockBody(semanticModel, originalDeclaration, currentDeclaration, expressionBody); - } - - private static LambdaExpressionSyntax WithExpressionBody(LambdaExpressionSyntax declaration, LanguageVersion languageVersion) - { - if (!UseExpressionBodyForLambdaDiagnosticAnalyzer.TryConvertToExpressionBody(declaration, languageVersion, ExpressionBodyPreference.WhenPossible, out var expressionBody)) - { - return declaration; - } - - var updatedDecl = declaration.WithBody(expressionBody); - - // If there will only be whitespace between the arrow and the body, then replace that - // with a single space so that the lambda doesn't have superfluous newlines in it. - if (declaration.ArrowToken.TrailingTrivia.All(t => t.IsWhitespaceOrEndOfLine()) && - expressionBody.GetLeadingTrivia().All(t => t.IsWhitespaceOrEndOfLine())) - { - updatedDecl = updatedDecl.WithArrowToken(updatedDecl.ArrowToken.WithTrailingTrivia(SyntaxFactory.ElasticSpace)); - } - - return updatedDecl; - } - - private static LambdaExpressionSyntax WithBlockBody( - SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration, ExpressionSyntax expressionBody) - { - var createReturnStatementForExpression = CreateReturnStatementForExpression( - semanticModel, originalDeclaration); - - if (!expressionBody.TryConvertToStatement( - semicolonTokenOpt: null, - createReturnStatementForExpression, - out var statement)) - { - return currentDeclaration; - } - - // If the user is converting to a block, it's likely they intend to add multiple - // statements to it. So make a multi-line block so that things are formatted properly - // for them to do so. - return currentDeclaration.WithBody(SyntaxFactory.Block( - SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithAppendedTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed), - SyntaxFactory.SingletonList(statement), - SyntaxFactory.Token(SyntaxKind.CloseBraceToken))); - } - - private static bool CreateReturnStatementForExpression( - SemanticModel semanticModel, LambdaExpressionSyntax declaration) - { - var lambdaType = (INamedTypeSymbol)semanticModel.GetTypeInfo(declaration).ConvertedType!; - if (lambdaType.DelegateInvokeMethod!.ReturnsVoid) - { - return false; - } - - // 'async Task' is effectively a void-returning lambda. we do not want to create - // 'return statements' when converting. - if (declaration.AsyncKeyword != default) - { - var returnType = lambdaType.DelegateInvokeMethod.ReturnType; - if (returnType.IsErrorType()) - { - // "async Goo" where 'Goo' failed to bind. If 'Goo' is 'Task' then it's - // reasonable to assume this is just a missing 'using' and that this is a true - // "async Task" lambda. If the name isn't 'Task', then this looks like a - // real return type, and we should use return statements. - return returnType.Name != nameof(Task); - } - - var taskType = semanticModel.Compilation.GetTypeByMetadataName(typeof(Task).FullName!); - if (returnType.Equals(taskType)) - { - // 'async Task'. definitely do not create a 'return' statement; - return false; - } - } - - return true; + (current, _) => UseExpressionBodyForLambdaCodeActionHelpers.Update(semanticModel, originalDeclaration, (LambdaExpressionSyntax)current)); } } } diff --git a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs index fedfd032e300b..ae7b13ce435b2 100644 --- a/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeRefactoringProvider.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeFixes.UseExpressionBodyForLambda; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; @@ -34,9 +35,9 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte var cancellationToken = context.CancellationToken; var optionProvider = await document.GetAnalyzerOptionsProviderAsync(cancellationToken).ConfigureAwait(false); - var optionValue = UseExpressionBodyForLambdaDiagnosticAnalyzer.GetCodeStyleOption(optionProvider); + var optionValue = UseExpressionBodyForLambdaHelpers.GetCodeStyleOption(optionProvider); - var severity = UseExpressionBodyForLambdaDiagnosticAnalyzer.GetOptionSeverity(optionValue); + var severity = UseExpressionBodyForLambdaHelpers.GetOptionSeverity(optionValue); switch (severity) { case ReportDiagnostic.Suppress: @@ -173,9 +174,9 @@ private static async Task> ComputeRefactoringsAsync( var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); using var resultDisposer = ArrayBuilder.GetInstance(out var result); - if (UseExpressionBodyForLambdaDiagnosticAnalyzer.CanOfferUseExpressionBody(option, lambdaNode, root.GetLanguageVersion())) + if (UseExpressionBodyForLambdaHelpers.CanOfferUseExpressionBody(option, lambdaNode, root.GetLanguageVersion())) { - var title = UseExpressionBodyForLambdaDiagnosticAnalyzer.UseExpressionBodyTitle.ToString(); + var title = UseExpressionBodyForLambdaHelpers.UseExpressionBodyTitle.ToString(); result.Add(CodeAction.Create( title, c => UpdateDocumentAsync( @@ -184,9 +185,9 @@ private static async Task> ComputeRefactoringsAsync( } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - if (UseExpressionBodyForLambdaDiagnosticAnalyzer.CanOfferUseBlockBody(semanticModel, option, lambdaNode, cancellationToken)) + if (UseExpressionBodyForLambdaHelpers.CanOfferUseBlockBody(semanticModel, option, lambdaNode, cancellationToken)) { - var title = UseExpressionBodyForLambdaDiagnosticAnalyzer.UseBlockBodyTitle.ToString(); + var title = UseExpressionBodyForLambdaHelpers.UseBlockBodyTitle.ToString(); result.Add(CodeAction.Create( title, c => UpdateDocumentAsync( @@ -204,7 +205,7 @@ private static async Task UpdateDocumentAsync( // We're only replacing a single declaration in the refactoring. So pass 'declaration' // as both the 'original' and 'current' declaration. - var updatedDeclaration = UseExpressionBodyForLambdaCodeFixProvider.Update(semanticModel, declaration, declaration); + var updatedDeclaration = UseExpressionBodyForLambdaCodeActionHelpers.Update(semanticModel, declaration, declaration); var newRoot = root.ReplaceNode(declaration, updatedDeclaration); return document.WithSyntaxRoot(newRoot); From e7daf8e68c28e052ae2ed3de996705e6617e6667 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Thu, 21 Jul 2022 11:23:17 +0200 Subject: [PATCH 08/10] Move tests --- src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems | 1 + .../UseExpressionBodyForLambdasAnalyzerTests.cs | 0 2 files changed, 1 insertion(+) rename src/{EditorFeatures/CSharpTest => Analyzers/CSharp/Tests}/UseExpressionBodyForLambda/UseExpressionBodyForLambdasAnalyzerTests.cs (100%) diff --git a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems index a57f7926abb75..84b7835a03d82 100644 --- a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems +++ b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems @@ -71,6 +71,7 @@ + diff --git a/src/EditorFeatures/CSharpTest/UseExpressionBodyForLambda/UseExpressionBodyForLambdasAnalyzerTests.cs b/src/Analyzers/CSharp/Tests/UseExpressionBodyForLambda/UseExpressionBodyForLambdasAnalyzerTests.cs similarity index 100% rename from src/EditorFeatures/CSharpTest/UseExpressionBodyForLambda/UseExpressionBodyForLambdasAnalyzerTests.cs rename to src/Analyzers/CSharp/Tests/UseExpressionBodyForLambda/UseExpressionBodyForLambdasAnalyzerTests.cs From 43ffb85e153ba871533e69e1515b617b84623233 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Thu, 21 Jul 2022 11:25:12 +0200 Subject: [PATCH 09/10] Make static --- .../UseExpressionBodyForLambdaCodeActionHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs index 921a43fdf2a9f..9ac8feb2cdabc 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseExpressionBodyForLambda/UseExpressionBodyForLambdaCodeActionHelpers.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.UseExpressionBodyForLambda { - internal class UseExpressionBodyForLambdaCodeActionHelpers + internal static class UseExpressionBodyForLambdaCodeActionHelpers { internal static LambdaExpressionSyntax Update(SemanticModel semanticModel, LambdaExpressionSyntax originalDeclaration, LambdaExpressionSyntax currentDeclaration) => UpdateWorker(semanticModel, originalDeclaration, currentDeclaration).WithAdditionalAnnotations(Formatter.Annotation); From d16289224c92f9d8cb809625ab7ce71740ab4bba Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Thu, 21 Jul 2022 11:27:15 +0200 Subject: [PATCH 10/10] Rename file --- src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems | 2 +- ...cAnalyzerHelpers.cs => UseExpressionBodyForLambdaHelpers.cs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/{UseExpressionBodyForLambdaDiagnosticAnalyzerHelpers.cs => UseExpressionBodyForLambdaHelpers.cs} (100%) diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems index 4183c1999e53a..9af2c5482d170 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems @@ -82,7 +82,7 @@ - + diff --git a/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzerHelpers.cs b/src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaHelpers.cs similarity index 100% rename from src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaDiagnosticAnalyzerHelpers.cs rename to src/Analyzers/CSharp/Analyzers/UseExpressionBodyForLambda/UseExpressionBodyForLambdaHelpers.cs