提交 ed31ebb0 编写于 作者: M manishv

Fix for bug 1096600.

1) Add VB and C# compiler tests to verify that all configurable compiler diagnostics (non-errors) that show up in the ruleset editor have a non-null and non-empty Title and Category.
2) Commented out some more unused diagnostic IDs in both compilers that were found through these tests.
3) Add title resource strings for hidden and info compiler diagnostics. These are configurable and show up in the ruleset editor. (changeset 1390346)
上级 1345b768
......@@ -8620,6 +8620,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to Unused extern alias..
/// </summary>
internal static string HDN_UnusedExternAlias_Title {
get {
return ResourceManager.GetString("HDN_UnusedExternAlias_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unnecessary using directive..
/// </summary>
......@@ -8629,6 +8638,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to Unnecessary using directive..
/// </summary>
internal static string HDN_UnusedUsingDirective_Title {
get {
return ResourceManager.GetString("HDN_UnusedUsingDirective_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to anonymous method.
/// </summary>
......@@ -9440,6 +9458,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to Skip loading types in analyzer assembly that fail due to a ReflectionTypeLoadException..
/// </summary>
internal static string INF_UnableToLoadSomeTypesInAnalyzer_Title {
get {
return ResourceManager.GetString("INF_UnableToLoadSomeTypesInAnalyzer_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Called GetDeclarationName for a declaration node that can possibly contain multiple variable declarators..
/// </summary>
......@@ -9728,6 +9755,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to Alignment value has a magnitude that may result in a large formatted string..
/// </summary>
internal static string WRN_AlignmentMagnitude_Title {
get {
return ResourceManager.GetString("WRN_AlignmentMagnitude_Title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The result of the expression is always &apos;null&apos; of type &apos;{0}&apos;.
/// </summary>
......@@ -11189,9 +11225,9 @@ internal class CSharpResources {
/// <summary>
/// Looks up a localized string similar to Type defines operator == or operator != but does not override Object.Equals(object o).
/// </summary>
internal static string WRN_EqualityOpWithoutEquals_Type {
internal static string WRN_EqualityOpWithoutEquals_Title {
get {
return ResourceManager.GetString("WRN_EqualityOpWithoutEquals_Type", resourceCulture);
return ResourceManager.GetString("WRN_EqualityOpWithoutEquals_Title", resourceCulture);
}
}
......
......@@ -1769,7 +1769,7 @@ If such a class is used as a base class and if the deriving class defines a dest
<data name="WRN_EqualityOpWithoutEquals" xml:space="preserve">
<value>'{0}' defines operator == or operator != but does not override Object.Equals(object o)</value>
</data>
<data name="WRN_EqualityOpWithoutEquals_Type" xml:space="preserve">
<data name="WRN_EqualityOpWithoutEquals_Title" xml:space="preserve">
<value>Type defines operator == or operator != but does not override Object.Equals(object o)</value>
</data>
<data name="WRN_EqualityOpWithoutGetHashCode" xml:space="preserve">
......@@ -2924,7 +2924,7 @@ A catch() block after a catch (System.Exception e) block can catch non-CLS excep
<value>Assembly reference is invalid and cannot be resolved</value>
</data>
<data name="WRN_InvalidAssemblyName_Description" xml:space="preserve">
<value>This warning indicates that an attribute, such as InternalsVisibleToAttribute, was not specified correctly.</value>
<value>This warning indicates that an attribute, such as InternalsVisibleToAttribute, was not specified correctly.</value>
</data>
<data name="WRN_UnifyReferenceMajMin" xml:space="preserve">
<value>Assuming assembly reference '{0}' used by '{1}' matches identity '{2}' of '{3}', you may need to supply runtime policy</value>
......@@ -4607,4 +4607,16 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="WRN_AlignmentMagnitude" xml:space="preserve">
<value>Alignment value {0} has a magnitude greater than {1} and may result in a large formatted string.</value>
</data>
</root>
<data name="HDN_UnusedExternAlias_Title" xml:space="preserve">
<value>Unused extern alias.</value>
</data>
<data name="HDN_UnusedUsingDirective_Title" xml:space="preserve">
<value>Unnecessary using directive.</value>
</data>
<data name="INF_UnableToLoadSomeTypesInAnalyzer_Title" xml:space="preserve">
<value>Skip loading types in analyzer assembly that fail due to a ReflectionTypeLoadException.</value>
</data>
<data name="WRN_AlignmentMagnitude_Title" xml:space="preserve">
<value>Alignment value has a magnitude that may result in a large formatted string.</value>
</data>
</root>
\ No newline at end of file
......@@ -316,7 +316,7 @@ internal enum ErrorCode
WRN_CmpAlwaysFalse = 464,
WRN_FinalizeMethod = 465,
ERR_ExplicitImplParams = 466,
WRN_AmbigLookupMeth = 467, //no longer issued in Roslyn, but preserved to keep /nowarn working
// WRN_AmbigLookupMeth = 467, //no longer issued in Roslyn
//ERR_SameFullNameThisAggThisAgg = 468, no longer used in Roslyn
WRN_GotoCaseShouldConvert = 469,
ERR_MethodImplementingAccessor = 470,
......@@ -850,7 +850,7 @@ internal enum ErrorCode
WRN_IllegalPPChecksum = 1695,
WRN_EndOfPPLineExpected = 1696,
WRN_ConflictingChecksum = 1697,
WRN_AssumedMatchThis = 1698,
// WRN_AssumedMatchThis = 1698, // This error code is unused.
// WRN_UseSwitchInsteadOfAttribute = 1699, // This error code is unused.
WRN_InvalidAssemblyName = 1700,
WRN_UnifyReferenceMajMin = 1701,
......@@ -861,7 +861,7 @@ internal enum ErrorCode
//ERR_AnonMethNotAllowed = 1706, Unused in Roslyn. Previously given when a lambda was supplied as an attribute argument.
// WRN_DelegateNewMethBind = 1707, // This error code is unused.
ERR_FixedNeedsLvalue = 1708,
WRN_EmptyFileName = 1709,
// WRN_EmptyFileName = 1709, // This error code is unused.
WRN_DuplicateTypeParamTag = 1710,
WRN_UnmatchedTypeParamTag = 1711,
WRN_MissingTypeParamTag = 1712,
......@@ -916,7 +916,7 @@ internal enum ErrorCode
WRN_ReferencedAssemblyReferencesLinkedPIA = 1762,
ERR_NotNullRefDefaultParameter = 1763,
ERR_FixedLocalInLambda = 1764,
WRN_TypeNotFoundForNoPIAWarning = 1765,
// WRN_TypeNotFoundForNoPIAWarning = 1765, // This error code is unused.
ERR_MissingMethodOnSourceInterface = 1766,
ERR_MissingSourceInterface = 1767,
ERR_GenericsUsedInNoPIAType = 1768,
......
......@@ -197,7 +197,6 @@ internal static int GetWarningLevel(ErrorCode code)
case ErrorCode.WRN_GlobalAliasDefn:
case ErrorCode.WRN_AlwaysNull:
case ErrorCode.WRN_CmpAlwaysFalse:
case ErrorCode.WRN_AmbigLookupMeth:
case ErrorCode.WRN_GotoCaseShouldConvert:
case ErrorCode.WRN_NubExprIsConstBool:
case ErrorCode.WRN_NubExprIsConstBool2:
......@@ -211,7 +210,6 @@ internal static int GetWarningLevel(ErrorCode code)
case ErrorCode.WRN_UnmatchedParamTag:
case ErrorCode.WRN_UnprocessedXMLComment:
case ErrorCode.WRN_InvalidSearchPathDir:
case ErrorCode.WRN_AssumedMatchThis:
case ErrorCode.WRN_UnifyReferenceMajMin:
case ErrorCode.WRN_DuplicateTypeParamTag:
case ErrorCode.WRN_UnmatchedTypeParamTag:
......@@ -266,11 +264,9 @@ internal static int GetWarningLevel(ErrorCode code)
case ErrorCode.WRN_IllegalPPChecksum:
case ErrorCode.WRN_EndOfPPLineExpected:
case ErrorCode.WRN_ConflictingChecksum:
case ErrorCode.WRN_EmptyFileName:
case ErrorCode.WRN_DotOnDefault:
case ErrorCode.WRN_BadXMLRefTypeVar:
case ErrorCode.WRN_ReferencedAssemblyReferencesLinkedPIA:
case ErrorCode.WRN_TypeNotFoundForNoPIAWarning:
case ErrorCode.WRN_MultipleRuntimeImplementationMatches:
case ErrorCode.WRN_MultipleRuntimeOverrideMatches:
case ErrorCode.WRN_FileAlreadyIncluded:
......
......@@ -2892,12 +2892,12 @@ public void WarningsParse()
Assert.Equal(4, parsedArgs.CompilationOptions.WarningLevel);
AssertSpecificDiagnostics(new[] { 1062, 1066, 1734 }, new[] { ReportDiagnostic.Default, ReportDiagnostic.Default, ReportDiagnostic.Default }, parsedArgs);
parsedArgs = CSharpCommandLineParser.Default.Parse(new string[] { "/warnaserror+:1062,1066,1734", "/warnaserror-:1765,1974", "a.cs" }, baseDirectory);
parsedArgs = CSharpCommandLineParser.Default.Parse(new string[] { "/warnaserror+:1062,1066,1734", "/warnaserror-:1762,1974", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(ReportDiagnostic.Default, parsedArgs.CompilationOptions.GeneralDiagnosticOption);
Assert.Equal(4, parsedArgs.CompilationOptions.WarningLevel);
AssertSpecificDiagnostics(
new[] { 1062, 1066, 1734, 1765, 1974 },
new[] { 1062, 1066, 1734, 1762, 1974 },
new[] { ReportDiagnostic.Error, ReportDiagnostic.Error, ReportDiagnostic.Error, ReportDiagnostic.Default, ReportDiagnostic.Default },
parsedArgs);
......@@ -2944,17 +2944,17 @@ public void WarningsParse()
Assert.Equal(4, parsedArgs.CompilationOptions.WarningLevel);
AssertSpecificDiagnostics(new[] { 1062, 1066, 1734 }, new[] { ReportDiagnostic.Suppress, ReportDiagnostic.Suppress, ReportDiagnostic.Suppress }, parsedArgs);
parsedArgs = CSharpCommandLineParser.Default.Parse(new string[] { "/nowarn:1062,1066,1734", "/warnaserror:1066,1765", "a.cs" }, baseDirectory);
parsedArgs = CSharpCommandLineParser.Default.Parse(new string[] { "/nowarn:1062,1066,1734", "/warnaserror:1066,1762", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(ReportDiagnostic.Default, parsedArgs.CompilationOptions.GeneralDiagnosticOption);
Assert.Equal(4, parsedArgs.CompilationOptions.WarningLevel);
AssertSpecificDiagnostics(new[] { 1062, 1066, 1734, 1765 }, new[] { ReportDiagnostic.Suppress, ReportDiagnostic.Suppress, ReportDiagnostic.Suppress, ReportDiagnostic.Error }, parsedArgs);
AssertSpecificDiagnostics(new[] { 1062, 1066, 1734, 1762 }, new[] { ReportDiagnostic.Suppress, ReportDiagnostic.Suppress, ReportDiagnostic.Suppress, ReportDiagnostic.Error }, parsedArgs);
parsedArgs = CSharpCommandLineParser.Default.Parse(new string[] { "/warnaserror:1066,1765", "/nowarn:1062,1066,1734", "a.cs" }, baseDirectory);
parsedArgs = CSharpCommandLineParser.Default.Parse(new string[] { "/warnaserror:1066,1762", "/nowarn:1062,1066,1734", "a.cs" }, baseDirectory);
parsedArgs.Errors.Verify();
Assert.Equal(ReportDiagnostic.Default, parsedArgs.CompilationOptions.GeneralDiagnosticOption);
Assert.Equal(4, parsedArgs.CompilationOptions.WarningLevel);
AssertSpecificDiagnostics(new[] { 1062, 1066, 1734, 1765 }, new[] { ReportDiagnostic.Suppress, ReportDiagnostic.Suppress, ReportDiagnostic.Suppress, ReportDiagnostic.Error }, parsedArgs);
AssertSpecificDiagnostics(new[] { 1062, 1066, 1734, 1762 }, new[] { ReportDiagnostic.Suppress, ReportDiagnostic.Suppress, ReportDiagnostic.Suppress, ReportDiagnostic.Error }, parsedArgs);
}
[Fact]
......
......@@ -10,6 +10,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Diagnostics.CSharp;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
......@@ -812,5 +813,43 @@ public class B
.VerifyCSharpAnalyzerDiagnostics(analyzers, null, null,
Diagnostic("MyFieldDiagnostic", @"public string field = ""field"";").WithLocation(4, 5));
}
[Fact, WorkItem(1096600)]
void TestDescriptorForConfigurableCompilerDiagnostics()
{
// Verify that all configurable compiler diagnostics, i.e. all non-error diagnostics,
// have a non-null and non-empty Title and Category.
// These diagnostic descriptor fields show up in the ruleset editor and hence must have a valid value.
var analyzer = new CSharpCompilerDiagnosticAnalyzer();
foreach (var descriptor in analyzer.SupportedDiagnostics)
{
Assert.Equal(descriptor.IsEnabledByDefault, true);
if (descriptor.IsNotConfigurable())
{
continue;
}
var title = descriptor.Title.ToString();
if (string.IsNullOrEmpty(title))
{
var id = Int32.Parse(descriptor.Id.Substring(2));
var missingResource = Enum.GetName(typeof(ErrorCode), id) + "_Title";
var message = string.Format("Add resource string named '{0}' for Title of '{1}' to '{2}'", missingResource, descriptor.Id, nameof(CSharpResources));
// This assert will fire if you are adding a new compiler diagnostic (non-error severity),
// but did not add a title resource string for the diagnostic.
Assert.True(false, message);
}
var category = descriptor.Category;
if (string.IsNullOrEmpty(title))
{
var message = string.Format("'{0}' must have a non-null non-empty 'Category'", descriptor.Id);
Assert.True(false, message);
}
}
}
}
}
......@@ -165,7 +165,6 @@ public void WarningLevel_2()
Assert.Equal(2, ErrorFacts.GetWarningLevel(ErrorCode.WRN_UnmatchedParamRefTag));
Assert.Equal(2, ErrorFacts.GetWarningLevel(ErrorCode.WRN_UnmatchedTypeParamRefTag));
Assert.Equal(1, ErrorFacts.GetWarningLevel(ErrorCode.WRN_ReferencedAssemblyReferencesLinkedPIA));
Assert.Equal(1, ErrorFacts.GetWarningLevel(ErrorCode.WRN_TypeNotFoundForNoPIAWarning));
Assert.Equal(2, ErrorFacts.GetWarningLevel(ErrorCode.WRN_DynamicDispatchToConditionalMethod));
Assert.Equal(3, ErrorFacts.GetWarningLevel(ErrorCode.WRN_IsDynamicIsConfusing));
Assert.Equal(2, ErrorFacts.GetWarningLevel(ErrorCode.WRN_NoSources));
......
......@@ -1694,10 +1694,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
WRN_SynthMemberShadowsSynthMember7 = 40018
WRN_UseOfObsoletePropertyAccessor3 = 40019
WRN_UseOfObsoletePropertyAccessor2 = 40020
WRN_MemberShadowsMemberInModule5 = 40021 ' no repro in legacy test, most probably not reachable
WRN_SynthMemberShadowsMemberInModule5 = 40022 ' no repro in legacy test, most probably not reachable
WRN_MemberShadowsSynthMemberInModule6 = 40023 ' no repro in legacy test, most probably not reachable
WRN_SynthMemberShadowsSynthMemberMod7 = 40024 ' no repro in legacy test, most probably not reachable
' WRN_MemberShadowsMemberInModule5 = 40021 ' no repro in legacy test, most probably not reachable. Unused in Roslyn.
' WRN_SynthMemberShadowsMemberInModule5 = 40022 ' no repro in legacy test, most probably not reachable. Unused in Roslyn.
' WRN_MemberShadowsSynthMemberInModule6 = 40023 ' no repro in legacy test, most probably not reachable. Unused in Roslyn.
' WRN_SynthMemberShadowsSynthMemberMod7 = 40024 ' no repro in legacy test, most probably not reachable. Unused in Roslyn.
WRN_FieldNotCLSCompliant1 = 40025
WRN_BaseClassNotCLSCompliant2 = 40026
WRN_ProcTypeNotCLSCompliant1 = 40027
......@@ -1716,7 +1716,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
WRN_OptionalValueNotCLSCompliant1 = 40042
WRN_CLSAttrInvalidOnGetSet = 40043
WRN_TypeConflictButMerged6 = 40046
WRN_TypeConflictButMerged7 = 40047 ' deprecated
' WRN_TypeConflictButMerged7 = 40047 ' deprecated
WRN_ShadowingGenericParamWithParam1 = 40048
WRN_CannotFindStandardLibrary1 = 40049
WRN_EventDelegateTypeNotCLSCompliant2 = 40050
......@@ -1749,11 +1749,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
WRN_RecursiveAddHandlerCall = 41998
WRN_ImplicitConversionCopyBack = 41999
WRN_MustShadowOnMultipleInheritance2 = 42000
WRN_ObsoleteClassInitialize = 42001 ' deprecated
WRN_ObsoleteClassTerminate = 42002 ' deprecated
' WRN_ObsoleteClassInitialize = 42001 ' deprecated
' WRN_ObsoleteClassTerminate = 42002 ' deprecated
WRN_RecursiveOperatorCall = 42004
WRN_IndirectlyImplementedBaseMember5 = 42014 ' deprecated
WRN_ImplementedBaseMember4 = 42015 ' deprecated
' WRN_IndirectlyImplementedBaseMember5 = 42014 ' deprecated
' WRN_ImplementedBaseMember4 = 42015 ' deprecated
WRN_ImplicitConversionSubst1 = 42016 '// populated by 42350/42332/42336/42337/42338/42339/42340
WRN_LateBindingResolution = 42017
......@@ -1834,10 +1834,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
WRN_LambdaPassedToRemoveHandler = 42326
WRN_LiftControlVariableQuery = 42327
WRN_RelDelegatePassedToRemoveHandler = 42328
WRN_QueryMissingAsClauseinVarDecl = 42329
' WRN_QueryMissingAsClauseinVarDecl = 42329 ' unused in Roslyn.
WRN_LiftUsingVariableInLambda1 = 42330
WRN_LiftUsingVariableInQuery1 = 42331
' WRN_LiftUsingVariableInLambda1 = 42330 ' unused in Roslyn.
' WRN_LiftUsingVariableInQuery1 = 42331 ' unused in Roslyn.
WRN_AmbiguousCastConversion2 = 42332 '// substitutes into 42016
WRN_VarianceDeclarationAmbiguous3 = 42333
WRN_ArrayInitNoTypeObjectAssumed = 42334
......
......@@ -11568,6 +11568,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Get
End Property
'''<summary>
''' Looks up a localized string similar to Unused import clause..
'''</summary>
Friend ReadOnly Property HDN_UnusedImportClause_Title() As String
Get
Return ResourceManager.GetString("HDN_UnusedImportClause_Title", resourceCulture)
End Get
End Property
'''<summary>
''' Looks up a localized string similar to Unused import statement..
'''</summary>
......@@ -11577,6 +11586,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Get
End Property
'''<summary>
''' Looks up a localized string similar to Unused import statement..
'''</summary>
Friend ReadOnly Property HDN_UnusedImportStatement_Title() As String
Get
Return ResourceManager.GetString("HDN_UnusedImportStatement_Title", resourceCulture)
End Get
End Property
'''<summary>
''' Looks up a localized string similar to IdentifierSyntax not within syntax tree.
'''</summary>
......@@ -11703,6 +11721,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Get
End Property
'''<summary>
''' Looks up a localized string similar to Skip loading types in analyzer assembly that fail due to a ReflectionTypeLoadException..
'''</summary>
Friend ReadOnly Property INF_UnableToLoadSomeTypesInAnalyzer_Title() As String
Get
Return ResourceManager.GetString("INF_UnableToLoadSomeTypesInAnalyzer_Title", resourceCulture)
End Get
End Property
'''<summary>
''' Looks up a localized string similar to Location must be provided in order to provide minimal type qualification..
'''</summary>
......
......@@ -5172,6 +5172,9 @@
<data name="INF_UnableToLoadSomeTypesInAnalyzer" xml:space="preserve">
<value>Skipping some types in analyzer assembly {0} due to a ReflectionTypeLoadException : {1}.</value>
</data>
<data name="INF_UnableToLoadSomeTypesInAnalyzer_Title" xml:space="preserve">
<value>Skip loading types in analyzer assembly that fail due to a ReflectionTypeLoadException.</value>
</data>
<data name="ERR_CantReadRulesetFile" xml:space="preserve">
<value>Error reading ruleset file {0} - {1}</value>
</data>
......@@ -5229,4 +5232,10 @@
<data name="ERR_InterpolationFormatWhitespace" xml:space="preserve">
<value>Format specifier may not contain trailing whitespace.</value>
</data>
<data name="HDN_UnusedImportClause_Title" xml:space="preserve">
<value>Unused import clause.</value>
</data>
<data name="HDN_UnusedImportStatement_Title" xml:space="preserve">
<value>Unused import statement.</value>
</data>
</root>
......@@ -5,6 +5,7 @@ Imports System.Globalization
Imports System.Runtime.Serialization
Imports System.Threading
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Diagnostics.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Roslyn.Test.Utilities
......@@ -583,5 +584,38 @@ End Class
compilation.VerifyDiagnostics()
compilation.VerifyAnalyzerDiagnostics({analyzer}, Nothing, AnalyzerDiagnostic("CodeBlockDiagnostic", <![CDATA[Public Sub Method()]]>))
End Sub
<Fact, WorkItem(1096600)>
Private Sub TestDescriptorForConfigurableCompilerDiagnostics()
' Verify that all configurable compiler diagnostics, i.e. all non-error diagnostics,
' have a non-null and non-empty Title and Category.
' These diagnostic descriptor fields show up in the ruleset editor and hence must have a valid value.
Dim analyzer = New VisualBasicCompilerDiagnosticAnalyzer()
For Each descriptor In analyzer.SupportedDiagnostics
Assert.Equal(descriptor.IsEnabledByDefault, True)
If descriptor.IsNotConfigurable() Then
Continue For
End If
Dim title = descriptor.Title.ToString()
If String.IsNullOrEmpty(title) Then
Dim id = Integer.Parse(descriptor.Id.Substring(2))
Dim missingResource = [Enum].GetName(GetType(ERRID), id) & "_Title"
Dim message = String.Format("Add resource string named '{0}' for Title of '{1}' to '{2}'", missingResource, descriptor.Id, NameOf(VBResources))
' This assert will fire if you are adding a new compiler diagnostic (non-error severity),
' but did not add a title resource string for the diagnostic.
Assert.True(False, message)
End If
Dim category = descriptor.Category
If String.IsNullOrEmpty(title) Then
Dim message = String.Format("'{0}' must have a non-null non-empty 'Category'", descriptor.Id)
Assert.True(False, message)
End If
Next
End Sub
End Class
End Namespace
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册