Skip to content

Commit

Permalink
F1 help for private protected and protected internal go to their expe…
Browse files Browse the repository at this point in the history
…cted places (#46343)

* F1 help for private protected and protected internal go to their expected places

* Clean up code analysis messages

* Simplify and extract logic

* Allow for modifiers in any order with other modifiers in between them because some people are monsters
  • Loading branch information
davidwengier authored Jul 28, 2020
1 parent 61dd560 commit 4fc1fa4
Show file tree
Hide file tree
Showing 5 changed files with 285 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public override async Task<string> GetHelpTermAsync(Document document, TextSpan
return string.Empty;
}

private bool IsValid(SyntaxToken token, TextSpan span)
private static bool IsValid(SyntaxToken token, TextSpan span)
{
// If the token doesn't actually intersect with our position, give up
return token.Kind() == SyntaxKind.EndIfDirectiveTrivia || token.Span.IntersectsWith(span);
Expand All @@ -115,6 +115,7 @@ private bool IsValid(SyntaxToken token, TextSpan span)
private string TryGetText(SyntaxToken token, SemanticModel semanticModel, Document document, ISyntaxFactsService syntaxFacts, CancellationToken cancellationToken)
{
if (TryGetTextForContextualKeyword(token, out var text) ||
TryGetTextForCombinationKeyword(token, syntaxFacts, out text) ||
TryGetTextForKeyword(token, syntaxFacts, out text) ||
TryGetTextForPreProcessor(token, syntaxFacts, out text) ||
TryGetTextForSymbol(token, semanticModel, document, cancellationToken, out text) ||
Expand Down Expand Up @@ -153,9 +154,9 @@ private bool TryGetTextForSymbol(SyntaxToken token, SemanticModel semanticModel,
}

// Local: return the name if it's the declaration, otherwise the type
if (symbol is ILocalSymbol && !symbol.DeclaringSyntaxReferences.Any(d => d.GetSyntax().DescendantTokens().Contains(token)))
if (symbol is ILocalSymbol localSymbol && !symbol.DeclaringSyntaxReferences.Any(d => d.GetSyntax().DescendantTokens().Contains(token)))
{
symbol = ((ILocalSymbol)symbol).Type;
symbol = localSymbol.Type;
}

// Range variable: use the type
Expand All @@ -176,7 +177,7 @@ private bool TryGetTextForSymbol(SyntaxToken token, SemanticModel semanticModel,
return symbol != null;
}

private bool TryGetTextForOperator(SyntaxToken token, Document document, out string text)
private static bool TryGetTextForOperator(SyntaxToken token, Document document, out string text)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
if (syntaxFacts.IsOperator(token) || syntaxFacts.IsPredefinedOperator(token) || SyntaxFacts.IsAssignmentExpressionOperatorToken(token.Kind()))
Expand Down Expand Up @@ -225,7 +226,7 @@ private bool TryGetTextForOperator(SyntaxToken token, Document document, out str
return false;
}

private bool TryGetTextForPreProcessor(SyntaxToken token, ISyntaxFactsService syntaxFacts, out string text)
private static bool TryGetTextForPreProcessor(SyntaxToken token, ISyntaxFactsService syntaxFacts, out string text)
{
if (syntaxFacts.IsPreprocessorKeyword(token))
{
Expand All @@ -243,7 +244,7 @@ private bool TryGetTextForPreProcessor(SyntaxToken token, ISyntaxFactsService sy
return false;
}

private bool TryGetTextForContextualKeyword(SyntaxToken token, out string text)
private static bool TryGetTextForContextualKeyword(SyntaxToken token, out string text)
{
if (token.Text == "nameof")
{
Expand Down Expand Up @@ -286,8 +287,31 @@ private bool TryGetTextForContextualKeyword(SyntaxToken token, out string text)
text = null;
return false;
}
private static bool TryGetTextForCombinationKeyword(SyntaxToken token, ISyntaxFactsService syntaxFacts, out string text)
{
switch (token.Kind())
{
case SyntaxKind.PrivateKeyword when ModifiersContains(token, syntaxFacts, SyntaxKind.ProtectedKeyword):
case SyntaxKind.ProtectedKeyword when ModifiersContains(token, syntaxFacts, SyntaxKind.PrivateKeyword):
text = "privateprotected_CSharpKeyword";
return true;

case SyntaxKind.ProtectedKeyword when ModifiersContains(token, syntaxFacts, SyntaxKind.InternalKeyword):
case SyntaxKind.InternalKeyword when ModifiersContains(token, syntaxFacts, SyntaxKind.ProtectedKeyword):
text = "protectedinternal_CSharpKeyword";
return true;
}

text = null;
return false;

static bool ModifiersContains(SyntaxToken token, ISyntaxFactsService syntaxFacts, SyntaxKind kind)
{
return syntaxFacts.GetModifiers(token.Parent).Any(t => t.IsKind(kind));
}
}

private bool TryGetTextForKeyword(SyntaxToken token, ISyntaxFactsService syntaxFacts, out string text)
private static bool TryGetTextForKeyword(SyntaxToken token, ISyntaxFactsService syntaxFacts, out string text)
{
if (token.Kind() == SyntaxKind.InKeyword)
{
Expand All @@ -311,7 +335,7 @@ private bool TryGetTextForKeyword(SyntaxToken token, ISyntaxFactsService syntaxF
}

if (token.ValueText == "var" && token.IsKind(SyntaxKind.IdentifierToken) &&
token.Parent.Parent is VariableDeclarationSyntax && token.Parent == ((VariableDeclarationSyntax)token.Parent.Parent).Type)
token.Parent.Parent is VariableDeclarationSyntax declaration && token.Parent == declaration.Type)
{
text = "var_CSharpKeyword";
return true;
Expand Down
105 changes: 103 additions & 2 deletions src/VisualStudio/CSharp/Test/F1Help/F1HelpTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class F1HelpTests
private static readonly ComposableCatalog s_catalog = TestExportProvider.EntireAssemblyCatalogWithCSharpAndVisualBasic.WithPart(typeof(CSharpHelpContextService));
private static readonly IExportProviderFactory s_exportProviderFactory = ExportProviderCache.GetOrCreateExportProviderFactory(s_catalog);

private async Task TestAsync(string markup, string expectedText)
private static async Task TestAsync(string markup, string expectedText)
{
using var workspace = TestWorkspace.CreateCSharp(markup, exportProvider: s_exportProviderFactory.CreateExportProvider());
var caret = workspace.Documents.First().CursorPosition;
Expand All @@ -34,11 +34,112 @@ private async Task TestAsync(string markup, string expectedText)
Assert.Equal(expectedText, actualText);
}

private async Task Test_KeywordAsync(string markup, string expectedText)
private static async Task Test_KeywordAsync(string markup, string expectedText)
{
await TestAsync(markup, expectedText + "_CSharpKeyword");
}

[Fact, Trait(Traits.Feature, Traits.Features.F1Help)]
public async Task TestInternal()
{
await Test_KeywordAsync(
@"intern[||]al class C
{
}", "internal");
}

[Fact, Trait(Traits.Feature, Traits.Features.F1Help)]
public async Task TestProtected()
{
await Test_KeywordAsync(
@"public class C
{
protec[||]ted void goo();
}", "protected");
}

[Fact, Trait(Traits.Feature, Traits.Features.F1Help)]
public async Task TestProtectedInternal1()
{
await Test_KeywordAsync(
@"public class C
{
internal protec[||]ted void goo();
}", "protectedinternal");
}

[Fact, Trait(Traits.Feature, Traits.Features.F1Help)]
public async Task TestProtectedInternal2()
{
await Test_KeywordAsync(
@"public class C
{
protec[||]ted internal void goo();
}", "protectedinternal");
}

[Fact, Trait(Traits.Feature, Traits.Features.F1Help)]
public async Task TestPrivateProtected1()
{
await Test_KeywordAsync(
@"public class C
{
private protec[||]ted void goo();
}", "privateprotected");
}

[Fact, Trait(Traits.Feature, Traits.Features.F1Help)]
public async Task TestPrivateProtected2()
{
await Test_KeywordAsync(
@"public class C
{
priv[||]ate protected void goo();
}", "privateprotected");
}

[Fact, Trait(Traits.Feature, Traits.Features.F1Help)]
public async Task TestPrivateProtected3()
{
await Test_KeywordAsync(
@"public class C
{
protected priv[||]ate void goo();
}", "privateprotected");
}

[Fact, Trait(Traits.Feature, Traits.Features.F1Help)]
public async Task TestPrivateProtected4()
{
await Test_KeywordAsync(
@"public class C
{
prot[||]ected private void goo();
}", "privateprotected");
}

[Fact, Trait(Traits.Feature, Traits.Features.F1Help)]
public async Task TestModifierSoup()
{
await Test_KeywordAsync(
@"public class C
{
private new prot[||]ected static unsafe void foo()
{
}
}", "privateprotected");
}

[Fact, Trait(Traits.Feature, Traits.Features.F1Help)]
public async Task TestModifierSoupField()
{
await Test_KeywordAsync(
@"public class C
{
new prot[||]ected static unsafe private goo;
}", "privateprotected");
}

[Fact, Trait(Traits.Feature, Traits.Features.F1Help)]
public async Task TestVoid()
{
Expand Down
109 changes: 108 additions & 1 deletion src/VisualStudio/Core/Test/Help/HelpTests.vb
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,121 @@ Imports Roslyn.Utilities
Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Help
<[UseExportProvider]>
Public Class HelpTests
Public Async Function TestAsync(markup As String, expected As String) As Tasks.Task
Public Shared Async Function TestAsync(markup As String, expected As String) As Tasks.Task
Using workspace = TestWorkspace.CreateVisualBasic(markup)
Dim caret = workspace.Documents.First().CursorPosition
Dim service = New VisualBasicHelpContextService()
Assert.Equal(expected, Await service.GetHelpTermAsync(workspace.CurrentSolution.Projects.First().Documents.First(), workspace.Documents.First().SelectedSpans.First(), CancellationToken.None))
End Using
End Function

<Fact, Trait(Traits.Feature, Traits.Features.F1Help)>
Public Async Function TestFriend() As Task
Dim text = <a>
Fri[||]end Class G
End Class</a>

Await TestAsync(text.Value, "vb.Friend")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.F1Help)>
Public Async Function TestProtected() As Task
Dim text = <a>
Public Class G
Protec[||]ted Sub M()
End Sub
End Class</a>

Await TestAsync(text.Value, "vb.Protected")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.F1Help)>
Public Async Function TestProtectedFriend1() As Task
Dim text = <a>
Public Class G
Protec[||]ted Friend Sub M()
End Sub
End Class</a>

Await TestAsync(text.Value, "vb.ProtectedFriend")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.F1Help)>
Public Async Function TestProtectedFriend2() As Task
Dim text = <a>
Public Class G
Friend Protec[||]ted Sub M()
End Sub
End Class</a>

Await TestAsync(text.Value, "vb.ProtectedFriend")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.F1Help)>
Public Async Function TestPrivateProtected1() As Task
Dim text = <a>
Public Class G
Private Protec[||]ted Sub M()
End Sub
End Class</a>

Await TestAsync(text.Value, "vb.PrivateProtected")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.F1Help)>
Public Async Function TestPrivateProtected2() As Task
Dim text = <a>
Public Class G
Priv[||]ate Protected Sub M()
End Sub
End Class</a>

Await TestAsync(text.Value, "vb.PrivateProtected")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.F1Help)>
Public Async Function TestPrivateProtected3() As Task
Dim text = <a>
Public Class G
Protected Priv[||]ate Sub M()
End Sub
End Class</a>

Await TestAsync(text.Value, "vb.PrivateProtected")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.F1Help)>
Public Async Function TestPrivateProtected4() As Task
Dim text = <a>
Public Class G
Protec[||]ted Private Sub M()
End Sub
End Class</a>

Await TestAsync(text.Value, "vb.PrivateProtected")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.F1Help)>
Public Async Function TestModifierSoup() As Task
Dim text = <a>
Public Class G
Protec[||]ted Async Shared Private Sub M()
End Sub
End Class</a>

Await TestAsync(text.Value, "vb.PrivateProtected")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.F1Help)>
Public Async Function TestModifierSoupField() As Task
Dim text = <a>
Public Class G
Private Shadows Shared Prot[||]ected foo as Boolean
End Class</a>

Await TestAsync(text.Value, "vb.PrivateProtected")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.F1Help)>
Public Async Function TestAddHandler1() As Task
Dim text = <a>
Expand Down
2 changes: 2 additions & 0 deletions src/VisualStudio/VisualBasic/Impl/Help/HelpKeywords.vb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Help
Friend Const Iterator As String = "vb.Iterator"
Friend Const Await As String = "vb.Await"
Friend Const Yield As String = "vb.Yield"
Friend Const PrivateProtected As String = "vb.PrivateProtected"
Friend Const ProtectedFriend As String = "vb.ProtectedFriend"

End Class

Expand Down
Loading

0 comments on commit 4fc1fa4

Please sign in to comment.