Skip to content

Commit efa5d64

Browse files
authored
Merge pull request #42893 from sharwell/ancestor-or-self-arg
Add FirstAncestorOrSelf extension with an argument
2 parents 8017891 + 9d3a600 commit efa5d64

File tree

15 files changed

+108
-21
lines changed

15 files changed

+108
-21
lines changed

src/Analyzers/Core/CodeFixes/ConvertAnonymousTypeToTuple/AbstractConvertAnonymousTypeToTupleCodeFixProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private async Task FixInCurrentMemberAsync(
7373
}
7474

7575
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
76-
var containingMember = creationNode.FirstAncestorOrSelf<SyntaxNode>(syntaxFacts.IsMethodLevelMember) ?? creationNode;
76+
var containingMember = creationNode.FirstAncestorOrSelf<SyntaxNode, ISyntaxFactsService>((node, syntaxFacts) => syntaxFacts.IsMethodLevelMember(node), syntaxFacts) ?? creationNode;
7777

7878
var childCreationNodes = containingMember.DescendantNodesAndSelf()
7979
.OfType<TAnonymousObjectCreationExpressionSyntax>();

src/Analyzers/Core/CodeFixes/RemoveUnusedParametersAndValues/AbstractRemoveUnusedValuesCodeFixProvider.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -621,10 +621,11 @@ TLocalDeclarationStatementSyntax CreateLocalDeclarationStatement(ITypeSymbol typ
621621
void InsertLocalDeclarationStatement(TLocalDeclarationStatementSyntax declarationStatement, SyntaxNode node)
622622
{
623623
// Find the correct place to insert the given declaration statement based on the node's ancestors.
624-
var insertionNode = node.FirstAncestorOrSelf<SyntaxNode>(n => n.Parent is TSwitchCaseBlockSyntax ||
624+
var insertionNode = node.FirstAncestorOrSelf<SyntaxNode, ISyntaxFactsService>((n, syntaxFacts) => n.Parent is TSwitchCaseBlockSyntax ||
625625
syntaxFacts.IsExecutableBlock(n.Parent) &&
626626
!(n is TCatchStatementSyntax) &&
627-
!(n is TCatchBlockSyntax));
627+
!(n is TCatchBlockSyntax),
628+
syntaxFacts);
628629
if (insertionNode is TSwitchCaseLabelOrClauseSyntax)
629630
{
630631
InsertAtStartOfSwitchCaseBlockForDeclarationInCaseLabelOrClause(insertionNode.GetAncestor<TSwitchCaseBlockSyntax>(), editor, declarationStatement);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System;
8+
9+
namespace Microsoft.CodeAnalysis
10+
{
11+
internal static class CodeStyleSyntaxNodeExtensions
12+
{
13+
/// <summary>
14+
/// Gets the first node of type TNode that matches the predicate.
15+
/// </summary>
16+
/// <remarks>
17+
/// This method was added to <see cref="SyntaxNode"/> as a public API. This extension method can be removed once
18+
/// the code style layer is updated to reference a version of Roslyn that includes it. It will be easy to
19+
/// identify since this method will show 0 references once the switch occurs.
20+
/// </remarks>
21+
internal static TNode? FirstAncestorOrSelf<TNode, TArg>(this SyntaxNode? node, Func<TNode, TArg, bool> predicate, TArg argument, bool ascendOutOfTrivia = true)
22+
where TNode : SyntaxNode
23+
{
24+
for (; node != null; node = GetParent(node, ascendOutOfTrivia))
25+
{
26+
if (node is TNode tnode && predicate(tnode, argument))
27+
{
28+
return tnode;
29+
}
30+
}
31+
32+
return null;
33+
}
34+
35+
private static SyntaxNode? GetParent(SyntaxNode node, bool ascendOutOfTrivia)
36+
{
37+
var parent = node.Parent;
38+
if (parent == null && ascendOutOfTrivia)
39+
{
40+
var structuredTrivia = node as IStructuredTriviaSyntax;
41+
if (structuredTrivia != null)
42+
{
43+
parent = structuredTrivia.ParentTrivia.Token.Parent;
44+
}
45+
}
46+
47+
return parent;
48+
}
49+
}
50+
}

src/Compilers/Core/Portable/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Microsoft.CodeAnalysis.SourceGeneratorContext.CancellationToken.get -> System.Th
2323
Microsoft.CodeAnalysis.SourceGeneratorContext.Compilation.get -> Microsoft.CodeAnalysis.Compilation
2424
Microsoft.CodeAnalysis.SourceGeneratorContext.ReportDiagnostic(Microsoft.CodeAnalysis.Diagnostic diagnostic) -> void
2525
Microsoft.CodeAnalysis.SourceGeneratorContext.SyntaxReceiver.get -> Microsoft.CodeAnalysis.ISyntaxReceiver
26+
Microsoft.CodeAnalysis.SyntaxNode.FirstAncestorOrSelf<TNode, TArg>(System.Func<TNode, TArg, bool> predicate, TArg argument, bool ascendOutOfTrivia = true) -> TNode
2627
Microsoft.CodeAnalysis.SyntaxReceiverCreator
2728
override Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGenerators() -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ISourceGenerator>
2829
virtual Microsoft.CodeAnalysis.Diagnostics.AnalyzerReference.GetGenerators() -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ISourceGenerator>

src/Compilers/Core/Portable/Syntax/SyntaxNode.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System;
88
using System.Collections.Generic;
99
using System.Diagnostics;
10+
using System.Diagnostics.CodeAnalysis;
1011
using System.IO;
1112
using System.Linq;
1213
using System.Text;
@@ -724,6 +725,24 @@ public IEnumerable<SyntaxNode> AncestorsAndSelf(bool ascendOutOfTrivia = true)
724725
return null;
725726
}
726727

728+
/// <summary>
729+
/// Gets the first node of type TNode that matches the predicate.
730+
/// </summary>
731+
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Required for consistent API usage patterns.")]
732+
public TNode? FirstAncestorOrSelf<TNode, TArg>(Func<TNode, TArg, bool> predicate, TArg argument, bool ascendOutOfTrivia = true)
733+
where TNode : SyntaxNode
734+
{
735+
for (var node = this; node != null; node = GetParent(node, ascendOutOfTrivia))
736+
{
737+
if (node is TNode tnode && predicate(tnode, argument))
738+
{
739+
return tnode;
740+
}
741+
}
742+
743+
return null;
744+
}
745+
727746
/// <summary>
728747
/// Gets a list of descendant nodes in prefix document order.
729748
/// </summary>
@@ -827,7 +846,7 @@ public SyntaxNode FindNode(TextSpan span, bool findInsideTrivia = false, bool ge
827846

828847
var node = FindToken(span.Start, findInsideTrivia)
829848
.Parent
830-
!.FirstAncestorOrSelf<SyntaxNode>(a => a.FullSpan.Contains(span));
849+
!.FirstAncestorOrSelf<SyntaxNode, TextSpan>((a, span) => a.FullSpan.Contains(span), span);
831850

832851
RoslynDebug.Assert(node is object);
833852
SyntaxNode? cuRoot = node.SyntaxTree?.GetRoot();

src/EditorFeatures/Core/Implementation/Peek/PeekHelpers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ internal static LinePositionSpan GetEntityOfInterestSpan(ISymbol symbol, Workspa
5656
case SymbolKind.Field:
5757
case SymbolKind.Method:
5858
case SymbolKind.Property:
59-
node = node.FirstAncestorOrSelf<SyntaxNode>(syntaxFactsService.IsMethodLevelMember) ?? node;
59+
node = node.FirstAncestorOrSelf<SyntaxNode, ISyntaxFactsService>((node, syntaxFactsService) => syntaxFactsService.IsMethodLevelMember(node), syntaxFactsService) ?? node;
6060
break;
6161

6262
case SymbolKind.NamedType:
6363
case SymbolKind.Namespace:
64-
node = node.FirstAncestorOrSelf<SyntaxNode>(syntaxFactsService.IsTopLevelNodeWithMembers) ?? node;
64+
node = node.FirstAncestorOrSelf<SyntaxNode, ISyntaxFactsService>((node, syntaxFactsService) => syntaxFactsService.IsTopLevelNodeWithMembers(node), syntaxFactsService) ?? node;
6565
break;
6666
}
6767

src/Features/CSharp/Portable/SignatureHelp/ElementAccessExpressionSignatureHelpProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,8 @@ internal static bool TryGetSyntax(SyntaxNode root, int position, ISyntaxFactsSer
386386
if (CommonSignatureHelpUtilities.TryGetSyntax(root, position, syntaxFacts, triggerReason, IsTriggerToken, IsArgumentListToken, cancellationToken, out ElementBindingExpressionSyntax elementBindingExpression))
387387
{
388388
// Find the first conditional access expression that starts left of our open bracket
389-
var conditionalAccess = elementBindingExpression.FirstAncestorOrSelf<ConditionalAccessExpressionSyntax>(
390-
c => c.SpanStart < elementBindingExpression.SpanStart);
389+
var conditionalAccess = elementBindingExpression.FirstAncestorOrSelf<ConditionalAccessExpressionSyntax, ElementBindingExpressionSyntax>(
390+
(c, elementBindingExpression) => c.SpanStart < elementBindingExpression.SpanStart, elementBindingExpression);
391391

392392
identifier = conditionalAccess.Expression;
393393
openBrace = elementBindingExpression.ArgumentList.OpenBracketToken;

src/Features/Core/Portable/AddImport/AbstractAddImportFeatureService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,6 @@ protected bool AncestorOrSelfIsAwaitExpression(ISyntaxFacts syntaxFactsService,
552552
=> FirstAwaitExpressionAncestor(syntaxFactsService, node) != null;
553553

554554
private SyntaxNode FirstAwaitExpressionAncestor(ISyntaxFacts syntaxFactsService, SyntaxNode node)
555-
=> node.FirstAncestorOrSelf<SyntaxNode>(n => syntaxFactsService.IsAwaitExpression(n));
555+
=> node.FirstAncestorOrSelf<SyntaxNode, ISyntaxFacts>((n, syntaxFactsService) => syntaxFactsService.IsAwaitExpression(n), syntaxFactsService);
556556
}
557557
}

src/Features/Core/Portable/ConvertAnonymousTypeToClass/AbstractConvertAnonymousTypeToClassCodeRefactoringProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ private async Task<Document> ConvertToClassAsync(Document document, TextSpan spa
118118
var editor = new SyntaxEditor(root, generator);
119119

120120
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
121-
var containingMember = anonymousObject.FirstAncestorOrSelf<SyntaxNode>(syntaxFacts.IsMethodLevelMember) ?? anonymousObject;
121+
var containingMember = anonymousObject.FirstAncestorOrSelf<SyntaxNode, ISyntaxFactsService>((node, syntaxFacts) => syntaxFacts.IsMethodLevelMember(node), syntaxFacts) ?? anonymousObject;
122122

123123
// Next, go and update any references to these anonymous type properties to match
124124
// the new PascalCased name we've picked for the new properties that will go in

src/Features/Core/Portable/ConvertTupleToStruct/AbstractConvertTupleToStructCodeRefactoringProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ private static ImmutableArray<DocumentToUpdate> GetDocumentsToUpdateForContainin
439439
Document document, SyntaxNode tupleExprOrTypeNode)
440440
{
441441
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
442-
var containingMember = tupleExprOrTypeNode.FirstAncestorOrSelf<SyntaxNode>(syntaxFacts.IsMethodLevelMember) ?? tupleExprOrTypeNode;
442+
var containingMember = tupleExprOrTypeNode.FirstAncestorOrSelf<SyntaxNode, ISyntaxFactsService>((node, syntaxFacts) => syntaxFacts.IsMethodLevelMember(node), syntaxFacts) ?? tupleExprOrTypeNode;
443443

444444
return ImmutableArray.Create(new DocumentToUpdate(
445445
document, ImmutableArray.Create(containingMember)));

0 commit comments

Comments
 (0)