From 7d33f637d7b6d6e5179581d977b046acb565518e Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:53:13 +0100 Subject: [PATCH 1/2] feat(analyzers): unify diagnostic IDs --- docs/docs/analyzers/MP0001.md | 32 +++ docs/docs/analyzers/MP0002.md | 32 +++ docs/docs/analyzers/MP0003.md | 32 +++ docs/docs/analyzers/MP0004.md | 32 +++ docs/docs/analyzers/MP0005.md | 32 +++ docs/docs/analyzers/MP0006.md | 32 +++ docs/docs/analyzers/MP0007.md | 32 +++ docs/docs/analyzers/MP0008.md | 32 +++ docs/docs/analyzers/MP0009.md | 32 +++ docs/docs/analyzers/MP0010.md | 32 +++ docs/docs/analyzers/MP0011.md | 32 +++ docs/docs/analyzers/MP0012.md | 32 +++ docs/docs/analyzers/MP0013.md | 32 +++ docs/docs/analyzers/MP0014.md | 32 +++ docs/docs/analyzers/MP0015.md | 32 +++ docs/docs/analyzers/MP0016.md | 32 +++ docs/docs/analyzers/MP0017.md | 32 +++ docs/docs/analyzers/MP0018.md | 32 +++ docs/docs/analyzers/MP0019.md | 32 +++ docs/docs/analyzers/_category_.json | 8 + docs/docs/analyzers/index.md | 31 +++ docs/docs/how-to/analyzers.md | 57 ++++- docs/package.json | 4 + docs/scripts/generate-analyzer-docs.mjs | 196 ++++++++++++++++++ .../AnalyzerMetadataTests.cs | 49 +++++ .../AnalyzerReleases.Unshipped.md | 36 ++-- .../AsyncModuleAnalyzer.cs | 2 +- .../AwaitThisAnalyzer.cs | 2 +- .../ConflictingDependsOnAttributeAnalyzer.cs | 4 +- .../ConsoleUseAnalyzer.cs | 4 +- .../DiagnosticDescriptorFactory.cs | 6 +- .../DuplicateDependsOnAnalyzer.cs | 2 +- .../EnumerableModuleResultAnalyzer.cs | 4 +- .../InvalidDependsOnTypeAnalyzer.cs | 4 +- .../LoggerInConstructorAnalyzer.cs | 4 +- .../MissingDependsOnAttributeAnalyzer.cs | 2 +- .../ModuleAsyncSafetyAnalyzer.cs | 8 +- .../ModuleRegistrationAnalyzer.cs | 4 +- .../SelfDependencyAnalyzer.cs | 4 +- .../StatefulModuleAnalyzer.cs | 2 +- .../AnalyzerReleases.Shipped.md | 8 +- .../AnalyzerReleases.Unshipped.md | 13 +- .../DiagnosticDescriptorFactory.cs | 39 ++++ .../Resources.Designer.cs | 32 +-- .../Resources.resx | 18 +- .../VirtualCommandAnalyzer.cs | 24 +-- .../VirtualCommandCodeFixProvider.cs | 6 +- .../VirtualSwitchPropertyAnalyzer.cs | 24 +-- .../VirtualSwitchPropertyCodeFixProvider.cs | 6 +- .../AnalyzerMetadataTests.cs | 28 +++ 50 files changed, 1122 insertions(+), 117 deletions(-) create mode 100644 docs/docs/analyzers/MP0001.md create mode 100644 docs/docs/analyzers/MP0002.md create mode 100644 docs/docs/analyzers/MP0003.md create mode 100644 docs/docs/analyzers/MP0004.md create mode 100644 docs/docs/analyzers/MP0005.md create mode 100644 docs/docs/analyzers/MP0006.md create mode 100644 docs/docs/analyzers/MP0007.md create mode 100644 docs/docs/analyzers/MP0008.md create mode 100644 docs/docs/analyzers/MP0009.md create mode 100644 docs/docs/analyzers/MP0010.md create mode 100644 docs/docs/analyzers/MP0011.md create mode 100644 docs/docs/analyzers/MP0012.md create mode 100644 docs/docs/analyzers/MP0013.md create mode 100644 docs/docs/analyzers/MP0014.md create mode 100644 docs/docs/analyzers/MP0015.md create mode 100644 docs/docs/analyzers/MP0016.md create mode 100644 docs/docs/analyzers/MP0017.md create mode 100644 docs/docs/analyzers/MP0018.md create mode 100644 docs/docs/analyzers/MP0019.md create mode 100644 docs/docs/analyzers/_category_.json create mode 100644 docs/docs/analyzers/index.md create mode 100644 docs/scripts/generate-analyzer-docs.mjs create mode 100644 src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/AnalyzerMetadataTests.cs create mode 100644 src/ModularPipelines.Development.Analyzers/DiagnosticDescriptorFactory.cs create mode 100644 test/ModularPipelines.Development.Analyzers.UnitTests/AnalyzerMetadataTests.cs diff --git a/docs/docs/analyzers/MP0001.md b/docs/docs/analyzers/MP0001.md new file mode 100644 index 00000000000..b7acd91c0aa --- /dev/null +++ b/docs/docs/analyzers/MP0001.md @@ -0,0 +1,32 @@ +--- +title: 'MP0001' +slug: /analyzers/MP0001 +sidebar_position: 2 +--- + + + +# MP0001 + +Accesses a module result without declaring the dependency. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Error | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0001.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0001 +// Code that intentionally violates MP0001. +#pragma warning restore MP0001 +``` diff --git a/docs/docs/analyzers/MP0002.md b/docs/docs/analyzers/MP0002.md new file mode 100644 index 00000000000..36f38d0efd8 --- /dev/null +++ b/docs/docs/analyzers/MP0002.md @@ -0,0 +1,32 @@ +--- +title: 'MP0002' +slug: /analyzers/MP0002 +sidebar_position: 3 +--- + + + +# MP0002 + +Uses IEnumerable as a module result instead of a concrete collection. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Error | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0002.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0002 +// Code that intentionally violates MP0002. +#pragma warning restore MP0002 +``` diff --git a/docs/docs/analyzers/MP0003.md b/docs/docs/analyzers/MP0003.md new file mode 100644 index 00000000000..652ce5a401d --- /dev/null +++ b/docs/docs/analyzers/MP0003.md @@ -0,0 +1,32 @@ +--- +title: 'MP0003' +slug: /analyzers/MP0003 +sidebar_position: 4 +--- + + + +# MP0003 + +Injects a logger into a module constructor instead of using the module context. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Error | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0003.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0003 +// Code that intentionally violates MP0003. +#pragma warning restore MP0003 +``` diff --git a/docs/docs/analyzers/MP0004.md b/docs/docs/analyzers/MP0004.md new file mode 100644 index 00000000000..9dcf70a701d --- /dev/null +++ b/docs/docs/analyzers/MP0004.md @@ -0,0 +1,32 @@ +--- +title: 'MP0004' +slug: /analyzers/MP0004 +sidebar_position: 5 +--- + + + +# MP0004 + +Writes directly to System.Console instead of using pipeline logging. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Error | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0004.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0004 +// Code that intentionally violates MP0004. +#pragma warning restore MP0004 +``` diff --git a/docs/docs/analyzers/MP0005.md b/docs/docs/analyzers/MP0005.md new file mode 100644 index 00000000000..bb7851df483 --- /dev/null +++ b/docs/docs/analyzers/MP0005.md @@ -0,0 +1,32 @@ +--- +title: 'MP0005' +slug: /analyzers/MP0005 +sidebar_position: 6 +--- + + + +# MP0005 + +Creates a circular dependency between modules. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Error | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0005.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0005 +// Code that intentionally violates MP0005. +#pragma warning restore MP0005 +``` diff --git a/docs/docs/analyzers/MP0006.md b/docs/docs/analyzers/MP0006.md new file mode 100644 index 00000000000..5465eb79897 --- /dev/null +++ b/docs/docs/analyzers/MP0006.md @@ -0,0 +1,32 @@ +--- +title: 'MP0006' +slug: /analyzers/MP0006 +sidebar_position: 7 +--- + + + +# MP0006 + +Implements ExecuteAsync without the async modifier. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Error | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0006.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0006 +// Code that intentionally violates MP0006. +#pragma warning restore MP0006 +``` diff --git a/docs/docs/analyzers/MP0007.md b/docs/docs/analyzers/MP0007.md new file mode 100644 index 00000000000..ec1937ad0de --- /dev/null +++ b/docs/docs/analyzers/MP0007.md @@ -0,0 +1,32 @@ +--- +title: 'MP0007' +slug: /analyzers/MP0007 +sidebar_position: 8 +--- + + + +# MP0007 + +Awaits the current module instance. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Error | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0007.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0007 +// Code that intentionally violates MP0007. +#pragma warning restore MP0007 +``` diff --git a/docs/docs/analyzers/MP0008.md b/docs/docs/analyzers/MP0008.md new file mode 100644 index 00000000000..31f026cc28f --- /dev/null +++ b/docs/docs/analyzers/MP0008.md @@ -0,0 +1,32 @@ +--- +title: 'MP0008' +slug: /analyzers/MP0008 +sidebar_position: 9 +--- + + + +# MP0008 + +Declares mutable instance state that can leak between module executions. + +| Property | Value | +|----------|-------| +| Category | Design | +| Default severity | Warning | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0008.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0008 +// Code that intentionally violates MP0008. +#pragma warning restore MP0008 +``` diff --git a/docs/docs/analyzers/MP0009.md b/docs/docs/analyzers/MP0009.md new file mode 100644 index 00000000000..4caabe7cb26 --- /dev/null +++ b/docs/docs/analyzers/MP0009.md @@ -0,0 +1,32 @@ +--- +title: 'MP0009' +slug: /analyzers/MP0009 +sidebar_position: 10 +--- + + + +# MP0009 + +References a dependency type that does not implement IModule. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Error | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0009.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0009 +// Code that intentionally violates MP0009. +#pragma warning restore MP0009 +``` diff --git a/docs/docs/analyzers/MP0010.md b/docs/docs/analyzers/MP0010.md new file mode 100644 index 00000000000..debe1ad21a2 --- /dev/null +++ b/docs/docs/analyzers/MP0010.md @@ -0,0 +1,32 @@ +--- +title: 'MP0010' +slug: /analyzers/MP0010 +sidebar_position: 11 +--- + + + +# MP0010 + +Declares a module dependency on itself. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Error | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0010.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0010 +// Code that intentionally violates MP0010. +#pragma warning restore MP0010 +``` diff --git a/docs/docs/analyzers/MP0011.md b/docs/docs/analyzers/MP0011.md new file mode 100644 index 00000000000..242d36a3ed3 --- /dev/null +++ b/docs/docs/analyzers/MP0011.md @@ -0,0 +1,32 @@ +--- +title: 'MP0011' +slug: /analyzers/MP0011 +sidebar_position: 12 +--- + + + +# MP0011 + +Requires generated CLI option properties to be virtual. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Warning | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0011.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0011 +// Code that intentionally violates MP0011. +#pragma warning restore MP0011 +``` diff --git a/docs/docs/analyzers/MP0012.md b/docs/docs/analyzers/MP0012.md new file mode 100644 index 00000000000..8063f60a084 --- /dev/null +++ b/docs/docs/analyzers/MP0012.md @@ -0,0 +1,32 @@ +--- +title: 'MP0012' +slug: /analyzers/MP0012 +sidebar_position: 13 +--- + + + +# MP0012 + +Requires generated CLI command methods to be virtual. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Warning | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0012.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0012 +// Code that intentionally violates MP0012. +#pragma warning restore MP0012 +``` diff --git a/docs/docs/analyzers/MP0013.md b/docs/docs/analyzers/MP0013.md new file mode 100644 index 00000000000..4686dca0f4f --- /dev/null +++ b/docs/docs/analyzers/MP0013.md @@ -0,0 +1,32 @@ +--- +title: 'MP0013' +slug: /analyzers/MP0013 +sidebar_position: 14 +--- + + + +# MP0013 + +Module is not registered with the pipeline. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Warning | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0013.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0013 +// Code that intentionally violates MP0013. +#pragma warning restore MP0013 +``` diff --git a/docs/docs/analyzers/MP0014.md b/docs/docs/analyzers/MP0014.md new file mode 100644 index 00000000000..dbb5f631f0d --- /dev/null +++ b/docs/docs/analyzers/MP0014.md @@ -0,0 +1,32 @@ +--- +title: 'MP0014' +slug: /analyzers/MP0014 +sidebar_position: 15 +--- + + + +# MP0014 + +Async void method in a module. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Error | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0014.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0014 +// Code that intentionally violates MP0014. +#pragma warning restore MP0014 +``` diff --git a/docs/docs/analyzers/MP0015.md b/docs/docs/analyzers/MP0015.md new file mode 100644 index 00000000000..017d2a3c7a7 --- /dev/null +++ b/docs/docs/analyzers/MP0015.md @@ -0,0 +1,32 @@ +--- +title: 'MP0015' +slug: /analyzers/MP0015 +sidebar_position: 16 +--- + + + +# MP0015 + +Blocking call in ExecuteAsync. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Warning | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0015.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0015 +// Code that intentionally violates MP0015. +#pragma warning restore MP0015 +``` diff --git a/docs/docs/analyzers/MP0016.md b/docs/docs/analyzers/MP0016.md new file mode 100644 index 00000000000..53c7404a62d --- /dev/null +++ b/docs/docs/analyzers/MP0016.md @@ -0,0 +1,32 @@ +--- +title: 'MP0016' +slug: /analyzers/MP0016 +sidebar_position: 17 +--- + + + +# MP0016 + +ExecuteAsync cancellation token is not flowed. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Warning | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0016.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0016 +// Code that intentionally violates MP0016. +#pragma warning restore MP0016 +``` diff --git a/docs/docs/analyzers/MP0017.md b/docs/docs/analyzers/MP0017.md new file mode 100644 index 00000000000..c2f09965d5f --- /dev/null +++ b/docs/docs/analyzers/MP0017.md @@ -0,0 +1,32 @@ +--- +title: 'MP0017' +slug: /analyzers/MP0017 +sidebar_position: 18 +--- + + + +# MP0017 + +Thread.Sleep in ExecuteAsync. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Warning | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0017.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0017 +// Code that intentionally violates MP0017. +#pragma warning restore MP0017 +``` diff --git a/docs/docs/analyzers/MP0018.md b/docs/docs/analyzers/MP0018.md new file mode 100644 index 00000000000..f8807bfd3dc --- /dev/null +++ b/docs/docs/analyzers/MP0018.md @@ -0,0 +1,32 @@ +--- +title: 'MP0018' +slug: /analyzers/MP0018 +sidebar_position: 19 +--- + + + +# MP0018 + +Module class is not public. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Warning | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0018.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0018 +// Code that intentionally violates MP0018. +#pragma warning restore MP0018 +``` diff --git a/docs/docs/analyzers/MP0019.md b/docs/docs/analyzers/MP0019.md new file mode 100644 index 00000000000..1216f8080e7 --- /dev/null +++ b/docs/docs/analyzers/MP0019.md @@ -0,0 +1,32 @@ +--- +title: 'MP0019' +slug: /analyzers/MP0019 +sidebar_position: 20 +--- + + + +# MP0019 + +Duplicate DependsOn declaration. + +| Property | Value | +|----------|-------| +| Category | Usage | +| Default severity | Error | + +## Configure severity + +```ini title=".editorconfig" +dotnet_diagnostic.MP0019.severity = warning +``` + +Use `none` to disable the rule, or `silent`, `suggestion`, `warning`, or `error` to change its severity. + +## Suppress a specific occurrence + +```csharp +#pragma warning disable MP0019 +// Code that intentionally violates MP0019. +#pragma warning restore MP0019 +``` diff --git a/docs/docs/analyzers/_category_.json b/docs/docs/analyzers/_category_.json new file mode 100644 index 00000000000..a0899758f57 --- /dev/null +++ b/docs/docs/analyzers/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Analyzer rules", + "position": 7, + "link": { + "type": "doc", + "id": "analyzers/index" + } +} diff --git a/docs/docs/analyzers/index.md b/docs/docs/analyzers/index.md new file mode 100644 index 00000000000..05099937898 --- /dev/null +++ b/docs/docs/analyzers/index.md @@ -0,0 +1,31 @@ +--- +title: Analyzer rules +slug: /analyzers +sidebar_position: 1 +--- + + + +# Analyzer rules + +| Rule | Category | Default severity | Description | +|------|----------|------------------|-------------| +| [`MP0001`](./MP0001.md) | Usage | Error | Accesses a module result without declaring the dependency. | +| [`MP0002`](./MP0002.md) | Usage | Error | Uses IEnumerable as a module result instead of a concrete collection. | +| [`MP0003`](./MP0003.md) | Usage | Error | Injects a logger into a module constructor instead of using the module context. | +| [`MP0004`](./MP0004.md) | Usage | Error | Writes directly to System.Console instead of using pipeline logging. | +| [`MP0005`](./MP0005.md) | Usage | Error | Creates a circular dependency between modules. | +| [`MP0006`](./MP0006.md) | Usage | Error | Implements ExecuteAsync without the async modifier. | +| [`MP0007`](./MP0007.md) | Usage | Error | Awaits the current module instance. | +| [`MP0008`](./MP0008.md) | Design | Warning | Declares mutable instance state that can leak between module executions. | +| [`MP0009`](./MP0009.md) | Usage | Error | References a dependency type that does not implement IModule. | +| [`MP0010`](./MP0010.md) | Usage | Error | Declares a module dependency on itself. | +| [`MP0011`](./MP0011.md) | Usage | Warning | Requires generated CLI option properties to be virtual. | +| [`MP0012`](./MP0012.md) | Usage | Warning | Requires generated CLI command methods to be virtual. | +| [`MP0013`](./MP0013.md) | Usage | Warning | Module is not registered with the pipeline. | +| [`MP0014`](./MP0014.md) | Usage | Error | Async void method in a module. | +| [`MP0015`](./MP0015.md) | Usage | Warning | Blocking call in ExecuteAsync. | +| [`MP0016`](./MP0016.md) | Usage | Warning | ExecuteAsync cancellation token is not flowed. | +| [`MP0017`](./MP0017.md) | Usage | Warning | Thread.Sleep in ExecuteAsync. | +| [`MP0018`](./MP0018.md) | Usage | Warning | Module class is not public. | +| [`MP0019`](./MP0019.md) | Usage | Error | Duplicate DependsOn declaration. | diff --git a/docs/docs/how-to/analyzers.md b/docs/docs/how-to/analyzers.md index 11f8bcc071f..6a361c0e330 100644 --- a/docs/docs/how-to/analyzers.md +++ b/docs/docs/how-to/analyzers.md @@ -4,12 +4,51 @@ title: Analyzers # Analyzers -## Built-in -A few Analyzers have been built and come out-of-the-box when using Modular Pipelines. This is to prevent errors at runtime, and bring them to the developer's attention at compile time. - -These include: -- Checks for injecting ILogger -- `Console` class usage -- Missing `DependsOn` attributes -- Conflicting `DependsOn` attributes -- Returning `IEnumerable` \ No newline at end of file +Modular Pipelines includes Roslyn analyzers that catch pipeline authoring mistakes at compile time. Each rule uses the `MP####` ID family, so it can be configured with standard .NET analyzer settings. + +See the [complete analyzer rule reference](../analyzers/) for every rule, its category, default severity, and suppression examples. + +## Configure a rule + +Set a rule's severity in `.editorconfig`: + +```ini +[*.cs] +dotnet_diagnostic.MP0001.severity = warning +``` + +Valid values are `default`, `none`, `silent`, `suggestion`, `warning`, and `error`. Use `none` to disable a rule. + +## Suppress a specific occurrence + +Use `#pragma` when a violation is intentional and narrowly scoped: + +```csharp +#pragma warning disable MP0001 +// Code that intentionally violates MP0001. +#pragma warning restore MP0001 +``` + +## ID migration + +The legacy prose IDs were renamed so `.editorconfig` settings can use one predictable family: + +| Legacy ID | Current ID | +|-----------|------------| +| `MissingDependsOnAttribute` | `MP0001` | +| `EnumerableModuleResult` | `MP0002` | +| `LoggerInConstructor` | `MP0003` | +| `ConsoleUse` | `MP0004` | +| `ConflictingDependsOnAttribute` | `MP0005` | +| `AsyncModule` | `MP0006` | +| `MPD0001` | `MP0011` | +| `MPD0002` | `MP0012` | +| `MPREG001` | `MP0013` | +| `MPASYNC001` | `MP0014` | +| `MPASYNC002` | `MP0015` | +| `MPASYNC003` | `MP0016` | +| `MPASYNC004` | `MP0017` | +| `MPTYPE001` | `MP0018` | +| `MPDEP004` | `MP0019` | + +Update existing `dotnet_diagnostic..severity` entries and warning suppressions to the current IDs. diff --git a/docs/package.json b/docs/package.json index 3c37978a730..66f82244d34 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,6 +4,10 @@ "private": true, "scripts": { "docusaurus": "docusaurus", + "generate-analyzer-docs": "node scripts/generate-analyzer-docs.mjs", + "check-analyzer-docs": "node scripts/generate-analyzer-docs.mjs --check", + "prebuild": "yarn check-analyzer-docs", + "prestart": "yarn generate-analyzer-docs", "start": "docusaurus start", "build": "docusaurus build", "swizzle": "docusaurus swizzle", diff --git a/docs/scripts/generate-analyzer-docs.mjs b/docs/scripts/generate-analyzer-docs.mjs new file mode 100644 index 00000000000..37db63cb4cd --- /dev/null +++ b/docs/scripts/generate-analyzer-docs.mjs @@ -0,0 +1,196 @@ +import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const scriptDirectory = dirname(fileURLToPath(import.meta.url)); +const repositoryRoot = resolve(scriptDirectory, '..', '..'); +const outputDirectory = join(repositoryRoot, 'docs', 'docs', 'analyzers'); +const checkOnly = process.argv.includes('--check'); + +const releaseFiles = [ + 'src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AnalyzerReleases.Shipped.md', + 'src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AnalyzerReleases.Unshipped.md', + 'src/ModularPipelines.Development.Analyzers/AnalyzerReleases.Shipped.md', + 'src/ModularPipelines.Development.Analyzers/AnalyzerReleases.Unshipped.md', +]; + +const rules = new Map(); + +for (const relativePath of releaseFiles) { + applyReleaseMetadata(join(repositoryRoot, relativePath), rules); +} + +const activeRules = [...rules.values()] + .filter(rule => /^MP\d{4}$/.test(rule.id)) + .sort((left, right) => left.id.localeCompare(right.id)); + +if (activeRules.length === 0) { + throw new Error('No active MP#### analyzer rules were found.'); +} + +const expectedFiles = new Map([ + ['index.md', renderIndex(activeRules)], + ...activeRules.map(rule => [`${rule.id}.md`, renderRule(rule)]), +]); + +if (checkOnly) { + checkGeneratedFiles(expectedFiles); +} else { + writeGeneratedFiles(expectedFiles); +} + +function applyReleaseMetadata(filePath, activeRulesById) { + let section = ''; + + for (const rawLine of readFileSync(filePath, 'utf8').split(/\r?\n/)) { + const line = rawLine.trim(); + const heading = /^###\s+(New|Removed|Changed) Rules$/i.exec(line); + + if (heading) { + section = heading[1].toLowerCase(); + continue; + } + + if (!section || !line.includes('|') || /^(Rule ID|-+\|)/i.test(line)) { + continue; + } + + const columns = line + .split('|') + .map(column => column.trim()) + .filter((column, index, values) => + column.length > 0 || (index > 0 && index < values.length - 1)); + + if (columns.length < 4) { + continue; + } + + const [id, category, severity, ...notes] = columns; + + if (section === 'removed') { + activeRulesById.delete(id); + continue; + } + + const rule = { id, category, severity, description: notes.join(' | ') }; + + if (section === 'new' && activeRulesById.has(id)) { + throw new Error(`Duplicate active analyzer rule ID '${id}' in ${filePath}.`); + } + + if (section === 'new' || activeRulesById.has(id)) { + activeRulesById.set(id, rule); + } + } +} + +function renderIndex(allRules) { + const rows = allRules + .map(rule => + `| [\`${rule.id}\`](./${rule.id}.md) | ${rule.category} | ${rule.severity} | ${sentence(rule.description)} |`) + .join('\n'); + + return `--- +title: Analyzer rules +slug: /analyzers +sidebar_position: 1 +--- + + + +# Analyzer rules + +| Rule | Category | Default severity | Description | +|------|----------|------------------|-------------| +${rows} +`; +} + +function renderRule(rule) { + return `--- +title: '${rule.id}' +slug: /analyzers/${rule.id} +sidebar_position: ${Number.parseInt(rule.id.slice(2), 10) + 1} +--- + + + +# ${rule.id} + +${sentence(rule.description)} + +| Property | Value | +|----------|-------| +| Category | ${rule.category} | +| Default severity | ${rule.severity} | + +## Configure severity + +\`\`\`ini title=".editorconfig" +dotnet_diagnostic.${rule.id}.severity = warning +\`\`\` + +Use \`none\` to disable the rule, or \`silent\`, \`suggestion\`, \`warning\`, or \`error\` to change its severity. + +## Suppress a specific occurrence + +\`\`\`csharp +#pragma warning disable ${rule.id} +// Code that intentionally violates ${rule.id}. +#pragma warning restore ${rule.id} +\`\`\` +`; +} + +function sentence(value) { + return /[.!?`]$/.test(value) ? value : `${value}.`; +} + +function checkGeneratedFiles(expected) { + const actualNames = existsSync(outputDirectory) + ? readdirSync(outputDirectory).filter(name => name.endsWith('.md')).sort() + : []; + const expectedNames = [...expected.keys()].sort(); + const failures = []; + + if (actualNames.join('\n') !== expectedNames.join('\n')) { + failures.push('Generated analyzer documentation file list is stale.'); + } + + for (const [name, content] of expected) { + const filePath = join(outputDirectory, name); + const actual = existsSync(filePath) + ? normalize(readFileSync(filePath, 'utf8')) + : ''; + + if (actual !== normalize(content)) { + failures.push(`${name} is stale.`); + } + } + + if (failures.length > 0) { + throw new Error(`${failures.join('\n')}\nRun yarn generate-analyzer-docs.`); + } + + console.log(`Verified ${activeRules.length} generated analyzer rule pages.`); +} + +function writeGeneratedFiles(expected) { + mkdirSync(outputDirectory, { recursive: true }); + + for (const existingName of readdirSync(outputDirectory).filter(name => name.endsWith('.md'))) { + if (!expected.has(existingName)) { + unlinkSync(join(outputDirectory, existingName)); + } + } + + for (const [name, content] of expected) { + writeFileSync(join(outputDirectory, name), normalize(content), 'utf8'); + } + + console.log(`Generated ${activeRules.length} analyzer rule pages.`); +} + +function normalize(value) { + return value.replace(/\r\n/g, '\n'); +} diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/AnalyzerMetadataTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/AnalyzerMetadataTests.cs new file mode 100644 index 00000000000..17a3d18ae90 --- /dev/null +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/AnalyzerMetadataTests.cs @@ -0,0 +1,49 @@ +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace ModularPipelines.Analyzers.Test; + +[TestClass] +public class AnalyzerMetadataTests +{ + private const string DocumentationBaseUrl = + "https://thomhurst.github.io/ModularPipelines/docs/analyzers/"; + + [TestMethod] + public void PublicRulesUseUnifiedIdsAndHelpLinks() + { + var rules = new[] + { + MissingDependsOnAttributeAnalyzer.Rule, + EnumerableModuleResultAnalyzer.Rule, + LoggerInConstructorAnalyzer.Rule, + ConsoleUseAnalyzer.Rule, + ConflictingDependsOnAttributeAnalyzer.Rule, + AsyncModuleAnalyzer.Rule, + AwaitThisAnalyzer.Rule, + StatefulModuleAnalyzer.Rule, + InvalidDependsOnTypeAnalyzer.Rule, + SelfDependencyAnalyzer.Rule, + ModuleRegistrationAnalyzer.UnregisteredModuleRule, + ModuleAsyncSafetyAnalyzer.AsyncVoidRule, + ModuleAsyncSafetyAnalyzer.BlockingCallRule, + ModuleAsyncSafetyAnalyzer.UnflowedCancellationTokenRule, + ModuleAsyncSafetyAnalyzer.ThreadSleepRule, + ModuleRegistrationAnalyzer.NonPublicModuleRule, + DuplicateDependsOnAnalyzer.Rule, + }; + + CollectionAssert.AreEquivalent( + Enumerable.Range(1, 10) + .Concat(Enumerable.Range(13, 7)) + .Select(index => $"MP{index:0000}") + .ToArray(), + rules.Select(rule => rule.Id).ToArray()); + + foreach (var rule in rules) + { + Assert.AreEqual(DocumentationBaseUrl + rule.Id, rule.HelpLinkUri); + Assert.IsFalse(string.IsNullOrWhiteSpace(rule.Description.ToString())); + } + } +} diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AnalyzerReleases.Unshipped.md b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AnalyzerReleases.Unshipped.md index 43f3754a04a..ae2c1e88de7 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AnalyzerReleases.Unshipped.md @@ -2,21 +2,31 @@ Rule ID | Category | Severity | Notes --------|----------|----------|-------------------- -AwaitThis | Usage | Error | Prohibit await this in modules -StatefulModule | Design | Warning | Detects mutable instance fields in modules that could leak state between executions -MPDEP001 | Usage | Error | DependsOn type does not implement IModule -MPDEP002 | Usage | Error | Circular dependency detected between modules (renamed from ConflictingDependsOnAttribute) -MPDEP003 | Usage | Error | Module depends on itself -MPREG001 | Usage | Warning | Module is not registered with the pipeline -MPASYNC001 | Usage | Error | Async void method in a module -MPASYNC002 | Usage | Warning | Blocking call in ExecuteAsync -MPASYNC003 | Usage | Warning | ExecuteAsync cancellation token is not flowed -MPASYNC004 | Usage | Warning | Thread.Sleep in ExecuteAsync -MPTYPE001 | Usage | Warning | Module class is not public -MPDEP004 | Usage | Error | Duplicate DependsOn declaration +MP0001 | Usage | Error | Accesses a module result without declaring the dependency +MP0002 | Usage | Error | Uses IEnumerable as a module result instead of a concrete collection +MP0003 | Usage | Error | Injects a logger into a module constructor instead of using the module context +MP0004 | Usage | Error | Writes directly to System.Console instead of using pipeline logging +MP0005 | Usage | Error | Creates a circular dependency between modules +MP0006 | Usage | Error | Implements ExecuteAsync without the async modifier +MP0007 | Usage | Error | Awaits the current module instance +MP0008 | Design | Warning | Declares mutable instance state that can leak between module executions +MP0009 | Usage | Error | References a dependency type that does not implement IModule +MP0010 | Usage | Error | Declares a module dependency on itself +MP0013 | Usage | Warning | Module is not registered with the pipeline +MP0014 | Usage | Error | Async void method in a module +MP0015 | Usage | Warning | Blocking call in ExecuteAsync +MP0016 | Usage | Warning | ExecuteAsync cancellation token is not flowed +MP0017 | Usage | Warning | Thread.Sleep in ExecuteAsync +MP0018 | Usage | Warning | Module class is not public +MP0019 | Usage | Error | Duplicate DependsOn declaration ### Removed Rules Rule ID | Category | Severity | Notes --------|----------|----------|-------------------- -ConflictingDependsOnAttribute | Usage | Error | Renamed to MPDEP002 +MissingDependsOnAttribute | Usage | Error | Renamed to MP0001 +EnumerableModuleResult | Usage | Error | Renamed to MP0002 +LoggerInConstructor | Usage | Error | Renamed to MP0003 +ConsoleUse | Usage | Error | Renamed to MP0004 +ConflictingDependsOnAttribute | Usage | Error | Renamed to MP0005 +AsyncModule | Usage | Error | Renamed to MP0006 diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AsyncModuleAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AsyncModuleAnalyzer.cs index 0161ca51d29..d0b21c9f408 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AsyncModuleAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AsyncModuleAnalyzer.cs @@ -12,7 +12,7 @@ namespace ModularPipelines.Analyzers; [ExcludeFromCodeCoverage] public class AsyncModuleAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "AsyncModule"; + public const string DiagnosticId = "MP0006"; public static DiagnosticDescriptor Rule { get; } = DiagnosticDescriptorFactory.Create( DiagnosticId, diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AwaitThisAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AwaitThisAnalyzer.cs index 9d6db8a96a0..dcdd563fa8d 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AwaitThisAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/AwaitThisAnalyzer.cs @@ -12,7 +12,7 @@ namespace ModularPipelines.Analyzers; [ExcludeFromCodeCoverage] public class AwaitThisAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "AwaitThis"; + public const string DiagnosticId = "MP0007"; public static DiagnosticDescriptor Rule { get; } = DiagnosticDescriptorFactory.Create( DiagnosticId, diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ConflictingDependsOnAttributeAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ConflictingDependsOnAttributeAnalyzer.cs index a28a36e7780..8f288d49c61 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ConflictingDependsOnAttributeAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ConflictingDependsOnAttributeAnalyzer.cs @@ -12,7 +12,7 @@ namespace ModularPipelines.Analyzers; [ExcludeFromCodeCoverage] public class ConflictingDependsOnAttributeAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "MPDEP002"; + public const string DiagnosticId = "MP0005"; public static DiagnosticDescriptor Rule { get; } = DiagnosticDescriptorFactory.Create( DiagnosticId, @@ -135,4 +135,4 @@ private static void ReportDiagnostics(SyntaxNodeAnalysisContext context, IEnumer .ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat))); } } -} \ No newline at end of file +} diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ConsoleUseAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ConsoleUseAnalyzer.cs index 68404a21809..81e9c290545 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ConsoleUseAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ConsoleUseAnalyzer.cs @@ -11,7 +11,7 @@ namespace ModularPipelines.Analyzers; [ExcludeFromCodeCoverage] public class ConsoleUseAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "ConsoleUse"; + public const string DiagnosticId = "MP0004"; public static DiagnosticDescriptor Rule { get; } = DiagnosticDescriptorFactory.Create( DiagnosticId, @@ -74,4 +74,4 @@ private static MemberAccessExpressionSyntax GetTopMemberAccessExpression(MemberA return memberAccessExpression; } -} \ No newline at end of file +} diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/DiagnosticDescriptorFactory.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/DiagnosticDescriptorFactory.cs index 3e1268465d0..59a7e30c0c2 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/DiagnosticDescriptorFactory.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/DiagnosticDescriptorFactory.cs @@ -8,6 +8,9 @@ namespace ModularPipelines.Analyzers; /// internal static class DiagnosticDescriptorFactory { + private const string DocumentationBaseUrl = + "https://thomhurst.github.io/ModularPipelines/docs/analyzers/"; + /// /// Creates a with localizable strings from the Resources file. /// @@ -37,6 +40,7 @@ public static DiagnosticDescriptor Create( category, severity, isEnabledByDefault: true, - description: description); + description: description, + helpLinkUri: DocumentationBaseUrl + id); } } diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/DuplicateDependsOnAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/DuplicateDependsOnAnalyzer.cs index 3d529b54e76..6196e29dbae 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/DuplicateDependsOnAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/DuplicateDependsOnAnalyzer.cs @@ -9,7 +9,7 @@ namespace ModularPipelines.Analyzers; [ExcludeFromCodeCoverage] public sealed class DuplicateDependsOnAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "MPDEP004"; + public const string DiagnosticId = "MP0019"; public static DiagnosticDescriptor Rule { get; } = DiagnosticDescriptorFactory.Create( diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/EnumerableModuleResultAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/EnumerableModuleResultAnalyzer.cs index d6442e31948..04a507334ab 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/EnumerableModuleResultAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/EnumerableModuleResultAnalyzer.cs @@ -12,7 +12,7 @@ namespace ModularPipelines.Analyzers; [ExcludeFromCodeCoverage] public class EnumerableModuleResultAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "EnumerableModuleResult"; + public const string DiagnosticId = "MP0002"; public static DiagnosticDescriptor Rule { get; } = DiagnosticDescriptorFactory.Create( DiagnosticId, @@ -77,4 +77,4 @@ private void AnalyzeIEnumerableModuleResults(SyntaxNodeAnalysisContext context) context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), properties, namedTypeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat))); } } -} \ No newline at end of file +} diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/InvalidDependsOnTypeAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/InvalidDependsOnTypeAnalyzer.cs index 384e32fc54f..1791f2c681a 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/InvalidDependsOnTypeAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/InvalidDependsOnTypeAnalyzer.cs @@ -10,13 +10,13 @@ namespace ModularPipelines.Analyzers; /// /// Analyzer that detects when DependsOnAttribute references a type that doesn't implement IModule. -/// Reports diagnostic MPDEP001 when DependsOn references a non-module type. +/// Reports diagnostic MP0009 when DependsOn references a non-module type. /// [DiagnosticAnalyzer(LanguageNames.CSharp)] [ExcludeFromCodeCoverage] public class InvalidDependsOnTypeAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "MPDEP001"; + public const string DiagnosticId = "MP0009"; public static DiagnosticDescriptor Rule { get; } = DiagnosticDescriptorFactory.Create( DiagnosticId, diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/LoggerInConstructorAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/LoggerInConstructorAnalyzer.cs index 503b667b3a4..f00144c0849 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/LoggerInConstructorAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/LoggerInConstructorAnalyzer.cs @@ -12,7 +12,7 @@ namespace ModularPipelines.Analyzers; [ExcludeFromCodeCoverage] public class LoggerInConstructorAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "LoggerInConstructor"; + public const string DiagnosticId = "MP0003"; public static DiagnosticDescriptor Rule { get; } = DiagnosticDescriptorFactory.Create( DiagnosticId, @@ -97,4 +97,4 @@ private static void ReportDiagnostic(SyntaxNodeAnalysisContext context, Location context.ReportDiagnostic(Diagnostic.Create(Rule, location, properties, namedTypeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat))); } -} \ No newline at end of file +} diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/MissingDependsOnAttributeAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/MissingDependsOnAttributeAnalyzer.cs index 4e47c3af53a..5ce664cb28b 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/MissingDependsOnAttributeAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/MissingDependsOnAttributeAnalyzer.cs @@ -12,7 +12,7 @@ namespace ModularPipelines.Analyzers; [ExcludeFromCodeCoverage] public class MissingDependsOnAttributeAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "MissingDependsOnAttribute"; + public const string DiagnosticId = "MP0001"; public static DiagnosticDescriptor Rule { get; } = DiagnosticDescriptorFactory.Create( DiagnosticId, diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ModuleAsyncSafetyAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ModuleAsyncSafetyAnalyzer.cs index 201ca95014f..dbd64d86412 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ModuleAsyncSafetyAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ModuleAsyncSafetyAnalyzer.cs @@ -9,10 +9,10 @@ namespace ModularPipelines.Analyzers; [ExcludeFromCodeCoverage] public sealed class ModuleAsyncSafetyAnalyzer : DiagnosticAnalyzer { - public const string AsyncVoidId = "MPASYNC001"; - public const string BlockingCallId = "MPASYNC002"; - public const string UnflowedCancellationTokenId = "MPASYNC003"; - public const string ThreadSleepId = "MPASYNC004"; + public const string AsyncVoidId = "MP0014"; + public const string BlockingCallId = "MP0015"; + public const string UnflowedCancellationTokenId = "MP0016"; + public const string ThreadSleepId = "MP0017"; public static DiagnosticDescriptor AsyncVoidRule { get; } = DiagnosticDescriptorFactory.Create( diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ModuleRegistrationAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ModuleRegistrationAnalyzer.cs index c6ee00b4af2..7360b1a22f3 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ModuleRegistrationAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/ModuleRegistrationAnalyzer.cs @@ -9,8 +9,8 @@ namespace ModularPipelines.Analyzers; [ExcludeFromCodeCoverage] public sealed class ModuleRegistrationAnalyzer : DiagnosticAnalyzer { - public const string UnregisteredModuleId = "MPREG001"; - public const string NonPublicModuleId = "MPTYPE001"; + public const string UnregisteredModuleId = "MP0013"; + public const string NonPublicModuleId = "MP0018"; public static DiagnosticDescriptor UnregisteredModuleRule { get; } = DiagnosticDescriptorFactory.Create( diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/SelfDependencyAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/SelfDependencyAnalyzer.cs index bb54490bc4e..017f04abcee 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/SelfDependencyAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/SelfDependencyAnalyzer.cs @@ -10,13 +10,13 @@ namespace ModularPipelines.Analyzers; /// /// Analyzer that detects when a module depends on itself via DependsOnAttribute. -/// Reports diagnostic MPDEP003 when a module has [DependsOn<Self>]. +/// Reports diagnostic MP0010 when a module has [DependsOn<Self>]. /// [DiagnosticAnalyzer(LanguageNames.CSharp)] [ExcludeFromCodeCoverage] public class SelfDependencyAnalyzer : DiagnosticAnalyzer { - public const string DiagnosticId = "MPDEP003"; + public const string DiagnosticId = "MP0010"; public static DiagnosticDescriptor Rule { get; } = DiagnosticDescriptorFactory.Create( DiagnosticId, diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/StatefulModuleAnalyzer.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/StatefulModuleAnalyzer.cs index 14411032ce8..ec0d59e9408 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/StatefulModuleAnalyzer.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/StatefulModuleAnalyzer.cs @@ -19,7 +19,7 @@ public class StatefulModuleAnalyzer : DiagnosticAnalyzer /// /// Diagnostic ID for mutable instance fields in modules. /// - public const string DiagnosticId = "StatefulModule"; + public const string DiagnosticId = "MP0008"; /// /// Gets the diagnostic rule for stateful modules. diff --git a/src/ModularPipelines.Development.Analyzers/AnalyzerReleases.Shipped.md b/src/ModularPipelines.Development.Analyzers/AnalyzerReleases.Shipped.md index 8dd1b9ada6f..96634c9de93 100644 --- a/src/ModularPipelines.Development.Analyzers/AnalyzerReleases.Shipped.md +++ b/src/ModularPipelines.Development.Analyzers/AnalyzerReleases.Shipped.md @@ -2,7 +2,7 @@ ### New Rules -| Rule ID | Category | Severity | Notes | -|---------|----------|----------|--------------------------------------------------| -| AB0001 | Naming | Warning | Type names should not contain the company name. | -| AB0002 | Usage | Warning | The speed must be lower than the Speed of Light. | \ No newline at end of file +Rule ID | Category | Severity | Notes +--------|----------|----------|-------------------- +MPD0001 | Usage | Warning | Requires generated CLI option properties to be virtual +MPD0002 | Usage | Warning | Requires generated CLI command methods to be virtual diff --git a/src/ModularPipelines.Development.Analyzers/AnalyzerReleases.Unshipped.md b/src/ModularPipelines.Development.Analyzers/AnalyzerReleases.Unshipped.md index 44f7c8f4ef7..72d275e144b 100644 --- a/src/ModularPipelines.Development.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/ModularPipelines.Development.Analyzers/AnalyzerReleases.Unshipped.md @@ -1,4 +1,13 @@ ### New Rules -| Rule ID | Category | Severity | Notes | -|---------|----------|----------|-------| \ No newline at end of file +Rule ID | Category | Severity | Notes +--------|----------|----------|-------------------- +MP0011 | Usage | Warning | Requires generated CLI option properties to be virtual +MP0012 | Usage | Warning | Requires generated CLI command methods to be virtual + +### Removed Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|-------------------- +MPD0001 | Usage | Warning | Renamed to MP0011 +MPD0002 | Usage | Warning | Renamed to MP0012 diff --git a/src/ModularPipelines.Development.Analyzers/DiagnosticDescriptorFactory.cs b/src/ModularPipelines.Development.Analyzers/DiagnosticDescriptorFactory.cs new file mode 100644 index 00000000000..a70497a7341 --- /dev/null +++ b/src/ModularPipelines.Development.Analyzers/DiagnosticDescriptorFactory.cs @@ -0,0 +1,39 @@ +using Microsoft.CodeAnalysis; + +namespace ModularPipelines.Development.Analyzers; + +internal static class DiagnosticDescriptorFactory +{ + private const string DocumentationBaseUrl = + "https://thomhurst.github.io/ModularPipelines/docs/analyzers/"; + + public static DiagnosticDescriptor Create( + string id, + string titleResourceName, + string messageFormatResourceName, + string descriptionResourceName) + { + var title = new LocalizableResourceString( + titleResourceName, + Resources.ResourceManager, + typeof(Resources)); + var messageFormat = new LocalizableResourceString( + messageFormatResourceName, + Resources.ResourceManager, + typeof(Resources)); + var description = new LocalizableResourceString( + descriptionResourceName, + Resources.ResourceManager, + typeof(Resources)); + + return new DiagnosticDescriptor( + id, + title, + messageFormat, + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: description, + helpLinkUri: DocumentationBaseUrl + id); + } +} diff --git a/src/ModularPipelines.Development.Analyzers/Resources.Designer.cs b/src/ModularPipelines.Development.Analyzers/Resources.Designer.cs index 28f7b80ceb6..b619c7ed69a 100644 --- a/src/ModularPipelines.Development.Analyzers/Resources.Designer.cs +++ b/src/ModularPipelines.Development.Analyzers/Resources.Designer.cs @@ -62,72 +62,72 @@ internal Resources() { /// /// Looks up a localized string similar to Add `virtual` keyword. /// - internal static string MPD0001CodeFixTitle { + internal static string MP0011CodeFixTitle { get { - return ResourceManager.GetString("MPD0001CodeFixTitle", resourceCulture); + return ResourceManager.GetString("MP0011CodeFixTitle", resourceCulture); } } /// /// Looks up a localized string similar to Properties should be virtual to allow overriding. /// - internal static string MPD0001Description { + internal static string MP0011Description { get { - return ResourceManager.GetString("MPD0001Description", resourceCulture); + return ResourceManager.GetString("MP0011Description", resourceCulture); } } /// /// Looks up a localized string similar to Properties should be virtual to allow overriding. /// - internal static string MPD0001MessageFormat { + internal static string MP0011MessageFormat { get { - return ResourceManager.GetString("MPD0001MessageFormat", resourceCulture); + return ResourceManager.GetString("MP0011MessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Properties should be virtual to allow overriding. /// - internal static string MPD0001Title { + internal static string MP0011Title { get { - return ResourceManager.GetString("MPD0001Title", resourceCulture); + return ResourceManager.GetString("MP0011Title", resourceCulture); } } /// /// Looks up a localized string similar to Add `virtual` keyword. /// - internal static string MPD0002CodeFixTitle { + internal static string MP0012CodeFixTitle { get { - return ResourceManager.GetString("MPD0002CodeFixTitle", resourceCulture); + return ResourceManager.GetString("MP0012CodeFixTitle", resourceCulture); } } /// /// Looks up a localized string similar to Method should be virtual to allow overriding. /// - internal static string MPD0002Description { + internal static string MP0012Description { get { - return ResourceManager.GetString("MPD0002Description", resourceCulture); + return ResourceManager.GetString("MP0012Description", resourceCulture); } } /// /// Looks up a localized string similar to Method should be virtual to allow overriding. /// - internal static string MPD0002MessageFormat { + internal static string MP0012MessageFormat { get { - return ResourceManager.GetString("MPD0002MessageFormat", resourceCulture); + return ResourceManager.GetString("MP0012MessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Method should be virtual to allow overriding. /// - internal static string MPD0002Title { + internal static string MP0012Title { get { - return ResourceManager.GetString("MPD0002Title", resourceCulture); + return ResourceManager.GetString("MP0012Title", resourceCulture); } } } diff --git a/src/ModularPipelines.Development.Analyzers/Resources.resx b/src/ModularPipelines.Development.Analyzers/Resources.resx index 8eb78f2bce0..72191746e72 100644 --- a/src/ModularPipelines.Development.Analyzers/Resources.resx +++ b/src/ModularPipelines.Development.Analyzers/Resources.resx @@ -18,28 +18,28 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + Properties should be virtual to allow overriding.An optional longer localizable description of the diagnostic. - + Properties should be virtual to allow overridingThe format-able message the diagnostic displays. - + Properties should be virtual to allow overridingThe title of the diagnostic. - + Add `virtual` keywordThe title of the code fix. - + Method should be virtual to allow overriding.An optional longer localizable description of the diagnostic. - + Method should be virtual to allow overridingThe format-able message the diagnostic displays. - + Method should be virtual to allow overridingThe title of the diagnostic. - + Add `virtual` keywordThe title of the code fix. - \ No newline at end of file + diff --git a/src/ModularPipelines.Development.Analyzers/VirtualCommandAnalyzer.cs b/src/ModularPipelines.Development.Analyzers/VirtualCommandAnalyzer.cs index b9e3fb60c28..9fcae92c78b 100644 --- a/src/ModularPipelines.Development.Analyzers/VirtualCommandAnalyzer.cs +++ b/src/ModularPipelines.Development.Analyzers/VirtualCommandAnalyzer.cs @@ -11,23 +11,13 @@ namespace ModularPipelines.Development.Analyzers; [ExcludeFromCodeCoverage] public class VirtualCommandAnalyzer : DiagnosticAnalyzer { - private const string Category = "Usage"; + public const string DiagnosticId = "MP0012"; - public const string DiagnosticId = "MPD0002"; - - private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.MPD0002Title), - Resources.ResourceManager, typeof(Resources)); - - private static readonly LocalizableString MessageFormat = - new LocalizableResourceString(nameof(Resources.MPD0002MessageFormat), Resources.ResourceManager, - typeof(Resources)); - - private static readonly LocalizableString Description = - new LocalizableResourceString(nameof(Resources.MPD0002Description), Resources.ResourceManager, - typeof(Resources)); - - private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category, - DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); + private static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorFactory.Create( + DiagnosticId, + nameof(Resources.MP0012Title), + nameof(Resources.MP0012MessageFormat), + nameof(Resources.MP0012Description)); public override ImmutableArray SupportedDiagnostics { get; } = [Rule]; @@ -82,4 +72,4 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) context.ReportDiagnostic(diagnostic); } -} \ No newline at end of file +} diff --git a/src/ModularPipelines.Development.Analyzers/VirtualCommandCodeFixProvider.cs b/src/ModularPipelines.Development.Analyzers/VirtualCommandCodeFixProvider.cs index 3e16bbf3c2a..fa2fa8a0055 100644 --- a/src/ModularPipelines.Development.Analyzers/VirtualCommandCodeFixProvider.cs +++ b/src/ModularPipelines.Development.Analyzers/VirtualCommandCodeFixProvider.cs @@ -49,9 +49,9 @@ public override sealed async Task RegisterCodeFixesAsync(CodeFixContext context) // Register a code action that will invoke the fix. context.RegisterCodeFix( CodeAction.Create( - title: Resources.MPD0002CodeFixTitle, + title: Resources.MP0012CodeFixTitle, createChangedDocument: c => AddVirtualKeyword(context.Document, declaration, c), - equivalenceKey: nameof(Resources.MPD0002CodeFixTitle)), + equivalenceKey: nameof(Resources.MP0012CodeFixTitle)), diagnostic); } @@ -85,4 +85,4 @@ private static async Task AddVirtualKeyword(Document document, return document.WithSyntaxRoot(root.ReplaceNode(methodDeclarationSyntax, newMethodDeclarationSyntax)); } -} \ No newline at end of file +} diff --git a/src/ModularPipelines.Development.Analyzers/VirtualSwitchPropertyAnalyzer.cs b/src/ModularPipelines.Development.Analyzers/VirtualSwitchPropertyAnalyzer.cs index 3686171ccdc..c17fc29fbec 100644 --- a/src/ModularPipelines.Development.Analyzers/VirtualSwitchPropertyAnalyzer.cs +++ b/src/ModularPipelines.Development.Analyzers/VirtualSwitchPropertyAnalyzer.cs @@ -11,23 +11,13 @@ namespace ModularPipelines.Development.Analyzers; [ExcludeFromCodeCoverage] public class VirtualSwitchPropertyAnalyzer : DiagnosticAnalyzer { - private const string Category = "Usage"; + public const string DiagnosticId = "MP0011"; - public const string DiagnosticId = "MPD0001"; - - private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.MPD0001Title), - Resources.ResourceManager, typeof(Resources)); - - private static readonly LocalizableString MessageFormat = - new LocalizableResourceString(nameof(Resources.MPD0001MessageFormat), Resources.ResourceManager, - typeof(Resources)); - - private static readonly LocalizableString Description = - new LocalizableResourceString(nameof(Resources.MPD0001Description), Resources.ResourceManager, - typeof(Resources)); - - private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category, - DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); + private static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorFactory.Create( + DiagnosticId, + nameof(Resources.MP0011Title), + nameof(Resources.MP0011MessageFormat), + nameof(Resources.MP0011Description)); public override ImmutableArray SupportedDiagnostics { get; } = [Rule]; @@ -69,4 +59,4 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) context.ReportDiagnostic(diagnostic); } -} \ No newline at end of file +} diff --git a/src/ModularPipelines.Development.Analyzers/VirtualSwitchPropertyCodeFixProvider.cs b/src/ModularPipelines.Development.Analyzers/VirtualSwitchPropertyCodeFixProvider.cs index 89ec1c9d5a6..398b6572470 100644 --- a/src/ModularPipelines.Development.Analyzers/VirtualSwitchPropertyCodeFixProvider.cs +++ b/src/ModularPipelines.Development.Analyzers/VirtualSwitchPropertyCodeFixProvider.cs @@ -49,9 +49,9 @@ public override sealed async Task RegisterCodeFixesAsync(CodeFixContext context) // Register a code action that will invoke the fix. context.RegisterCodeFix( CodeAction.Create( - title: Resources.MPD0001CodeFixTitle, + title: Resources.MP0011CodeFixTitle, createChangedDocument: c => AddVirtualKeyword(context.Document, declaration, c), - equivalenceKey: nameof(Resources.MPD0001CodeFixTitle)), + equivalenceKey: nameof(Resources.MP0011CodeFixTitle)), diagnostic); } @@ -77,4 +77,4 @@ private static async Task AddVirtualKeyword(Document document, return document.WithSyntaxRoot(root.ReplaceNode(propertyDeclarationSyntax, newPropertyDeclarationSyntax)); } -} \ No newline at end of file +} diff --git a/test/ModularPipelines.Development.Analyzers.UnitTests/AnalyzerMetadataTests.cs b/test/ModularPipelines.Development.Analyzers.UnitTests/AnalyzerMetadataTests.cs new file mode 100644 index 00000000000..a8e339d02e6 --- /dev/null +++ b/test/ModularPipelines.Development.Analyzers.UnitTests/AnalyzerMetadataTests.cs @@ -0,0 +1,28 @@ +using Microsoft.CodeAnalysis.Diagnostics; + +namespace ModularPipelines.Development.Analyzers.UnitTests; + +public class AnalyzerMetadataTests +{ + private const string DocumentationBaseUrl = + "https://thomhurst.github.io/ModularPipelines/docs/analyzers/"; + + [Test] + public async Task DevelopmentRulesContinueTheUnifiedIdFamily() + { + DiagnosticAnalyzer[] analyzers = + [ + new VirtualSwitchPropertyAnalyzer(), + new VirtualCommandAnalyzer(), + ]; + + var rules = analyzers.SelectMany(analyzer => analyzer.SupportedDiagnostics).ToArray(); + + await Assert.That(rules.Select(rule => rule.Id)).IsEquivalentTo(["MP0011", "MP0012"]); + + foreach (var rule in rules) + { + await Assert.That(rule.HelpLinkUri).IsEqualTo(DocumentationBaseUrl + rule.Id); + } + } +} From 5c98a9e233bc3d5dde03fc6de0a3c3f2c28e65ab Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:31:27 +0100 Subject: [PATCH 2/2] fix: finish analyzer ID migration --- docs/docs/how-to/analyzers.md | 5 +++++ docs/scripts/generate-analyzer-docs.mjs | 5 ++++- .../AnalyzerMetadataTests.cs | 9 ++++----- .../DiagnosticDescriptorFactory.cs | 2 +- src/ModularPipelines.Build/Program.cs | 4 ++-- .../DiagnosticDescriptorFactory.cs | 2 +- .../AnalyzerMetadataTests.cs | 2 +- 7 files changed, 18 insertions(+), 11 deletions(-) diff --git a/docs/docs/how-to/analyzers.md b/docs/docs/how-to/analyzers.md index 6a361c0e330..973b71fe32c 100644 --- a/docs/docs/how-to/analyzers.md +++ b/docs/docs/how-to/analyzers.md @@ -41,6 +41,11 @@ The legacy prose IDs were renamed so `.editorconfig` settings can use one predic | `ConsoleUse` | `MP0004` | | `ConflictingDependsOnAttribute` | `MP0005` | | `AsyncModule` | `MP0006` | +| `AwaitThis` | `MP0007` | +| `StatefulModule` | `MP0008` | +| `MPDEP001` | `MP0009` | +| `MPDEP002` | `MP0005` | +| `MPDEP003` | `MP0010` | | `MPD0001` | `MP0011` | | `MPD0002` | `MP0012` | | `MPREG001` | `MP0013` | diff --git a/docs/scripts/generate-analyzer-docs.mjs b/docs/scripts/generate-analyzer-docs.mjs index 37db63cb4cd..5b50a21d04a 100644 --- a/docs/scripts/generate-analyzer-docs.mjs +++ b/docs/scripts/generate-analyzer-docs.mjs @@ -65,7 +65,10 @@ function applyReleaseMetadata(filePath, activeRulesById) { continue; } - const [id, category, severity, ...notes] = columns; + const [id, category, severity] = columns; + const notes = section === 'changed' + ? columns.slice(5) + : columns.slice(3); if (section === 'removed') { activeRulesById.delete(id); diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/AnalyzerMetadataTests.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/AnalyzerMetadataTests.cs index 17a3d18ae90..aa5ae35e77a 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/AnalyzerMetadataTests.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers.Test/AnalyzerMetadataTests.cs @@ -7,7 +7,7 @@ namespace ModularPipelines.Analyzers.Test; public class AnalyzerMetadataTests { private const string DocumentationBaseUrl = - "https://thomhurst.github.io/ModularPipelines/docs/analyzers/"; + "https://thomhurst.github.io/ModularPipelines/docs/next/analyzers/"; [TestMethod] public void PublicRulesUseUnifiedIdsAndHelpLinks() @@ -33,12 +33,11 @@ public void PublicRulesUseUnifiedIdsAndHelpLinks() DuplicateDependsOnAnalyzer.Rule, }; - CollectionAssert.AreEquivalent( + Assert.AreSequenceEqual( Enumerable.Range(1, 10) .Concat(Enumerable.Range(13, 7)) - .Select(index => $"MP{index:0000}") - .ToArray(), - rules.Select(rule => rule.Id).ToArray()); + .Select(index => $"MP{index:0000}"), + rules.Select(rule => rule.Id)); foreach (var rule in rules) { diff --git a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/DiagnosticDescriptorFactory.cs b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/DiagnosticDescriptorFactory.cs index 59a7e30c0c2..5560cb65b32 100644 --- a/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/DiagnosticDescriptorFactory.cs +++ b/src/ModularPipelines.Analyzers/ModularPipelines.Analyzers/DiagnosticDescriptorFactory.cs @@ -9,7 +9,7 @@ namespace ModularPipelines.Analyzers; internal static class DiagnosticDescriptorFactory { private const string DocumentationBaseUrl = - "https://thomhurst.github.io/ModularPipelines/docs/analyzers/"; + "https://thomhurst.github.io/ModularPipelines/docs/next/analyzers/"; /// /// Creates a with localizable strings from the Resources file. diff --git a/src/ModularPipelines.Build/Program.cs b/src/ModularPipelines.Build/Program.cs index fc909dde10a..1e1e0f3338e 100644 --- a/src/ModularPipelines.Build/Program.cs +++ b/src/ModularPipelines.Build/Program.cs @@ -229,14 +229,14 @@ private static async Task IsRedisDiscoveryAvailableAsync(string restUrl, s private static void WriteWarning(string message) { -#pragma warning disable ConsoleUse // Logging is not configured until after distributed startup succeeds. +#pragma warning disable MP0004 // Logging is not configured until after distributed startup succeeds. Console.Error.WriteLine($"WARNING: {message}"); if (string.Equals(Environment.GetEnvironmentVariable("GITHUB_ACTIONS"), "true", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine($"::warning title=Distributed pipeline degraded::{message}"); } -#pragma warning restore ConsoleUse +#pragma warning restore MP0004 } private static void ConfigureArtifactStore(PipelineBuilder builder) diff --git a/src/ModularPipelines.Development.Analyzers/DiagnosticDescriptorFactory.cs b/src/ModularPipelines.Development.Analyzers/DiagnosticDescriptorFactory.cs index a70497a7341..0b45f209530 100644 --- a/src/ModularPipelines.Development.Analyzers/DiagnosticDescriptorFactory.cs +++ b/src/ModularPipelines.Development.Analyzers/DiagnosticDescriptorFactory.cs @@ -5,7 +5,7 @@ namespace ModularPipelines.Development.Analyzers; internal static class DiagnosticDescriptorFactory { private const string DocumentationBaseUrl = - "https://thomhurst.github.io/ModularPipelines/docs/analyzers/"; + "https://thomhurst.github.io/ModularPipelines/docs/next/analyzers/"; public static DiagnosticDescriptor Create( string id, diff --git a/test/ModularPipelines.Development.Analyzers.UnitTests/AnalyzerMetadataTests.cs b/test/ModularPipelines.Development.Analyzers.UnitTests/AnalyzerMetadataTests.cs index a8e339d02e6..a2340632b2e 100644 --- a/test/ModularPipelines.Development.Analyzers.UnitTests/AnalyzerMetadataTests.cs +++ b/test/ModularPipelines.Development.Analyzers.UnitTests/AnalyzerMetadataTests.cs @@ -5,7 +5,7 @@ namespace ModularPipelines.Development.Analyzers.UnitTests; public class AnalyzerMetadataTests { private const string DocumentationBaseUrl = - "https://thomhurst.github.io/ModularPipelines/docs/analyzers/"; + "https://thomhurst.github.io/ModularPipelines/docs/next/analyzers/"; [Test] public async Task DevelopmentRulesContinueTheUnifiedIdFamily()