diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems index 64c484b1d0853..a4ddf64e88ce9 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems @@ -30,6 +30,7 @@ + diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx index 466c18e00e098..782235465b2c1 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx @@ -343,4 +343,10 @@ Use parameter null checking + + Lambda expression can be removed + + + Remove unnecessary lambda expression + \ No newline at end of file diff --git a/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryLambdaExpression/CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryLambdaExpression/CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer.cs new file mode 100644 index 0000000000000..25cf721c05f31 --- /dev/null +++ b/src/Analyzers/CSharp/Analyzers/RemoveUnnecessaryLambdaExpression/CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer.cs @@ -0,0 +1,317 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryLambdaExpression +{ + /// + /// DiagnosticAnalyzer that looks code like Goo(() => Bar()) and offers to convert it to Goo(Bar). + /// This is only offered on C# 11 and above where this delegate can be cached and will not cause allocations each + /// time. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal sealed class CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer + { + public CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer() + : base(IDEDiagnosticIds.RemoveUnnecessaryLambdaExpressionDiagnosticId, + EnforceOnBuildValues.RemoveUnnecessaryLambdaExpression, + CSharpCodeStyleOptions.PreferMethodGroupConversion, + LanguageNames.CSharp, + new LocalizableResourceString(nameof(CSharpAnalyzersResources.Remove_unnecessary_lambda_expression), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)), + new LocalizableResourceString(nameof(CSharpAnalyzersResources.Lambda_expression_can_be_removed), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)), + isUnnecessary: true) + { + } + + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() + => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; + + protected override void InitializeWorker(AnalysisContext context) + { + context.RegisterCompilationStartAction(context => + { + if (context.Compilation.LanguageVersion().IsCSharp11OrAbove()) + { + var expressionType = context.Compilation.ExpressionOfTType(); + context.RegisterSyntaxNodeAction( + c => AnalyzeSyntax(c, expressionType), + SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.AnonymousMethodExpression); + } + }); + } + + private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, INamedTypeSymbol? expressionType) + { + var cancellationToken = context.CancellationToken; + var semanticModel = context.SemanticModel; + var syntaxTree = semanticModel.SyntaxTree; + + var preference = context.GetOption(CSharpCodeStyleOptions.PreferMethodGroupConversion); + if (preference.Notification.Severity == ReportDiagnostic.Suppress) + { + // User doesn't care about this rule. + return; + } + + var anonymousFunction = (AnonymousFunctionExpressionSyntax)context.Node; + + // Syntax checks first. + + // Don't simplify static lambdas. The user made them explicitly static to make it clear it must only cause + // a single allocation for the cached delegate. If we get rid of the lambda (and thus the static-keyword) it + // won't be clear anymore if the member-group-conversion allocation is cached or not. + if (anonymousFunction.Modifiers.Any(SyntaxKind.StaticKeyword)) + return; + + if (!TryGetAnonymousFunctionInvocation(anonymousFunction, out var invocation, out var wasAwaited)) + return; + + // If we had an async function, but we didn't await the expression inside then we can't convert this. The + // underlying value was wrapped into a task, and that won't work if directly referencing the function. + if (wasAwaited != anonymousFunction.Modifiers.Any(SyntaxKind.AsyncKeyword)) + return; + + // We have to have an invocation in the lambda like `() => X()` or `() => expr.X()`. + var invokedExpression = invocation.Expression; + if (invokedExpression is not SimpleNameSyntax and not MemberAccessExpressionSyntax) + return; + + // lambda and invocation have to agree on number of parameters. + var parameters = GetParameters(anonymousFunction); + if (parameters.Count != invocation.ArgumentList.Arguments.Count) + return; + + // parameters must be passed 1:1 from lambda to invocation. + for (int i = 0, n = parameters.Count; i < n; i++) + { + var parameter = parameters[i]; + var argument = invocation.ArgumentList.Arguments[i]; + + if (argument.Expression is not IdentifierNameSyntax argumentIdentifier) + return; + + if (parameter.Identifier.ValueText != argumentIdentifier.Identifier.ValueText) + return; + } + + // if we have `() => new C().X()` then converting to `new C().X` very much changes the meaning. + if (MayHaveSideEffects(invokedExpression)) + return; + + // Looks like a reasonable candidate to simplify. Now switch to semantics to check for sure. + + if (CSharpSemanticFacts.Instance.IsInExpressionTree(semanticModel, anonymousFunction, expressionType, cancellationToken)) + return; + + // If we have `object obj = x => Goo(x);` we don't want to simplify. The compiler warns if you write + // `object obj = Goo;` because of the conversion to a non-delegate type. While we could insert a cast here + // to make this work, that goes against the spirit of this analyzer/fixer just removing code. + var lambdaTypeInfo = semanticModel.GetTypeInfo(anonymousFunction, cancellationToken); + if (lambdaTypeInfo.ConvertedType == null || lambdaTypeInfo.ConvertedType.SpecialType is SpecialType.System_Object) + return; + + var lambdaSymbolInfo = semanticModel.GetSymbolInfo(anonymousFunction, cancellationToken); + if (lambdaSymbolInfo.Symbol is not IMethodSymbol lambdaMethod) + return; + + var invokedSymbolInfo = semanticModel.GetSymbolInfo(invokedExpression, cancellationToken); + if (invokedSymbolInfo.Symbol is not IMethodSymbol invokedMethod) + return; + + // If we're calling a generic method, we have to have supplied type arguments. They cannot be inferred once + // we remove the arguments during simplification. + var invokedTypeArguments = invokedExpression.GetRightmostName() is GenericNameSyntax genericName + ? genericName.TypeArgumentList.Arguments + : default; + + if (invokedMethod.TypeArguments.Length != invokedTypeArguments.Count) + return; + + // Methods have to be complimentary. That means the same number of parameters, with proper + // co-contravariance for the parameters and return type. + if (lambdaMethod.Parameters.Length != invokedMethod.Parameters.Length) + return; + + var compilation = semanticModel.Compilation; + + // Must be able to convert the invoked method return type to the lambda's return type. + if (!IsIdentityOrImplicitConversion(compilation, invokedMethod.ReturnType, lambdaMethod.ReturnType)) + return; + + for (int i = 0, n = lambdaMethod.Parameters.Length; i < n; i++) + { + var lambdaParameter = lambdaMethod.Parameters[i]; + var invokedParameter = invokedMethod.Parameters[i]; + + if (lambdaParameter.RefKind != invokedParameter.RefKind) + return; + + // All the lambda parameters must be convertible to the invoked method parameters. + if (!IsIdentityOrImplicitConversion(compilation, lambdaParameter.Type, invokedParameter.Type)) + return; + } + + // Semantically, this looks good to go. Now, do an actual speculative replacement to ensure that the + // non-invoked method reference refers to the same method symbol, and that it converts to the same type that + // the lambda was. + var analyzer = new SpeculationAnalyzer(anonymousFunction, invokedExpression, semanticModel, cancellationToken); + + var rewrittenExpression = analyzer.ReplacedExpression; + var rewrittenSemanticModel = analyzer.SpeculativeSemanticModel; + + var rewrittenSymbolInfo = rewrittenSemanticModel.GetSymbolInfo(rewrittenExpression, cancellationToken); + if (rewrittenSymbolInfo.Symbol is not IMethodSymbol rewrittenMethod || + !invokedMethod.Equals(rewrittenMethod)) + { + return; + } + + var rewrittenConvertedType = rewrittenSemanticModel.GetTypeInfo(rewrittenExpression, cancellationToken).ConvertedType; + if (!lambdaTypeInfo.ConvertedType.Equals(rewrittenConvertedType)) + return; + + if (OverloadsChanged( + semanticModel, anonymousFunction.GetRequiredParent(), + rewrittenSemanticModel, rewrittenExpression.GetRequiredParent(), cancellationToken)) + { + return; + } + + var startReportSpan = TextSpan.FromBounds(anonymousFunction.SpanStart, invokedExpression.SpanStart); + var endReportSpan = TextSpan.FromBounds(invokedExpression.Span.End, anonymousFunction.Span.End); + + context.ReportDiagnostic(DiagnosticHelper.CreateWithLocationTags( + Descriptor, + syntaxTree.GetLocation(startReportSpan), + preference.Notification.Severity, + additionalLocations: ImmutableArray.Create(anonymousFunction.GetLocation()), + additionalUnnecessaryLocations: ImmutableArray.Create( + syntaxTree.GetLocation(startReportSpan), + syntaxTree.GetLocation(endReportSpan)))); + } + + private static bool OverloadsChanged( + SemanticModel semanticModel1, + SyntaxNode? node1, + SemanticModel semanticModel2, + SyntaxNode? node2, + CancellationToken cancellationToken) + { + while (node1 != null && node2 != null) + { + cancellationToken.ThrowIfCancellationRequested(); + + var method1 = semanticModel1.GetSymbolInfo(node1, cancellationToken).Symbol as IMethodSymbol; + var method2 = semanticModel2.GetSymbolInfo(node2, cancellationToken).Symbol as IMethodSymbol; + + if (method1 is null != method2 is null) + return true; + + if (method1 is not null && !method1.Equals(method2, SymbolEqualityComparer.IncludeNullability)) + return true; + + node1 = node1.Parent; + node2 = node2.Parent; + } + + return false; + } + + private static bool IsIdentityOrImplicitConversion(Compilation compilation, ITypeSymbol type1, ITypeSymbol type2) + { + // Dynamic can have an identity conversion between types. But it can have a very different effect on the + // generated code. Do not allow the change if these are not in agreement. + if (type1 is IDynamicTypeSymbol != type2 is IDynamicTypeSymbol) + return false; + + var conversion = compilation.ClassifyConversion(type1, type2); + return conversion.IsIdentityOrImplicitReference(); + } + + private static bool MayHaveSideEffects(ExpressionSyntax expression) + { + // Checks to see if the expression being invoked looks side-effect free. If so, changing from executing + // each time in the lambda to only executing it once could have impact on the program. + + return !expression.DescendantNodesAndSelf().All( + n => n is TypeSyntax or + TypeArgumentListSyntax or + MemberAccessExpressionSyntax or + InstanceExpressionSyntax or + LiteralExpressionSyntax); + } + + private static SeparatedSyntaxList GetParameters(AnonymousFunctionExpressionSyntax expression) + => expression switch + { + AnonymousMethodExpressionSyntax anonymousMethod => anonymousMethod.ParameterList?.Parameters ?? default, + SimpleLambdaExpressionSyntax simpleLambda => SyntaxFactory.SingletonSeparatedList(simpleLambda.Parameter), + ParenthesizedLambdaExpressionSyntax parenthesizedLambda => parenthesizedLambda.ParameterList.Parameters, + _ => throw ExceptionUtilities.UnexpectedValue(expression.Kind()), + }; + + public static bool TryGetAnonymousFunctionInvocation( + AnonymousFunctionExpressionSyntax anonymousFunction, + [NotNullWhen(true)] out InvocationExpressionSyntax? invocation, + out bool wasAwaited) + { + if (anonymousFunction.ExpressionBody != null) + return TryGetInvocation(anonymousFunction.ExpressionBody, out invocation, out wasAwaited); + + if (anonymousFunction.Block != null && anonymousFunction.Block.Statements.Count == 1) + { + var statement = anonymousFunction.Block.Statements[0]; + if (statement is ReturnStatementSyntax { Expression: { } expression }) + return TryGetInvocation(expression, out invocation, out wasAwaited); + + if (statement is ExpressionStatementSyntax expressionStatement) + return TryGetInvocation(expressionStatement.Expression, out invocation, out wasAwaited); + } + + invocation = null; + wasAwaited = false; + return false; + } + + private static bool TryGetInvocation( + ExpressionSyntax expression, + [NotNullWhen(true)] out InvocationExpressionSyntax? invocation, + out bool wasAwaited) + { + wasAwaited = false; + + // if we have either `await Goo()` or `await Goo().ConfigureAwait` then unwrap to get at `Goo()`. + if (expression is AwaitExpressionSyntax awaitExpression) + { + wasAwaited = true; + expression = awaitExpression.Expression; + if (expression is InvocationExpressionSyntax + { + Expression: MemberAccessExpressionSyntax { Name.Identifier.ValueText: nameof(Task.ConfigureAwait), Expression: var underlying } + }) + { + expression = underlying; + } + } + + invocation = expression as InvocationExpressionSyntax; + return invocation != null; + } + } +} diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf index 60ed538229495..4d8bb9ec8187a 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf @@ -67,6 +67,11 @@ Vložená deklarace proměnné + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive Nesprávné umístění direktivy using @@ -122,6 +127,11 @@ Odebrat operátory potlačení + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator Odebrat nepotřebný operátor potlačení diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf index 13a8c62f2c15b..4cc936f9ba175 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf @@ -67,6 +67,11 @@ Inlinevariablendeklaration + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive Die using-Anweisung wurde falsch platziert. @@ -122,6 +127,11 @@ Unterdrückungsoperatoren entfernen + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator Unnötigen Unterdrückungsoperator entfernen diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf index 4555025e70ad8..382ef105eac47 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf @@ -67,6 +67,11 @@ Declaración de variables alineada + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive Directiva using mal colocada @@ -122,6 +127,11 @@ Quitar operadores de supresión + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator Quitar operador de supresión innecesario diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf index a8a5ac05e3c33..2d1104f4cd7ef 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf @@ -67,6 +67,11 @@ Déclaration de variable inline + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive Directive using mal placée @@ -122,6 +127,11 @@ Supprimer les opérateurs de suppression + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator Supprimer l'opérateur de suppression inutile diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf index 0b436eb14578b..9fbaa5533fc67 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf @@ -67,6 +67,11 @@ Dichiarazione di variabile inline + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive Direttiva using in posizione errata @@ -122,6 +127,11 @@ Rimuovi gli operatori di eliminazione + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator Rimuovi l'operatore di eliminazione non necessario diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf index 364e8205812a2..7172a70422f65 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf @@ -67,6 +67,11 @@ インライン変数宣言 + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive using ディレクティブが正しく配置されていません @@ -122,6 +127,11 @@ 抑制演算子の削除 + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator 不要な抑制演算子を削除します diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf index bfb8d961fe9ab..b19b442b7667e 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf @@ -67,6 +67,11 @@ 인라인 변수 선언 + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive 위치가 잘못된 using 지시문 @@ -122,6 +127,11 @@ 비표시 오류(Suppression) 연산자 제거 + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator 불필요한 비표시 오류(Suppression) 연산자 제거 diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf index ecac3f0c4cb4f..f8db95470a852 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf @@ -67,6 +67,11 @@ Deklaracja zmiennej wbudowanej + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive Nieprawidłowo umieszczona dyrektywa using @@ -122,6 +127,11 @@ Usuń operatory pomijania + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator Usuń niepotrzebny operator pomijania diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf index a06f12ba0e9b9..9995149d5537c 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf @@ -67,6 +67,11 @@ Declaração de variável embutida + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive Diretiva using em local incorreto @@ -122,6 +127,11 @@ Remover operadores de supressão + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator Remover operador de supressão desnecessário diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf index fa246d6762cec..b3bac6215c6fd 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf @@ -67,6 +67,11 @@ Объявление встроенной переменной + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive Неправильно расположенная директива using @@ -122,6 +127,11 @@ Удалить операторы подавления + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator Удалить ненужный оператор подавления diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf index bd40af1e443e9..edb1e34ddf413 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf @@ -67,6 +67,11 @@ Satır içi değişken bildirimi + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive Yanlış yerleştirilmiş using yönergesi @@ -122,6 +127,11 @@ Gizleme işleçlerini kaldır + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator Gereksiz gizleme işlecini kaldır diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf index e6d71376160ef..09adb06f36210 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf @@ -67,6 +67,11 @@ 内联变量声明 + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive 错放了 using 指令 @@ -122,6 +127,11 @@ 请删除忽略运算符 + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator 请删除不必要的忽略运算符 diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf index eb0e3857ae079..06eaa74caeefb 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf @@ -67,6 +67,11 @@ 內嵌變數宣告 + + Lambda expression can be removed + Lambda expression can be removed + + Misplaced using directive using 指示詞位置錯誤 @@ -122,6 +127,11 @@ 移除隱藏的運算子 + + Remove unnecessary lambda expression + Remove unnecessary lambda expression + + Remove unnecessary suppression operator 移除隱藏運算子中不需要的運算子 diff --git a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems index d411389a4b28d..720560e502c03 100644 --- a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems +++ b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems @@ -36,6 +36,7 @@ + diff --git a/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryLambdaExpression/CSharpRemoveUnnecessaryLambdaExpressionCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryLambdaExpression/CSharpRemoveUnnecessaryLambdaExpressionCodeFixProvider.cs new file mode 100644 index 0000000000000..d5f32eb3fb2bc --- /dev/null +++ b/src/Analyzers/CSharp/CodeFixes/RemoveUnnecessaryLambdaExpression/CSharpRemoveUnnecessaryLambdaExpressionCodeFixProvider.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryLambdaExpression +{ + using static CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer; + + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveUnnecessaryLambdaExpression), Shared] + internal partial class CSharpRemoveUnnecessaryLambdaExpressionCodeFixProvider : SyntaxEditorBasedCodeFixProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpRemoveUnnecessaryLambdaExpressionCodeFixProvider() + { + } + + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(IDEDiagnosticIds.RemoveUnnecessaryLambdaExpressionDiagnosticId); + + internal sealed override CodeFixCategory CodeFixCategory => CodeFixCategory.CodeStyle; + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + context.RegisterCodeFix( + new MyCodeAction(c => FixAsync(context.Document, context.Diagnostics.First(), c)), + context.Diagnostics); + return Task.CompletedTask; + } + + protected override Task FixAllAsync( + Document document, ImmutableArray diagnostics, + SyntaxEditor editor, CancellationToken cancellationToken) + { + foreach (var diagnostic in diagnostics) + { + var anonymousFunction = diagnostic.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken); + + editor.ReplaceNode(anonymousFunction, + (current, generator) => + { + if (current is AnonymousFunctionExpressionSyntax anonymousFunction && + TryGetAnonymousFunctionInvocation(anonymousFunction, out var invocation, out _)) + { + return invocation.Expression.WithTriviaFrom(current).Parenthesize(); + } + + return current; + }); + } + + return Task.CompletedTask; + } + + private class MyCodeAction : CustomCodeActions.DocumentChangeAction + { + public MyCodeAction(Func> createChangedDocument) + : base(CSharpAnalyzersResources.Remove_unnecessary_lambda_expression, createChangedDocument, nameof(CSharpRemoveUnnecessaryLambdaExpressionCodeFixProvider)) + { + } + } + } +} diff --git a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs index 4f5e6d27eedfe..bf6a967e39024 100644 --- a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs +++ b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs @@ -76,6 +76,7 @@ internal static class EnforceOnBuildValues public const EnforceOnBuild UseImplicitObjectCreation = /*IDE0090*/ EnforceOnBuild.Recommended; public const EnforceOnBuild RemoveRedundantEquality = /*IDE0100*/ EnforceOnBuild.Recommended; public const EnforceOnBuild RemoveUnnecessaryDiscardDesignation = /*IDE0110*/ EnforceOnBuild.Recommended; + public const EnforceOnBuild RemoveUnnecessaryLambdaExpression = /*IDE0200*/ EnforceOnBuild.Recommended; public const EnforceOnBuild InvokeDelegateWithConditionalAccess = /*IDE1005*/ EnforceOnBuild.Recommended; public const EnforceOnBuild NamingRule = /*IDE1006*/ EnforceOnBuild.Recommended; public const EnforceOnBuild MatchFolderAndNamespace = /*IDE0130*/ EnforceOnBuild.Recommended; diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs index 592390e6f38ab..364fb3468a509 100644 --- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs +++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs @@ -169,6 +169,8 @@ internal static class IDEDiagnosticIds public const string UseParameterNullCheckingId = "IDE0190"; + public const string RemoveUnnecessaryLambdaExpressionDiagnosticId = "IDE0200"; + // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; public const string AnalyzerDependencyConflictId = "IDE1002"; diff --git a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs index e84bd13bf549f..94eec78057a11 100644 --- a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs +++ b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs @@ -91,6 +91,7 @@ internal static class PredefinedCodeFixProviderNames public const string RemoveUnnecessaryCast = nameof(RemoveUnnecessaryCast); public const string RemoveUnnecessaryDiscardDesignation = nameof(RemoveUnnecessaryDiscardDesignation); public const string RemoveUnnecessaryImports = nameof(RemoveUnnecessaryImports); + public const string RemoveUnnecessaryLambdaExpression = nameof(RemoveUnnecessaryLambdaExpression); public const string RemoveUnnecessaryParentheses = nameof(RemoveUnnecessaryParentheses); public const string RemoveUnnecessaryPragmaSuppressions = nameof(RemoveUnnecessaryPragmaSuppressions); public const string RemoveUnreachableCode = nameof(RemoveUnreachableCode); diff --git a/src/Compilers/Test/Core/Traits/Traits.cs b/src/Compilers/Test/Core/Traits/Traits.cs index cb0992ea28c49..444e63dd244b1 100644 --- a/src/Compilers/Test/Core/Traits/Traits.cs +++ b/src/Compilers/Test/Core/Traits/Traits.cs @@ -118,7 +118,7 @@ public static class Features public const string CodeActionsInvertIf = "CodeActions.InvertIf"; public const string CodeActionsInvertLogical = "CodeActions.InvertLogical"; public const string CodeActionsInvokeDelegateWithConditionalAccess = "CodeActions.InvokeDelegateWithConditionalAccess"; - public const string CodeActionsLambdaSimplifier = "CodeActions.LambdaSimplifier"; + public const string CodeActionsRemoveUnnecessaryLambdaExpression = "CodeActions.RemoveUnnecessaryLambdaExpression"; public const string CodeActionsMakeFieldReadonly = "CodeActions.MakeFieldReadonly"; public const string CodeActionsMakeLocalFunctionStatic = "CodeActions.MakeLocalFunctionStatic"; public const string CodeActionsMakeMethodAsynchronous = "CodeActions.MakeMethodAsynchronous"; diff --git a/src/EditorFeatures/CSharpTest/CodeActions/LambdaSimplifier/LambdaSimplifierTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/LambdaSimplifier/LambdaSimplifierTests.cs deleted file mode 100644 index 365d272454b37..0000000000000 --- a/src/EditorFeatures/CSharpTest/CodeActions/LambdaSimplifier/LambdaSimplifierTests.cs +++ /dev/null @@ -1,643 +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.CSharp.CodeRefactorings.LambdaSimplifier; -using Microsoft.CodeAnalysis.CSharp.Test.Utilities; -using Microsoft.CodeAnalysis.Test.Utilities; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings.LambdaSimplifier -{ - public class LambdaSimplifierTests : AbstractCSharpCodeActionTest - { - protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) - => new LambdaSimplifierCodeRefactoringProvider(); - - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestFixAll1() - { - await TestInRegularAndScriptAsync( -@"using System; - -class C -{ - void Goo() - { - Bar(s [||]=> Quux(s)); - } - - void Bar(Func f); - string Quux(int i); -}", -@"using System; - -class C -{ - void Goo() - { - Bar(Quux); - } - - void Bar(Func f); - string Quux(int i); -}", - index: 1); - } - - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestFixCoContravariance1() - { - await TestInRegularAndScriptAsync( -@"using System; - -class C -{ - void Goo() - { - Bar(s [||]=> Quux(s)); - } - - void Bar(Func f); - string Quux(object o); -}", -@"using System; - -class C -{ - void Goo() - { - Bar(Quux); - } - - void Bar(Func f); - string Quux(object o); -}", - index: 1); - } - - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestFixCoContravariance2() - { - await TestInRegularAndScriptAsync( -@"using System; - -class C -{ - void Goo() - { - Bar(s [||]=> Quux(s)); - } - - void Bar(Func f); - string Quux(object o); -}", -@"using System; - -class C -{ - void Goo() - { - Bar(Quux); - } - - void Bar(Func f); - string Quux(object o); -}", - index: 1); - } - - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestFixCoContravariance3() - { - await TestMissingInRegularAndScriptAsync( -@"using System; - -class C -{ - void Goo() - { - Bar(s [||]=> Quux(s)); - } - - void Bar(Func f); - object Quux(object o); -}"); - } - - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestFixCoContravariance4() - { - await TestMissingInRegularAndScriptAsync( -@"using System; - -class C -{ - void Goo() - { - Bar(s [||]=> Quux(s)); - } - - void Bar(Func f); - string Quux(string o); -}"); - } - - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestFixCoContravariance5() - { - await TestMissingInRegularAndScriptAsync( -@"using System; - -class C -{ - void Goo() - { - Bar(s [||]=> Quux(s)); - } - - void Bar(Func f); - object Quux(string o); -}"); - } - - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestFixAll2() - { - await TestInRegularAndScriptAsync( -@"using System; - -class C -{ - void Goo() - { - Bar((s1, s2) [||]=> Quux(s1, s2)); - } - - void Bar(Func f); - string Quux(int i, bool b); -}", -@"using System; - -class C -{ - void Goo() - { - Bar(Quux); - } - - void Bar(Func f); - string Quux(int i, bool b); -}", - index: 1); - } - - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestFixAll3() - { - await TestInRegularAndScriptAsync( -@"using System; - -class C -{ - void Goo() - { - Bar((s1, s2) [||]=> { - return Quux(s1, s2); - }); - } - - void Bar(Func f); - string Quux(int i, bool b); -}", -@"using System; - -class C -{ - void Goo() - { - Bar(Quux); - } - - void Bar(Func f); - string Quux(int i, bool b); -}", - index: 1); - } - - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestFixAll4() - { - await TestInRegularAndScriptAsync( -@"using System; - -class C -{ - void Goo() - { - Bar((s1, s2) [||]=> { - return this.Quux(s1, s2); - }); - } - - void Bar(Func f); - string Quux(int i, bool b); -}", -@"using System; - -class C -{ - void Goo() - { - Bar(this.Quux); - } - - void Bar(Func f); - string Quux(int i, bool b); -}", - index: 1); - } - - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestFixOneOrAll() - { - await TestInRegularAndScriptAsync( -@"using System; - -class C -{ - void Goo() - { - Bar(s [||]=> Quux(s)); - Bar(s => Quux(s)); - } - - void Bar(Func f); - string Quux(int i); -}", -@"using System; - -class C -{ - void Goo() - { - Bar(Quux); - Bar(s => Quux(s)); - } - - void Bar(Func f); - string Quux(int i); -}", - index: 0); - - await TestInRegularAndScriptAsync( -@"using System; - -class C -{ - void Goo() - { - Bar(s [||]=> Quux(s)); - Bar(s => Quux(s)); - } - - void Bar(Func f); - string Quux(int i); -}", -@"using System; - -class C -{ - void Goo() - { - Bar(Quux); - Bar(Quux); - } - - void Bar(Func f); - string Quux(int i); -}", - index: 1); - } - - [WorkItem(542562, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542562")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestMissingOnAmbiguity1_CSharp7() - { - await TestMissingInRegularAndScriptAsync( -@"using System; - -class A -{ - static void Goo(T x) where T : class - { - } - - static void Bar(Action x) - { - } - - static void Bar(Action x) - { - } - - static void Main() - { - Bar(x [||]=> Goo(x)); - } -}", parameters: new TestParameters(TestOptions.Regular7)); - } - - [WorkItem(542562, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542562")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestMissingOnAmbiguity1() - { - var code = @" -using System; -class A -{ - static void Goo(T x) where T : class - { - } - - static void Bar(Action x) - { - } - - static void Bar(Action x) - { - } - - static void Main() - { - Bar(x [||]=> Goo(x)); - } -}"; - - var expected = @" -using System; -class A -{ - static void Goo(T x) where T : class - { - } - - static void Bar(Action x) - { - } - - static void Bar(Action x) - { - } - - static void Main() - { - Bar(Goo); - } -}"; - - await TestInRegularAndScriptAsync(code, expected, parseOptions: TestOptions.Regular7_3); - } - - [WorkItem(627092, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/627092")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestMissingOnLambdaWithDynamic_1() - { - await TestMissingInRegularAndScriptAsync( -@"using System; - -class Program -{ - static void Main() - { - C.InvokeGoo(); - } -} - -class C -{ - public static void InvokeGoo() - { - Action goo = (x, y) => [||]C.Goo(x, y); // Simplify lambda expression - goo(1, ""); - } - - static void Goo(object x, object y) - { - Console.WriteLine(""Goo(object x, object y)""); - } - - static void Goo(object x, T y) - { - Console.WriteLine(""Goo(object x, T y)""); - } -}"); - } - - [WorkItem(627092, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/627092")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestMissingOnLambdaWithDynamic_2() - { - await TestMissingInRegularAndScriptAsync( -@"using System; - -class Program -{ - static void Main() - { - C.InvokeGoo(); - } -} - -class Casd -{ - public static void InvokeGoo() - { - Action goo = x => [||]Casd.Goo(x); // Simplify lambda expression - goo(1, ""); - } - - private static void Goo(dynamic x) - { - throw new NotImplementedException(); - } - - static void Goo(object x, object y) - { - Console.WriteLine(""Goo(object x, object y)""); - } - - static void Goo(object x, T y) - { - Console.WriteLine(""Goo(object x, T y)""); - } -}"); - } - - [WorkItem(544625, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544625")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task ParenthesizeIfParseChanges() - { - var code = @" -using System; -class C -{ - static void M() - { - C x = new C(); - int y = 1; - Bar(() [||]=> { return Console.ReadLine(); } < x, y > (1 + 2)); - } - - static void Bar(object a, object b) { } - public static bool operator <(Func y, C x) { return true; } - public static bool operator >(Func y, C x) { return true; } -}"; - - var expected = @" -using System; -class C -{ - static void M() - { - C x = new C(); - int y = 1; - Bar((Console.ReadLine) < x, y > (1 + 2)); - } - - static void Bar(object a, object b) { } - public static bool operator <(Func y, C x) { return true; } - public static bool operator >(Func y, C x) { return true; } -}"; - - await TestInRegularAndScriptAsync(code, expected); - } - - [WorkItem(545856, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545856")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestWarningOnSideEffects() - { - await TestInRegularAndScriptAsync( -@"using System; - -class C -{ - void Main() - { - Func a = () [||]=> new C().ToString(); - } -}", -@"using System; - -class C -{ - void Main() - { - Func a = {|Warning:new C()|}.ToString; - } -}"); - } - - [WorkItem(545994, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545994")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestNonReturnBlockSyntax() - { - await TestInRegularAndScriptAsync( -@"using System; - -class Program -{ - static void Main() - { - Action a = [||]() => { - Console.WriteLine(); - }; - } -}", -@"using System; - -class Program -{ - static void Main() - { - Action a = Console.WriteLine; - } -}"); - } - - [WorkItem(35180, "https://github.com/dotnet/roslyn/issues/35180")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestMissingCaretPositionInside() - { - await TestMissingInRegularAndScriptAsync( -@"using System; - -class Program -{ - static void Main() - { - Action a = () => { - Console.[||]WriteLine(); - }; - } -}"); - } - - [WorkItem(35180, "https://github.com/dotnet/roslyn/issues/35180")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestCaretPositionBeforeBody() - { - await TestInRegularAndScriptAsync( -@"using System; - -class Program -{ - static void Main() - { - Action a = () => [||]Console.WriteLine(); - } -}", -@"using System; - -class Program -{ - static void Main() - { - Action a = Console.WriteLine; - } -}"); - } - - [WorkItem(35180, "https://github.com/dotnet/roslyn/issues/35180")] - [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsLambdaSimplifier)] - public async Task TestCaretPosition() - { - await TestInRegularAndScriptAsync( -@"using System; - -class Program -{ - static void Main() - { - [|Action a = () => { - Console.WriteLine(); - };|] - } -}", -@"using System; - -class Program -{ - static void Main() - { - Action a = Console.WriteLine; - } -}"); - } - } -} diff --git a/src/EditorFeatures/CSharpTest/RemoveUnnecessaryLambdaExpression/RemoveUnnecessaryLambdaExpressionTests.cs b/src/EditorFeatures/CSharpTest/RemoveUnnecessaryLambdaExpression/RemoveUnnecessaryLambdaExpressionTests.cs new file mode 100644 index 0000000000000..f51e59af519b1 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/RemoveUnnecessaryLambdaExpression/RemoveUnnecessaryLambdaExpressionTests.cs @@ -0,0 +1,1494 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryLambdaExpression; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RemoveUnnecessaryLambdaExpression +{ + using VerifyCS = CSharpCodeFixVerifier< + CSharpRemoveUnnecessaryLambdaExpressionDiagnosticAnalyzer, + CSharpRemoveUnnecessaryLambdaExpressionCodeFixProvider>; + + public class RemoveUnnecessaryLambdaExpressionTests + { + private static async Task TestInRegularAndScriptAsync(string testCode, string fixedCode, LanguageVersion version = LanguageVersion.Preview) + { + await new VerifyCS.Test + { + TestCode = testCode, + FixedCode = fixedCode, + LanguageVersion = version, + }.RunAsync(); + } + + private static Task TestMissingInRegularAndScriptAsync(string testCode, LanguageVersion version = LanguageVersion.Preview) + => TestInRegularAndScriptAsync(testCode, testCode, version); + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestMissingInCSharp10() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar(s => Quux(s)); + } + + void Bar(Func f) { } + string Quux(int i) => default; +}", LanguageVersion.CSharp10); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestBasicCase() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar([|s => |]Quux(s)); + } + + void Bar(Func f) { } + string Quux(int i) => default; +}", +@"using System; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + string Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestWithOptionOff() + { + var code = @"using System; + +class C +{ + void Goo() + { + Bar(s => Quux(s)); + } + + void Bar(Func f) { } + string Quux(int i) => default; +}"; + await new VerifyCS.Test + { + TestCode = code, + FixedCode = code, + LanguageVersion = LanguageVersion.Preview, + Options = { { CSharpCodeStyleOptions.PreferMethodGroupConversion, new CodeStyleOption2(false, NotificationOption2.None) } } + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestNotOnStaticLambda() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar(static s => Quux(s)); + } + + void Bar(Func f) { } + static string Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestNotWithOptionalParameter() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar(s => Quux(s)); + } + + void Bar(Func f) { } + static string Quux(int i, int j = 0) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestNotWithParams1() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar(s => Quux(s)); + } + + void Bar(Func f) { } + static string Quux(int i, params int[] j) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestNotWithParams2() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar(s => Quux(s)); + } + + void Bar(Func f) { } + static string Quux(params object[] j) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestWithParams1() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar([|s => |]Quux(s)); + } + + void Bar(Func f) { } + string Quux(params object[] o) => default; +}", +@"using System; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + string Quux(params object[] o) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestNotWithRefChange1() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar(s => Quux(ref s)); + } + + void Bar(Func f) { } + static string Quux(ref int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestNotWithRefChange2() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +delegate string X(ref int i); + +class C +{ + void Goo() + { + Bar((ref int s) => Quux(s)); + } + + void Bar(X x) { } + static string Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestWithSameRef() + { + await TestInRegularAndScriptAsync( +@"using System; + +delegate string X(ref int i); + +class C +{ + void Goo() + { + Bar([|(ref int s) => |]Quux(ref s)); + } + + void Bar(X x) { } + static string Quux(ref int i) => default; +}", + +@"using System; + +delegate string X(ref int i); + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(X x) { } + static string Quux(ref int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestNotOnConversionToObject() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + object o = (int s) => Quux(s); + } + + void Bar(Func f) { } + static string Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestWithParenthesizedLambda() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar([|(int s) => |]Quux(s)); + } + + void Bar(Func f) { } + string Quux(int i) => default; +}", +@"using System; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + string Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestWithAnonymousMethod() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar([|delegate (int s) { return |]Quux(s); }); + } + + void Bar(Func f) { } + string Quux(int i) => default; +}", +@"using System; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + string Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestWithAnonymousMethodNoParameterList() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar([|delegate { return |]Quux(); }); + } + + void Bar(Func f) { } + string Quux() => default; +}", +@"using System; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + string Quux() => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestFixCoContravariance1() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar([|s => |]Quux(s)); + } + + void Bar(Func f) { } + string Quux(object o) => default; +}", +@"using System; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + string Quux(object o) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestFixCoContravariance2() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar([|s => |]Quux(s)); + } + + void Bar(Func f) { } + string Quux(object o) => default; +}", +@"using System; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + string Quux(object o) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestFixCoContravariance3() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar(s => {|CS1662:{|CS0266:Quux(s)|}|}); + } + + void Bar(Func f) { } + object Quux(object o) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestFixCoContravariance4() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar(s => Quux({|CS1503:s|})); + } + + void Bar(Func f) { } + string Quux(string o) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestFixCoContravariance5() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar(s => Quux({|CS1503:s|})); + } + + void Bar(Func f) { } + object Quux(string o) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestTwoArgs() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar([|(s1, s2) => |]Quux(s1, s2)); + } + + void Bar(Func f) { } + string Quux(int i, bool b) => default; +}", +@"using System; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + string Quux(int i, bool b) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestMultipleArgIncorrectPassing1() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar((s1, s2) => Quux(s2, s1)); + } + + void Bar(Func f) { } + string Quux(int i, int b) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestMultipleArgIncorrectPassing2() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar((s1, s2) => Quux(s1, s1)); + } + + void Bar(Func f) { } + string Quux(int i, int b) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestMultipleArgIncorrectPassing3() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar((s1, s2) => Quux(s1, true)); + } + + void Bar(Func f) { } + string Quux(int i, bool b) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestReturnStatement() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar([|(s1, s2) => { + return |]Quux(s1, s2); + }); + } + + void Bar(Func f) { } + string Quux(int i, bool b) => default; +}", +@"using System; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + string Quux(int i, bool b) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestReturnStatement2() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar([|(s1, s2) => { + return |]this.Quux(s1, s2); + }); + } + + void Bar(Func f) { } + string Quux(int i, bool b) => default; +}", +@"using System; + +class C +{ + void Goo() + { + Bar(this.Quux); + } + + void Bar(Func f) { } + string Quux(int i, bool b) => default; +}"); + } + + [WorkItem(542562, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542562")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestMissingOnAmbiguity1() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class A +{ + static void Goo(T x) + { + } + + static void Bar(Action x) + { + } + + static void Bar(Action x) + { + } + + static void Main() + { + {|CS0121:Bar|}(x => Goo(x)); + } +}"); + } + + [WorkItem(542562, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542562")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestWithConstraint1() + { + var code = @" +using System; +class A +{ + static void Goo(T x) where T : class + { + } + + static void Bar(Action x) + { + } + + static void Bar(Action x) + { + } + + static void Main() + { + Bar([|x => |]Goo(x)); + } +}"; + + var expected = @" +using System; +class A +{ + static void Goo(T x) where T : class + { + } + + static void Bar(Action x) + { + } + + static void Bar(Action x) + { + } + + static void Main() + { + Bar(Goo); + } +}"; + await TestInRegularAndScriptAsync(code, expected); + } + + [WorkItem(542562, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542562")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestWithConstraint2() + { + var code = @" +using System; +class A +{ + static void Goo(T x) where T : class + { + } + + static void Bar(Action x) + { + } + + static void Bar(Action x) + { + } + + static void Main() + { + Bar(x => Goo(x)); + } +}"; + await TestMissingInRegularAndScriptAsync(code); + } + + [WorkItem(627092, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/627092")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestMissingOnLambdaWithDynamic_1() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class Program +{ + static void Main() + { + C.InvokeGoo(); + } +} + +class C +{ + public static void InvokeGoo() + { + Action goo = (x, y) => C.Goo(x, y); // Simplify lambda expression + goo(1, """"); + } + + static void Goo(object x, object y) + { + Console.WriteLine(""Goo(object x, object y)""); + } + + static void Goo(object x, T y) + { + Console.WriteLine(""Goo(object x, T y)""); + } +}"); + } + + [WorkItem(627092, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/627092")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestWithLambdaWithDynamic() + { + await TestInRegularAndScriptAsync( +@"using System; + +class Program +{ + static void Main() + { + C.InvokeGoo(); + } +} + +class C +{ + public static void InvokeGoo() + { + Action goo = [|x => |]C.Goo(x); // Simplify lambda expression + goo(1); + } + + private static void Goo(dynamic x) + { + throw new NotImplementedException(); + } + + static void Goo(object x, object y) + { + Console.WriteLine(""Goo(object x, object y)""); + } + + static void Goo(object x, T y) + { + Console.WriteLine(""Goo(object x, T y)""); + } +}", +@"using System; + +class Program +{ + static void Main() + { + C.InvokeGoo(); + } +} + +class C +{ + public static void InvokeGoo() + { + Action goo = C.Goo; // Simplify lambda expression + goo(1); + } + + private static void Goo(dynamic x) + { + throw new NotImplementedException(); + } + + static void Goo(object x, object y) + { + Console.WriteLine(""Goo(object x, object y)""); + } + + static void Goo(object x, T y) + { + Console.WriteLine(""Goo(object x, T y)""); + } +}"); + } + + [WorkItem(544625, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544625")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task ParenthesizeIfParseChanges() + { + var code = @" +using System; +class C +{ + static void M() + { + C x = new C(); + int y = 1; + Bar([|() => { return |]Console.ReadLine(); } < x, y > (1 + 2)); + } + + static void Bar(object a, object b) { } + public static bool operator <(Func y, C x) { return true; } + public static bool operator >(Func y, C x) { return true; } +}"; + + var expected = @" +using System; +class C +{ + static void M() + { + C x = new C(); + int y = 1; + Bar((Console.ReadLine) < x, y > (1 + 2)); + } + + static void Bar(object a, object b) { } + public static bool operator <(Func y, C x) { return true; } + public static bool operator >(Func y, C x) { return true; } +}"; + + await TestInRegularAndScriptAsync(code, expected); + } + + [WorkItem(545856, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545856")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestNotWithSideEffects() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Main() + { + Func a = () => new C().ToString(); + } +}"); + } + + [WorkItem(545994, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/545994")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestExpressionStatement() + { + await TestInRegularAndScriptAsync( +@"using System; + +class Program +{ + static void Main() + { + Action a = [|() => { + |]Console.WriteLine(); + }; + } +}", +@"using System; + +class Program +{ + static void Main() + { + Action a = Console.WriteLine; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestTaskOfT1() + { + await TestInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar([|s => |]Quux(s)); + } + + void Bar(Func> f) { } + Task Quux(int i) => default; +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func> f) { } + Task Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestAsyncTaskOfT1() + { + await TestInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar([|async s => await |]Quux(s)); + } + + void Bar(Func> f) { } + Task Quux(int i) => default; +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func> f) { } + Task Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestAsyncTaskOfT2() + { + await TestInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar([|async s => await |]Quux(s).ConfigureAwait(false)); + } + + void Bar(Func> f) { } + Task Quux(int i) => default; +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func> f) { } + Task Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestAsyncNoAwait1() + { + await TestMissingInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(async s => Quux(s)); + } + + void Bar(Func> f) { } + string Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestTaskOfT1_Return() + { + await TestInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar([|s => { return |]Quux(s); }); + } + + void Bar(Func> f) { } + Task Quux(int i) => default; +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func> f) { } + Task Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestAsyncTaskOfT1_Return() + { + await TestInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar([|async s => { return await |]Quux(s); }); + } + + void Bar(Func> f) { } + Task Quux(int i) => default; +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func> f) { } + Task Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestAsyncTaskOfT2_Return() + { + await TestInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar([|async s => { return await |]Quux(s).ConfigureAwait(false); }); + } + + void Bar(Func> f) { } + Task Quux(int i) => default; +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func> f) { } + Task Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestAsyncNoAwait1_Return() + { + await TestMissingInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(async s => { return Quux(s); }); + } + + void Bar(Func> f) { } + string Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestTask1() + { + await TestInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar([|s => |]Quux(s)); + } + + void Bar(Func f) { } + Task Quux(int i) => default; +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + Task Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestAsyncTask1() + { + await TestInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar([|async s => await |]Quux(s)); + } + + void Bar(Func f) { } + Task Quux(int i) => default; +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + Task Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestAsyncTask2() + { + await TestInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar([|async s => await |]Quux(s).ConfigureAwait(false)); + } + + void Bar(Func f) { } + Task Quux(int i) => default; +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + Task Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestTask1_ExpressionStatement() + { + await TestMissingInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(s {|CS1643:=>|} { Quux(s); }); + } + + void Bar(Func f) { } + Task Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestAsyncTask1_ExpressionStatement() + { + await TestInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar([|async s => { await |]Quux(s); }); + } + + void Bar(Func f) { } + Task Quux(int i) => default; +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + Task Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestAsyncTask2_ExpressionStatement() + { + await TestInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar([|async s => { await |]Quux(s).ConfigureAwait(false); }); + } + + void Bar(Func f) { } + Task Quux(int i) => default; +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(Quux); + } + + void Bar(Func f) { } + Task Quux(int i) => default; +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestAsyncNoAwait1_ExpressionStatement() + { + await TestMissingInRegularAndScriptAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + void Goo() + { + Bar(async s => { Quux(s); }); + } + + void Bar(Func f) { } + void Quux(int i) { } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestExplicitGenericCall() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Action a = [|() => |]Quux(); + } + + void Quux() { } +}", +@"using System; + +class C +{ + void Goo() + { + Action a = Quux; + } + + void Quux() { } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestImplicitGenericCall() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Action a = b => Quux(b); + } + + void Quux(T t) { } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestNullabilityChanges() + { + await TestMissingInRegularAndScriptAsync( +@" +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +class C +{ + void Goo(List assemblies, HashSet usedProjectFileNames) + { + var projectAssemblyFileNames = Select(assemblies, a => GetFileName(a)); + var v = Any(projectAssemblyFileNames, usedProjectFileNames.Contains); + } + + static List Select(List items, Func map) => new(); + + [return: NotNullIfNotNull(""path"")] + static string? GetFileName(string? path) => path; + + static bool Any(List immutableArray, Func predicate) => true; +} + +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] + public sealed class NotNullIfNotNullAttribute : Attribute + { + public string ParameterName => """"; + + public NotNullIfNotNullAttribute(string parameterName) + { + } + } +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryLambdaExpression)] + public async Task TestTrivia1() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + void Goo() + { + Bar(/*before*/[|s => |]Quux(s)/*after*/); + } + + void Bar(Func f) { } + string Quux(int i) => default; +}", +@"using System; + +class C +{ + void Goo() + { + Bar(/*before*/Quux/*after*/); + } + + void Bar(Func f) { } + string Quux(int i) => default; +}"); + } + } +} diff --git a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs index 5d8b95a977999..f565458cb4cc7 100644 --- a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs @@ -432,6 +432,9 @@ public void CSharp_VerifyIDEDiagnosticSeveritiesAreConfigurable() # IDE0190 dotnet_diagnostic.IDE0190.severity = %value% +# IDE0200 +dotnet_diagnostic.IDE0200.severity = %value% + # IDE2000 dotnet_diagnostic.IDE2000.severity = %value% @@ -1016,6 +1019,9 @@ No editorconfig based code style option # IDE0190, PreferParameterNullChecking csharp_style_prefer_parameter_null_checking = true +# IDE0200, PreferMethodGroupConversion +csharp_style_prefer_method_group_conversion = true + # IDE1005, PreferConditionalDelegateCall csharp_style_conditional_delegate_call = true diff --git a/src/Features/CSharp/Portable/CodeRefactorings/LambdaSimplifier/LambdaSimplifierCodeRefactoringProvider.Rewriter.cs b/src/Features/CSharp/Portable/CodeRefactorings/LambdaSimplifier/LambdaSimplifierCodeRefactoringProvider.Rewriter.cs deleted file mode 100644 index 5777b3900fa1f..0000000000000 --- a/src/Features/CSharp/Portable/CodeRefactorings/LambdaSimplifier/LambdaSimplifierCodeRefactoringProvider.Rewriter.cs +++ /dev/null @@ -1,86 +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.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Shared.Utilities; - -namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.LambdaSimplifier -{ - internal partial class LambdaSimplifierCodeRefactoringProvider - { - private class Rewriter : CSharpSyntaxRewriter - { - private readonly SemanticDocument _document; - private readonly Func _predicate; - private readonly CancellationToken _cancellationToken; - - public Rewriter( - SemanticDocument document, - Func predicate, - CancellationToken cancellationToken) - { - _document = document; - _predicate = predicate; - _cancellationToken = cancellationToken; - } - - private ExpressionSyntax SimplifyInvocation(InvocationExpressionSyntax invocation) - { - var expression = invocation.Expression; - if (expression is MemberAccessExpressionSyntax memberAccess) - { - var symbolMap = SemanticMap.From(_document.SemanticModel, memberAccess.Expression, _cancellationToken); - var anySideEffects = symbolMap.AllReferencedSymbols.Any(s => - s.Kind is SymbolKind.Method or SymbolKind.Property); - - if (anySideEffects) - { - var annotation = WarningAnnotation.Create(CSharpFeaturesResources.Warning_Expression_may_change_code_meaning); - expression = expression.ReplaceNode(memberAccess.Expression, memberAccess.Expression.WithAdditionalAnnotations(annotation)); - } - } - - return expression.Parenthesize() - .WithAdditionalAnnotations(Formatter.Annotation); - } - - public override SyntaxNode VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) - { - if (_predicate(node) && CanSimplify(_document, node, _cancellationToken)) - { - var invocation = TryGetInvocationExpression(node.Body); - if (invocation != null) - { - return SimplifyInvocation(invocation); - } - } - - return base.VisitSimpleLambdaExpression(node); - } - - public override SyntaxNode VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node) - { - if (_predicate(node) && CanSimplify(_document, node, _cancellationToken)) - { - var invocation = TryGetInvocationExpression(node.Body); - if (invocation != null) - { - return SimplifyInvocation(invocation); - } - } - - return base.VisitParenthesizedLambdaExpression(node); - } - } - } -} diff --git a/src/Features/CSharp/Portable/CodeRefactorings/LambdaSimplifier/LambdaSimplifierCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/LambdaSimplifier/LambdaSimplifierCodeRefactoringProvider.cs deleted file mode 100644 index 2d672d9b145d7..0000000000000 --- a/src/Features/CSharp/Portable/CodeRefactorings/LambdaSimplifier/LambdaSimplifierCodeRefactoringProvider.cs +++ /dev/null @@ -1,293 +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.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeRefactorings; -using Microsoft.CodeAnalysis.CSharp.Extensions; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.LambdaSimplifier -{ - // Disabled due to: https://github.com/dotnet/roslyn/issues/5835 & https://github.com/dotnet/roslyn/pull/6642 - // [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.SimplifyLambda)] - internal partial class LambdaSimplifierCodeRefactoringProvider : CodeRefactoringProvider - { - public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) - { - var (document, textSpan, cancellationToken) = context; - if (cancellationToken.IsCancellationRequested) - { - return; - } - - if (document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles) - { - return; - } - - var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); - - var lambda = await context.TryGetRelevantNodeAsync().ConfigureAwait(false); - if (lambda == null) - { - return; - } - - if (!CanSimplify(semanticDocument, lambda as SimpleLambdaExpressionSyntax, cancellationToken) && - !CanSimplify(semanticDocument, lambda as ParenthesizedLambdaExpressionSyntax, cancellationToken)) - { - return; - } - - context.RegisterRefactoring( - new MyCodeAction( - CSharpFeaturesResources.Simplify_lambda_expression, - c => SimplifyLambdaAsync(document, lambda, c)), - lambda.Span); - - context.RegisterRefactoring( - new MyCodeAction( - CSharpFeaturesResources.Simplify_all_occurrences, - c => SimplifyAllLambdasAsync(document, c)), - lambda.Span); - } - - private static async Task SimplifyLambdaAsync( - Document document, - SyntaxNode lambda, - CancellationToken cancellationToken) - { - var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); - var rewriter = new Rewriter(semanticDocument, n => n == lambda, cancellationToken); - var result = rewriter.Visit(semanticDocument.Root); - return document.WithSyntaxRoot(result); - } - - private static async Task SimplifyAllLambdasAsync( - Document document, - CancellationToken cancellationToken) - { - var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); - var rewriter = new Rewriter(semanticDocument, n => true, cancellationToken); - var result = rewriter.Visit(semanticDocument.Root); - return document.WithSyntaxRoot(result); - } - - private static bool CanSimplify( - SemanticDocument document, - SimpleLambdaExpressionSyntax node, - CancellationToken cancellationToken) - { - if (node == null) - { - return false; - } - - var paramName = node.Parameter.Identifier; - var invocation = TryGetInvocationExpression(node.Body); - return CanSimplify(document, node, new List() { paramName }, invocation, cancellationToken); - } - - private static bool CanSimplify( - SemanticDocument document, - ParenthesizedLambdaExpressionSyntax node, - CancellationToken cancellationToken) - { - if (node == null) - { - return false; - } - - var paramNames = node.ParameterList.Parameters.Select(p => p.Identifier).ToList(); - var invocation = TryGetInvocationExpression(node.Body); - return CanSimplify(document, node, paramNames, invocation, cancellationToken); - } - - private static bool CanSimplify( - SemanticDocument document, - ExpressionSyntax lambda, - List paramNames, - InvocationExpressionSyntax invocation, - CancellationToken cancellationToken) - { - if (invocation == null) - { - return false; - } - - if (invocation.ArgumentList.Arguments.Count != paramNames.Count) - { - return false; - } - - for (var i = 0; i < paramNames.Count; i++) - { - var argument = invocation.ArgumentList.Arguments[i]; - if (argument.NameColon != null || - argument.RefOrOutKeyword.Kind() != SyntaxKind.None || - !argument.Expression.IsKind(SyntaxKind.IdentifierName, out IdentifierNameSyntax identifierName)) - { - return false; - } - - if (identifierName.Identifier.ValueText != paramNames[i].ValueText) - { - return false; - } - } - - var semanticModel = document.SemanticModel; - var lambdaSemanticInfo = semanticModel.GetSymbolInfo(lambda, cancellationToken); - var invocationSemanticInfo = semanticModel.GetSymbolInfo(invocation, cancellationToken); - if (lambdaSemanticInfo.Symbol == null || - invocationSemanticInfo.Symbol == null) - { - // Don't offer this if there are any errors or ambiguities. - return false; - } - - if (lambdaSemanticInfo.Symbol is not IMethodSymbol lambdaMethod || invocationSemanticInfo.Symbol is not IMethodSymbol invocationMethod) - { - return false; - } - - // TODO(cyrusn): Handle extension methods as well. - if (invocationMethod.IsExtensionMethod) - { - return false; - } - - // Check if any of the parameter is of Type Dynamic - foreach (var parameter in lambdaMethod.Parameters) - { - if (parameter.Type != null && parameter.Type.Kind == SymbolKind.DynamicType) - { - return false; - } - } - - // Check if the parameter and return types match between the lambda and the - // invocation. Note: return types can be covariant and argument types can be - // contravariant. - if (lambdaMethod.ReturnsVoid != invocationMethod.ReturnsVoid || - lambdaMethod.Parameters.Length != invocationMethod.Parameters.Length) - { - return false; - } - - if (!lambdaMethod.ReturnsVoid) - { - // Return type has to be covariant. - var conversion = document.SemanticModel.Compilation.ClassifyConversion( - invocationMethod.ReturnType, lambdaMethod.ReturnType); - if (!conversion.IsIdentityOrImplicitReference()) - { - return false; - } - } - - // Parameter types have to be contravariant. - for (var i = 0; i < lambdaMethod.Parameters.Length; i++) - { - var conversion = document.SemanticModel.Compilation.ClassifyConversion( - lambdaMethod.Parameters[i].Type, invocationMethod.Parameters[i].Type); - - if (!conversion.IsIdentityOrImplicitReference()) - { - return false; - } - } - - if (WouldCauseAmbiguity(lambda, invocation, semanticModel, cancellationToken)) - { - return false; - } - - // Looks like something we can simplify. - return true; - } - - // Ensure that if we replace the invocation with its expression that its expression will - // bind unambiguously. This can happen with awesome cases like: -#if false - static void Goo(T x) where T : class { } - static void Bar(Action x) { } - static void Bar(Action x) { } - static void Main() - { - Bar(x => Goo(x)); // error CS0121: The call is ambiguous between the following methods or properties: 'A.Bar(System.Action)' and 'A.Bar(System.Action)' - } -#endif - private static bool WouldCauseAmbiguity( - ExpressionSyntax lambda, - InvocationExpressionSyntax invocation, - SemanticModel oldSemanticModel, - CancellationToken cancellationToken) - { - var annotation = new SyntaxAnnotation(); - - // In order to check if there will be a problem, we actually make the change, fork the - // compilation, and then verify that the new expression bound unambiguously. - var oldExpression = invocation.Expression.WithAdditionalAnnotations(annotation); - var oldCompilation = oldSemanticModel.Compilation; - var oldTree = oldSemanticModel.SyntaxTree; - var oldRoot = oldTree.GetRoot(cancellationToken); - - var newRoot = oldRoot.ReplaceNode(lambda, oldExpression); - - var newTree = oldTree.WithRootAndOptions(newRoot, oldTree.Options); - - var newCompilation = oldCompilation.ReplaceSyntaxTree(oldTree, newTree); - var newExpression = newTree.GetRoot(cancellationToken).GetAnnotatedNodesAndTokens(annotation).First().AsNode(); - var newSemanticModel = newCompilation.GetSemanticModel(newTree); - - var info = newSemanticModel.GetSymbolInfo(newExpression, cancellationToken); - - return info.CandidateReason != CandidateReason.None; - } - - private static InvocationExpressionSyntax TryGetInvocationExpression( - SyntaxNode lambdaBody) - { - if (lambdaBody is ExpressionSyntax exprBody) - { - return exprBody.WalkDownParentheses() as InvocationExpressionSyntax; - } - else if (lambdaBody is BlockSyntax block) - { - if (block.Statements.Count == 1) - { - var statement = block.Statements.First(); - if (statement is ReturnStatementSyntax returnStatement) - { - return returnStatement.Expression.WalkDownParentheses() as InvocationExpressionSyntax; - } - else if (statement is ExpressionStatementSyntax exprStatement) - { - return exprStatement.Expression.WalkDownParentheses() as InvocationExpressionSyntax; - } - } - } - - return null; - } - - private class MyCodeAction : CodeAction.DocumentChangeAction - { - public MyCodeAction(string title, Func> createChangedDocument) - : base(title, createChangedDocument, title) - { - } - } - } -} diff --git a/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicStructuralTypeDisplayService.vb b/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicStructuralTypeDisplayService.vb index eb15819db5410..b852e1217b408 100644 --- a/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicStructuralTypeDisplayService.vb +++ b/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicStructuralTypeDisplayService.vb @@ -61,8 +61,9 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LanguageServices Return members.ToImmutableAndFree() End Function - Private Shared Function MassageDelegateParts(delegateInvoke As IMethodSymbol, - parts As IEnumerable(Of SymbolDisplayPart)) As IEnumerable(Of SymbolDisplayPart) + Private Shared Function MassageDelegateParts( + delegateInvoke As IMethodSymbol, + parts As IEnumerable(Of SymbolDisplayPart)) As IEnumerable(Of SymbolDisplayPart) ' So ugly. We remove the 'Invoke' name that was added by the symbol display service. Dim result = New List(Of SymbolDisplayPart) For Each part In parts diff --git a/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs b/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs index 5a391c3d78d37..7b370876367e7 100644 --- a/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs +++ b/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs @@ -136,6 +136,10 @@ private IEnumerable GetCodeBlockCodeStyleOptions(AnalyzerConfi valueDescriptions: new[] { CSharpVSResources.Block_scoped, CSharpVSResources.File_scoped }, editorConfigOptions: editorConfigOptions, visualStudioOptions: visualStudioOptions, updater: updaterService, fileName: FileName); + yield return CodeStyleSetting.Create(option: CSharpCodeStyleOptions.PreferMethodGroupConversion, + description: ServicesVSResources.Prefer_method_group_conversion, + editorConfigOptions: editorConfigOptions, + visualStudioOptions: visualStudioOptions, updater: updaterService, fileName: FileName); } private IEnumerable GetExpressionCodeStyleOptions(AnalyzerConfigOptions editorConfigOptions, OptionSet visualStudioOptions, OptionUpdater updaterService) diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs index f4b23b785a1b0..527c4b7de2e52 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs @@ -69,6 +69,12 @@ public string Style_PreferCoalesceExpression set { SetXmlOption(CodeStyleOptions2.PreferCoalesceExpression, value); } } + public string Style_PreferMethodGroupConversion + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferMethodGroupConversion); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferMethodGroupConversion, value); } + } + public string Style_PreferNullPropagation { get { return GetXmlOption(CodeStyleOptions2.PreferNullPropagation); } diff --git a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs index 4a4d360b1675b..57acab1007e6c 100644 --- a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs +++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs @@ -958,6 +958,32 @@ public override int GetHashCode() }} //] }} +"; + + private static readonly string s_preferMethodGroupConversion = $@" +using System; + +class Customer1 +{{ + public void M() + {{ +//[ + // {ServicesVSResources.Prefer_colon} + Action writeObject = Console.Write; +//] + }} +}} +class Customer2 +{{ + public void M() + {{ +//[ + // {ServicesVSResources.Over_colon} + Action writeObject = obj => Console.Write(obj); +//] + }} +//] +}} "; private static readonly string s_preferLocalFunctionOverAnonymousFunction = $@" @@ -2088,6 +2114,7 @@ internal StyleViewModel(OptionStore optionStore, IServiceProvider serviceProvide CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferAutoProperties, ServicesVSResources.analyzer_Prefer_auto_properties, s_preferAutoProperties, s_preferAutoProperties, this, optionStore, codeBlockPreferencesGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferSimpleUsingStatement, ServicesVSResources.Prefer_simple_using_statement, s_preferSimpleUsingStatement, s_preferSimpleUsingStatement, this, optionStore, codeBlockPreferencesGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferSystemHashCode, ServicesVSResources.Prefer_System_HashCode_in_GetHashCode, s_preferSystemHashCode, s_preferSystemHashCode, this, optionStore, codeBlockPreferencesGroupTitle)); + CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferMethodGroupConversion, ServicesVSResources.Prefer_method_group_conversion, s_preferMethodGroupConversion, s_preferMethodGroupConversion, this, optionStore, codeBlockPreferencesGroupTitle)); AddParenthesesOptions(OptionStore); @@ -2111,15 +2138,15 @@ internal StyleViewModel(OptionStore optionStore, IServiceProvider serviceProvide CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferTupleSwap, ServicesVSResources.Prefer_tuple_swap, s_preferTupleSwap, s_preferTupleSwap, this, optionStore, expressionPreferencesGroupTitle)); + AddExpressionBodyOptions(optionStore, expressionPreferencesGroupTitle); + AddUnusedValueOptions(optionStore, expressionPreferencesGroupTitle); + // Pattern matching CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferPatternMatching, CSharpVSResources.Prefer_pattern_matching, s_preferPatternMatching, s_preferPatternMatching, this, optionStore, patternMatchingPreferencesGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferPatternMatchingOverIsWithCastCheck, CSharpVSResources.Prefer_pattern_matching_over_is_with_cast_check, s_preferPatternMatchingOverIsWithCastCheck, s_preferPatternMatchingOverIsWithCastCheck, this, optionStore, patternMatchingPreferencesGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferPatternMatchingOverAsWithNullCheck, CSharpVSResources.Prefer_pattern_matching_over_as_with_null_check, s_preferPatternMatchingOverAsWithNullCheck, s_preferPatternMatchingOverAsWithNullCheck, this, optionStore, patternMatchingPreferencesGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferNotPattern, CSharpVSResources.Prefer_pattern_matching_over_mixed_type_check, s_preferPatternMatchingOverMixedTypeCheck, s_preferPatternMatchingOverMixedTypeCheck, this, optionStore, patternMatchingPreferencesGroupTitle)); - AddExpressionBodyOptions(optionStore, expressionPreferencesGroupTitle); - AddUnusedValueOptions(optionStore, expressionPreferencesGroupTitle); - // Variable preferences CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferInlinedVariableDeclaration, ServicesVSResources.Prefer_inlined_variable_declaration, s_preferInlinedVariableDeclaration, s_preferInlinedVariableDeclaration, this, optionStore, variablePreferencesGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferDeconstructedVariableDeclaration, ServicesVSResources.Prefer_deconstructed_variable_declaration, s_preferDeconstructedVariableDeclaration, s_preferDeconstructedVariableDeclaration, this, optionStore, variablePreferencesGroupTitle)); diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx index 6a1d1178d5642..5e006552b86b9 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.resx +++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx @@ -1,17 +1,17 @@ - @@ -1900,4 +1900,7 @@ Additional information: {1} Automatically open Stack Trace Explorer on focus "Stack Trace Explorer" is a tool window that is owned by the Roslyn package + + Prefer method group conversion + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index fcd981d487234..91aa6b84acfbc 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -992,6 +992,11 @@ Preferovat operátor indexu + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure Preferovat strukturu oboru názvů a shody složek diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index 529459b546735..9214803885316 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -992,6 +992,11 @@ Indexoperator bevorzugen + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure Namespace- und Ordnerübereinstimmungsstruktur bevorzugen diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index 1b36dfb32fe2d..95bdc345a9c42 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -992,6 +992,11 @@ Preferir operador de índice + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure Preferir estructura de coincidencia de espacio de nombres y carpetas diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index bc379136aefad..c2f98dd332ac2 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -992,6 +992,11 @@ Préférer l'opérateur d'index + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure Préférer l’espace de noms et la structure de correspondance de dossier diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index dd280670703ec..7e0d381778a7a 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -992,6 +992,11 @@ Preferisci operatore di indice + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure Preferire la struttura di corrispondenza spazio dei nomi e cartella diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index 2b00fdf122d0c..b3100a936ca46 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -992,6 +992,11 @@ インデックス演算子を優先 + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure 名前空間とフォルダー一致構造を優先する diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index 2b2812a9b0524..1db9539ee2c27 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -992,6 +992,11 @@ 인덱스 연산자 선호 + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure 네임스페이스 및 폴더 일치 구조 선호 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index 0b0dd74607534..a143d4c773a03 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -992,6 +992,11 @@ Preferuj operator indeksowania + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure Preferuj strukturę dopasowania przestrzeni nazw i folderów diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index f52ec924844f0..4197537e2a0d9 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -992,6 +992,11 @@ Preferir operador de índice + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure Prefira espaço de nomes e estrutura de correspondência de pasta diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index 91a2006438203..19f9a6da16b0a 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -992,6 +992,11 @@ Предпочитать оператор index + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure Предпочитать пространство имен и структуру совпадений папок diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index 6830da5ab9ac3..b85e07a628f81 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -992,6 +992,11 @@ Dizin işlecini tercih et + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure Ad alanı ve klasör eşleşme yapısını tercih et diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index 34c3d97eda05d..c2ac3476cd0d5 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -992,6 +992,11 @@ 首选索引运算符 + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure 首选命名空间和文件夹匹配结构 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index 5f3d5d4b33555..4ec614b139bc2 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -992,6 +992,11 @@ 優先使用索引運算子 + + Prefer method group conversion + Prefer method group conversion + + Prefer namespace and folder match structure 優先使用命名空間和資料夾相符結構 diff --git a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb index 0fd050a669a7b..9cd8b5dd4b538 100644 --- a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb +++ b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb @@ -128,6 +128,7 @@ csharp_preferred_modifier_order = public,private,protected,internal,static,exter csharp_prefer_braces = true csharp_prefer_simple_using_statement = true csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true # Expression-level preferences csharp_prefer_simple_default_expression = true @@ -362,6 +363,7 @@ csharp_preferred_modifier_order = public,private,protected,internal,static,exter csharp_prefer_braces = true csharp_prefer_simple_using_statement = true csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true # Expression-level preferences csharp_prefer_simple_default_expression = true diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs index a9430fceb2571..0eda8223d9e16 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs @@ -339,6 +339,12 @@ private static Option2> CreateN new(NamespaceDeclarationPreference.BlockScoped, NotificationOption2.Silent), "csharp_style_namespace_declarations"); + public static readonly Option2> PreferMethodGroupConversion = CreateOption( + CSharpCodeStyleOptionGroups.CodeBlockPreferences, nameof(PreferMethodGroupConversion), + defaultValue: s_trueWithSilentEnforcement, + "csharp_style_prefer_method_group_conversion", + "TextEditor.CSharp.Specific.PreferMethodGroupConversion"); + #if false public static readonly Option2> VarElsewhere = CreateOption( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/LanguageVersionExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/LanguageVersionExtensions.cs index b84c4abd638c9..3b81b9c6ed3d5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/LanguageVersionExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/LanguageVersionExtensions.cs @@ -6,6 +6,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Shared.Extensions { internal static class LanguageVersionExtensions { + public static bool IsCSharp11OrAbove(this LanguageVersion languageVersion) + => languageVersion >= LanguageVersion.Preview; + public static bool HasConstantInterpolatedStrings(this LanguageVersion languageVersion) => languageVersion >= LanguageVersion.CSharp10; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs index 3391a170fdc94..9c0544538f439 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SemanticFacts/CSharpSemanticFacts.cs @@ -355,5 +355,8 @@ public ImmutableArray GetLocalFunctionSymbols(Compilation compila return builder.ToImmutable(); } + + public bool IsInExpressionTree(SemanticModel semanticModel, SyntaxNode node, INamedTypeSymbol expressionTypeOpt, CancellationToken cancellationToken) + => node.IsInExpressionTree(semanticModel, expressionTypeOpt, cancellationToken); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SemanticFacts/ISemanticFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SemanticFacts/ISemanticFacts.cs index 68e7015031163..b02ce80d860fa 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SemanticFacts/ISemanticFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SemanticFacts/ISemanticFacts.cs @@ -99,5 +99,7 @@ internal partial interface ISemanticFacts /// Finds all local function definitions within the syntax references for a given /// ImmutableArray GetLocalFunctionSymbols(Compilation compilation, ISymbol symbol, CancellationToken cancellationToken); + + bool IsInExpressionTree(SemanticModel semanticModel, SyntaxNode node, INamedTypeSymbol expressionTypeOpt, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SemanticFacts/VisualBasicSemanticFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SemanticFacts/VisualBasicSemanticFacts.vb index ddc82bff9c78e..792810f70c4ea 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SemanticFacts/VisualBasicSemanticFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SemanticFacts/VisualBasicSemanticFacts.vb @@ -242,5 +242,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Public Function GetLocalFunctionSymbols(compilation As Compilation, symbol As ISymbol, cancellationToken As CancellationToken) As ImmutableArray(Of IMethodSymbol) Implements ISemanticFacts.GetLocalFunctionSymbols Return ImmutableArray(Of IMethodSymbol).Empty End Function + + Public Function IsInExpressionTree(semanticModel As SemanticModel, node As SyntaxNode, expressionTypeOpt As INamedTypeSymbol, cancellationToken As CancellationToken) As Boolean Implements ISemanticFacts.IsInExpressionTree + Return node.IsInExpressionTree(semanticModel, expressionTypeOpt, cancellationToken) + End Function End Class End Namespace diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSemanticFactsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSemanticFactsService.cs index 0665a2f2b99eb..9e0299091bcf9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSemanticFactsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpSemanticFactsService.cs @@ -75,9 +75,6 @@ public bool IsExpressionContext(SemanticModel semanticModel, int position, Cance attributes: true, cancellationToken: cancellationToken, semanticModelOpt: semanticModel); } - public bool IsInExpressionTree(SemanticModel semanticModel, SyntaxNode node, INamedTypeSymbol expressionTypeOpt, CancellationToken cancellationToken) - => node.IsInExpressionTree(semanticModel, expressionTypeOpt, cancellationToken); - public bool IsStatementContext(SemanticModel semanticModel, int position, CancellationToken cancellationToken) { return semanticModel.SyntaxTree.IsStatementContext( diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs index d827973246a5b..3a4125bd00b3e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/AbstractSemanticFactsService.cs @@ -162,6 +162,10 @@ public bool IsInsideNameOfExpression(SemanticModel semanticModel, SyntaxNode nod public ImmutableArray GetLocalFunctionSymbols(Compilation compilation, ISymbol symbol, CancellationToken cancellationToken) => SemanticFacts.GetLocalFunctionSymbols(compilation, symbol, cancellationToken); + + public bool IsInExpressionTree(SemanticModel semanticModel, SyntaxNode node, INamedTypeSymbol expressionTypeOpt, CancellationToken cancellationToken) + => SemanticFacts.IsInExpressionTree(semanticModel, node, expressionTypeOpt, cancellationToken); + #endregion } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs index 04ec70a31cda9..97260b5d15584 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/LanguageServices/SemanticsFactsService/ISemanticFactsService.cs @@ -25,8 +25,6 @@ internal partial interface ISemanticFactsService : ISemanticFacts, ILanguageServ bool IsLabelContext(SemanticModel semanticModel, int position, CancellationToken cancellationToken); bool IsAttributeNameContext(SemanticModel semanticModel, int position, CancellationToken cancellationToken); - bool IsInExpressionTree(SemanticModel semanticModel, SyntaxNode node, INamedTypeSymbol expressionTypeOpt, CancellationToken cancellationToken); - SyntaxToken GenerateUniqueName( SemanticModel semanticModel, SyntaxNode location, SyntaxNode containerOpt, string baseName, CancellationToken cancellationToken); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSemanticFactsService.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSemanticFactsService.vb index 467065ec27d6b..5bee2fee605dc 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSemanticFactsService.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/LanguageServices/VisualBasicSemanticFactsService.vb @@ -50,10 +50,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return semanticModel.SyntaxTree.IsExpressionContext(position, token, cancellationToken, semanticModel) End Function - Public Function IsInExpressionTree(semanticModel As SemanticModel, node As SyntaxNode, expressionTypeOpt As INamedTypeSymbol, cancellationToken As CancellationToken) As Boolean Implements ISemanticFactsService.IsInExpressionTree - Return node.IsInExpressionTree(semanticModel, expressionTypeOpt, cancellationToken) - End Function - Public Function IsMemberDeclarationContext(semanticModel As SemanticModel, position As Integer, cancellationToken As CancellationToken) As Boolean Implements ISemanticFactsService.IsMemberDeclarationContext Dim token = semanticModel.SyntaxTree.GetTargetToken(position, cancellationToken) Return semanticModel.SyntaxTree.IsInterfaceMemberDeclarationKeywordContext(position, token, cancellationToken) OrElse