diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs
index ba9c1c76ae68f..69c3de99c3b5a 100644
--- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs
+++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs
@@ -77,6 +77,9 @@ public enum SyntaxKind : ushort
/// Represents .. token.
DotDotToken = 8222,
+ // Values ranging from 8193 (TildeToken) to 8287 (GreaterThanGreaterThanGreaterThanEqualsToken) are reserved for punctuation kinds.
+ // This gap is included within that range. So if you add a value here make sure `SyntaxFacts.GetPunctuationKinds` includes it in the returned enumeration
+
// additional xml tokens
/// Represents /> token.
SlashGreaterThanToken = 8232, // xml empty element end
@@ -95,6 +98,9 @@ public enum SyntaxKind : ushort
/// Represents ?> token.
XmlProcessingInstructionEndToken = 8239, // ?>
+ // Values ranging from 8193 (TildeToken) to 8287 (GreaterThanGreaterThanGreaterThanEqualsToken) are reserved for punctuation kinds.
+ // This gap is included within that range. So if you add a value here make sure `SyntaxFacts.GetPunctuationKinds` includes it in the returned enumeration
+
// compound punctuation
/// Represents || token.
BarBarToken = 8260,
diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs
index 652092a89b94f..6a19e29e2e469 100644
--- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs
+++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs
@@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using System.Collections.Generic;
+using System.Diagnostics;
namespace Microsoft.CodeAnalysis.CSharp
{
@@ -17,6 +19,7 @@ public static IEnumerable GetReservedKeywordKinds()
{
for (int i = (int)SyntaxKind.BoolKeyword; i <= (int)SyntaxKind.ImplicitKeyword; i++)
{
+ Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i));
yield return (SyntaxKind)i;
}
}
@@ -142,9 +145,10 @@ public static IEnumerable GetPreprocessorKeywordKinds()
yield return SyntaxKind.TrueKeyword;
yield return SyntaxKind.FalseKeyword;
yield return SyntaxKind.DefaultKeyword;
- yield return SyntaxKind.HiddenKeyword;
+
for (int i = (int)SyntaxKind.ElifKeyword; i <= (int)SyntaxKind.RestoreKeyword; i++)
{
+ Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i));
yield return (SyntaxKind)i;
}
}
@@ -172,10 +176,26 @@ private static bool IsDebuggerSpecialPunctuation(SyntaxKind kind)
public static IEnumerable GetPunctuationKinds()
{
- for (int i = (int)SyntaxKind.TildeToken; i <= (int)SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken; i++)
+ for (int i = (int)SyntaxKind.TildeToken; i <= (int)SyntaxKind.DotDotToken; i++)
+ {
+ Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i));
+ yield return (SyntaxKind)i;
+ }
+
+ for (int i = (int)SyntaxKind.SlashGreaterThanToken; i <= (int)SyntaxKind.XmlProcessingInstructionEndToken; i++)
{
+ Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i));
yield return (SyntaxKind)i;
}
+
+ for (int i = (int)SyntaxKind.BarBarToken; i <= (int)SyntaxKind.QuestionQuestionEqualsToken; i++)
+ {
+ Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i));
+ yield return (SyntaxKind)i;
+ }
+
+ yield return SyntaxKind.GreaterThanGreaterThanGreaterThanToken;
+ yield return SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken;
}
public static bool IsPunctuationOrKeyword(SyntaxKind kind)
@@ -1148,7 +1168,12 @@ public static IEnumerable GetContextualKeywordKinds()
{
for (int i = (int)SyntaxKind.YieldKeyword; i <= (int)SyntaxKind.FileKeyword; i++)
{
- yield return (SyntaxKind)i;
+ // 8441 corresponds to a deleted kind (DataKeyword) that was previously shipped.
+ if (i != 8441)
+ {
+ Debug.Assert(Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i));
+ yield return (SyntaxKind)i;
+ }
}
}
diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTests.cs
index f8f0d20bfe449..b9d54c3d01f96 100644
--- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTests.cs
+++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxTests.cs
@@ -5,6 +5,9 @@
#nullable disable
using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
@@ -276,5 +279,68 @@ public void IsAttributeTargetSpecifier()
}
}
}
+
+ [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72300")]
+ public void TestAllKindsReturnedFromGetKindsMethodsExist()
+ {
+ foreach (var method in typeof(SyntaxFacts).GetMethods(BindingFlags.Public | BindingFlags.Static))
+ {
+ if (method.ReturnType == typeof(IEnumerable) && method.GetParameters() is [])
+ {
+ foreach (var kind in (IEnumerable)method.Invoke(null, null))
+ {
+ Assert.True(Enum.IsDefined(typeof(SyntaxKind), kind), $"Nonexistent kind '{kind}' returned from method '{method.Name}'");
+ }
+ }
+ }
+ }
+
+ [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72300")]
+ [InlineData(nameof(SyntaxFacts.GetContextualKeywordKinds), SyntaxKind.YieldKeyword, SyntaxKind.ElifKeyword)]
+ [InlineData(nameof(SyntaxFacts.GetPunctuationKinds), SyntaxKind.TildeToken, SyntaxKind.BoolKeyword)]
+ [InlineData(nameof(SyntaxFacts.GetReservedKeywordKinds), SyntaxKind.BoolKeyword, SyntaxKind.YieldKeyword)]
+ public void TestRangeBasedGetKindsMethodsReturnExpectedResults(string methodName, SyntaxKind lowerBoundInclusive, SyntaxKind upperBoundExclusive)
+ {
+ var method = typeof(SyntaxFacts).GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
+
+ Assert.NotNull(method);
+ Assert.Equal(0, method.GetParameters().Length);
+ Assert.Equal(typeof(IEnumerable), method.ReturnType);
+
+ var returnedKindsInts = ((IEnumerable)method.Invoke(null, null)).Select(static k => (int)k).ToHashSet();
+
+ for (int i = (int)lowerBoundInclusive; i < (int)upperBoundExclusive; i++)
+ {
+ if (Enum.IsDefined(typeof(SyntaxKind), (SyntaxKind)i))
+ {
+ Assert.True(returnedKindsInts.Remove(i));
+ }
+ else
+ {
+ Assert.DoesNotContain(i, returnedKindsInts);
+ }
+ }
+
+ // We've already removed all expected kinds from the set. It should be empty now
+ Assert.Empty(returnedKindsInts);
+ }
+
+ [Fact]
+ public void TestGetPreprocessorKeywordKindsReturnsExpectedResults()
+ {
+ var returnedKindsInts = SyntaxFacts.GetPreprocessorKeywordKinds().Select(static k => (int)k).ToHashSet();
+
+ Assert.True(returnedKindsInts.Remove((int)SyntaxKind.TrueKeyword));
+ Assert.True(returnedKindsInts.Remove((int)SyntaxKind.FalseKeyword));
+ Assert.True(returnedKindsInts.Remove((int)SyntaxKind.DefaultKeyword));
+
+ for (int i = (int)SyntaxKind.ElifKeyword; i < (int)SyntaxKind.ReferenceKeyword; i++)
+ {
+ Assert.True(returnedKindsInts.Remove(i));
+ }
+
+ // We've already removed all expected kinds from the set. It should be empty now
+ Assert.Empty(returnedKindsInts);
+ }
}
}
diff --git a/src/Compilers/VisualBasic/Test/Syntax/Syntax/SyntaxFactsTest.vb b/src/Compilers/VisualBasic/Test/Syntax/Syntax/SyntaxFactsTest.vb
index 3b82a4a5be9fd..0c1d011590a7e 100644
--- a/src/Compilers/VisualBasic/Test/Syntax/Syntax/SyntaxFactsTest.vb
+++ b/src/Compilers/VisualBasic/Test/Syntax/Syntax/SyntaxFactsTest.vb
@@ -3,6 +3,7 @@
' See the LICENSE file in the project root for more information.
Imports System.IO
+Imports System.Reflection
Imports System.Text
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.VisualBasic
@@ -1262,4 +1263,15 @@ End Module
Assert.Equal(isReserved, SyntaxFacts.IsReservedTupleElementName(elementName))
End Sub
+
+ Public Sub TestAllKindsReturnedFromGetKindsMethodsExist()
+ For Each method In GetType(SyntaxFacts).GetMethods(BindingFlags.Public Or BindingFlags.Static)
+ If method.ReturnType = GetType(IEnumerable(Of SyntaxKind)) AndAlso method.GetParameters().Length = 0 Then
+ For Each kind As SyntaxKind In DirectCast(method.Invoke(Nothing, Nothing), IEnumerable(Of SyntaxKind))
+ Assert.True([Enum].IsDefined(GetType(SyntaxKind), kind), $"Nonexistent kind '{kind}' returned from method '{method.Name}'")
+ Next
+ End If
+ Next
+ End Sub
+
End Class