diff --git a/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md b/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md
index f2b7fad657..5ef7f5dc72 100644
--- a/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md
+++ b/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md
@@ -1,2 +1,8 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
+
+### New Rules
+
+Rule ID | Category | Severity | Notes
+--------|----------|----------|-------
+MSTEST0058 | Usage | Warning | DoNotUseParallelizeAndDoNotParallelizeTogetherAnalyzer
diff --git a/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs b/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
index d7f58d9e67..712872903e 100644
--- a/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
+++ b/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
@@ -62,4 +62,5 @@ internal static class DiagnosticIds
public const string IgnoreStringMethodReturnValueRuleId = "MSTEST0055";
public const string TestMethodAttributeShouldSetDisplayNameCorrectlyRuleId = "MSTEST0056";
public const string TestMethodAttributeShouldPropagateSourceInformationRuleId = "MSTEST0057";
+ public const string DoNotUseParallelizeAndDoNotParallelizeTogetherRuleId = "MSTEST0058";
}
diff --git a/src/Analyzers/MSTest.Analyzers/Resources.resx b/src/Analyzers/MSTest.Analyzers/Resources.resx
index 0c8a17d969..01f07ad530 100644
--- a/src/Analyzers/MSTest.Analyzers/Resources.resx
+++ b/src/Analyzers/MSTest.Analyzers/Resources.resx
@@ -684,4 +684,16 @@ The type declaring these methods should also respect the following rules:
Methods like Contains, StartsWith, and EndsWith return boolean values that indicate whether the condition was met. Ignoring these return values is likely a mistake.
+
+ Do not use both '[Parallelize]' and '[DoNotParallelize]' attributes
+ {Locked="Parallelize","DoNotParallelize"}
+
+
+ Assembly has both '[Parallelize]' and '[DoNotParallelize]' attributes which creates ambiguity
+ {Locked="[Parallelize]","[DoNotParallelize]"}
+
+
+ An assembly should have either [Parallelize] or [DoNotParallelize] attribute, but not both. Having both attributes creates an ambiguous configuration. When both are present, [DoNotParallelize] takes precedence and parallelization will be disabled.
+ {Locked="[Parallelize]","[DoNotParallelize]"}
+
\ No newline at end of file
diff --git a/src/Analyzers/MSTest.Analyzers/UseParallelizeAttributeAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/UseParallelizeAttributeAnalyzer.cs
index c55f840285..416489c1b6 100644
--- a/src/Analyzers/MSTest.Analyzers/UseParallelizeAttributeAnalyzer.cs
+++ b/src/Analyzers/MSTest.Analyzers/UseParallelizeAttributeAnalyzer.cs
@@ -14,6 +14,7 @@ namespace MSTest.Analyzers;
///
/// MSTEST0001: .
+/// MSTEST0058: .
///
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class UseParallelizeAttributeAnalyzer : DiagnosticAnalyzer
@@ -22,6 +23,10 @@ public sealed class UseParallelizeAttributeAnalyzer : DiagnosticAnalyzer
private static readonly LocalizableResourceString MessageFormat = new(nameof(Resources.UseParallelizeAttributeAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableResourceString Description = new(nameof(Resources.UseParallelizeAttributeAnalyzerDescription), Resources.ResourceManager, typeof(Resources));
+ private static readonly LocalizableResourceString BothAttributesTitle = new(nameof(Resources.DoNotUseParallelizeAndDoNotParallelizeTogetherTitle), Resources.ResourceManager, typeof(Resources));
+ private static readonly LocalizableResourceString BothAttributesMessageFormat = new(nameof(Resources.DoNotUseParallelizeAndDoNotParallelizeTogetherMessageFormat), Resources.ResourceManager, typeof(Resources));
+ private static readonly LocalizableResourceString BothAttributesDescription = new(nameof(Resources.DoNotUseParallelizeAndDoNotParallelizeTogetherDescription), Resources.ResourceManager, typeof(Resources));
+
///
public static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
DiagnosticIds.UseParallelizedAttributeRuleId,
@@ -32,9 +37,19 @@ public sealed class UseParallelizeAttributeAnalyzer : DiagnosticAnalyzer
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
+ ///
+ public static readonly DiagnosticDescriptor DoNotUseBothAttributesRule = DiagnosticDescriptorHelper.Create(
+ DiagnosticIds.DoNotUseParallelizeAndDoNotParallelizeTogetherRuleId,
+ BothAttributesTitle,
+ BothAttributesMessageFormat,
+ BothAttributesDescription,
+ Category.Usage,
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
///
public override ImmutableArray SupportedDiagnostics { get; }
- = ImmutableArray.Create(Rule);
+ = ImmutableArray.Create(Rule, DoNotUseBothAttributesRule);
///
public override void Initialize(AnalysisContext context)
@@ -47,35 +62,52 @@ public override void Initialize(AnalysisContext context)
private static void AnalyzeCompilation(CompilationAnalysisContext context)
{
- bool hasTestAdapter = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.IsMSTestTestAdapterReferenced", out string? isAdapterReferenced) &&
- bool.TryParse(isAdapterReferenced, out bool isAdapterReferencedValue) &&
- isAdapterReferencedValue;
-
- if (!hasTestAdapter)
- {
- // We shouldn't produce a diagnostic if only the test framework is referenced, but not the adapter.
- return;
- }
-
INamedTypeSymbol? parallelizeAttributeSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingParallelizeAttribute);
INamedTypeSymbol? doNotParallelizeAttributeSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingDoNotParallelizeAttribute);
- bool hasParallelizeAttribute = false;
- bool hasDoNotParallelizeAttribute = false;
+ AttributeData? parallelizeAttribute = null;
+ AttributeData? doNotParallelizeAttribute = null;
foreach (AttributeData attribute in context.Compilation.Assembly.GetAttributes())
{
if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, parallelizeAttributeSymbol))
{
- hasParallelizeAttribute = true;
+ parallelizeAttribute = attribute;
}
if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, doNotParallelizeAttributeSymbol))
{
- hasDoNotParallelizeAttribute = true;
+ doNotParallelizeAttribute = attribute;
+ }
+ }
+
+ if (parallelizeAttribute is not null && doNotParallelizeAttribute is not null)
+ {
+ // Both attributes are present - this is an error
+ // Report on both attribute locations
+ if (parallelizeAttribute.ApplicationSyntaxReference is not null)
+ {
+ context.ReportDiagnostic(parallelizeAttribute.ApplicationSyntaxReference.CreateDiagnostic(DoNotUseBothAttributesRule, context.CancellationToken));
+ }
+
+ if (doNotParallelizeAttribute.ApplicationSyntaxReference is not null)
+ {
+ context.ReportDiagnostic(doNotParallelizeAttribute.ApplicationSyntaxReference.CreateDiagnostic(DoNotUseBothAttributesRule, context.CancellationToken));
}
+
+ return;
+ }
+
+ bool hasTestAdapter = context.Options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.IsMSTestTestAdapterReferenced", out string? isAdapterReferenced) &&
+ bool.TryParse(isAdapterReferenced, out bool isAdapterReferencedValue) &&
+ isAdapterReferencedValue;
+
+ if (!hasTestAdapter)
+ {
+ // We shouldn't produce a diagnostic if only the test framework is referenced, but not the adapter.
+ return;
}
- if (!hasParallelizeAttribute && !hasDoNotParallelizeAttribute)
+ if (parallelizeAttribute is null && doNotParallelizeAttribute is null)
{
// We cannot provide any good location for assembly level missing attributes
context.ReportNoLocationDiagnostic(Rule);
diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
index ba62836f66..4324f2289b 100644
--- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
+++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
@@ -989,6 +989,21 @@ Typ deklarující tyto metody by měl také respektovat následující pravidla:
Parametr Assert.Throws by měl obsahovat jenom jeden příkaz nebo výraz
+
+ An assembly should have either [Parallelize] or [DoNotParallelize] attribute, but not both. Having both attributes creates an ambiguous configuration. When both are present, [DoNotParallelize] takes precedence and parallelization will be disabled.
+ An assembly should have either [Parallelize] or [DoNotParallelize] attribute, but not both. Having both attributes creates an ambiguous configuration. When both are present, [DoNotParallelize] takes precedence and parallelization will be disabled.
+ {Locked="[Parallelize]","[DoNotParallelize]"}
+
+
+ Assembly has both '[Parallelize]' and '[DoNotParallelize]' attributes which creates ambiguity
+ Assembly has both '[Parallelize]' and '[DoNotParallelize]' attributes which creates ambiguity
+ {Locked="[Parallelize]","[DoNotParallelize]"}
+
+
+ Do not use both '[Parallelize]' and '[DoNotParallelize]' attributes
+ Do not use both '[Parallelize]' and '[DoNotParallelize]' attributes
+ {Locked="Parallelize","DoNotParallelize"}
+