Skip to content

Commit 845a3c2

Browse files
committed
Classify the langword attribute value in DocComments
1 parent 56a3e41 commit 845a3c2

File tree

3 files changed

+202
-3
lines changed

3 files changed

+202
-3
lines changed

src/EditorFeatures/CSharpTest/Classification/SyntacticClassifierTests.cs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1712,6 +1712,158 @@ await TestAsync(code,
17121712
Punctuation.CloseCurly);
17131713
}
17141714

1715+
[Theory, CombinatorialData]
1716+
public async Task InXmlDocLangWord_Keyword(TestHost testHost)
1717+
{
1718+
await TestAsync(
1719+
"""
1720+
/// <summary>
1721+
/// <see langword="true"/>
1722+
/// </summary>
1723+
class MyClass
1724+
{
1725+
}
1726+
""", testHost,
1727+
XmlDoc.Delimiter("///"),
1728+
XmlDoc.Text(" "),
1729+
XmlDoc.Delimiter("<"),
1730+
XmlDoc.Name("summary"),
1731+
XmlDoc.Delimiter(">"),
1732+
XmlDoc.Delimiter("///"),
1733+
XmlDoc.Text(" "),
1734+
XmlDoc.Delimiter("<"),
1735+
XmlDoc.Name("see"),
1736+
XmlDoc.AttributeName("langword"),
1737+
XmlDoc.Delimiter("="),
1738+
XmlDoc.AttributeQuotes("\""),
1739+
Keyword("true"),
1740+
XmlDoc.AttributeQuotes("\""),
1741+
XmlDoc.Delimiter("/>"),
1742+
XmlDoc.Delimiter("///"),
1743+
XmlDoc.Text(" "),
1744+
XmlDoc.Delimiter("</"),
1745+
XmlDoc.Name("summary"),
1746+
XmlDoc.Delimiter(">"),
1747+
Keyword("class"),
1748+
Class("MyClass"),
1749+
Punctuation.OpenCurly,
1750+
Punctuation.CloseCurly);
1751+
}
1752+
1753+
[Theory, CombinatorialData]
1754+
public async Task InXmlDocLangWord_ControlKeyword(TestHost testHost)
1755+
{
1756+
await TestAsync(
1757+
"""
1758+
/// <summary>
1759+
/// <see langword="return"/>
1760+
/// </summary>
1761+
class MyClass
1762+
{
1763+
}
1764+
""", testHost,
1765+
XmlDoc.Delimiter("///"),
1766+
XmlDoc.Text(" "),
1767+
XmlDoc.Delimiter("<"),
1768+
XmlDoc.Name("summary"),
1769+
XmlDoc.Delimiter(">"),
1770+
XmlDoc.Delimiter("///"),
1771+
XmlDoc.Text(" "),
1772+
XmlDoc.Delimiter("<"),
1773+
XmlDoc.Name("see"),
1774+
XmlDoc.AttributeName("langword"),
1775+
XmlDoc.Delimiter("="),
1776+
XmlDoc.AttributeQuotes("\""),
1777+
ControlKeyword("return"),
1778+
XmlDoc.AttributeQuotes("\""),
1779+
XmlDoc.Delimiter("/>"),
1780+
XmlDoc.Delimiter("///"),
1781+
XmlDoc.Text(" "),
1782+
XmlDoc.Delimiter("</"),
1783+
XmlDoc.Name("summary"),
1784+
XmlDoc.Delimiter(">"),
1785+
Keyword("class"),
1786+
Class("MyClass"),
1787+
Punctuation.OpenCurly,
1788+
Punctuation.CloseCurly);
1789+
}
1790+
1791+
[Theory, CombinatorialData]
1792+
public async Task InXmlDocLangWord_ContextualKeyword(TestHost testHost)
1793+
{
1794+
await TestAsync(
1795+
"""
1796+
/// <summary>
1797+
/// <see langword="with"/>
1798+
/// </summary>
1799+
class MyClass
1800+
{
1801+
}
1802+
""", testHost,
1803+
XmlDoc.Delimiter("///"),
1804+
XmlDoc.Text(" "),
1805+
XmlDoc.Delimiter("<"),
1806+
XmlDoc.Name("summary"),
1807+
XmlDoc.Delimiter(">"),
1808+
XmlDoc.Delimiter("///"),
1809+
XmlDoc.Text(" "),
1810+
XmlDoc.Delimiter("<"),
1811+
XmlDoc.Name("see"),
1812+
XmlDoc.AttributeName("langword"),
1813+
XmlDoc.Delimiter("="),
1814+
XmlDoc.AttributeQuotes("\""),
1815+
Keyword("with"),
1816+
XmlDoc.AttributeQuotes("\""),
1817+
XmlDoc.Delimiter("/>"),
1818+
XmlDoc.Delimiter("///"),
1819+
XmlDoc.Text(" "),
1820+
XmlDoc.Delimiter("</"),
1821+
XmlDoc.Name("summary"),
1822+
XmlDoc.Delimiter(">"),
1823+
Keyword("class"),
1824+
Class("MyClass"),
1825+
Punctuation.OpenCurly,
1826+
Punctuation.CloseCurly);
1827+
}
1828+
1829+
[Theory, CombinatorialData]
1830+
public async Task InXmlDocLangWord_NonKeyword(TestHost testHost)
1831+
{
1832+
await TestAsync(
1833+
"""
1834+
/// <summary>
1835+
/// <see langword="MyWord"/>
1836+
/// </summary>
1837+
class MyClass
1838+
{
1839+
}
1840+
""", testHost,
1841+
XmlDoc.Delimiter("///"),
1842+
XmlDoc.Text(" "),
1843+
XmlDoc.Delimiter("<"),
1844+
XmlDoc.Name("summary"),
1845+
XmlDoc.Delimiter(">"),
1846+
XmlDoc.Delimiter("///"),
1847+
XmlDoc.Text(" "),
1848+
XmlDoc.Delimiter("<"),
1849+
XmlDoc.Name("see"),
1850+
XmlDoc.AttributeName("langword"),
1851+
XmlDoc.Delimiter("="),
1852+
XmlDoc.AttributeQuotes("\""),
1853+
XmlDoc.AttributeValue("MyWord"),
1854+
XmlDoc.AttributeQuotes("\""),
1855+
XmlDoc.Delimiter("/>"),
1856+
XmlDoc.Delimiter("///"),
1857+
XmlDoc.Text(" "),
1858+
XmlDoc.Delimiter("</"),
1859+
XmlDoc.Name("summary"),
1860+
XmlDoc.Delimiter(">"),
1861+
Keyword("class"),
1862+
Class("MyClass"),
1863+
Punctuation.OpenCurly,
1864+
Punctuation.CloseCurly);
1865+
}
1866+
17151867
[Theory, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/531155")]
17161868
[CombinatorialData]
17171869
public async Task XmlDocComment_ExteriorTriviaInsideCRef(TestHost testHost)

src/Workspaces/CSharp/Portable/Classification/ClassificationHelpers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ private static bool IsControlKeyword(SyntaxToken token)
6363
IsControlKeywordKind(token.Kind()) &&
6464
IsControlStatementKind(token.Parent.Kind());
6565

66-
private static bool IsControlKeywordKind(SyntaxKind kind)
66+
public static bool IsControlKeywordKind(SyntaxKind kind)
6767
{
6868
switch (kind)
6969
{
@@ -94,7 +94,7 @@ private static bool IsControlKeywordKind(SyntaxKind kind)
9494
}
9595
}
9696

97-
private static bool IsControlStatementKind(SyntaxKind kind)
97+
public static bool IsControlStatementKind(SyntaxKind kind)
9898
{
9999
switch (kind)
100100
{

src/Workspaces/CSharp/Portable/Classification/Worker_DocumentationComments.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,13 @@ private void ClassifyXmlAttribute(XmlAttributeSyntax attribute)
253253
switch (attribute.Kind())
254254
{
255255
case SyntaxKind.XmlTextAttribute:
256-
ClassifyXmlTextTokens(((XmlTextAttributeSyntax)attribute).TextTokens);
256+
// Since the langword attribute in `<see langword="..." />` is not parsed into its own
257+
// SyntaxNode as cref is, we need to handle it specially.
258+
if (IsLangWordAttribute(attribute))
259+
ClassifyLangWordTextTokenList(((XmlTextAttributeSyntax)attribute).TextTokens);
260+
else
261+
ClassifyXmlTextTokens(((XmlTextAttributeSyntax)attribute).TextTokens);
262+
257263
break;
258264
case SyntaxKind.XmlCrefAttribute:
259265
ClassifyNode(((XmlCrefAttributeSyntax)attribute).Cref);
@@ -264,6 +270,47 @@ private void ClassifyXmlAttribute(XmlAttributeSyntax attribute)
264270
}
265271

266272
AddXmlClassification(attribute.EndQuoteToken, ClassificationTypeNames.XmlDocCommentAttributeQuotes);
273+
274+
static bool IsLangWordAttribute(XmlAttributeSyntax attribute)
275+
{
276+
return attribute.Name.LocalName.Text == DocumentationCommentXmlNames.LangwordAttributeName && IsSeeElement(attribute.Parent);
277+
}
278+
279+
static bool IsSeeElement(SyntaxNode? node)
280+
{
281+
return node is XmlElementStartTagSyntax { Name: XmlNameSyntax { Prefix: null, LocalName: SyntaxToken { Text: DocumentationCommentXmlNames.SeeElementName } } }
282+
|| node is XmlEmptyElementSyntax { Name: XmlNameSyntax { Prefix: null, LocalName: SyntaxToken { Text: DocumentationCommentXmlNames.SeeElementName } } };
283+
}
284+
}
285+
286+
private void ClassifyLangWordTextTokenList(SyntaxTokenList list)
287+
{
288+
foreach (var token in list)
289+
{
290+
if (token.HasLeadingTrivia)
291+
ClassifyXmlTrivia(token.LeadingTrivia);
292+
293+
ClassifyLangWordTextToken(token);
294+
295+
if (token.HasTrailingTrivia)
296+
ClassifyXmlTrivia(token.TrailingTrivia);
297+
}
298+
}
299+
300+
private void ClassifyLangWordTextToken(SyntaxToken token)
301+
{
302+
var kind = SyntaxFacts.GetKeywordKind(token.Text);
303+
if (kind is SyntaxKind.None)
304+
kind = SyntaxFacts.GetContextualKeywordKind(token.Text);
305+
306+
if (kind is SyntaxKind.None)
307+
{
308+
ClassifyXmlTextToken(token);
309+
return;
310+
}
311+
312+
var isControlKeyword = ClassificationHelpers.IsControlKeywordKind(kind) || ClassificationHelpers.IsControlStatementKind(kind);
313+
AddClassification(token, isControlKeyword ? ClassificationTypeNames.ControlKeyword : ClassificationTypeNames.Keyword);
267314
}
268315

269316
private void ClassifyXmlText(XmlTextSyntax node)

0 commit comments

Comments
 (0)