提交 421f61b0 编写于 作者: L lorcanmooney 提交者: Sam Harwell

Suggest language keywords inside 'langword' documentation attribute

Fixes #11490
上级 b57c9b1e
......@@ -1084,6 +1084,41 @@ static void Main(string[] args)
", "args");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task ParamNames()
{
await VerifyItemExistsAsync(@"
/// <param name=""$$""/>
static void Goo(string str)
{
}
", "str");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task TypeParamRefNames()
{
await VerifyItemExistsAsync(@"
/// <summary>
/// <typeparamref name=""$$""/>
/// </summary>
static void Goo<T>()
{
}
", "T");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task TypeParamNames()
{
await VerifyItemExistsAsync(@"
/// <typeparam name=""$$""/>
static void Goo<T>()
{
}
", "T");
}
[WorkItem(8322, "https://github.com/dotnet/roslyn/issues/8322")]
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task PartialTagCompletion()
......@@ -1135,5 +1170,31 @@ static void Goo(string str)
{
}", "param name=\"str\"");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task ListTypes()
{
await VerifyItemsExistAsync(@"
/// <summary>
/// <list type=""$$""
/// </summary>
static void Goo()
{
}
", "bullet", "number", "table");
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task SeeLangword()
{
await VerifyItemsExistAsync(@"
/// <summary>
/// <see langword=""$$""
/// </summary>
static void Goo()
{
}
", "await", "class");
}
}
}
......@@ -543,5 +543,89 @@ End Class
Await VerifyItemsExistAsync(text, "returns")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestListTypes() As Task
Dim text = "
Class C
''' <summary>
''' <list type=""$$""
''' </summary>
Sub Goo()
End Sub
End Class
"
Await VerifyItemsExistAsync(text, "number", "bullet", "table")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestSeeLangword() As Task
Dim text = "
Class C
''' <summary>
''' <see langword=""$$""
''' </summary>
Sub Goo()
End Sub
End Class
"
Await VerifyItemsExistAsync(text, "Await", "Class")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestParamNames() As Task
Dim text = "
Class C
''' <param name=""$$""
Sub Goo(Of T)(i as Integer)
End Sub
End Class
"
Await VerifyItemsExistAsync(text, "i")
Await VerifyItemsAbsentAsync(text, "T")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestParamRefNames() As Task
Dim text = "
Class C
''' <summary>
''' <paramref name=""$$""
''' </summary>
Sub Goo(Of T)(i as Integer)
End Sub
End Class
"
Await VerifyItemsExistAsync(text, "i")
Await VerifyItemsAbsentAsync(text, "T")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestTypeParamNames() As Task
Dim text = "
Class C
''' <typeparam name=""$$""
Sub Goo(Of T)(i as Integer)
End Sub
End Class
"
Await VerifyItemsExistAsync(text, "T")
Await VerifyItemsAbsentAsync(text, "i")
End Function
<Fact, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestTypeParamRefNames() As Task
Dim text = "
Class C
''' <summary>
''' <typeparamref name=""$$""
''' </summary>
Sub Goo(Of T)(i as Integer)
End Sub
End Class
"
Await VerifyItemsExistAsync(text, "T")
Await VerifyItemsAbsentAsync(text, "i")
End Function
End Class
End Namespace
......@@ -62,26 +62,11 @@ internal override bool IsInsertionTrigger(SourceText text, int characterPosition
}
}
// User is trying to write a name, try to suggest only names.
if (token.Parent.IsKind(SyntaxKind.XmlNameAttribute) ||
(token.Parent.IsKind(SyntaxKind.IdentifierName) && token.Parent.IsParentKind(SyntaxKind.XmlNameAttribute)))
{
string parentElementName = null;
var emptyElement = token.GetAncestor<XmlEmptyElementSyntax>();
if (emptyElement != null)
{
parentElementName = emptyElement.Name.LocalName.Text;
}
string tagName, attributeName;
if (parentElementName == ParamRefTagName)
{
return GetParamNameItems(declaredSymbol);
}
else if (parentElementName == TypeParamRefTagName)
{
return GetTypeParamNameItems(declaredSymbol);
}
if (IsAttributeValueContext(token, out tagName, out attributeName))
{
return GetAttributeValueItems(declaredSymbol, tagName, attributeName);
}
if (token.Parent.Kind() == SyntaxKind.XmlEmptyElement || token.Parent.Kind() == SyntaxKind.XmlText ||
......@@ -142,6 +127,66 @@ internal override bool IsInsertionTrigger(SourceText text, int characterPosition
return items;
}
private bool IsAttributeValueContext(SyntaxToken token, out string tagName, out string attributeName)
{
XmlAttributeSyntax attributeSyntax;
if (IsAttributeValueContext(token, out attributeSyntax))
{
attributeName = attributeSyntax.Name.LocalName.ValueText;
var emptyElement = attributeSyntax.GetAncestor<XmlEmptyElementSyntax>();
if (emptyElement != null)
{
tagName = emptyElement.Name.LocalName.Text;
return true;
}
else
{
var startTagSyntax = token.GetAncestor<XmlElementStartTagSyntax>();
if (startTagSyntax != null)
{
tagName = startTagSyntax.Name.LocalName.Text;
return true;
}
}
}
attributeName = null;
tagName = null;
return false;
}
private bool IsAttributeValueContext(SyntaxToken token, out XmlAttributeSyntax syntax)
{
if (token.IsParentKind(SyntaxKind.IdentifierName) && token.Parent.IsParentKind(SyntaxKind.XmlNameAttribute))
{
syntax = (XmlAttributeSyntax)token.Parent.Parent;
return true;
}
else if (token.IsKind(SyntaxKind.XmlTextLiteralToken) && token.IsParentKind(SyntaxKind.XmlTextAttribute))
{
syntax = (XmlAttributeSyntax)token.Parent;
return true;
}
else if (token.IsParentKind(SyntaxKind.XmlNameAttribute) || token.IsParentKind(SyntaxKind.XmlTextAttribute))
{
syntax = (XmlAttributeSyntax)token.Parent;
return token == syntax.StartQuoteToken;
}
else
{
syntax = null;
return false;
}
}
protected override IEnumerable<CompletionItem> GetKeywordItems()
{
return SyntaxFacts.GetKeywordKinds()
.Select(keyword => CreateCompletionItem(SyntaxFacts.GetText(keyword)));
}
private IEnumerable<CompletionItem> GetTopLevelSingleUseNames(DocumentationCommentTriviaSyntax parentTrivia)
{
var names = new HashSet<string>(new[] { SummaryTagName, RemarksTagName, ExampleTagName, CompletionListTagName });
......@@ -270,22 +315,6 @@ private IEnumerable<CompletionItem> GetTagsForMethod(IMethodSymbol symbol, Docum
return items;
}
protected IEnumerable<CompletionItem> GetParamNameItems(ISymbol declaredSymbol)
{
var items = declaredSymbol?.GetParameters()
.Select(parameter => CreateCompletionItem(parameter.Name));
return items ?? SpecializedCollections.EmptyEnumerable<CompletionItem>();
}
protected IEnumerable<CompletionItem> GetTypeParamNameItems(ISymbol declaredSymbol)
{
var items = declaredSymbol?.GetTypeParameters()
.Select(typeParameter => CreateCompletionItem(typeParameter.Name));
return items ?? SpecializedCollections.EmptyEnumerable<CompletionItem>();
}
private static CompletionItemRules s_defaultRules =
CompletionItemRules.Create(
filterCharacterRules: FilterRules,
......
......@@ -43,6 +43,7 @@ internal abstract class AbstractDocCommentCompletionProvider : CommonCompletionP
// Attribute names
protected const string CrefAttributeName = "cref";
protected const string FileAttributeName = "file";
protected const string LangwordAttributeName = "langword";
protected const string NameAttributeName = "name";
protected const string PathAttributeName = "path";
protected const string TypeAttributeName = "type";
......@@ -69,13 +70,19 @@ internal abstract class AbstractDocCommentCompletionProvider : CommonCompletionP
new[] { ExceptionTagName, CrefAttributeName, $"{CrefAttributeName}=\"", "\"" },
new[] { PermissionTagName, CrefAttributeName, $"{CrefAttributeName}=\"", "\"" },
new[] { SeeTagName, CrefAttributeName, $"{CrefAttributeName}=\"", "\"" },
new[] { SeeTagName, LangwordAttributeName, $"{LangwordAttributeName}=\"", "\"" },
new[] { SeeAlsoTagName, CrefAttributeName, $"{CrefAttributeName}=\"", "\"" },
new[] { ListTagName, TypeAttributeName, $"{TypeAttributeName}=\"", "\"" },
new[] { ParamTagName, NameAttributeName, $"{NameAttributeName}=\"", "\"" },
new[] { ParamRefTagName, NameAttributeName, $"{NameAttributeName}=\"", "\"" },
new[] { TypeParamTagName, NameAttributeName, $"{NameAttributeName}=\"", "\"" },
new[] { TypeParamRefTagName, NameAttributeName, $"{NameAttributeName}=\"", "\"" },
new[] { IncludeTagName, FileAttributeName, $"{FileAttributeName}=\"", "\"" },
new[] { IncludeTagName, PathAttributeName, $"{PathAttributeName}=\"", "\"" }
};
private readonly ImmutableArray<string> _listTypeValues = ImmutableArray.Create("bullet", "number", "table");
public override async Task ProvideCompletionsAsync(CompletionContext context)
{
if (!context.Options.GetOption(CompletionControllerOptions.ShowXmlDocCommentCompletion))
......@@ -158,6 +165,49 @@ private IEnumerable<CompletionItem> GetTypeParamRefItems(ISymbol declaredSymbol)
afterCaretText: string.Empty));
}
protected IEnumerable<CompletionItem> GetAttributeValueItems(ISymbol symbol, string tagName, string attributeName)
{
if (attributeName == NameAttributeName)
{
if (tagName == ParamTagName || tagName == ParamRefTagName)
{
return GetParamNameItems(symbol);
}
else if (tagName == TypeParamTagName || tagName == TypeParamRefTagName)
{
return GetTypeParamNameItems(symbol);
}
}
else if (attributeName == LangwordAttributeName && tagName == SeeTagName)
{
return GetKeywordItems();
}
else if (attributeName == TypeAttributeName && tagName == ListTagName)
{
return _listTypeValues.Select(value => CreateCompletionItem(value));
}
return null;
}
protected IEnumerable<CompletionItem> GetParamNameItems(ISymbol declaredSymbol)
{
var items = declaredSymbol?.GetParameters()
.Select(parameter => CreateCompletionItem(parameter.Name));
return items ?? SpecializedCollections.EmptyEnumerable<CompletionItem>();
}
protected IEnumerable<CompletionItem> GetTypeParamNameItems(ISymbol declaredSymbol)
{
var items = declaredSymbol?.GetTypeParameters()
.Select(typeParameter => CreateCompletionItem(typeParameter.Name));
return items ?? SpecializedCollections.EmptyEnumerable<CompletionItem>();
}
protected abstract IEnumerable<CompletionItem> GetKeywordItems();
protected IEnumerable<CompletionItem> GetTopLevelRepeatableItems()
{
return new[] { ExceptionTagName, IncludeTagName, PermissionTagName }.Select(GetItem);
......
......@@ -171,32 +171,46 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
position As Integer,
items As List(Of CompletionItem),
symbol As ISymbol)
Dim tagNameSyntax As XmlNameSyntax = Nothing
Dim tagAttributes As SyntaxList(Of XmlNodeSyntax) = Nothing
Dim startTagSyntax = token.GetAncestor(Of XmlElementStartTagSyntax)()
If startTagSyntax IsNot Nothing Then
tagNameSyntax = TryCast(startTagSyntax.Name, XmlNameSyntax)
tagAttributes = startTagSyntax.Attributes
Else
Dim emptyElementSyntax = token.GetAncestor(Of XmlEmptyElementSyntax)()
If emptyElementSyntax IsNot Nothing Then
tagNameSyntax = TryCast(emptyElementSyntax.Name, XmlNameSyntax)
tagAttributes = emptyElementSyntax.Attributes
End If
End If
If tagNameSyntax IsNot Nothing Then
Dim targetToken = GetPreviousTokenIfTouchingText(token, position)
Dim tagName = tagNameSyntax.LocalName.ValueText
If targetToken.IsChildToken(Function(n As XmlNameSyntax) n.LocalName) AndAlso targetToken.Parent Is startTagSyntax.Name Then
If targetToken.IsChildToken(Function(n As XmlNameSyntax) n.LocalName) AndAlso targetToken.Parent Is tagNameSyntax Then
' <exception |
items.AddRange(GetAttributes(startTagSyntax))
items.AddRange(GetAttributes(tagName, tagAttributes))
End If
'<exception a|
If targetToken.IsChildToken(Function(n As XmlNameSyntax) n.LocalName) AndAlso targetToken.Parent.IsParentKind(SyntaxKind.XmlAttribute) Then
' <exception |
items.AddRange(GetAttributes(startTagSyntax))
items.AddRange(GetAttributes(tagName, tagAttributes))
End If
'<exception a=""|
If targetToken.IsChildToken(Function(s As XmlStringSyntax) s.EndQuoteToken) AndAlso targetToken.Parent.IsParentKind(SyntaxKind.XmlAttribute) Then
items.AddRange(GetAttributes(startTagSyntax))
items.AddRange(GetAttributes(tagName, tagAttributes))
End If
' <param name="|"
If (targetToken.IsChildToken(Function(s As XmlStringSyntax) s.StartQuoteToken) AndAlso targetToken.Parent.IsParentKind(SyntaxKind.XmlAttribute)) OrElse
targetToken.IsChildToken(Function(a As XmlNameAttributeSyntax) a.StartQuoteToken) Then
Dim name = TryCast(startTagSyntax.Name, XmlNameSyntax)
Dim tagName = name.LocalName.ValueText
Dim attributeName As String
Dim xmlAttributeName = targetToken.GetAncestor(Of XmlNameAttributeSyntax)()
......@@ -206,19 +220,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
attributeName = DirectCast(targetToken.GetAncestor(Of XmlAttributeSyntax)().Name, XmlNameSyntax).LocalName.ValueText
End If
If attributeName = NameAttributeName Then
If tagName = ParamTagName Then
items.AddRange(symbol.GetParameters().Select(Function(s) CreateCompletionItem(s.Name)))
End If
If tagName = TypeParamTagName Then
items.AddRange(symbol.GetTypeArguments().Select(Function(s) CreateCompletionItem(s.Name)))
End If
End If
items.AddRange(GetAttributeValueItems(symbol, tagName, attributeName))
End If
End If
End Sub
Protected Overrides Function GetKeywordItems() As IEnumerable(Of CompletionItem)
Return SyntaxFacts.GetKeywordKinds() _
.Select(Function(keyword) CreateCompletionItem(SyntaxFacts.GetText(keyword)))
End Function
Private Function GetTagsForSymbol(symbol As ISymbol,
parent As DocumentationCommentTriviaSyntax) As IEnumerable(Of CompletionItem)
Dim items = New List(Of CompletionItem)()
......@@ -351,15 +362,18 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
Next
End Sub
Private Function GetAttributes(startTag As XmlElementStartTagSyntax) As IEnumerable(Of CompletionItem)
Dim nameSyntax = TryCast(startTag.Name, XmlNameSyntax)
If nameSyntax IsNot Nothing Then
Dim name = nameSyntax.LocalName.ValueText
Dim existingAttributeNames = startTag.Attributes.OfType(Of XmlAttributeSyntax).Select(Function(a) DirectCast(a.Name, XmlNameSyntax).LocalName.ValueText)
Return GetAttributeItem(name).Where(Function(i) Not existingAttributeNames.Contains(i.DisplayText))
End If
Private Function GetAttributes(tagName As String, attributes As SyntaxList(Of XmlNodeSyntax)) As IEnumerable(Of CompletionItem)
Dim existingAttributeNames = attributes.Select(AddressOf GetAttributeName).WhereNotNull()
Return GetAttributeItem(tagName).Where(Function(i) Not existingAttributeNames.Contains(i.DisplayText))
End Function
Return Nothing
Private Shared Function GetAttributeName(node As XmlNodeSyntax) As String
Dim nameSyntax As XmlNameSyntax = node.TypeSwitch(
Function(attribute As XmlAttributeSyntax) TryCast(attribute.Name, XmlNameSyntax),
Function(attribute As XmlNameAttributeSyntax) attribute.Name,
Function(attribute As XmlCrefAttributeSyntax) attribute.Name)
Return nameSyntax?.LocalName.ValueText
End Function
Private Shared s_defaultRules As CompletionItemRules =
......
......@@ -43,6 +43,11 @@ public static bool IsKind(this SyntaxToken token, params SyntaxKind[] kinds)
return kinds.Contains(token.Kind());
}
public static bool IsParentKind(this SyntaxToken token, SyntaxKind kind)
{
return token.Parent != null && token.Parent.IsKind(kind);
}
public static bool IsLiteral(this SyntaxToken token)
{
switch (token.Kind())
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册