diff --git a/Brighter.slnx b/Brighter.slnx index e9fdd0a42c..2a1fc58280 100644 --- a/Brighter.slnx +++ b/Brighter.slnx @@ -6,6 +6,9 @@ + + + @@ -153,6 +156,7 @@ + @@ -180,14 +184,18 @@ + + + - + + @@ -259,6 +267,9 @@ + + + @@ -267,5 +278,4 @@ - diff --git a/Directory.Packages.props b/Directory.Packages.props index 7c998201d7..9118b42ddc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -58,6 +58,10 @@ + + + + diff --git a/docs/adr/0037-provide-roslyn-analyzers-for-brighter.md b/docs/adr/0037-provide-roslyn-analyzers-for-brighter.md new file mode 100644 index 0000000000..b10f354927 --- /dev/null +++ b/docs/adr/0037-provide-roslyn-analyzers-for-brighter.md @@ -0,0 +1,59 @@ +# 37. Provide Roslyn Analyzers for Brighter + +Date: 2026-01-25 + +## Status + +Proposed + +## Context + +Using Brighter correctly often involves adhering to specific patterns and conventions that are not strictly enforced by the C# compiler alone. For example, developers must ensure that: +- Attributes like `[Wrap]` are used correctly to configure the pipeline. +- `Publication` objects are initialized with a valid `RequestType` that implements `IRequest` for proper mapping. +- Subscriptions are configured with valid parameters. + +Currently, violations of these rules often result in runtime errors (e.g., `RuntimeBinderException`, `ArgumentException`) or silent failures. identifying these issues requires running the application, which delays the feedback loop and increases the risk of bugs reaching production. + +We want to "shift left" on these checks, providing feedback to developers as they write code. + +## Decision + +We will introduce a new project, `Paramore.Brighter.Analyzer`, containing custom Roslyn analyzers. +This project will be distributed as a NuGet package that developers can include in their projects. + +The analyzers will inspect code at compile-time and report diagnostics (warnings or errors) for known invalid usage patterns of Brighter components. + +The initial implementation will include the following analyzers: + +1. **PublicationRequestTypeAssignmentAnalyzer**: + - Ensures that when a `Publication` object is created, the `RequestType` property is explicitly assigned. + - Verifies that the assigned type implements `IRequest`. + +2. **SubscriptionConstructorAnalyzer**: + - Validates that `Subscription` objects are instantiated with correct arguments and configuration. + +3. **WrapAttributeAnalyzer**: + - Ensures that attributes used for wrapping (e.g., encryption, compression) are applied to the correct mapping method (`MapToMessage` or `MapToRequest`). + - Prevents incorrect pipeline configuration by verifying that attributes intended for requests are not applied to message mapping and vice versa. + +## Technical Implementation Strategy + +The logic for the analyzers will be implemented using the **Roslyn API** and the **Visitor Pattern**. + +1. **Roslyn Syntax & Operation Trees**: We leverage Roslyn to inspect the code structure. We specifically focus on the **Operation Tree (IOperation)**, which provides a semantic view of the code (e.g., understanding that a line is an object creation regardless of syntax). +2. **Visitor Pattern**: To efficiently traverse the code, we implement a `Visitor` (e.g., `RequestTypeAssignmentVisitor`). + - The **Visitor** "walks" through specific nodes of the code tree (like `ObjectCreation`). + - We utilize **Double Dispatch** (via the `Accept` method) to ensure the correct `Visit` method is called for each node type. This avoids the need for explicit type casting and massive `if-else` or `switch` statements to determine node types, making the code cleaner and more maintainable. + - It maintains state as it visits nodes (e.g., "I am currently inside a Publication object creation"). + +## Consequences + +### Positive +- **Immediate Feedback**: Developers receive feedback on incorrect usage within the IDE and at build time. +- **Reduced Runtime Errors**: Prevents a class of configuration and usage errors from occurring at runtime. +- **Better Discovery**: Helps new users learn Brighter's constraints and requirements through compiler messages rather than documentation lookup or trial-and-error. + +### Negative +- **Maintenance**: The analyzer project must be maintained and updated as Brighter's API and patterns evolve. + diff --git a/samples/Analyzer/AnalyzerSamples/AnalyzerSamples.csproj b/samples/Analyzer/AnalyzerSamples/AnalyzerSamples.csproj new file mode 100644 index 0000000000..546304d333 --- /dev/null +++ b/samples/Analyzer/AnalyzerSamples/AnalyzerSamples.csproj @@ -0,0 +1,17 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + diff --git a/samples/Analyzer/AnalyzerSamples/PublicationSample.cs b/samples/Analyzer/AnalyzerSamples/PublicationSample.cs new file mode 100644 index 0000000000..fa6b322323 --- /dev/null +++ b/samples/Analyzer/AnalyzerSamples/PublicationSample.cs @@ -0,0 +1,48 @@ +using Paramore.Brighter; + +namespace AnalyzerSamples +{ + public static class PublicationCreator + { + public static Publication GetPublicationNoRequestType() + { + return new PublicationSample() { + Subject="test" + }; + } + public static Publication GetPublicationWrongRequestType() + { + return new PublicationSample() + { + Subject = "test", + RequestType = typeof(EventSample2) + }; + } + public static Publication GetPublicationWithRequestType() + { + return new PublicationSample() + { + Subject = "test", + RequestType = typeof(EventSample) + }; + } + public static AnyClass GetAnyOtherClass() + { + return new AnyClass(); + } + } + public class PublicationSample : Publication + { + } + public class EventSample(Id id) : Event(id) + { + } + public class EventSample2 + { + } + + public class AnyClass + { + + } +} diff --git a/samples/Analyzer/AnalyzerSamples/SubscriptionSample.cs b/samples/Analyzer/AnalyzerSamples/SubscriptionSample.cs new file mode 100644 index 0000000000..3fc5041dc2 --- /dev/null +++ b/samples/Analyzer/AnalyzerSamples/SubscriptionSample.cs @@ -0,0 +1,37 @@ + + +using Paramore.Brighter; + +namespace AnalyzerSamples +{ + internal class SubscriptionSample + { + } + public static class SubscriptionCreator + { + public static Subscription GetSubscription_MessagePumpSpecified() + { + return new SubscriptionTest("name", "name", "key", messagePumpType: MessagePumpType.Reactor); + } + public static Subscription GetSubscription_NoMessagePumpSpecified() + { + return new SubscriptionTest("name", "name", "key"); + } + public static Subscription GetSubscriptionNested_NoMessagePumpSpecified() + { + return new SubscriptionTestNested("name", "name", "key"); + } + public class SubscriptionTest : Subscription + { + public SubscriptionTest(SubscriptionName subscriptionName, ChannelName channelName, RoutingKey routingKey, Type? requestType = null, Func? getRequestType = null, int bufferSize = 1, int noOfPerformers = 1, TimeSpan? timeOut = null, int requeueCount = -1, TimeSpan? requeueDelay = null, int unacceptableMessageLimit = 0, MessagePumpType messagePumpType = MessagePumpType.Unknown, IAmAChannelFactory? channelFactory = null, OnMissingChannel makeChannels = OnMissingChannel.Create, TimeSpan? emptyChannelDelay = null, TimeSpan? channelFailureDelay = null) : base(subscriptionName, channelName, routingKey, requestType, getRequestType, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, unacceptableMessageLimit, messagePumpType, channelFactory, makeChannels, emptyChannelDelay, channelFailureDelay) + { + } + } + public class SubscriptionTestNested : SubscriptionTest + { + public SubscriptionTestNested(SubscriptionName subscriptionName, ChannelName channelName, RoutingKey routingKey, Type? requestType = null, Func? getRequestType = null, int bufferSize = 1, int noOfPerformers = 1, TimeSpan? timeOut = null, int requeueCount = -1, TimeSpan? requeueDelay = null, int unacceptableMessageLimit = 0, MessagePumpType messagePumpType = MessagePumpType.Unknown, IAmAChannelFactory? channelFactory = null, OnMissingChannel makeChannels = OnMissingChannel.Create, TimeSpan? emptyChannelDelay = null, TimeSpan? channelFailureDelay = null) : base(subscriptionName, channelName, routingKey, requestType, getRequestType, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, unacceptableMessageLimit, messagePumpType, channelFactory, makeChannels, emptyChannelDelay, channelFailureDelay) + { + } + } + } +} diff --git a/samples/Analyzer/AnalyzerSamples/WrapWithMapperSample.cs b/samples/Analyzer/AnalyzerSamples/WrapWithMapperSample.cs new file mode 100644 index 0000000000..506709f6d2 --- /dev/null +++ b/samples/Analyzer/AnalyzerSamples/WrapWithMapperSample.cs @@ -0,0 +1,47 @@ +using Paramore.Brighter; +using Paramore.Brighter.Transforms.Attributes; + +namespace AnalyzerSamples +{ + public class WrapWithMapperAsyncSample: IAmAMessageMapperAsync + { + public IRequestContext? Context { get; set ; } + + [Compress(0)] + [ClaimCheck(0)] + public async Task MapToRequestAsync(Message message, CancellationToken cancellationToken = default) + { + await Task.Yield(); + return new SampleEvent(message.Id); + } + + [Decompress(0)] + public async Task MapToMessageAsync(SampleEvent request, Publication publication, CancellationToken cancellationToken = default) + { + await Task.Yield(); + throw new NotImplementedException(); + } + } + public class WrapWithMapperSample: IAmAMessageMapper + { + public IRequestContext? Context { get; set ; } + + [Compress(0)] + [ClaimCheck(0)] + public SampleEvent MapToRequest(Message message) + { + return new SampleEvent(message.Id); + } + + [Decompress(0)] + public Message MapToMessage(SampleEvent request, Publication publication) + { + throw new NotImplementedException(); + } + } + + + public class SampleEvent(Id id) : Event(id) + { + } +} diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 48bc70b36a..e127a59c4f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -29,6 +29,7 @@ 10.0.0 + netstandard2.0; netstandard2.0;net8.0;net9.0;net10.0 net462;net8.0;net9.0;net10.0 net8.0;net9.0;net10.0 diff --git a/src/Paramore.Brighter.Analyzer.Package/Paramore.Brighter.Analyzer.Package.csproj b/src/Paramore.Brighter.Analyzer.Package/Paramore.Brighter.Analyzer.Package.csproj new file mode 100644 index 0000000000..34bbc026c8 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer.Package/Paramore.Brighter.Analyzer.Package.csproj @@ -0,0 +1,36 @@ + + + + $(BrighterNetStandardTargetFrameworks) + false + true + true + + + + AboubakrNasef + false + Paramore.Brighter.Analyzer + Analyzer For usage of Brighter Command + Paramore.Brighter,Paramore.Brighter.Analyzer, analyzers + true + true + + $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput + + + + + + + + + + + + + + + + + diff --git a/src/Paramore.Brighter.Analyzer.Package/build/Paramore.Brighter.Analyzer.props b/src/Paramore.Brighter.Analyzer.Package/build/Paramore.Brighter.Analyzer.props new file mode 100644 index 0000000000..ead6e08f09 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer.Package/build/Paramore.Brighter.Analyzer.props @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Paramore.Brighter.Analyzer.Package/configuration/default.editorconfig b/src/Paramore.Brighter.Analyzer.Package/configuration/default.editorconfig new file mode 100644 index 0000000000..c4458e30f1 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer.Package/configuration/default.editorconfig @@ -0,0 +1,5 @@ +dotnet_diagnostic.BRT001.severity = warning +dotnet_diagnostic.BRT002.severity = warning +dotnet_diagnostic.BRT003.severity = warning +dotnet_diagnostic.BRT004.severity = warning +dotnet_diagnostic.BRT005.severity = warning \ No newline at end of file diff --git a/src/Paramore.Brighter.Analyzer.Package/configuration/none.editorconfig b/src/Paramore.Brighter.Analyzer.Package/configuration/none.editorconfig new file mode 100644 index 0000000000..aef953fc0e --- /dev/null +++ b/src/Paramore.Brighter.Analyzer.Package/configuration/none.editorconfig @@ -0,0 +1,5 @@ +dotnet_diagnostic.BRT001.severity = none +dotnet_diagnostic.BRT002.severity = none +dotnet_diagnostic.BRT003.severity = none +dotnet_diagnostic.BRT004.severity = none +dotnet_diagnostic.BRT005.severity = none \ No newline at end of file diff --git a/src/Paramore.Brighter.Analyzer.Package/tools/install.ps1 b/src/Paramore.Brighter.Analyzer.Package/tools/install.ps1 new file mode 100644 index 0000000000..53136a238f --- /dev/null +++ b/src/Paramore.Brighter.Analyzer.Package/tools/install.ps1 @@ -0,0 +1,275 @@ +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) + } + } + } +} +# SIG # Begin signature block +# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA/i+qRUHsWzI0s +# FVk99zLgt/HOEQ33uvkFsWtHTHZgf6CCDXYwggX0MIID3KADAgECAhMzAAADTrU8 +# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU +# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1 +# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm +# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa +# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq +# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw +# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW +# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci +# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG +# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu +# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 +# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk +# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31 +# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2 +# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d +# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM +# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh +# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX +# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir +# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8 +# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A +# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H +# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq +# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG +# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG +# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg +# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 +# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr +# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg +# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy +# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 +# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh +# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k +# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB +# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn +# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 +# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w +# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o +# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD +# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa +# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny +# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG +# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t +# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV +# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG +# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl +# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb +# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l +# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 +# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 +# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 +# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam +# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa +# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah +# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA +# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt +# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr +# /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw +# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN +# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp +# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB +# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO +# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIEY4Ow3COroWH11sAEOoStJj +# 1u4sq9rcx0dAx0gyZLHCMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A +# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB +# BQAEggEAFlSLlk17KQp2AwLbr5e4T5zyE44MGZOdNejIip+Mg8co8qho16qdNSvg +# AhvoJYxPJJr70DSCU9BIUoWY7hXoi9S4P08YlAid7BUT5OciIgHlrb8I900LaE+S +# 83rSvfVU1CZCjiSwcgng5DD2VPRo0Lu4G9p2Ky14dyOPwtPvrpsib5s9kewZLdiy +# /KxEDLKX8P+cHat1xH7RaZLDNxweRS6GSomjE2vjOlQHNSW879XR8bSoAt/m4uR1 +# WyrAxTGZb4miEYX+I5HsrWvbZLw9NSCJ/crbbap3LIobfQtK5binjY7v4MQp/5Oq +# y4S/4FAfwhWDXfaQfq6YTeOjHRVQbKGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC +# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq +# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl +# AwQCAQUABCCV3irbWIoYz2Llfx9YQhUNtcP6GOrL7+YTUXQ4y5qzWAIGZNTJrsAW +# GBMyMDIzMDgzMTAwMTI1OC45ODNaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV +# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE +# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l +# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0w +# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg +# ghHqMIIHIDCCBQigAwIBAgITMwAAAc9SNr5xS81IygABAAABzzANBgkqhkiG9w0B +# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE +# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD +# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy +# MTFaFw0yNDAyMDExOTEyMTFaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz +# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv +# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z +# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0wNUUwLUQ5NDcxJTAjBgNV +# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQC4Pct+15TYyrUje553lzBQodgmd5Bz7WuH8SdHpAoW +# z+01TrHExBSuaMKnxvVMsyYtas5h6aopUGAS5WKVLZAvUtH62TKmAE0JK+i1hafi +# CSXLZPcRexxeRkOqeZefLBzXp0nudMOXUUab333Ss8LkoK4l3LYxm1Ebsr3b2OTo +# 2ebsAoNJ4kSxmVuPM7C+RDhGtVKR/EmHsQ9GcwGmluu54bqiVFd0oAFBbw4txTU1 +# mruIGWP/i+sgiNqvdV/wah/QcrKiGlpWiOr9a5aGrJaPSQD2xgEDdPbrSflYxsRM +# dZCJI8vzvOv6BluPcPPGGVLEaU7OszdYjK5f4Z5Su/lPK1eST5PC4RFsVcOiS4L0 +# sI4IFZywIdDJHoKgdqWRp6Q5vEDk8kvZz6HWFnYLOlHuqMEYvQLr6OgooYU9z0A5 +# cMLHEIHYV1xiaBzx2ERiRY9MUPWohh+TpZWEUZlUm/q9anXVRN0ujejm6OsUVFDs +# sIMszRNCqEotJGwtHHm5xrCKuJkFr8GfwNelFl+XDoHXrQYL9zY7Np+frsTXQpKR +# NnmI1ashcn5EC+wxUt/EZIskWzewEft0/+/0g3+8YtMkUdaQE5+8e7C8UMiXOHkM +# K25jNNQqLCedlJwFIf9ir9SpMc72NR+1j6Uebiz/ZPV74do3jdVvq7DiPFlTb92U +# KwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFDaeKPtp0eTSVdG+gZc5BDkabTg4MB8G +# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG +# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy +# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w +# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy +# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG +# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD +# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBQgm4pnA0xkd/9uKXJMzdMYyxUfUm/ZusU +# Ba32MEZXQuMGp20pSuX2VW9/tpTMo5bkaJdBVoUyd2DbDsNb1kjr/36ntT0jvL3A +# oWStAFhZBypmpPbx+BPK49ZlejlM4d5epX668tRRGfFip9Til9yKRfXBrXnM/q64 +# IinN7zXEQ3FFQhdJMzt8ibXClO7eFA+1HiwZPWysYWPb/ZOFobPEMvXie+GmEbTK +# bhE5tze6RrA9aejjP+v1ouFoD5bMj5Qg+wfZXqe+hfYKpMd8QOnQyez+Nlj1ityn +# OZWfwHVR7dVwV0yLSlPT+yHIO8g+3fWiAwpoO17bDcntSZ7YOBljXrIgad4W4gX+ +# 4tp1eBsc6XWIITPBNzxQDZZRxD4rXzOB6XRlEVJdYZQ8gbXOirg/dNvS2GxcR50Q +# dOXDAumdEHaGNHb6y2InJadCPp2iT5QLC4MnzR+YZno1b8mWpCdOdRs9g21QbbrI +# 06iLk9KD61nx7K5ReSucuS5Z9nbkIBaLUxDesFhr1wmd1ynf0HQ51Swryh7YI7TX +# T0jr81mbvvI9xtoqjFvIhNBsICdCfTR91ylJTH8WtUlpDhEgSqWt3gzNLPTSvXAx +# XTpIM583sZdd+/2YGADMeWmt8PuMce6GsIcLCOF2NiYZ10SXHZS5HRrLrChuzedD +# RisWpIu5uTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI +# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw +# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x +# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy +# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg +# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF +# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 +# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp +# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu +# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E +# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 +# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q +# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ +# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA +# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw +# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG +# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV +# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj +# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK +# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG +# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x +# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC +# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 +# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM +# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS +# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d +# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn +# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs +# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL +# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL +# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN +# MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp +# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw +# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn +# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjkyMDAtMDVFMC1EOTQ3MSUwIwYDVQQD +# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDq +# 8xzVXwLguauAQj1rrJ4/TyEMm6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w +# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6JpNsjAiGA8yMDIzMDgzMDIzMjIy +# NloYDzIwMjMwODMxMjMyMjI2WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDomk2y +# AgEAMAcCAQACAi7oMAcCAQACAhLnMAoCBQDom58yAgEAMDYGCisGAQQBhFkKBAIx +# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI +# hvcNAQELBQADggEBADE16CHIs3WeXYKQG9djja+StAB7gsNJxV9p+CETL3847UL3 +# +DIeoj6p5g0FkS8PJK2xc+UbcNx6XJO+WUXEbU9GL4mrOCQjYOM+i3r8FEU+l3Gh +# 6ZG/9ygsIYRXEfDKK4lsbrrUkFQs9nDISHT3f8JZEJJXSsGmwcHWlNC0LC8bv0Jp +# e2Bw2+SNc6SlGD8Vv45r4WFPHhfSioCz4HSsF1He3/2Wku7OH85FKvugBlsca7+F +# bpGsDSL4LO9bv60DxuD+8xBZuyTB8s64ifCGlOXCNpK5VaHND48PhoJbuD0COwlM +# Rn5NlT6T4hhtkPOqNscMlzYHmTOKc5NhWK8PyrIxggQNMIIECQIBATCBkzB8MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy +# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAc9SNr5xS81IygABAAABzzAN +# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G +# CSqGSIb3DQEJBDEiBCAjGiC3/PscCMBvPZjpZqbYcL2WRZ+Ecf74oiIPQKkjSzCB +# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EILPpsLqeNS4NuYXE2VJlMuvQeWVA +# 80ZDFhpOPjSzhPa/MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh +# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD +# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw +# MTACEzMAAAHPUja+cUvNSMoAAQAAAc8wIgQgWdbLA3Co0zf3nE5086HU0tdn0vK7 +# yXyU9aAFpszvWFIwDQYJKoZIhvcNAQELBQAEggIANrHN06oaP0N1lUSxxoJteJgI +# fQjN82xV5VSE0cwNCBy05fg1VydPERF59+MIwIlJHhikSo3YOj5tt3AohC78U46P +# 2IdrLHKlA3rjiLUGwQvGUKJUSsEH4uueA1mmh9jwUBJhY3NjTcMQaQqp/oxZTaya +# l7NqbubBIZvsDD126SUr8jtVWtZZzw8pnWCFb4Rijii4fY1UiQzfQLFwqQuid6tE +# I0AaY3IoTlp7U9K2wfAPWcP1G7n3qv+990GEiQlGJlCfIJSSJQodzL2QZF5HCn/K +# SfgkRPn3y/Aax8683mWCT8zricYzO3MZ9j0T7tAcqOiWb7PFCsk5Va44lq4Gv0u+ +# +60FYCAA/Qn6eMuNkqIpBeIK0+NYUcMSwPdY/riKXdgkVLwChEJC5WWznD/Iqsil +# Jj9XYailhXzYx7Pa2MLays62LPwCnUxRQBTD9/rL3XQMj69iA4lisb51dKAtrqAU +# 53aRXFn+twYYFTAUQ/oNK14nZEQE5H53xAfhphMokJOu+CnQQKeCMeYlex6Q4zfw +# TQxP/xXZ+QW2cSZTwh1d5iE0XMhKCZxhxIOF/rRA+75L5GUz60reRZPeH/7USYZL +# VPc0+kxIdSbNFJAhAp0u59wSMQdBofor3+HfDfmxmoSjfCSH4TvOkNIulX1PPJPX +# UB7H4n7XHWJPkUU0cUw= +# SIG # End signature block diff --git a/src/Paramore.Brighter.Analyzer.Package/tools/uninstall.ps1 b/src/Paramore.Brighter.Analyzer.Package/tools/uninstall.ps1 new file mode 100644 index 0000000000..1f6b6d4f0e --- /dev/null +++ b/src/Paramore.Brighter.Analyzer.Package/tools/uninstall.ps1 @@ -0,0 +1,282 @@ +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 + { + + } + } + } + } +} +# SIG # Begin signature block +# MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDC68wb97fg0QGL +# yXrxJhYfmibzcOh8caqC0uZprfczDaCCDXYwggX0MIID3KADAgECAhMzAAADTrU8 +# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU +# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1 +# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm +# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa +# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq +# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw +# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW +# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci +# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG +# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu +# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 +# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk +# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31 +# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2 +# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d +# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM +# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh +# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX +# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir +# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8 +# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A +# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H +# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq +# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG +# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG +# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg +# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 +# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr +# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg +# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy +# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 +# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh +# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k +# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB +# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn +# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 +# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w +# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o +# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD +# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa +# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny +# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG +# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t +# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV +# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG +# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl +# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb +# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l +# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 +# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 +# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 +# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam +# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa +# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah +# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA +# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt +# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr +# /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw +# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN +# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp +# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB +# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO +# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIBdcqRcs5QL71hlQnl3M636V +# 5iTZvb6co3MHeMuIr36qMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A +# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB +# BQAEggEARbhO/zqm6SWBf6DSJ3P1r82VjSyMGXaLtMfTOq/bqHOXPOqC25R1v5uO +# zu4ri+UOS8dU6EfW6C9Xf/Z1Ue/oxrvxn5j8mPvsmcs5OyDO0hW0Wv6pYEy5Z5up +# mfcqvfUfl3+ir29lgPuz0f1mLpz0XxqhjqElEi5RfZD1k1YVg65f0qHroP2txql5 +# TfC77DXJ8verzVm1wqXBHTAERQD94TJobahYTCmyaudMjLUVFakv2lTMv0YTnrQR +# So006ZQg3i1jcVCJt/bRDGKh3xUo1IHgoh3NjMEkxT3iWt8rnX8Us6T6Zg8B2OxC +# 0EnuIu/eYYUlYTLQxO9eks7w1kVcUqGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC +# F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq +# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl +# AwQCAQUABCBQ1HvJFFsT0ICl6oEZlSNN8DlUeQobBHt/oTUGN6iKBgIGZNTKS0Bc +# GBMyMDIzMDgzMTAwMTI1OC4zODVaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV +# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE +# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l +# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046N0YwMC0w +# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg +# ghHtMIIHIDCCBQigAwIBAgITMwAAAdWpAs/Fp8npWgABAAAB1TANBgkqhkiG9w0B +# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE +# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD +# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy +# MzBaFw0yNDAyMDExOTEyMzBaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz +# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv +# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z +# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046N0YwMC0wNUUwLUQ5NDcxJTAjBgNV +# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQDFfak57Oph9vuxtloABiLc6enT+yKH619b+OhGdkyh +# gNzkX80KUGI/jEqOVMV4Sqt/UPFFidx2t7v2SETj2tAzuVKtDfq2HBpu80vZ0vyQ +# DydVt4MDL4tJSKqgYofCxDIBrWzJJjgBolKdOJx1ut2TyOc+UOm7e92tVPHpjdg+ +# Omf31TLUf/oouyAOJ/Inn2ih3ASP0QYm+AFQjhYDNDu8uzMdwHF5QdwsscNa9PVS +# GedLdDLo9jL6DoPF4NYo06lvvEQuSJ9ImwZfBGLy/8hpE7RD4ewvJKmM1+t6eQuE +# sTXjrGM2WjkW18SgUZ8n+VpL2uk6AhDkCa355I531p0Jkqpoon7dHuLUdZSQO40q +# mVIQ6qQCanvImTqmNgE/rPJ0rgr0hMPI/uR1T/iaL0mEq4bqak+3sa8I+FAYOI/P +# C7V+zEek+sdyWtaX+ndbGlv/RJb5mQaGn8NunbkfvHD1Qt5D0rmtMOekYMq7QjYq +# E3FEP/wAY4TDuJxstjsa2HXi2yUDEg4MJL6/JvsQXToOZ+IxR6KT5t5fB5FpZYBp +# VLMma3pm5z6VXvkXrYs33NXJqVWLwiswa7NUFV87Es2sou9Idw3yAZmHIYWgOQ+D +# IY1nY3aG5DODiwN1rJyEb+mbWDagrdVxcncr6UKKO49eoNTXEW+scUf6GwXG0KEy +# mQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFK/QXKNO35bBMOz3R5giX7Ala2OaMB8G +# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG +# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy +# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w +# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy +# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG +# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD +# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBmRddqvQuyjRpx0HGxvOqffFrbgFAg0j82 +# v0v7R+/8a70S2V4t7yKYKSsQGI6pvt1A8JGmuZyjmIXmw23AkI5bZkxvSgws8rrB +# tJw9vakEckcWFQb7JG6b618x0s9Q3DL0dRq46QZRnm7U6234lecvjstAow30dP0T +# nIacPWKpPc3QgB+WDnglN2fdT1ruQ6WIVBenmpjpG9ypRANKUx5NRcpdJAQW2FqE +# HTS3Ntb+0tCqIkNHJ5aFsF6ehRovWZp0MYIz9bpJHix0VrjdLVMOpe7wv62t90E3 +# UrE2KmVwpQ5wsMD6YUscoCsSRQZrA5AbwTOCZJpeG2z3vDo/huvPK8TeTJ2Ltu/I +# tXgxIlIOQp/tbHAiN8Xptw/JmIZg9edQ/FiDaIIwG5YHsfm2u7TwOFyd6OqLw18Z +# 5j/IvDPzlkwWJxk6RHJF5dS4s3fnyLw3DHBe5Dav6KYB4n8x/cEmD/R44/8gS5Pf +# uG1srjLdyyGtyh0KiRDSmjw+fa7i1VPoemidDWNZ7ksNadMad4ZoDvgkqOV4A6a+ +# N8HIc/P6g0irrezLWUgbKXSN8iH9RP+WJFx5fBHE4AFxrbAUQ2Zn5jDmHAI3wYcQ +# DnnEYP51A75WFwPsvBrfrb1+6a1fuTEH1AYdOOMy8fX8xKo0E0Ys+7bxIvFPsUpS +# zfFjBolmhzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI +# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw +# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x +# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy +# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg +# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF +# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 +# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp +# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu +# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E +# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 +# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q +# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ +# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA +# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw +# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG +# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV +# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj +# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK +# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG +# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x +# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC +# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 +# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM +# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS +# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d +# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn +# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs +# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL +# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL +# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ +# MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp +# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw +# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn +# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjdGMDAtMDVFMC1EOTQ3MSUwIwYDVQQD +# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBO +# Ei+S/ZVFe6w1Id31m6Kge26lNKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w +# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6JpN3jAiGA8yMDIzMDgzMDIzMjMx +# MFoYDzIwMjMwODMxMjMyMzEwWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDomk3e +# AgEAMAoCAQACAiLLAgH/MAcCAQACAhLTMAoCBQDom59eAgEAMDYGCisGAQQBhFkK +# BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ +# KoZIhvcNAQELBQADggEBAC/zAZ9IEqD3nHBUWwxFTfnDSRErnCJ6XGxH5jOVtR0t +# 9pi7yCpApLpA2D12g1lcv4ugnwGpsbwmFjrcF4WlHemRa77qv409xNhNKrnh3H+U +# X2hvy9Utp9LiJiqS7lOW5VN1Uv+LbnA+FWt//4J+YLv44D/dliUGjYX623X7KiEX +# dbdXPR/Sn+W2YVQ19O8liKaFDnDnIAz+WLCfL6EaoGu4Te/Mr65Khy3YWTwQfXxr +# gR/JMDzLzWossnGszYCN8S8d9X6mfzWuYv4JHLEiThW++WbMLeT2hhKPomcbvqU4 +# wPb/ylDrTrWuAr/fVndECXVjCIzYJiFwOWn/ZfN9FpQxggQNMIIECQIBATCBkzB8 +# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk +# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N +# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAdWpAs/Fp8npWgABAAAB +# 1TANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE +# MC8GCSqGSIb3DQEJBDEiBCAHS+EjBkJ/YPugQQv1D7eXVQOI4DWPIYpUxpssheDN +# lTCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EINm/I4YM166JMM7EKIcYvlcb +# r2CHjKC0LUOmpZIbBsH/MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m +# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB +# IDIwMTACEzMAAAHVqQLPxafJ6VoAAQAAAdUwIgQgBVj4mNvMkXn0xEeEe3i6urr/ +# L5Eh2p4uPk3zOcR/jHIwDQYJKoZIhvcNAQELBQAEggIAiKbnM5VQAwi5gWDevpEJ +# oHiZRrb0HApEUkca54Oye2+5tyRfWo3ruZvLZueHWsbEzYXclOscHVPjyR1t6s+p +# W13utx4mDab1BaJCBq1q9IH1HDfCf/kSMb0H02UDNBumrt1s5/oaiiY75S3kigpt +# XHCKV/AKbi+4vORYdBwJhWvy4kug+zZHWAp7+K4rVeAaMbWOitriFPQaSdZlOkNT +# VKMbYUtX6VEnhyfr5O390TxPJCdkDznT6rAf5FlQEkim7wb2gb/Osi73KF0dtnRd +# 0ePNT4GxulYTrAhHsZmyuinh/FIyqJDtW3C+2eVt/lx6GJfAtDA/gVCe9mAL4bYv +# 7vjocHeLvWJ8bc0PYMCRZXMsU2zkJqVJ7pZWq+z5hGQAHemrQj9hUZBXCEFVZ8dd +# jEZbIsg2nwZOFakhvdAvvGlTXPbRMOCHblToXAKA8ksRbLob8CDL6Cstoy5VL3al +# fOAWLj3FYITlvGvAYCtJzHrHdqyphL805Co1syR6YDopR8tDrxgWzJKAby5fIolP +# 7SfunXsCa0n3xx80aaxR0apljIXZWBSMGZdJmVzASQFxlexNsU1PmaLmmpn0fih/ +# 567e+kyzFG1wiw5btwttSW9hKgCX+yze1B4IK4yquTQSfPikrCpbBaFZ0OlWmOnL +# r0eKAKY3WDyxYgNyeg6KeCc= +# SIG # End signature block diff --git a/src/Paramore.Brighter.Analyzer/AnalyzerReleases.Shipped.md b/src/Paramore.Brighter.Analyzer/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000000..a32c29b2e2 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/AnalyzerReleases.Shipped.md @@ -0,0 +1,11 @@ +## Release 1.0 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|-------------------- +BRT001 | Design | Warning | ([BRT001](./docs/BRT001.md)) Request Type assignment is Missing +BRT002 | Design | Warning | ([BRT002](./docs/BRT002.md)) RequestType is not child of IRequest +BRT003 | Design | Warning | ([BRT003](./docs/BRT003.md)) MessagePump assignment is Missing +BRT004 | Design | Warning | ([BRT004](./docs/BRT004.md)) Wrap attribute is applied to wrong Method +BRT005 | Design | Warning | ([BRT005](./docs/BRT005.md)) UnWrap attribute is applied to wrong Method \ No newline at end of file diff --git a/src/Paramore.Brighter.Analyzer/AnalyzerReleases.Unshipped.md b/src/Paramore.Brighter.Analyzer/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/AnalyzerReleases.Unshipped.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Paramore.Brighter.Analyzer/Analyzers/PublicationRequestTypeAssignmentAnalyzer.cs b/src/Paramore.Brighter.Analyzer/Analyzers/PublicationRequestTypeAssignmentAnalyzer.cs new file mode 100644 index 0000000000..6ecb90de3a --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/Analyzers/PublicationRequestTypeAssignmentAnalyzer.cs @@ -0,0 +1,81 @@ +#region License +/* The MIT License (MIT) +Copyright © 2026 Aboubakr Nasef + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ +#endregion + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Paramore.Brighter.Analyzer.Visitors.Operation; + +namespace Paramore.Brighter.Analyzer.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class PublicationRequestTypeAssignmentAnalyzer : DiagnosticAnalyzer + { + private const string RequestPublicationCategory = "Design"; + + public static DiagnosticDescriptor RequestTypeMissingRule = new DiagnosticDescriptor( + id: DiagnosticsIds.RequestTypeMissing, + title: "Request Type Missing", + messageFormat: "RequestType assignment is Missing from {0}", + category: RequestPublicationCategory, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true + ); + public static DiagnosticDescriptor WrongRequestTypeRule = new DiagnosticDescriptor( + id: DiagnosticsIds.WrongRequestType, + title: "Wrong Request Type", + messageFormat: "RequestType '{0}' is not child of IRequest", + category: RequestPublicationCategory, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true + ); + public override ImmutableArray SupportedDiagnostics { get { return [RequestTypeMissingRule, WrongRequestTypeRule]; } } + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterOperationAction(AnalyzerOperation, OperationKind.ObjectCreation); + } + + private void AnalyzerOperation(OperationAnalysisContext context) + { + var visitor = new RequestTypeAssignmentVisitor(); + context.Operation.Accept(visitor); + if (visitor.IsPublicationType && !visitor.IsRequestTypeAssigned) + { + context.ReportDiagnostic(Diagnostic.Create( + RequestTypeMissingRule, + context.Operation.Syntax.GetLocation(), + visitor.PublicationName)); + } + else if (visitor.IsNotTypeOfIRequest) + { + context.ReportDiagnostic(Diagnostic.Create( + WrongRequestTypeRule, + visitor.TypeOfLocation, + visitor.TypeOfName)); + } + } + } +} diff --git a/src/Paramore.Brighter.Analyzer/Analyzers/SubscriptionConstructorAnalyzer.cs b/src/Paramore.Brighter.Analyzer/Analyzers/SubscriptionConstructorAnalyzer.cs new file mode 100644 index 0000000000..9c03dbf795 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/Analyzers/SubscriptionConstructorAnalyzer.cs @@ -0,0 +1,68 @@ +#region License +/* The MIT License (MIT) +Copyright © 2026 Aboubakr Nasef + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ +#endregion + +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Paramore.Brighter.Analyzer.Visitors.Operation; + +namespace Paramore.Brighter.Analyzer.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class SubscriptionConstructorAnalyzer : DiagnosticAnalyzer + { + private const string SubscriptionCategory = "Design"; + + public static DiagnosticDescriptor MessagePumpMissingRule = new DiagnosticDescriptor( + id: DiagnosticsIds.MessagePumpMissing, + title: "MessagePump Missing", + messageFormat: "MessagePump assignment is Missing from {0}", + category: SubscriptionCategory, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true + ); + public override ImmutableArray SupportedDiagnostics => [MessagePumpMissingRule]; + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterOperationAction(AnalyzeOperation, OperationKind.ObjectCreation); + } + + private void AnalyzeOperation(OperationAnalysisContext context) + { + var visitor = new SubscriptionConstructorVisitor(); + context.Operation.Accept(visitor); + if (visitor.IsSubscriptionType && visitor.IsMessagePumpDefault) + { + var diagnostic = Diagnostic.Create( + MessagePumpMissingRule, + context.Operation.Syntax.GetLocation(), + visitor.SubscriptionName); + context.ReportDiagnostic(diagnostic); + } + } + } +} diff --git a/src/Paramore.Brighter.Analyzer/Analyzers/WrapAttributeAnalyzer.cs b/src/Paramore.Brighter.Analyzer/Analyzers/WrapAttributeAnalyzer.cs new file mode 100644 index 0000000000..0c603c7971 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/Analyzers/WrapAttributeAnalyzer.cs @@ -0,0 +1,78 @@ +#region License +/* The MIT License (MIT) +Copyright © 2026 Aboubakr Nasef + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ +#endregion + +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Paramore.Brighter.Analyzer.Visitors.Operation; +using Paramore.Brighter.Analyzer.Visitors.Symbol; + +namespace Paramore.Brighter.Analyzer.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class WrapAttributeAnalyzer : DiagnosticAnalyzer + { + private const string WrapAttributeCategory = "Design"; + + public static DiagnosticDescriptor WrapAttributeRule = new DiagnosticDescriptor( + id: DiagnosticsIds.WrapWithAttribute, + title: "WrapWithAttribute", + messageFormat: $"{{0}} should be applied '{BrighterAnalyzerGlobals.MapToMessage}' Method", + category: WrapAttributeCategory, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true + ); + public static DiagnosticDescriptor UnWrapWithAttributeRule = new DiagnosticDescriptor( + id: DiagnosticsIds.UnWrapWithAttribute, + title: "UnWrapWithAttribute", + messageFormat: $"{{0}} should be applied '{BrighterAnalyzerGlobals.MapToRequest}' Method", + category: WrapAttributeCategory, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true + ); + public override ImmutableArray SupportedDiagnostics => [WrapAttributeRule, UnWrapWithAttributeRule]; + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method); + } + + private void AnalyzeSymbol(SymbolAnalysisContext context) + { + var visitor = new WrapAttributeSymbolVisitor(); + context.Symbol.Accept(visitor); + foreach (var item in visitor.WrongWrappedMapToRequest) + { + context.ReportDiagnostic(Diagnostic.Create(WrapAttributeRule, item.Location, item.Name)); + } + foreach (var item in visitor.WrongWrappedMapToMessage) + { + context.ReportDiagnostic(Diagnostic.Create(UnWrapWithAttributeRule, item.Location, item.Name)); + } + + } + } +} diff --git a/src/Paramore.Brighter.Analyzer/BrighterAnalyzerGlobals.cs b/src/Paramore.Brighter.Analyzer/BrighterAnalyzerGlobals.cs new file mode 100644 index 0000000000..4ad546154d --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/BrighterAnalyzerGlobals.cs @@ -0,0 +1,43 @@ +#region License +/* The MIT License (MIT) +Copyright © 2026 Aboubakr Nasef + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +namespace Paramore.Brighter.Analyzer +{ + public class BrighterAnalyzerGlobals + { + public const string PublicationClassName = "Publication"; + public const string BrighterAssembly = "Paramore.Brighter"; + public const string RequestTypeProperty = "RequestType"; + public const string IRequestInterface = "IRequest"; + + public const string MessagePumpTypeEnumName = "MessagePumpType"; + public const string SubscriptionClassName = "Subscription"; + + public const string MessageMapperInterface = "IAmAMessageMapper"; + public const string UnwrapWithAttribute = "UnwrapWithAttribute"; + public const string WrapWithAttribute = "WrapWithAttribute"; + public const string MapToMessage = "MapToMessage"; + public const string MapToRequest = "MapToRequest"; + } +} diff --git a/src/Paramore.Brighter.Analyzer/DiagnosticsIds.cs b/src/Paramore.Brighter.Analyzer/DiagnosticsIds.cs new file mode 100644 index 0000000000..96e86e3c18 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/DiagnosticsIds.cs @@ -0,0 +1,36 @@ +#region License +/* The MIT License (MIT) +Copyright © 2026 Aboubakr Nasef + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + + +namespace Paramore.Brighter.Analyzer +{ + public static class DiagnosticsIds + { + public const string RequestTypeMissing = "BRT001"; + public const string WrongRequestType = "BRT002"; + public const string MessagePumpMissing = "BRT003"; + public const string WrapWithAttribute = "BRT004"; + public const string UnWrapWithAttribute = "BRT005"; + } +} diff --git a/src/Paramore.Brighter.Analyzer/Paramore.Brighter.Analyzer.csproj b/src/Paramore.Brighter.Analyzer/Paramore.Brighter.Analyzer.csproj new file mode 100644 index 0000000000..71264c5b3a --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/Paramore.Brighter.Analyzer.csproj @@ -0,0 +1,16 @@ + + + + $(BrighterNetStandardTargetFrameworks) + Aboubakr Nasef + Analyzers for the brighter library + Analyzer;Scheduler;Message Scheduling;Command Processor;Brighter + false + true + true + + + + + + diff --git a/src/Paramore.Brighter.Analyzer/Visitors/Operation/RequestTypeAssignmentVisitor.cs b/src/Paramore.Brighter.Analyzer/Visitors/Operation/RequestTypeAssignmentVisitor.cs new file mode 100644 index 0000000000..cc276c4ed1 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/Visitors/Operation/RequestTypeAssignmentVisitor.cs @@ -0,0 +1,70 @@ +#region License +/* The MIT License (MIT) +Copyright © 2026 Aboubakr Nasef + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Operations; +using Paramore.Brighter.Analyzer.Visitors.Symbol; + + +namespace Paramore.Brighter.Analyzer.Visitors.Operation +{ + public class RequestTypeAssignmentVisitor : OperationWalker + { + public bool IsPublicationType { get; private set; } + public bool IsRequestTypeAssigned { get; private set; } + public string PublicationName { get; private set; } + public bool IsNotTypeOfIRequest { get; private set; } + public Location TypeOfLocation { get; private set; } + public string TypeOfName { get; private set; } + + public override void VisitObjectCreation(IObjectCreationOperation operation) + { + if (operation.Type!.Accept(new ChildOfVisitor(BrighterAnalyzerGlobals.PublicationClassName, BrighterAnalyzerGlobals.BrighterAssembly))) + { + PublicationName = operation.Type.Name; + IsPublicationType = true; + base.VisitObjectCreation(operation); + } + } + public override void VisitPropertyReference(IPropertyReferenceOperation operation) + { + if (operation.Property.Name == BrighterAnalyzerGlobals.RequestTypeProperty) + { + IsRequestTypeAssigned = true; + base.VisitPropertyReference(operation); + } + } + public override void VisitTypeOf(ITypeOfOperation operation) + { + if (!operation.TypeOperand.AllInterfaces.Any(i => i.Name == BrighterAnalyzerGlobals.IRequestInterface && i.ContainingAssembly.Name == BrighterAnalyzerGlobals.BrighterAssembly)) + { + TypeOfLocation = operation.Syntax.GetLocation(); + TypeOfName = operation.TypeOperand.Name; + IsNotTypeOfIRequest = true; + } + base.VisitTypeOf(operation); + } + } +} diff --git a/src/Paramore.Brighter.Analyzer/Visitors/Operation/SubscriptionConstructorVisitor.cs b/src/Paramore.Brighter.Analyzer/Visitors/Operation/SubscriptionConstructorVisitor.cs new file mode 100644 index 0000000000..58fd98a830 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/Visitors/Operation/SubscriptionConstructorVisitor.cs @@ -0,0 +1,53 @@ +#region License +/* The MIT License (MIT) +Copyright © 2026 Aboubakr Nasef + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion + +using Microsoft.CodeAnalysis.Operations; +using Paramore.Brighter.Analyzer.Visitors.Symbol; + +namespace Paramore.Brighter.Analyzer.Visitors.Operation +{ + public class SubscriptionConstructorVisitor : OperationWalker + { + public bool IsMessagePumpDefault { get; private set; } = false; + public string SubscriptionName { get; private set; } + public bool IsSubscriptionType { get; private set; } + + public override void VisitObjectCreation(IObjectCreationOperation operation) + { + if (operation.Type!.Accept(new ChildOfVisitor(BrighterAnalyzerGlobals.SubscriptionClassName, BrighterAnalyzerGlobals.BrighterAssembly))) + { + SubscriptionName = operation.Type.Name; + IsSubscriptionType = true; + base.VisitObjectCreation(operation); + } + } + public override void VisitArgument(IArgumentOperation operation) + { + if (operation.Value.Type.Name == BrighterAnalyzerGlobals.MessagePumpTypeEnumName && operation.ArgumentKind == ArgumentKind.DefaultValue) + { + IsMessagePumpDefault = true; + } + } + } +} diff --git a/src/Paramore.Brighter.Analyzer/Visitors/Symbol/ChildOfVisitor.cs b/src/Paramore.Brighter.Analyzer/Visitors/Symbol/ChildOfVisitor.cs new file mode 100644 index 0000000000..c535b4d64f --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/Visitors/Symbol/ChildOfVisitor.cs @@ -0,0 +1,45 @@ +#region License +/* The MIT License (MIT) +Copyright © 2026 Aboubakr Nasef + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion +using Microsoft.CodeAnalysis; + +namespace Paramore.Brighter.Analyzer.Visitors.Symbol +{ + public class ChildOfVisitor(string className, string assmeblyName) : SymbolVisitor + { + public override bool VisitNamedType(INamedTypeSymbol symbol) + { + var type = symbol; + var assembly = type.ContainingAssembly; + if (symbol.Name == className && assembly.Name == assmeblyName) + { + return true; + } + if (symbol.BaseType != null) + { + return symbol.BaseType.Accept(this); + } + return false; + } + } +} diff --git a/src/Paramore.Brighter.Analyzer/Visitors/Symbol/WrapAttributeSymbolVisitor.cs b/src/Paramore.Brighter.Analyzer/Visitors/Symbol/WrapAttributeSymbolVisitor.cs new file mode 100644 index 0000000000..233f31ffa8 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/Visitors/Symbol/WrapAttributeSymbolVisitor.cs @@ -0,0 +1,73 @@ +#region License +/* The MIT License (MIT) +Copyright © 2026 Aboubakr Nasef + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. */ + +#endregion +using System; +using System.Collections.Generic; +using System.Linq; + +using Microsoft.CodeAnalysis; +using Paramore.Brighter.Analyzer.Visitors.Symbol; + +namespace Paramore.Brighter.Analyzer.Visitors.Symbol +{ + public class WrapAttributeSymbolVisitor : SymbolVisitor + { + private List<(string Name, Location Location)> _wrongWrappedMapToRequest = new(); + public IReadOnlyList<(string Name, Location Location)> WrongWrappedMapToRequest => _wrongWrappedMapToRequest; + private List<(string Name, Location Location)> _wrongWrappedMapToMessage = new(); + public IReadOnlyList<(string Name, Location Location)> WrongWrappedMapToMessage => _wrongWrappedMapToMessage; + public override void VisitMethod(IMethodSymbol symbol) + { + var isMessageMapperType = symbol.ContainingType.AllInterfaces + .Any(c => c.Name.StartsWith(BrighterAnalyzerGlobals.MessageMapperInterface) + && c.ContainingAssembly.Name.Equals(BrighterAnalyzerGlobals.BrighterAssembly)); + if (!isMessageMapperType) + return; + + if (symbol.Name.StartsWith(BrighterAnalyzerGlobals.MapToRequest)) + { + ReportInvalidAttributes(symbol, BrighterAnalyzerGlobals.WrapWithAttribute, _wrongWrappedMapToRequest); + } + else if (symbol.Name.StartsWith(BrighterAnalyzerGlobals.MapToMessage)) + { + ReportInvalidAttributes(symbol, BrighterAnalyzerGlobals.UnwrapWithAttribute, _wrongWrappedMapToMessage); + } + + base.VisitMethod(symbol); + } + private void ReportInvalidAttributes(IMethodSymbol symbol, string invalidAttributeBase, List<(string, Location)> collection) + { + var visitor = new ChildOfVisitor(invalidAttributeBase, BrighterAnalyzerGlobals.BrighterAssembly); + + foreach (var attr in symbol.GetAttributes()) + { + if (attr.AttributeClass?.Accept(visitor) == true) + { + var location = attr.ApplicationSyntaxReference?.GetSyntax()?.GetLocation() ?? symbol.Locations[0]; + collection.Add((attr.AttributeClass.Name, location)); + } + } + } + } +} + diff --git a/src/Paramore.Brighter.Analyzer/docs/BRT001.md b/src/Paramore.Brighter.Analyzer/docs/BRT001.md new file mode 100644 index 0000000000..e0bae21518 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/docs/BRT001.md @@ -0,0 +1,24 @@ +# BRT001: Request Type Missing + +## Description +This rule ensures that a `Publication` object creation has a `RequestType` assigned. Without specifying the request type, Brighter cannot determine which message type this publication or subscription is for. + +## Why is this a warning? +Failing to specify the `RequestType` will lead to runtime errors or incorrect message routing, as the system won't know how to handle the publication or subscription. + +## How to fix +Assign a valid request type to the `RequestType` property during object initialization. + +### Example +```csharp +// Warning: RequestType is missing + +var publication = new Publication { + // Missing RequestType +}; + +// Fixed: RequestType is assigned +var publication = new Publication { + RequestType = typeof(MyRequest) +}; +``` diff --git a/src/Paramore.Brighter.Analyzer/docs/BRT002.md b/src/Paramore.Brighter.Analyzer/docs/BRT002.md new file mode 100644 index 0000000000..07e1d32260 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/docs/BRT002.md @@ -0,0 +1,23 @@ +# BRT002: Wrong Request Type + +## Description +This rule ensures that the type assigned to `RequestType` in a `Publication` implements the `IRequest` interface. + +## Why is this a warning? +Brighter requires all request types to implement `IRequest`. Using a type that doesn't implement this interface will cause internal errors when attempting to route messages. + +## How to fix +Ensure the type used in `RequestType` implements `Paramore.Brighter.IRequest`. + +### Example +```csharp +// Error: MyType does not implement IRequest +var publication = new Publication { + RequestType = typeof(MyType) +}; + +// Fixed: MyRequest implements IRequest +var publication = new Publication { + RequestType = typeof(MyRequest) +}; +``` diff --git a/src/Paramore.Brighter.Analyzer/docs/BRT003.md b/src/Paramore.Brighter.Analyzer/docs/BRT003.md new file mode 100644 index 0000000000..070998d35f --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/docs/BRT003.md @@ -0,0 +1,25 @@ +# BRT003: MessagePump Missing + +## Description +This rule identifies when a `Subscription` is created without specifying a `MessagePumpType`. + +## Why is this a warning? +A subscription needs a message pump to process incoming messages. If not specified, the subscription will not function as expected in a service host environment. + +## How to fix +Assign a valid message pump type to the `MessagePumpType` property during initialization. + +### Example +```csharp +// Error: MessagePumpType is missing +var subscription = new Subscription( +"MySubscription", ... +); + +// Fixed: MessagePumpType is assigned +var subscription = new Subscription( + "MySubscription", + messagePumpType: MessagePumpType.Reactor, + ... +); +``` diff --git a/src/Paramore.Brighter.Analyzer/docs/BRT004.md b/src/Paramore.Brighter.Analyzer/docs/BRT004.md new file mode 100644 index 0000000000..5071f3fad5 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/docs/BRT004.md @@ -0,0 +1,43 @@ +# BRT004: WrapWithAttribute Correct Usage + +## Description +This rule ensures that the "Wrap" attributes are correctly applied to methods that map domain requests back to messages (typically `MapToMessage`). + +## Why is this a warning? +Applying a "Wrap" attribute to the wrong mapping method can lead to unexpected behavior in the middleware pipeline, such as attempting to wrap a message into a request when it's already a request, or vice-versa. + +## How to fix +Ensure that attributes intended for wrapping are applied to the `MapToMessage` method. + +### Example +Check your `IAmAMessageMapper ,IAmAMessageMapperAsync` implementation to ensure attributes are on the correct method. +```csharp + public class WrapWithMapperSample: IAmAMessageMapper + { + /* ommited code */ + + // Error: Attributes should be assigned to the MapToMessage method + [Compress(0)] + [ClaimCheck(0)] + public SampleEvent MapToRequest(Message message) + { + /* ommited code*/ + } + /* ommited code */ + } + + // Fixed: Attributes assigned to the MapToMessage method + public class WrapWithMapperSample: IAmAMessageMapper + { + /* ommited code */ + + [Compress(0)] + [ClaimCheck(0)] + public Message MapToMessage(SampleEvent request, Publication publication) + { + /* ommited code*/ + } + + /* ommited code */ + } +``` diff --git a/src/Paramore.Brighter.Analyzer/docs/BRT005.md b/src/Paramore.Brighter.Analyzer/docs/BRT005.md new file mode 100644 index 0000000000..bf25e7eb17 --- /dev/null +++ b/src/Paramore.Brighter.Analyzer/docs/BRT005.md @@ -0,0 +1,42 @@ +# BRT005: UnWrapWithAttribute Correct Usage + +## Description +This rule ensures that "Unwrap" attributes are correctly applied to methods that map messages to domain requests (typically `MapToRequest`). + +## Why is this a warning? +Applying an "Unwrap" attribute to the wrong mapping method can disrupt the flow of message processing in Brighter's pipeline. + +## How to fix +Ensure that attributes intended for unwrapping are applied to the `MapToRequest` method. + +### Example +Check your `IAmAMessageMapper , IAmAMessageMapperAsync` implementation to ensure attributes are on the correct method. +```csharp + public class UnwrapWithMapperSample: IAmAMessageMapper + { + /* + ommited code + */ + + // Error: Attributes should be assigned to the MapToRequest method + [Decompress(0)] + public Message MapToMessage(SampleEvent request, Publication publication) + { + /* ommited code*/ + } + /* ommited code */ + } + // Fixed: Attributes assigned to the MapToRequest method + public class UnwrapWithMapperSample: IAmAMessageMapper + { + /* ommited code */ + + [Decompress(0)] + public SampleEvent MapToRequest(Message message) + { + /* ommited code*/ + } + + /* ommited code */ + } +``` diff --git a/src/Paramore.Brighter.ServiceActivator.Control.Api/Paramore.Brighter.ServiceActivator.Control.Api.csproj b/src/Paramore.Brighter.ServiceActivator.Control.Api/Paramore.Brighter.ServiceActivator.Control.Api.csproj index 092ad45f5a..428c4eabd0 100644 --- a/src/Paramore.Brighter.ServiceActivator.Control.Api/Paramore.Brighter.ServiceActivator.Control.Api.csproj +++ b/src/Paramore.Brighter.ServiceActivator.Control.Api/Paramore.Brighter.ServiceActivator.Control.Api.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core API extensions for Paramore.Brighter Service Activator Control module. Provides REST endpoints for querying service activator node status and health information. @@ -16,8 +16,4 @@ - - - - diff --git a/tests/Paramore.Brighter.Analyzer.Tests/Analyzers/BaseAnalyzerTest.cs b/tests/Paramore.Brighter.Analyzer.Tests/Analyzers/BaseAnalyzerTest.cs new file mode 100644 index 0000000000..7f3e4da868 --- /dev/null +++ b/tests/Paramore.Brighter.Analyzer.Tests/Analyzers/BaseAnalyzerTest.cs @@ -0,0 +1,24 @@ + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Paramore.Brighter.Analyzer.Analyzers; + +namespace Paramore.Brighter.Analyzer.Tests.Analyzers +{ + public abstract class BaseAnalyzerTest where T : DiagnosticAnalyzer, new() + { + protected CSharpAnalyzerTest testContext; + protected BaseAnalyzerTest() + { + testContext = new CSharpAnalyzerTest + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net90 + }; + testContext.TestState.OutputKind = OutputKind.ConsoleApplication; + testContext.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(Publication).Assembly.Location)); + testContext.CompilerDiagnostics = CompilerDiagnostics.None; + } + } +} diff --git a/tests/Paramore.Brighter.Analyzer.Tests/Analyzers/PublicationRequestTypeAssignmentAnalyzerTest.cs b/tests/Paramore.Brighter.Analyzer.Tests/Analyzers/PublicationRequestTypeAssignmentAnalyzerTest.cs new file mode 100644 index 0000000000..a8e904138a --- /dev/null +++ b/tests/Paramore.Brighter.Analyzer.Tests/Analyzers/PublicationRequestTypeAssignmentAnalyzerTest.cs @@ -0,0 +1,98 @@ +using Microsoft.CodeAnalysis.Testing; +using Paramore.Brighter.Analyzer.Analyzers; +using Paramore.Brighter.Analyzer.Tests.Analyzers; + + +namespace Paramore.Brighter.Analyzer.Test.Analyzers +{ + public class PublicationRequestTypeAssignmentAnalyzerTest : BaseAnalyzerTest + { + + [Fact] + public async Task When_Initializing_Publication_WithOut_RequestType() + { + + testContext.TestCode = /* lang=c#-test */ """ +using Paramore.Brighter; +namespace TestNamespace +{ +var publication = {|#0:new PublicationTest()|}; + + public class PublicationTest : Publication + { + } + } +"""; + + testContext.ExpectedDiagnostics.Add(new DiagnosticResult(PublicationRequestTypeAssignmentAnalyzer.RequestTypeMissingRule).WithLocation(0).WithArguments("PublicationTest")); + await testContext.RunAsync(); + } + + [Fact] + public async Task When_Initializing_Publication_With_Right_RequestType() + { + testContext.TestCode = /* lang=c#-test */ """ +using Paramore.Brighter; +namespace TestNamespace +{ +var publication =new PublicationTest() +{ +RequestType = typeof(EventSample) +}; + + public class PublicationTest : Publication + { + } + public class EventSample : Event +{ + public EventSample(Id id) : base(id) + { + } +} + } +"""; + await testContext.RunAsync(); + } + + [Fact] + public async Task When_Initializing_Publication_With_Wrong_RequestType() + { + testContext.TestCode = /* lang=c#-test */ """ +using Paramore.Brighter; +namespace TestNamespace +{ +var publication = new PublicationTest() +{ +RequestType = {|#0:typeof(EventSample)|} +}; + + public class PublicationTest : Publication + {} + public class EventSample{} + } +"""; + testContext.ExpectedDiagnostics.Add(new DiagnosticResult(PublicationRequestTypeAssignmentAnalyzer.WrongRequestTypeRule).WithLocation(0).WithArguments("EventSample")); + + await testContext.RunAsync(); + } + + [Fact] + public async Task When_Initializing_Non_Publication_Type() + { + testContext.TestCode = /* lang=c#-test */ """ + using System.Collections.Generic; + namespace TestNamespace + { + public class AnyClass + { + public void Method() + { + var list = new List(); // Should be ignored + } + } + } +"""; + await testContext.RunAsync(); + } + } +} diff --git a/tests/Paramore.Brighter.Analyzer.Tests/Analyzers/SubscriptionConstructorAnalyzerTest.cs b/tests/Paramore.Brighter.Analyzer.Tests/Analyzers/SubscriptionConstructorAnalyzerTest.cs new file mode 100644 index 0000000000..8d2bf4d8a2 --- /dev/null +++ b/tests/Paramore.Brighter.Analyzer.Tests/Analyzers/SubscriptionConstructorAnalyzerTest.cs @@ -0,0 +1,83 @@ +using Microsoft.CodeAnalysis.Testing; +using Paramore.Brighter.Analyzer.Analyzers; +using Paramore.Brighter.Analyzer.Tests.Analyzers; + +namespace Paramore.Brighter.Analyzer.Test.Analyzers +{ + public class SubscriptionConstructorAnalyzerTest : BaseAnalyzerTest + { + [Fact] + public async Task When_Initializing_Subscription_With_MessagePump() + { + + testContext.TestCode = /* lang=c#-test */ """ + using Paramore.Brighter; + namespace TestNamespace + { + var subscription = {|#0:new SubscriptionTest("name", "name", "key", messagePumpType: MessagePumpType.Reactor)|}; + + public class SubscriptionTest : Subscription + { + public SubscriptionTest(SubscriptionName subscriptionName, ChannelName channelName, RoutingKey routingKey, Type? requestType = null, Func? getRequestType = null, int bufferSize = 1, int noOfPerformers = 1, TimeSpan? timeOut = null, int requeueCount = -1, TimeSpan? requeueDelay = null, int unacceptableMessageLimit = 0, MessagePumpType messagePumpType = MessagePumpType.Unknown, IAmAChannelFactory? channelFactory = null, OnMissingChannel makeChannels = OnMissingChannel.Create, TimeSpan? emptyChannelDelay = null, TimeSpan? channelFailureDelay = null) : base(subscriptionName, channelName, routingKey, requestType, getRequestType, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, unacceptableMessageLimit, messagePumpType, channelFactory, makeChannels, emptyChannelDelay, channelFailureDelay) + { + } + } + } +"""; + + await testContext.RunAsync(); + } + + [Fact] + public async Task When_Initializing_Subscription_WithOut_MessagePump() + { + + testContext.TestCode = /* lang=c#-test */ """ + using Paramore.Brighter; + namespace TestNamespace + { + var subscription = {|#0:new SubscriptionTest("name", "name", "key")|}; + + public class SubscriptionTest : Subscription + { + public SubscriptionTest(SubscriptionName subscriptionName, ChannelName channelName, RoutingKey routingKey, Type? requestType = null, Func? getRequestType = null, int bufferSize = 1, int noOfPerformers = 1, TimeSpan? timeOut = null, int requeueCount = -1, TimeSpan? requeueDelay = null, int unacceptableMessageLimit = 0, MessagePumpType messagePumpType = MessagePumpType.Unknown, IAmAChannelFactory? channelFactory = null, OnMissingChannel makeChannels = OnMissingChannel.Create, TimeSpan? emptyChannelDelay = null, TimeSpan? channelFailureDelay = null) : base(subscriptionName, channelName, routingKey, requestType, getRequestType, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, unacceptableMessageLimit, messagePumpType, channelFactory, makeChannels, emptyChannelDelay, channelFailureDelay) + { + } + } + } +"""; + + testContext.ExpectedDiagnostics.Add(new DiagnosticResult(SubscriptionConstructorAnalyzer.MessagePumpMissingRule).WithLocation(0).WithArguments("SubscriptionTest")); + await testContext.RunAsync(); + } + [Fact] + public async Task When_Initializing_SubscriptionNested_WithOut_MessagePump() + { + + testContext.TestCode = /* lang=c#-test */ """ + using Paramore.Brighter; + namespace TestNamespace + { + var subscription = {|#0:new SubscriptionTestNested("name", "name", "key")|}; + + public class SubscriptionTest : Subscription + { + public SubscriptionTest(SubscriptionName subscriptionName, ChannelName channelName, RoutingKey routingKey, Type? requestType = null, Func? getRequestType = null, int bufferSize = 1, int noOfPerformers = 1, TimeSpan? timeOut = null, int requeueCount = -1, TimeSpan? requeueDelay = null, int unacceptableMessageLimit = 0, MessagePumpType messagePumpType = MessagePumpType.Unknown, IAmAChannelFactory? channelFactory = null, OnMissingChannel makeChannels = OnMissingChannel.Create, TimeSpan? emptyChannelDelay = null, TimeSpan? channelFailureDelay = null) : base(subscriptionName, channelName, routingKey, requestType, getRequestType, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, unacceptableMessageLimit, messagePumpType, channelFactory, makeChannels, emptyChannelDelay, channelFailureDelay) + { + } + } + public class SubscriptionTestNested : SubscriptionTest + { + public SubscriptionTestNested(SubscriptionName subscriptionName, ChannelName channelName, RoutingKey routingKey, Type? requestType = null, Func? getRequestType = null, int bufferSize = 1, int noOfPerformers = 1, TimeSpan? timeOut = null, int requeueCount = -1, TimeSpan? requeueDelay = null, int unacceptableMessageLimit = 0, MessagePumpType messagePumpType = MessagePumpType.Unknown, IAmAChannelFactory? channelFactory = null, OnMissingChannel makeChannels = OnMissingChannel.Create, TimeSpan? emptyChannelDelay = null, TimeSpan? channelFailureDelay = null) : base(subscriptionName, channelName, routingKey, requestType, getRequestType, bufferSize, noOfPerformers, timeOut, requeueCount, requeueDelay, unacceptableMessageLimit, messagePumpType, channelFactory, makeChannels, emptyChannelDelay, channelFailureDelay) + { + } + } + } +"""; + + testContext.ExpectedDiagnostics.Add(new DiagnosticResult(SubscriptionConstructorAnalyzer.MessagePumpMissingRule).WithLocation(0).WithArguments("SubscriptionTestNested")); + await testContext.RunAsync(); + } + + } +} diff --git a/tests/Paramore.Brighter.Analyzer.Tests/Analyzers/WrapAttributeAnalyzerTest.cs b/tests/Paramore.Brighter.Analyzer.Tests/Analyzers/WrapAttributeAnalyzerTest.cs new file mode 100644 index 0000000000..8ad50ccffd --- /dev/null +++ b/tests/Paramore.Brighter.Analyzer.Tests/Analyzers/WrapAttributeAnalyzerTest.cs @@ -0,0 +1,46 @@ +using Microsoft.CodeAnalysis.Testing; +using Paramore.Brighter.Analyzer.Analyzers; +using Paramore.Brighter.Analyzer.Tests.Analyzers; + +namespace Paramore.Brighter.Analyzer.Test.Analyzers +{ + public class WrapAttributeAnalyzerTest: BaseAnalyzerTest + { + + [Fact] + public async Task When_Adding_Attribute_To_MessageMapper() + { + + testContext.TestCode = /* lang=c#-test */ """ + using Paramore.Brighter; + using Paramore.Brighter.Transforms.Attributes; + namespace TestNamespace + { + public class WrapWithSample: IAmAMessageMapper + { + public IRequestContext? Context { get; set ; } + + [{|#0:Compress(0)|}] + public SampleEvent MapToRequest(Message message) + { + return new SampleEvent(message.Id); + } + [{|#1:Decompress(0)|}] + public Message MapToMessage(SampleEvent request, Publication publication) + { + throw new NotImplementedException(); + } + } + + + public class SampleEvent(Id id) : Event(id) + { + } +"""; + + testContext.ExpectedDiagnostics.Add(new DiagnosticResult(WrapAttributeAnalyzer.WrapAttributeRule ).WithLocation(0).WithArguments("CompressAttribute")); + testContext.ExpectedDiagnostics.Add(new DiagnosticResult(WrapAttributeAnalyzer.UnWrapWithAttributeRule ).WithLocation(1).WithArguments("DecompressAttribute")); + await testContext.RunAsync(); + } + } +} diff --git a/tests/Paramore.Brighter.Analyzer.Tests/Paramore.Brighter.Analyzer.Tests.csproj b/tests/Paramore.Brighter.Analyzer.Tests/Paramore.Brighter.Analyzer.Tests.csproj new file mode 100644 index 0000000000..9efa9a02df --- /dev/null +++ b/tests/Paramore.Brighter.Analyzer.Tests/Paramore.Brighter.Analyzer.Tests.csproj @@ -0,0 +1,33 @@ + + + + $(BrighterTestNineOnlyTargetFrameworks) + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + + + +