提交 8fc80a87 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #20912 from CyrusNajmabadi/useLocalFunction

Add an analyzer that offers to convert from a lambda to a local function.
......@@ -106,6 +106,7 @@ public static class Features
public const string CodeActionsUseExplicitType = "CodeActions.UseExplicitType";
public const string CodeActionsUseExplicitTupleName = "CodeActions.UseExplicitTupleName";
public const string CodeActionsUseFrameworkType = "CodeActions.UseFrameworkType";
public const string CodeActionsUseLocalFunction = "CodeActions.UseLocalFunction";
public const string CodeActionsUseNullPropagation = "CodeActions.UseNullPropagation";
public const string CodeActionsUseNamedArguments = "CodeActions.UseNamedArguments";
public const string CodeActionsUseObjectInitializer = "CodeActions.UseObjectInitializer";
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
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.Formatting;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.UseLocalFunction
{
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
internal class CSharpUseLocalFunctionCodeFixProvider : SyntaxEditorBasedCodeFixProvider
{
private static TypeSyntax s_voidType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword));
private static TypeSyntax s_objectType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword));
public override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(IDEDiagnosticIds.UseLocalFunctionDiagnosticId);
protected override bool IncludeDiagnosticDuringFixAll(Diagnostic diagnostic)
=> diagnostic.Severity != DiagnosticSeverity.Hidden;
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
context.RegisterCodeFix(
new MyCodeAction(c => FixAsync(context.Document, context.Diagnostics.First(), c)),
context.Diagnostics);
return SpecializedTasks.EmptyTask;
}
protected override async Task FixAllAsync(
Document document, ImmutableArray<Diagnostic> diagnostics,
SyntaxEditor editor, CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var localDeclarationToLambda = new Dictionary<LocalDeclarationStatementSyntax, LambdaExpressionSyntax>();
var nodesToTrack = new HashSet<SyntaxNode>();
foreach (var diagnostic in diagnostics)
{
var localDeclaration = (LocalDeclarationStatementSyntax)diagnostic.AdditionalLocations[0].FindNode(cancellationToken);
var lambda = (LambdaExpressionSyntax)diagnostic.AdditionalLocations[1].FindNode(cancellationToken);
localDeclarationToLambda[localDeclaration] = lambda;
nodesToTrack.Add(localDeclaration);
nodesToTrack.Add(lambda);
}
var root = editor.OriginalRoot;
var currentRoot = root.TrackNodes(nodesToTrack);
// Process declarations in reverse order so that we see the effects of nested
// declarations befor processing the outer decls.
foreach (var (originalLocalDeclaration, originalLambda) in localDeclarationToLambda.OrderByDescending(kvp => kvp.Value.SpanStart))
{
var delegateType = (INamedTypeSymbol)semanticModel.GetTypeInfo(originalLambda, cancellationToken).ConvertedType;
var parameterList = GenerateParameterList(semanticModel, originalLambda, cancellationToken);
var currentLocalDeclaration = currentRoot.GetCurrentNode(originalLocalDeclaration);
var currentLambda = currentRoot.GetCurrentNode(originalLambda);
currentRoot = ReplaceAnonymousWithLocalFunction(
document.Project.Solution.Workspace, currentRoot,
currentLocalDeclaration, currentLambda,
delegateType, parameterList,
cancellationToken);
}
editor.ReplaceNode(root, currentRoot);
}
private SyntaxNode ReplaceAnonymousWithLocalFunction(
Workspace workspace, SyntaxNode currentRoot,
LocalDeclarationStatementSyntax localDeclaration, LambdaExpressionSyntax lambda,
INamedTypeSymbol delegateType, ParameterListSyntax parameterList,
CancellationToken cancellationToken)
{
var newLocalFunctionStatement = CreateLocalFunctionStatement(
localDeclaration, lambda, delegateType, parameterList, cancellationToken);
newLocalFunctionStatement = newLocalFunctionStatement.WithTriviaFrom(localDeclaration)
.WithAdditionalAnnotations(Formatter.Annotation);
var editor = new SyntaxEditor(currentRoot, workspace);
editor.ReplaceNode(localDeclaration, newLocalFunctionStatement);
var lambdaStatement = lambda.GetAncestor<StatementSyntax>();
if (lambdaStatement != localDeclaration)
{
// This is the split decl+init form. Remove the second statement as we're
// merging into the first one.
editor.RemoveNode(lambdaStatement);
}
return editor.GetChangedRoot();
}
private LocalFunctionStatementSyntax CreateLocalFunctionStatement(
LocalDeclarationStatementSyntax localDeclaration,
LambdaExpressionSyntax lambda,
INamedTypeSymbol delegateType,
ParameterListSyntax parameterList,
CancellationToken cancellationToken)
{
var modifiers = lambda.AsyncKeyword.IsKind(SyntaxKind.AsyncKeyword)
? new SyntaxTokenList(lambda.AsyncKeyword)
: default;
var invokeMethod = delegateType.DelegateInvokeMethod;
var returnType = invokeMethod.ReturnsVoid
? s_voidType
: invokeMethod.ReturnType.GenerateTypeSyntax();
var identifier = localDeclaration.Declaration.Variables[0].Identifier;
var typeParameterList = default(TypeParameterListSyntax);
var constraintClauses = default(SyntaxList<TypeParameterConstraintClauseSyntax>);
var body = lambda.Body.IsKind(SyntaxKind.Block)
? (BlockSyntax)lambda.Body
: null;
var expressionBody = lambda.Body is ExpressionSyntax expression
? SyntaxFactory.ArrowExpressionClause(lambda.ArrowToken, expression)
: null;
var semicolonToken = lambda.Body is ExpressionSyntax
? localDeclaration.SemicolonToken
: default;
return SyntaxFactory.LocalFunctionStatement(
modifiers, returnType, identifier, typeParameterList, parameterList,
constraintClauses, body, expressionBody, semicolonToken);
}
private ParameterListSyntax GenerateParameterList(
SemanticModel semanticModel, AnonymousFunctionExpressionSyntax anonymousFunction, CancellationToken cancellationToken)
{
switch (anonymousFunction)
{
case SimpleLambdaExpressionSyntax simpleLambda:
return GenerateSimpleLambdaParameterList(semanticModel, simpleLambda, cancellationToken);
case ParenthesizedLambdaExpressionSyntax parenthesizedLambda:
return GenerateParenthesizedLambdaParameterList(semanticModel, parenthesizedLambda, cancellationToken);
default:
throw ExceptionUtilities.UnexpectedValue(anonymousFunction);
}
}
private ParameterListSyntax GenerateSimpleLambdaParameterList(
SemanticModel semanticModel, SimpleLambdaExpressionSyntax lambdaExpression, CancellationToken cancellationToken)
{
var parameter = semanticModel.GetDeclaredSymbol(lambdaExpression.Parameter, cancellationToken);
var type = parameter?.Type.GenerateTypeSyntax() ?? s_objectType;
return SyntaxFactory.ParameterList(
SyntaxFactory.SeparatedList<ParameterSyntax>().Add(
SyntaxFactory.Parameter(lambdaExpression.Parameter.Identifier).WithType(type)));
}
private ParameterListSyntax GenerateParenthesizedLambdaParameterList(
SemanticModel semanticModel, ParenthesizedLambdaExpressionSyntax lambdaExpression, CancellationToken cancellationToken)
{
return lambdaExpression.ParameterList.ReplaceNodes(
lambdaExpression.ParameterList.Parameters,
(parameterNode, _) =>
{
if (parameterNode.Type != null)
{
return parameterNode;
}
var parameter = semanticModel.GetDeclaredSymbol(parameterNode, cancellationToken);
return parameterNode.WithType(parameter?.Type.GenerateTypeSyntax() ?? s_objectType);
});
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
: base(FeaturesResources.Use_local_function, createChangedDocument, FeaturesResources.Use_local_function)
{
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.CSharp.UseLocalFunction
{
/// <summary>
/// Looks for code of the form:
///
/// Func&lt;int, int&gt; fib = n =>
/// {
/// if (n &lt;= 2)
/// return 1
///
/// return fib(n - 1) + fib(n - 2);
/// }
///
/// and converts it to:
///
/// int fib(int n)
/// {
/// if (n &lt;= 2)
/// return 1
///
/// return fib(n - 1) + fib(n - 2);
/// }
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class CSharpUseLocalFunctionDiagnosticAnalyzer : AbstractCodeStyleDiagnosticAnalyzer
{
public override bool OpenFileOnly(Workspace workspace) => false;
public CSharpUseLocalFunctionDiagnosticAnalyzer()
: base(IDEDiagnosticIds.UseLocalFunctionDiagnosticId,
new LocalizableResourceString(
nameof(FeaturesResources.Use_local_function), FeaturesResources.ResourceManager, typeof(FeaturesResources)))
{
}
protected override void InitializeWorker(AnalysisContext context)
=> context.RegisterSyntaxNodeAction(SyntaxNodeAction, SyntaxKind.SimpleLambdaExpression, SyntaxKind.ParenthesizedLambdaExpression);
private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext)
{
var options = syntaxContext.Options;
var syntaxTree = syntaxContext.Node.SyntaxTree;
var cancellationToken = syntaxContext.CancellationToken;
var optionSet = options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult();
if (optionSet == null)
{
return;
}
var styleOption = optionSet.GetOption(CSharpCodeStyleOptions.PreferLocalOverAnonymousFunction);
if (!styleOption.Value)
{
// Bail immediately if the user has disabled this feature.
return;
}
// Local functions are only available in C# 7.0 and above. Don't offer this refactoring
// in projects targetting a lesser version.
if (((CSharpParseOptions)syntaxTree.Options).LanguageVersion < LanguageVersion.CSharp7)
{
return;
}
var severity = styleOption.Notification.Value;
var anonymousFunction = (AnonymousFunctionExpressionSyntax)syntaxContext.Node;
var semanticModel = syntaxContext.SemanticModel;
if (!CheckForPattern(semanticModel, anonymousFunction, cancellationToken,
out var localDeclaration))
{
return;
}
if (localDeclaration.Declaration.Variables.Count != 1)
{
return;
}
if (!(localDeclaration.Parent is BlockSyntax block))
{
return;
}
var local = semanticModel.GetDeclaredSymbol(localDeclaration.Declaration.Variables[0], cancellationToken);
if (local == null)
{
return;
}
if (IsWrittenAfter(semanticModel, local, block, anonymousFunction, cancellationToken))
{
return;
}
var delegateType = semanticModel.GetTypeInfo(anonymousFunction, cancellationToken).ConvertedType as INamedTypeSymbol;
if (!delegateType.IsDelegateType() ||
delegateType.DelegateInvokeMethod == null)
{
return;
}
// Looks good!
var additionalLocations = ImmutableArray.Create(
localDeclaration.GetLocation(),
anonymousFunction.GetLocation());
if (severity != DiagnosticSeverity.Hidden)
{
// If the diagnostic is not hidden, then just place the user visible part
// on the local being initialized with the lambda.
syntaxContext.ReportDiagnostic(Diagnostic.Create(
GetDescriptorWithSeverity(severity),
localDeclaration.Declaration.Variables[0].Identifier.GetLocation(),
additionalLocations));
}
else
{
// If the diagnostic is hidden, place it on the entire construct.
syntaxContext.ReportDiagnostic(Diagnostic.Create(
GetDescriptorWithSeverity(severity),
localDeclaration.GetLocation(),
additionalLocations));
var anonymousFunctionStatement = anonymousFunction.GetAncestor<StatementSyntax>();
if (localDeclaration != anonymousFunctionStatement)
{
syntaxContext.ReportDiagnostic(Diagnostic.Create(
GetDescriptorWithSeverity(severity),
anonymousFunctionStatement.GetLocation(),
additionalLocations));
}
}
}
private bool CheckForPattern(
SemanticModel semanticModel,
AnonymousFunctionExpressionSyntax anonymousFunction,
CancellationToken cancellationToken,
out LocalDeclarationStatementSyntax localDeclaration)
{
// Look for:
//
// Type t = <anonymous function>
// var t = (Type)(<anonymous function>)
//
// Type t = null;
// t = <anonymous function>
return CheckForSimpleLocalDeclarationPattern(semanticModel, anonymousFunction, cancellationToken, out localDeclaration) ||
CheckForCastedLocalDeclarationPattern(semanticModel, anonymousFunction, cancellationToken, out localDeclaration) ||
CheckForLocalDeclarationAndAssignment(semanticModel, anonymousFunction, cancellationToken, out localDeclaration);
}
private bool CheckForSimpleLocalDeclarationPattern(
SemanticModel semanticModel,
AnonymousFunctionExpressionSyntax anonymousFunction,
CancellationToken cancellationToken,
out LocalDeclarationStatementSyntax localDeclaration)
{
// Type t = <anonymous function>
if (anonymousFunction.IsParentKind(SyntaxKind.EqualsValueClause) &&
anonymousFunction.Parent.IsParentKind(SyntaxKind.VariableDeclarator) &&
anonymousFunction.Parent.Parent.IsParentKind(SyntaxKind.VariableDeclaration) &&
anonymousFunction.Parent.Parent.Parent.IsParentKind(SyntaxKind.LocalDeclarationStatement))
{
localDeclaration = (LocalDeclarationStatementSyntax)anonymousFunction.Parent.Parent.Parent.Parent;
if (!localDeclaration.Declaration.Type.IsVar)
{
return true;
}
}
localDeclaration = null;
return false;
}
private bool IsWrittenAfter(
SemanticModel semanticModel, ISymbol local, BlockSyntax block,
AnonymousFunctionExpressionSyntax anonymousFunction, CancellationToken cancellationToken)
{
var anonymousFunctionStart = anonymousFunction.SpanStart;
foreach (var descendentNode in block.DescendantNodes())
{
var descendentStart = descendentNode.Span.Start;
if (descendentStart <= anonymousFunctionStart)
{
// This node is before the local declaration. Can ignore it entirely as it could
// not be an access to the local.
continue;
}
if (descendentNode.IsKind(SyntaxKind.IdentifierName))
{
var identifierName = (IdentifierNameSyntax)descendentNode;
if (identifierName.Identifier.ValueText == local.Name &&
identifierName.IsWrittenTo() &&
local.Equals(semanticModel.GetSymbolInfo(identifierName, cancellationToken).GetAnySymbol()))
{
return true;
}
}
}
return false;
}
private bool CheckForCastedLocalDeclarationPattern(
SemanticModel semanticModel,
AnonymousFunctionExpressionSyntax anonymousFunction,
CancellationToken cancellationToken,
out LocalDeclarationStatementSyntax localDeclaration)
{
// var t = (Type)(<anonymous function>)
var containingStatement = anonymousFunction.GetAncestor<StatementSyntax>();
if (containingStatement.IsKind(SyntaxKind.LocalDeclarationStatement))
{
localDeclaration = (LocalDeclarationStatementSyntax)containingStatement;
if (localDeclaration.Declaration.Variables.Count == 1)
{
var variableDeclarator = localDeclaration.Declaration.Variables[0];
if (variableDeclarator.Initializer != null)
{
var value = variableDeclarator.Initializer.Value.WalkDownParentheses();
if (value is CastExpressionSyntax castExpression)
{
if (castExpression.Expression.WalkDownParentheses() == anonymousFunction)
{
return true;
}
}
}
}
}
localDeclaration = null;
return false;
}
private bool CheckForLocalDeclarationAndAssignment(
SemanticModel semanticModel,
AnonymousFunctionExpressionSyntax anonymousFunction,
CancellationToken cancellationToken,
out LocalDeclarationStatementSyntax localDeclaration)
{
// Type t = null;
// t = <anonymous function>
if (anonymousFunction.IsParentKind(SyntaxKind.SimpleAssignmentExpression) &&
anonymousFunction.Parent.IsParentKind(SyntaxKind.ExpressionStatement) &&
anonymousFunction.Parent.Parent.IsParentKind(SyntaxKind.Block))
{
var assignment = (AssignmentExpressionSyntax)anonymousFunction.Parent;
if (assignment.Left.IsKind(SyntaxKind.IdentifierName))
{
var expressionStatement = (ExpressionStatementSyntax)assignment.Parent;
var block = (BlockSyntax)expressionStatement.Parent;
var expressionStatementIndex = block.Statements.IndexOf(expressionStatement);
if (expressionStatementIndex >= 1)
{
var previousStatement = block.Statements[expressionStatementIndex - 1];
if (previousStatement.IsKind(SyntaxKind.LocalDeclarationStatement))
{
localDeclaration = (LocalDeclarationStatementSyntax)previousStatement;
if (localDeclaration.Declaration.Variables.Count == 1)
{
var variableDeclarator = localDeclaration.Declaration.Variables[0];
if (variableDeclarator.Initializer != null)
{
var value = variableDeclarator.Initializer.Value;
if (value.IsKind(SyntaxKind.NullLiteralExpression) ||
value.IsKind(SyntaxKind.DefaultLiteralExpression) ||
value.IsKind(SyntaxKind.DefaultExpression))
{
var identifierName = (IdentifierNameSyntax)assignment.Left;
if (variableDeclarator.Identifier.ValueText == identifierName.Identifier.ValueText)
{
return true;
}
}
}
}
}
}
}
}
localDeclaration = null;
return false;
}
public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> DiagnosticAnalyzerCategory.SemanticDocumentAnalysis;
}
}
......@@ -54,6 +54,7 @@ internal static class IDEDiagnosticIds
public const string InlineIsTypeWithoutNameCheckId = "IDE0038";
public const string UseLocalFunctionDiagnosticId = "IDE0039";
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
......
......@@ -3453,6 +3453,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Use local function.
/// </summary>
internal static string Use_local_function {
get {
return ResourceManager.GetString("Use_local_function", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use local version &apos;{0}&apos;.
/// </summary>
......
......@@ -1244,7 +1244,7 @@ This version used in: {2}</value>
<data name="default_expression_can_be_simplified" xml:space="preserve">
<value>'default' expression can be simplified</value>
</data>
<data name="Use_inferred_member_name" xml:space="preserve">
<data name="Use_inferred_member_name" xml:space="preserve">
<value>Use inferred member name</value>
</data>
<data name="Member_name_can_be_simplified" xml:space="preserve">
......@@ -1271,6 +1271,9 @@ This version used in: {2}</value>
<data name="in_0_project_1" xml:space="preserve">
<value>in {0} (project {1})</value>
</data>
<data name="Use_local_function" xml:space="preserve">
<value>Use local function</value>
</data>
<data name="Warning_colon_Declaration_changes_scope_and_may_change_meaning" xml:space="preserve">
<value>Warning: Declaration changes scope and may change meaning.</value>
</data>
......
......@@ -523,6 +523,31 @@ public int GetAge()
//]
}}
}}
";
private static readonly string s_preferLocalFunctionOverAnonymousFunction = $@"
using System;
class Customer
{{
public Customer(string value)
{{
//[
// {ServicesVSResources.Prefer_colon}
int fibonacci(int n)
{{
return n <= 1 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
}}
// {ServicesVSResources.Over_colon}
Func<int, int> fibonacci = null;
fibonacci = (int n) =>
{{
return n <= 1 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
}};
//]
}}
}}
";
private const string s_preferExpressionBodyForMethods = @"
......@@ -747,6 +772,7 @@ internal StyleViewModel(OptionSet optionSet, IServiceProvider serviceProvider) :
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferSimpleDefaultExpression, ServicesVSResources.Prefer_simple_default_expression, s_preferSimpleDefaultExpression, s_preferSimpleDefaultExpression, this, optionSet, expressionPreferencesGroupTitle));
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferInferredTupleNames, ServicesVSResources.Prefer_inferred_tuple_names, s_preferInferredTupleName, s_preferInferredTupleName, this, optionSet, expressionPreferencesGroupTitle));
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferInferredAnonymousTypeMemberNames, ServicesVSResources.Prefer_inferred_anonymous_type_member_names, s_preferInferredAnonymousTypeMemberName, s_preferInferredAnonymousTypeMemberName, this, optionSet, expressionPreferencesGroupTitle));
CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferLocalOverAnonymousFunction, ServicesVSResources.Prefer_local_function_over_anonymous_function, s_preferLocalFunctionOverAnonymousFunction, s_preferLocalFunctionOverAnonymousFunction, this, optionSet, expressionPreferencesGroupTitle));
AddExpressionBodyOptions(optionSet, expressionPreferencesGroupTitle);
......
......@@ -1639,6 +1639,15 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Prefere local function over anonymous function.
/// </summary>
internal static string Prefer_local_function_over_anonymous_function {
get {
return ResourceManager.GetString("Prefer_local_function_over_anonymous_function", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prefer null propagation.
/// </summary>
......
......@@ -959,4 +959,7 @@ Additional information: {1}</value>
<data name="ChangesNotAllowedWhileCodeIsRunning" xml:space="preserve">
<value>Changes are not allowed while code is running.</value>
</data>
<data name="Prefer_local_function_over_anonymous_function" xml:space="preserve">
<value>Prefere local function over anonymous function</value>
</data>
</root>
\ No newline at end of file
......@@ -174,6 +174,12 @@ internal static class CSharpCodeStyleOptions
EditorConfigStorageLocation.ForStringCodeStyleOption("csharp_preferred_modifier_order"),
new RoamingProfileStorageLocation($"TextEditor.CSharp.Specific.{nameof(PreferredModifierOrder)}")});
public static readonly Option<CodeStyleOption<bool>> PreferLocalOverAnonymousFunction = new Option<CodeStyleOption<bool>>(
nameof(CodeStyleOptions), nameof(PreferLocalOverAnonymousFunction), defaultValue: CodeStyleOptions.TrueWithSuggestionEnforcement,
storageLocations: new OptionStorageLocation[] {
EditorConfigStorageLocation.ForBoolCodeStyleOption("csharp_style_pattern_local_over_anonymous_function"),
new RoamingProfileStorageLocation($"TextEditor.CSharp.Specific.{nameof(PreferLocalOverAnonymousFunction)}")});
public static IEnumerable<Option<CodeStyleOption<bool>>> GetCodeStyleOptions()
{
yield return UseImplicitTypeForIntrinsicTypes;
......@@ -186,6 +192,7 @@ public static IEnumerable<Option<CodeStyleOption<bool>>> GetCodeStyleOptions()
yield return PreferSimpleDefaultExpression;
yield return PreferInferredTupleNames;
yield return PreferInferredAnonymousTypeMemberNames;
yield return PreferLocalOverAnonymousFunction;
}
public static IEnumerable<Option<CodeStyleOption<ExpressionBodyPreference>>> GetExpressionBodyOptions()
......
......@@ -47,7 +47,18 @@ public static IEnumerable<TNode> GetAncestors<TNode>(this SyntaxNode node)
public static TNode GetAncestor<TNode>(this SyntaxNode node)
where TNode : SyntaxNode
{
return node?.GetAncestors<TNode>().FirstOrDefault();
var current = node.Parent;
while (current != null)
{
if (current is TNode tNode)
{
return tNode;
}
current = current.GetParent();
}
return null;
}
public static TNode GetAncestorOrThis<TNode>(this SyntaxNode node)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册