diff --git a/Machine.Specifications.sln b/Machine.Specifications.sln
index 60b1dbbe..355d18f5 100644
--- a/Machine.Specifications.sln
+++ b/Machine.Specifications.sln
@@ -1,22 +1,10 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
-VisualStudioVersion = 17.0.31903.59
+VisualStudioVersion = 17.12.35527.113 d17.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications", "src\Machine.Specifications\Machine.Specifications.csproj", "{EC054D80-8858-4A61-9FD9-0185EA3F4643}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{1D85E618-71A9-43F2-B76D-95DC161E2A13}"
- ProjectSection(SolutionItems) = preProject
- build.ps1 = build.ps1
- build.sh = build.sh
- .github\workflows\build.yml = .github\workflows\build.yml
- CONTRIBUTING.md = CONTRIBUTING.md
- .config\dotnet-tools.json = .config\dotnet-tools.json
- GitVersion.yml = GitVersion.yml
- .github\workflows\publish.yml = .github\workflows\publish.yml
- README.md = README.md
- EndProjectSection
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Runner.Utility", "src\Machine.Specifications.Runner.Utility\Machine.Specifications.Runner.Utility.csproj", "{7E11A6CD-5900-4FFD-AE68-231BDA2436BA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Runner.Utility.Specs", "src\Machine.Specifications.Runner.Utility.Specs\Machine.Specifications.Runner.Utility.Specs.csproj", "{068B7D50-95A9-4510-96DC-59AB697D6C5F}"
@@ -27,12 +15,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Shou
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Core", "src\Machine.Specifications.Core\Machine.Specifications.Core.csproj", "{12F8A086-25B8-4636-8520-5985CA56CB01}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Interfaces", "src\Machine.Specifications.Interfaces\Machine.Specifications.Interfaces.csproj", "{5A18854E-1B66-409F-8122-3E12A13B9F43}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Machine.Specifications.Core.Specs", "src\Machine.Specifications.Core.Specs\Machine.Specifications.Core.Specs.csproj", "{0370391F-C7F2-41E7-BBC2-3D966F6F9ED9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Fixtures", "src\Machine.Specifications.Fixtures\Machine.Specifications.Fixtures.csproj", "{2934AB5B-7506-4150-B61B-FE16EBB13D50}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Analyzers", "src\Machine.Specifications.Analyzers\Machine.Specifications.Analyzers.csproj", "{3463F8CE-3CBE-4AD0-84BC-07C7E5FC9464}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Machine.Specifications.Analyzers.Tests", "src\Machine.Specifications.Analyzers.Tests\Machine.Specifications.Analyzers.Tests.csproj", "{C58845FC-ACD6-4CC2-BF7F-0FD7CCF201C6}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -63,10 +53,6 @@ Global
{12F8A086-25B8-4636-8520-5985CA56CB01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12F8A086-25B8-4636-8520-5985CA56CB01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12F8A086-25B8-4636-8520-5985CA56CB01}.Release|Any CPU.Build.0 = Release|Any CPU
- {5A18854E-1B66-409F-8122-3E12A13B9F43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5A18854E-1B66-409F-8122-3E12A13B9F43}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5A18854E-1B66-409F-8122-3E12A13B9F43}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5A18854E-1B66-409F-8122-3E12A13B9F43}.Release|Any CPU.Build.0 = Release|Any CPU
{0370391F-C7F2-41E7-BBC2-3D966F6F9ED9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0370391F-C7F2-41E7-BBC2-3D966F6F9ED9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0370391F-C7F2-41E7-BBC2-3D966F6F9ED9}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -75,6 +61,14 @@ Global
{2934AB5B-7506-4150-B61B-FE16EBB13D50}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2934AB5B-7506-4150-B61B-FE16EBB13D50}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2934AB5B-7506-4150-B61B-FE16EBB13D50}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3463F8CE-3CBE-4AD0-84BC-07C7E5FC9464}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3463F8CE-3CBE-4AD0-84BC-07C7E5FC9464}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3463F8CE-3CBE-4AD0-84BC-07C7E5FC9464}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3463F8CE-3CBE-4AD0-84BC-07C7E5FC9464}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C58845FC-ACD6-4CC2-BF7F-0FD7CCF201C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C58845FC-ACD6-4CC2-BF7F-0FD7CCF201C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C58845FC-ACD6-4CC2-BF7F-0FD7CCF201C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C58845FC-ACD6-4CC2-BF7F-0FD7CCF201C6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/build/build.csproj b/build/build.csproj
index e061b305..512e5706 100644
--- a/build/build.csproj
+++ b/build/build.csproj
@@ -2,7 +2,7 @@
Exe
- net7.0
+ net8.0
diff --git a/src/Machine.Specifications.Analyzers.Tests/AnalyzerVerifier.cs b/src/Machine.Specifications.Analyzers.Tests/AnalyzerVerifier.cs
new file mode 100644
index 00000000..2ed0b4fa
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers.Tests/AnalyzerVerifier.cs
@@ -0,0 +1,48 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace Machine.Specifications.Analyzers.Tests;
+
+public static class AnalyzerVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+{
+ public static DiagnosticResult Diagnostic()
+ {
+ return CSharpAnalyzerVerifier.Diagnostic();
+ }
+
+ public static DiagnosticResult Diagnostic(string diagnosticId)
+ {
+ return CSharpAnalyzerVerifier.Diagnostic(diagnosticId);
+ }
+
+ public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
+ {
+ return CSharpAnalyzerVerifier.Diagnostic(descriptor);
+ }
+
+ public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected)
+ {
+ var test = new Test
+ {
+ TestCode = source
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+
+ await test.RunAsync(CancellationToken.None);
+ }
+
+ private class Test : CSharpAnalyzerTest
+ {
+ public Test()
+ {
+ ReferenceAssemblies = VerifierHelper.MspecAssemblies;
+ SolutionTransforms.Add(VerifierHelper.GetNullableTransform);
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers.Tests/CodeFixVerifier.cs b/src/Machine.Specifications.Analyzers.Tests/CodeFixVerifier.cs
new file mode 100644
index 00000000..fd60d4c4
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers.Tests/CodeFixVerifier.cs
@@ -0,0 +1,74 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace Machine.Specifications.Analyzers.Tests
+{
+ public static class CodeFixVerifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ where TCodeFix : CodeFixProvider, new()
+ {
+ public static DiagnosticResult Diagnostic()
+ {
+ return CSharpCodeFixVerifier.Diagnostic();
+ }
+
+ public static DiagnosticResult Diagnostic(string diagnosticId)
+ {
+ return CSharpCodeFixVerifier.Diagnostic(diagnosticId);
+ }
+
+ public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
+ {
+ return CSharpCodeFixVerifier.Diagnostic(descriptor);
+ }
+
+ public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected)
+ {
+ var test = new Test
+ {
+ TestCode = source
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+
+ await test.RunAsync(CancellationToken.None);
+ }
+
+ public static async Task VerifyCodeFixAsync(string source, string fixedSource)
+ {
+ await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource);
+ }
+
+ public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource)
+ {
+ await VerifyCodeFixAsync(source, new[] {expected}, fixedSource);
+ }
+
+ public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource)
+ {
+ var test = new Test
+ {
+ TestCode = source,
+ FixedCode = fixedSource
+ };
+
+ test.ExpectedDiagnostics.AddRange(expected);
+
+ await test.RunAsync(CancellationToken.None);
+ }
+
+ private class Test : CSharpCodeFixTest
+ {
+ public Test()
+ {
+ ReferenceAssemblies = VerifierHelper.MspecAssemblies;
+ SolutionTransforms.Add(VerifierHelper.GetNullableTransform);
+ }
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers.Tests/Machine.Specifications.Analyzers.Tests.csproj b/src/Machine.Specifications.Analyzers.Tests/Machine.Specifications.Analyzers.Tests.csproj
new file mode 100644
index 00000000..f5030e7a
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers.Tests/Machine.Specifications.Analyzers.Tests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ enable
+ enable
+ latest
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/src/Machine.Specifications.Analyzers.Tests/Maintainability/AccessModifierShouldNotBeUsedTests.cs b/src/Machine.Specifications.Analyzers.Tests/Maintainability/AccessModifierShouldNotBeUsedTests.cs
new file mode 100644
index 00000000..275b80c2
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers.Tests/Maintainability/AccessModifierShouldNotBeUsedTests.cs
@@ -0,0 +1,287 @@
+using System.Threading.Tasks;
+using Xunit;
+using Verify = Machine.Specifications.Analyzers.Tests.CodeFixVerifier<
+ Machine.Specifications.Analyzers.Maintainability.AccessModifierShouldNotBeUsedAnalyzer,
+ Machine.Specifications.Analyzers.Maintainability.AccessModifierShouldNotBeUsedCodeFixProvider>;
+
+namespace Machine.Specifications.Analyzers.Tests.Maintainability;
+
+public class AccessModifierShouldNotBeUsedTests
+{
+ [Fact]
+ public async Task NoErrorsInValidSource()
+ {
+ const string source = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ class SpecsClass
+ {
+ static string value;
+
+ It should_do_something = () =>
+ true.ShouldBeTrue();
+
+ class inner_specs
+ {
+ Establish context = () =>
+ value = string.Empty;
+ }
+ }
+}";
+
+ await Verify.VerifyAnalyzerAsync(source);
+ }
+
+ [Fact]
+ public async Task RemovesClassAccessModifier()
+ {
+ const string source = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ public class {|#0:SpecsClass|}
+ {
+ It should_do_something = () =>
+ true.ShouldBeTrue();
+ }
+}";
+
+ const string fixedSource = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ class SpecsClass
+ {
+ It should_do_something = () =>
+ true.ShouldBeTrue();
+ }
+}";
+
+ var expected = Verify.Diagnostic(DiagnosticIds.Maintainability.AccessModifierShouldNotBeUsed)
+ .WithLocation(0)
+ .WithArguments("SpecsClass");
+
+ await Verify.VerifyCodeFixAsync(source, expected, fixedSource);
+ }
+
+ [Fact]
+ public async Task RemovesFieldAccessModifier()
+ {
+ const string source = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ class SpecsClass
+ {
+ private It {|#0:should_do_something|} = () =>
+ true.ShouldBeTrue();
+ }
+}";
+
+ const string fixedSource = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ class SpecsClass
+ {
+ It should_do_something = () =>
+ true.ShouldBeTrue();
+ }
+}";
+
+ var expected = Verify.Diagnostic(DiagnosticIds.Maintainability.AccessModifierShouldNotBeUsed)
+ .WithLocation(0)
+ .WithArguments("should_do_something");
+
+ await Verify.VerifyCodeFixAsync(source, expected, fixedSource);
+ }
+
+ [Fact]
+ public async Task RemovesFieldAndClassAccessModifiers()
+ {
+ const string source = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ public class {|#0:SpecsClass|}
+ {
+ private It {|#1:should_do_something|} = () =>
+ true.ShouldBeTrue();
+ }
+}";
+
+ const string fixedSource = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ class SpecsClass
+ {
+ It should_do_something = () =>
+ true.ShouldBeTrue();
+ }
+}";
+
+ var expected = new[]
+ {
+ Verify.Diagnostic(DiagnosticIds.Maintainability.AccessModifierShouldNotBeUsed)
+ .WithLocation(0)
+ .WithArguments("SpecsClass"),
+
+ Verify.Diagnostic(DiagnosticIds.Maintainability.AccessModifierShouldNotBeUsed)
+ .WithLocation(1)
+ .WithArguments("should_do_something")
+ };
+
+ await Verify.VerifyCodeFixAsync(source, expected, fixedSource);
+ }
+
+ [Fact]
+ public async Task RemovesInnerFieldAndClassAccessModifiers()
+ {
+ const string source = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ class SpecsClass
+ {
+ private static string {|#0:value|};
+
+ It should_do_something = () =>
+ true.ShouldBeTrue();
+
+ internal class {|#1:InnerClass|}
+ {
+ private Establish {|#2:context|} = () =>
+ value = string.Empty;
+ }
+ }
+}";
+
+ const string fixedSource = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ class SpecsClass
+ {
+ static string value;
+
+ It should_do_something = () =>
+ true.ShouldBeTrue();
+
+ class InnerClass
+ {
+ Establish context = () =>
+ value = string.Empty;
+ }
+ }
+}";
+
+ var expected = new[]
+ {
+ Verify.Diagnostic(DiagnosticIds.Maintainability.AccessModifierShouldNotBeUsed)
+ .WithLocation(0)
+ .WithArguments("value"),
+
+ Verify.Diagnostic(DiagnosticIds.Maintainability.AccessModifierShouldNotBeUsed)
+ .WithLocation(1)
+ .WithArguments("InnerClass"),
+
+ Verify.Diagnostic(DiagnosticIds.Maintainability.AccessModifierShouldNotBeUsed)
+ .WithLocation(2)
+ .WithArguments("context")
+ };
+
+ await Verify.VerifyCodeFixAsync(source, expected, fixedSource);
+ }
+
+ [Fact]
+ public async Task RemovesFieldAccessModifierWithLeadingTrivia()
+ {
+ const string source = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ class SpecsClass
+ {
+ static private string [|value|];
+
+ It should_do_something = () =>
+ true.ShouldBeTrue();
+ }
+}";
+
+ const string fixedSource = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ class SpecsClass
+ {
+ static string value;
+
+ It should_do_something = () =>
+ true.ShouldBeTrue();
+ }
+}";
+
+ await Verify.VerifyCodeFixAsync(source, fixedSource);
+ }
+
+ [Fact]
+ public async Task RemovesMultipleAccessModifiers()
+ {
+ const string source = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ class SpecsClass
+ {
+ private protected static string [|value|];
+
+ It should_do_something = () =>
+ true.ShouldBeTrue();
+ }
+}";
+
+ const string fixedSource = @"
+using System;
+using Machine.Specifications;
+
+namespace ConsoleApplication1
+{
+ class SpecsClass
+ {
+ static string value;
+
+ It should_do_something = () =>
+ true.ShouldBeTrue();
+ }
+}";
+
+ await Verify.VerifyCodeFixAsync(source, fixedSource);
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers.Tests/VerifierHelper.cs b/src/Machine.Specifications.Analyzers.Tests/VerifierHelper.cs
new file mode 100644
index 00000000..e0c32030
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers.Tests/VerifierHelper.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace Machine.Specifications.Analyzers.Tests;
+
+internal static class VerifierHelper
+{
+ internal static ImmutableDictionary NullableWarnings { get; } = GetNullableWarningsFromCompiler();
+
+ internal static ReferenceAssemblies MspecAssemblies { get; } = ReferenceAssemblies.Default.AddPackages(
+ ImmutableArray.Create(
+ new PackageIdentity("Machine.Specifications", "1.0.0"),
+ new PackageIdentity("Machine.Specifications.Should", "1.0.0")));
+
+ public static Solution GetNullableTransform(Solution solution, ProjectId projectId)
+ {
+ var project = solution.GetProject(projectId);
+
+ var options = project!.CompilationOptions!.WithSpecificDiagnosticOptions(
+ project.CompilationOptions.SpecificDiagnosticOptions.SetItems(NullableWarnings));
+
+ return solution.WithProjectCompilationOptions(projectId, options);
+ }
+
+ private static ImmutableDictionary GetNullableWarningsFromCompiler()
+ {
+ string[] args = { "/warnaserror:nullable" };
+ var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, Environment.CurrentDirectory, Environment.CurrentDirectory);
+ var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions;
+
+ return nullableWarnings
+ .SetItem("CS8632", ReportDiagnostic.Error)
+ .SetItem("CS8669", ReportDiagnostic.Error);
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/DiagnosticCategories.cs b/src/Machine.Specifications.Analyzers/DiagnosticCategories.cs
new file mode 100644
index 00000000..00384509
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/DiagnosticCategories.cs
@@ -0,0 +1,10 @@
+namespace Machine.Specifications.Analyzers;
+
+public static class DiagnosticCategories
+{
+ public const string Maintainability = nameof(Maintainability);
+
+ public const string Naming = nameof(Naming);
+
+ public const string Layout = nameof(Layout);
+}
diff --git a/src/Machine.Specifications.Analyzers/DiagnosticIds.cs b/src/Machine.Specifications.Analyzers/DiagnosticIds.cs
new file mode 100644
index 00000000..66e2973c
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/DiagnosticIds.cs
@@ -0,0 +1,16 @@
+namespace Machine.Specifications.Analyzers;
+
+public static class DiagnosticIds
+{
+ public static class Naming
+ {
+ public const string ClassMustBeUpper = "MSP1001";
+ }
+
+ public static class Maintainability
+ {
+ public const string AccessModifierShouldNotBeUsed = "MSP2001";
+
+ public const string PrivateDelegateFieldWarning = "MSP2002";
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/FieldDeclarationSyntaxExtensions.cs b/src/Machine.Specifications.Analyzers/FieldDeclarationSyntaxExtensions.cs
new file mode 100644
index 00000000..aaa82f6f
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/FieldDeclarationSyntaxExtensions.cs
@@ -0,0 +1,25 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Machine.Specifications.Analyzers;
+
+public static class FieldDeclarationSyntaxExtensions
+{
+ public static bool IsSpecification(this FieldDeclarationSyntax syntax, SyntaxNodeAnalysisContext context)
+ {
+ var symbolInfo = context.SemanticModel.GetSymbolInfo(syntax.Declaration.Type, context.CancellationToken);
+ var symbol = symbolInfo.Symbol as ITypeSymbol;
+
+ if (!symbol.IsMember())
+ {
+ return false;
+ }
+
+ return symbol?.Name is
+ MetadataNames.MspecItDelegate or
+ MetadataNames.MspecBehavesLikeDelegate or
+ MetadataNames.MspecBecause or
+ MetadataNames.MspecEstablishDelegate;
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/Layout/DelegateShouldNotBeOnSingleLineAnalyzer.cs b/src/Machine.Specifications.Analyzers/Layout/DelegateShouldNotBeOnSingleLineAnalyzer.cs
new file mode 100644
index 00000000..1e5e0314
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/Layout/DelegateShouldNotBeOnSingleLineAnalyzer.cs
@@ -0,0 +1,15 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Machine.Specifications.Analyzers.Layout;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public class DelegateShouldNotBeOnSingleLineAnalyzer : DiagnosticAnalyzer
+{
+ public override ImmutableArray SupportedDiagnostics { get; }
+
+ public override void Initialize(AnalysisContext context)
+ {
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/Layout/SingleLineStatementShouldNotUseBracesAnalyzer.cs b/src/Machine.Specifications.Analyzers/Layout/SingleLineStatementShouldNotUseBracesAnalyzer.cs
new file mode 100644
index 00000000..33b90672
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/Layout/SingleLineStatementShouldNotUseBracesAnalyzer.cs
@@ -0,0 +1,15 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Machine.Specifications.Analyzers.Layout;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public class SingleLineStatementShouldNotUseBracesAnalyzer : DiagnosticAnalyzer
+{
+ public override ImmutableArray SupportedDiagnostics { get; }
+
+ public override void Initialize(AnalysisContext context)
+ {
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/Machine.Specifications.Analyzers.csproj b/src/Machine.Specifications.Analyzers/Machine.Specifications.Analyzers.csproj
new file mode 100644
index 00000000..60af2214
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/Machine.Specifications.Analyzers.csproj
@@ -0,0 +1,41 @@
+
+
+
+ netstandard2.0
+ enable
+ enable
+ latest
+ true
+
+ $(TargetsForTfmSpecificContentInPackage);PackageItems
+
+
+
+ Roslyn analyzers and code fixes for Machine.Specifications
+ Machine Specifications
+ mspec;test;unit;testing;context;specification;bdd;tdd;analyzers;roslyn
+ See https://github.com/machine/machine.specifications.analyzers/releases
+ icon.png
+ https://github.com/machine/machine.specifications
+ https://github.com/machine/machine.specifications.analyzers
+ git
+ MIT
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Machine.Specifications.Analyzers/MachineSpecStubs.cs b/src/Machine.Specifications.Analyzers/MachineSpecStubs.cs
new file mode 100644
index 00000000..1affc07d
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/MachineSpecStubs.cs
@@ -0,0 +1,21 @@
+namespace Machine.Specifications;
+
+public class SetupDelegateAttribute : Attribute
+{
+}
+
+public class ActDelegateAttribute : Attribute
+{
+}
+
+public class BehaviorDelegateAttribute : Attribute
+{
+}
+
+public class AssertDelegateAttribute : Attribute
+{
+}
+
+public class CleanupDelegateAttribute : Attribute
+{
+}
diff --git a/src/Machine.Specifications.Analyzers/Maintainability/AccessModifierShouldNotBeUsedAnalyzer.cs b/src/Machine.Specifications.Analyzers/Maintainability/AccessModifierShouldNotBeUsedAnalyzer.cs
new file mode 100644
index 00000000..46b944eb
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/Maintainability/AccessModifierShouldNotBeUsedAnalyzer.cs
@@ -0,0 +1,87 @@
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Machine.Specifications.Analyzers.Maintainability;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public class AccessModifierShouldNotBeUsedAnalyzer : DiagnosticAnalyzer
+{
+ private static readonly DiagnosticDescriptor Rule = new(
+ DiagnosticIds.Maintainability.AccessModifierShouldNotBeUsed,
+ "Access modifier should not be declared",
+ "Element '{0}' should not declare an access modifier",
+ DiagnosticCategories.Maintainability,
+ DiagnosticSeverity.Warning,
+ true);
+
+ public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.EnableConcurrentExecution();
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+
+ context.RegisterSyntaxNodeAction(AnalyzeTypeSyntax, SyntaxKind.ClassDeclaration);
+ context.RegisterSyntaxNodeAction(AnalyzeFieldSyntax, SyntaxKind.FieldDeclaration);
+ }
+
+ private void AnalyzeTypeSyntax(SyntaxNodeAnalysisContext context)
+ {
+ var type = (TypeDeclarationSyntax) context.Node;
+
+ if (!type.IsSpecificationClass(context))
+ {
+ return;
+ }
+
+ CheckAccessModifier(context, type.Identifier, type.Modifiers);
+ }
+
+ private void AnalyzeFieldSyntax(SyntaxNodeAnalysisContext context)
+ {
+ var field = (FieldDeclarationSyntax) context.Node;
+
+ if (!field.Parent.IsKind(SyntaxKind.ClassDeclaration))
+ {
+ return;
+ }
+
+ if (field.Parent is not TypeDeclarationSyntax type || !type.IsSpecificationClass(context))
+ {
+ return;
+ }
+
+ var variable = field.Declaration.Variables
+ .FirstOrDefault(x => !x.Identifier.IsMissing);
+
+ if (variable == null)
+ {
+ return;
+ }
+
+ CheckAccessModifier(context, variable.Identifier, field.Modifiers);
+ }
+
+ private void CheckAccessModifier(SyntaxNodeAnalysisContext context, SyntaxToken identifier, SyntaxTokenList modifiers)
+ {
+ if (identifier.IsMissing)
+ {
+ return;
+ }
+
+ var modifier = modifiers
+ .FirstOrDefault(x => x.IsKind(SyntaxKind.PublicKeyword) ||
+ x.IsKind(SyntaxKind.PrivateKeyword) ||
+ x.IsKind(SyntaxKind.InternalKeyword) ||
+ x.IsKind(SyntaxKind.ProtectedKeyword));
+
+ if (!modifier.IsKind(SyntaxKind.None))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(Rule, identifier.GetLocation(), identifier));
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/Maintainability/AccessModifierShouldNotBeUsedCodeFixProvider.cs b/src/Machine.Specifications.Analyzers/Maintainability/AccessModifierShouldNotBeUsedCodeFixProvider.cs
new file mode 100644
index 00000000..52d0b017
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/Maintainability/AccessModifierShouldNotBeUsedCodeFixProvider.cs
@@ -0,0 +1,112 @@
+using System.Collections.Immutable;
+using System.Composition;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Machine.Specifications.Analyzers.Maintainability;
+
+[Shared]
+[ExportCodeFixProvider(LanguageNames.CSharp)]
+public class AccessModifierShouldNotBeUsedCodeFixProvider : CodeFixProvider
+{
+ public override ImmutableArray FixableDiagnosticIds { get; } =
+ ImmutableArray.Create(DiagnosticIds.Maintainability.AccessModifierShouldNotBeUsed);
+
+ public override FixAllProvider GetFixAllProvider()
+ {
+ return WellKnownFixAllProviders.BatchFixer;
+ }
+
+ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken)
+ .ConfigureAwait(false);
+
+ if (root == null)
+ {
+ return;
+ }
+
+ foreach (var diagnostic in context.Diagnostics)
+ {
+ var node = root.FindNode(diagnostic.Location.SourceSpan, true);
+
+ if (node.IsMissing)
+ {
+ continue;
+ }
+
+ var declaration = GetParentDeclaration(node);
+
+ if (declaration == null)
+ {
+ continue;
+ }
+
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ "Remove access modifier",
+ _ => TransformAsync(context.Document, root, declaration),
+ nameof(AccessModifierShouldNotBeUsedCodeFixProvider)),
+ diagnostic);
+ }
+ }
+
+ private Task TransformAsync(Document document, SyntaxNode root, SyntaxNode declaration)
+ {
+ var fixedDeclaration = declaration.Kind() switch
+ {
+ SyntaxKind.ClassDeclaration => HandleDeclaration((ClassDeclarationSyntax) declaration),
+ SyntaxKind.FieldDeclaration => HandleDeclaration((FieldDeclarationSyntax) declaration),
+ _ => declaration
+ };
+
+ var fixedRoot = root.ReplaceNode(declaration, fixedDeclaration);
+
+ return Task.FromResult(document.WithSyntaxRoot(fixedRoot));
+ }
+
+ private SyntaxNode HandleDeclaration(MemberDeclarationSyntax declaration)
+ {
+ if (!declaration.Modifiers.Any())
+ {
+ return declaration;
+ }
+
+ var trivia = declaration.Modifiers.First().LeadingTrivia;
+
+ var modifiers = declaration.Modifiers
+ .Where(x => !IsAccessModifer(x))
+ .ToArray();
+
+ return declaration
+ .WithModifiers(SyntaxFactory.TokenList(modifiers))
+ .WithLeadingTrivia(trivia);
+ }
+
+ private SyntaxNode GetParentDeclaration(SyntaxNode declaration)
+ {
+ while (declaration != null)
+ {
+ if (declaration.IsKind(SyntaxKind.ClassDeclaration) || declaration.IsKind(SyntaxKind.FieldDeclaration))
+ {
+ return declaration;
+ }
+
+ declaration = declaration.Parent;
+ }
+
+ return null;
+ }
+
+ private bool IsAccessModifer(SyntaxToken token)
+ {
+ return token.IsKind(SyntaxKind.PublicKeyword) ||
+ token.IsKind(SyntaxKind.PrivateKeyword) ||
+ token.IsKind(SyntaxKind.ProtectedKeyword) ||
+ token.IsKind(SyntaxKind.InternalKeyword);
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/Maintainability/PrivateDelegateFieldWarningSuppressor.cs b/src/Machine.Specifications.Analyzers/Maintainability/PrivateDelegateFieldWarningSuppressor.cs
new file mode 100644
index 00000000..fc8ae009
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/Maintainability/PrivateDelegateFieldWarningSuppressor.cs
@@ -0,0 +1,86 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Machine.Specifications.Analyzers.Maintainability;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public class PrivateDelegateFieldWarningSuppressor : DiagnosticSuppressor
+{
+ private static readonly SuppressionDescriptor Descriptor =
+ new SuppressionDescriptor(DiagnosticIds.Maintainability.PrivateDelegateFieldWarning, "IDE0051", "Private delegate field used by Machine.Specifications runner");
+
+ private static readonly Type[] SuppressedAttributeTypes =
+ [
+ typeof(SetupDelegateAttribute),
+ typeof(ActDelegateAttribute),
+ typeof(BehaviorDelegateAttribute),
+ typeof(AssertDelegateAttribute),
+ typeof(CleanupDelegateAttribute)
+ ];
+
+ public override ImmutableArray SupportedSuppressions { get; } = [Descriptor];
+
+ public override void ReportSuppressions(SuppressionAnalysisContext context)
+ {
+ foreach (var diagnostic in context.ReportedDiagnostics)
+ {
+ AnalyzeDiagnostic(diagnostic, context);
+ }
+ }
+
+ private void AnalyzeDiagnostic(Diagnostic diagnostic, SuppressionAnalysisContext context)
+ {
+ var fieldDeclarationSyntax = context.GetSuppressibleNode(diagnostic);
+
+ if (fieldDeclarationSyntax == null)
+ {
+ return;
+ }
+
+ var syntaxTree = diagnostic.Location.SourceTree;
+
+ if (syntaxTree == null)
+ {
+ return;
+ }
+
+ var model = context.GetSemanticModel(syntaxTree);
+
+ if (model.GetDeclaredSymbol(fieldDeclarationSyntax) is not IFieldSymbol fieldSymbol)
+ {
+ return;
+ }
+
+ if (!IsSuppressable(fieldSymbol))
+ {
+ return;
+ }
+
+ foreach (var descriptor in SupportedSuppressions.Where(d => d.SuppressedDiagnosticId == diagnostic.Id))
+ {
+ context.ReportSuppression(Suppression.Create(descriptor, diagnostic));
+ }
+ }
+
+ private static bool IsSuppressable(IFieldSymbol fieldSymbol)
+ {
+ var fieldTypeAttributes = fieldSymbol.Type.GetAttributes()
+ .Where(x => x.AttributeClass != null)
+ .Select(x => x.AttributeClass!);
+
+
+ if (fieldTypeAttributes.Any(x => SuppressedAttributeTypes.Any(y => x.Matches(y))))
+ {
+ return true;
+ }
+
+ if (fieldSymbol.DeclaredAccessibility == Accessibility.Public && fieldSymbol.ContainingType.Extends(typeof(object)))
+ {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/MetadataNames.cs b/src/Machine.Specifications.Analyzers/MetadataNames.cs
new file mode 100644
index 00000000..9bda61e8
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/MetadataNames.cs
@@ -0,0 +1,18 @@
+namespace Machine.Specifications.Analyzers;
+
+public static class MetadataNames
+{
+ public const string MspecAssemblyName = "Machine.Specifications";
+
+ public const string MspecCoreAssemblyName = "Machine.Specifications.Core";
+
+ public const string MspecNamespace = "Machine.Specifications";
+
+ public const string MspecItDelegate = "It";
+
+ public const string MspecBehavesLikeDelegate = "Behaves_like";
+
+ public const string MspecEstablishDelegate = "Establish";
+
+ public const string MspecBecause = "Because";
+}
diff --git a/src/Machine.Specifications.Analyzers/Naming/ElementNameShouldBeSnakeCasedAnalyzer.cs b/src/Machine.Specifications.Analyzers/Naming/ElementNameShouldBeSnakeCasedAnalyzer.cs
new file mode 100644
index 00000000..610216ed
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/Naming/ElementNameShouldBeSnakeCasedAnalyzer.cs
@@ -0,0 +1,15 @@
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Machine.Specifications.Analyzers.Naming;
+
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public class ElementNameShouldBeSnakeCasedAnalyzer : DiagnosticAnalyzer
+{
+ public override ImmutableArray SupportedDiagnostics { get; }
+
+ public override void Initialize(AnalysisContext context)
+ {
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/SuppressionAnalysisContextExtensions.cs b/src/Machine.Specifications.Analyzers/SuppressionAnalysisContextExtensions.cs
new file mode 100644
index 00000000..4d8e8959
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/SuppressionAnalysisContextExtensions.cs
@@ -0,0 +1,25 @@
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis;
+
+namespace Machine.Specifications.Analyzers;
+
+internal static class SuppressionAnalysisContextExtensions
+{
+ public static T? GetSuppressibleNode(this SuppressionAnalysisContext context, Diagnostic diagnostic)
+ where T : SyntaxNode
+ {
+ return GetSuppressibleNode(context, diagnostic, _ => true);
+ }
+
+ public static T? GetSuppressibleNode(this SuppressionAnalysisContext context, Diagnostic diagnostic, Func predicate)
+ where T : SyntaxNode
+ {
+ var root = diagnostic.Location.SourceTree?.GetRoot(context.CancellationToken);
+
+ return root?
+ .FindNode(diagnostic.Location.SourceSpan)
+ .DescendantNodesAndSelf()
+ .OfType()
+ .FirstOrDefault(predicate);
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/SymbolExtensions.cs b/src/Machine.Specifications.Analyzers/SymbolExtensions.cs
new file mode 100644
index 00000000..cc0f7774
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/SymbolExtensions.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis;
+
+namespace Machine.Specifications.Analyzers;
+
+public static class SymbolExtensions
+{
+ public static bool IsMember(this ITypeSymbol symbol)
+ {
+ return symbol.IsMspecAssembly() &&
+ symbol.ContainingNamespace?.ToString() == MetadataNames.MspecNamespace &&
+ symbol.TypeKind == TypeKind.Delegate;
+ }
+
+ private static bool IsMspecAssembly(this ITypeSymbol symbol)
+ {
+ return symbol.ContainingAssembly?.Name is MetadataNames.MspecAssemblyName or MetadataNames.MspecCoreAssemblyName;
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/TypeDeclarationSyntaxExtensions.cs b/src/Machine.Specifications.Analyzers/TypeDeclarationSyntaxExtensions.cs
new file mode 100644
index 00000000..44e6a9ca
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/TypeDeclarationSyntaxExtensions.cs
@@ -0,0 +1,23 @@
+using System.Linq;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Machine.Specifications.Analyzers;
+
+public static class TypeDeclarationSyntaxExtensions
+{
+ public static bool IsSpecificationClass(this TypeDeclarationSyntax type, SyntaxNodeAnalysisContext context)
+ {
+ return type
+ .DescendantNodesAndSelf()
+ .OfType()
+ .Any(x => x.HasSpecificationMember(context));
+ }
+
+ public static bool HasSpecificationMember(this TypeDeclarationSyntax declaration, SyntaxNodeAnalysisContext context)
+ {
+ return declaration.Members
+ .OfType()
+ .Any(x => x.IsSpecification(context));
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/TypeSymbolExtensions.cs b/src/Machine.Specifications.Analyzers/TypeSymbolExtensions.cs
new file mode 100644
index 00000000..10fb3948
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/TypeSymbolExtensions.cs
@@ -0,0 +1,77 @@
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+
+namespace Machine.Specifications.Analyzers;
+
+internal static class TypeSymbolExtensions
+{
+ public static bool Extends(this ITypeSymbol? symbol, Type? type)
+ {
+ if (symbol == null || type == null)
+ {
+ return false;
+ }
+
+ while (symbol != null)
+ {
+ if (symbol.Matches(type))
+ {
+ return true;
+ }
+
+ symbol = symbol.BaseType;
+ }
+
+ return false;
+ }
+
+ public static bool Matches(this ITypeSymbol symbol, Type type)
+ {
+ switch (symbol.SpecialType)
+ {
+ case SpecialType.System_Void:
+ return type == typeof(void);
+
+ case SpecialType.System_Boolean:
+ return type == typeof(bool);
+
+ case SpecialType.System_Int32:
+ return type == typeof(int);
+
+ case SpecialType.System_Single:
+ return type == typeof(float);
+ }
+
+ if (type.IsArray)
+ {
+ return symbol is IArrayTypeSymbol array && Matches(array.ElementType, type.GetElementType()!);
+ }
+
+ if (symbol is not INamedTypeSymbol named)
+ {
+ return false;
+ }
+
+ if (type.IsConstructedGenericType)
+ {
+ var args = type.GetTypeInfo().GenericTypeArguments;
+
+ if (args.Length != named.TypeArguments.Length)
+ {
+ return false;
+ }
+
+ for (var i = 0; i < args.Length; i++)
+ {
+ if (!Matches(named.TypeArguments[i], args[i]))
+ {
+ return false;
+ }
+ }
+
+ return Matches(named.ConstructedFrom, type.GetGenericTypeDefinition());
+ }
+
+ return named.Name == type.Name && named.ContainingNamespace?.ToDisplayString() == type.Namespace;
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/install.ps1 b/src/Machine.Specifications.Analyzers/install.ps1
new file mode 100644
index 00000000..ff051759
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/install.ps1
@@ -0,0 +1,58 @@
+param($installPath, $toolsPath, $package, $project)
+
+if($project.Object.SupportsPackageDependencyResolution)
+{
+ if($project.Object.SupportsPackageDependencyResolution())
+ {
+ # Do not install analyzers via install.ps1, instead let the project system handle it.
+ return
+ }
+}
+
+$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
+
+foreach($analyzersPath in $analyzersPaths)
+{
+ if (Test-Path $analyzersPath)
+ {
+ # Install the language agnostic analyzers.
+ foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
+ {
+ if($project.Object.AnalyzerReferences)
+ {
+ $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
+ }
+ }
+ }
+}
+
+# $project.Type gives the language name like (C# or VB.NET)
+$languageFolder = ""
+if($project.Type -eq "C#")
+{
+ $languageFolder = "cs"
+}
+if($project.Type -eq "VB.NET")
+{
+ $languageFolder = "vb"
+}
+if($languageFolder -eq "")
+{
+ return
+}
+
+foreach($analyzersPath in $analyzersPaths)
+{
+ # Install language specific analyzers.
+ $languageAnalyzersPath = join-path $analyzersPath $languageFolder
+ if (Test-Path $languageAnalyzersPath)
+ {
+ foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
+ {
+ if($project.Object.AnalyzerReferences)
+ {
+ $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName)
+ }
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Analyzers/uninstall.ps1 b/src/Machine.Specifications.Analyzers/uninstall.ps1
new file mode 100644
index 00000000..4bed3337
--- /dev/null
+++ b/src/Machine.Specifications.Analyzers/uninstall.ps1
@@ -0,0 +1,65 @@
+param($installPath, $toolsPath, $package, $project)
+
+if($project.Object.SupportsPackageDependencyResolution)
+{
+ if($project.Object.SupportsPackageDependencyResolution())
+ {
+ # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it.
+ return
+ }
+}
+
+$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve
+
+foreach($analyzersPath in $analyzersPaths)
+{
+ # Uninstall the language agnostic analyzers.
+ if (Test-Path $analyzersPath)
+ {
+ foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll)
+ {
+ if($project.Object.AnalyzerReferences)
+ {
+ $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
+ }
+ }
+ }
+}
+
+# $project.Type gives the language name like (C# or VB.NET)
+$languageFolder = ""
+if($project.Type -eq "C#")
+{
+ $languageFolder = "cs"
+}
+if($project.Type -eq "VB.NET")
+{
+ $languageFolder = "vb"
+}
+if($languageFolder -eq "")
+{
+ return
+}
+
+foreach($analyzersPath in $analyzersPaths)
+{
+ # Uninstall language specific analyzers.
+ $languageAnalyzersPath = join-path $analyzersPath $languageFolder
+ if (Test-Path $languageAnalyzersPath)
+ {
+ foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll)
+ {
+ if($project.Object.AnalyzerReferences)
+ {
+ try
+ {
+ $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName)
+ }
+ catch
+ {
+
+ }
+ }
+ }
+ }
+}
diff --git a/src/Machine.Specifications.Core/Machine.Specifications.Core.csproj b/src/Machine.Specifications.Core/Machine.Specifications.Core.csproj
index 422e5e56..02f34c80 100644
--- a/src/Machine.Specifications.Core/Machine.Specifications.Core.csproj
+++ b/src/Machine.Specifications.Core/Machine.Specifications.Core.csproj
@@ -1,7 +1,7 @@
- net472;netstandard2.0
+ net472;net6.0
Machine.Specifications
Machine.Specifications
@@ -19,10 +19,6 @@
-
-
-
-
diff --git a/src/Machine.Specifications.Interfaces/Machine.Specifications.Interfaces.csproj b/src/Machine.Specifications.Interfaces/Machine.Specifications.Interfaces.csproj
deleted file mode 100644
index f6324381..00000000
--- a/src/Machine.Specifications.Interfaces/Machine.Specifications.Interfaces.csproj
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- netstandard2.0
- Machine.Specifications
-
- SDK and common runner interfaces used by Machine.Specifications
- Machine Specifications
- test;unit;testing;context;specification;bdd;tdd;mspec
- https://github.com/machine/machine.specifications/releases
- icon.png
- http://github.com/machine/machine.specifications
- MIT
-
-
-
-
-
-
-
diff --git a/src/Machine.Specifications.Runner.Utility/Machine.Specifications.Runner.Utility.csproj b/src/Machine.Specifications.Runner.Utility/Machine.Specifications.Runner.Utility.csproj
index 8036fa5a..f4db6973 100644
--- a/src/Machine.Specifications.Runner.Utility/Machine.Specifications.Runner.Utility.csproj
+++ b/src/Machine.Specifications.Runner.Utility/Machine.Specifications.Runner.Utility.csproj
@@ -1,7 +1,7 @@
- net472;netstandard2.0
+ net472;net6.0
Provides a version-independent runner for Machine.Specifications
Machine Specifications
@@ -21,7 +21,6 @@
-
diff --git a/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj b/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj
index 7d19d8dc..d5ecb542 100644
--- a/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj
+++ b/src/Machine.Specifications.Should/Machine.Specifications.Should.csproj
@@ -1,7 +1,7 @@
- net472;netstandard2.0
+ net472;net6.0
Machine.Specifications
Assertion library for Machine.Specifications
diff --git a/src/Machine.Specifications/Machine.Specifications.csproj b/src/Machine.Specifications/Machine.Specifications.csproj
index 29ba8768..50a903c3 100644
--- a/src/Machine.Specifications/Machine.Specifications.csproj
+++ b/src/Machine.Specifications/Machine.Specifications.csproj
@@ -1,8 +1,12 @@
- net472;netstandard2.0
+ net472;net6.0
+ false
+ NU5128
+
+
Machine.Specifications is a Context/Specification framework geared towards removing language noise and simplifying tests
Machine Specifications
test;unit;testing;context;specification;bdd;tdd;mspec
@@ -10,9 +14,6 @@
icon.png
http://github.com/machine/machine.specifications
MIT
- false
-
- NU5128