From 98e0721e41e2ac67e8e5ac6497b739369a91ae2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Sun, 6 Sep 2026 18:00:29 -0400 Subject: [PATCH] Make the analysis of generated code opt-in Analyzing generated code costs build time, and most projects never report anything in it, so the rules now skip it unless the MEZIANTOU_ANALYZER_GENERATED_CODE environment variable opts in with "1" or "true". The rules that need generated code to be correct, such as MA0053, and the ones that report in it by default, such as the Blazor rules, are unchanged, as the flags they pass to ConfigureAnalysisOfGeneratedCode are still the minimum. report_generated_code only decides what a rule reports among the code it analyzes, so it now needs the environment variable to report in generated code with a rule that skips it. Setting it to false still always works. --- AGENTS.md | 2 +- README.md | 29 +++---- docs/generated-code.md | 45 ++++++----- src/Meziantou.Analyzer/BannedSymbols.txt | 2 +- .../Internals/AnalysisContextExtensions.cs | 16 ++-- .../Internals/GeneratedCodeReporting.cs | 10 +-- .../AnalysisContextExtensionsTests.cs | 28 +++---- .../Rules/GeneratedCodeAnalysisTests.cs | 80 ++++++++++++++----- 8 files changed, 125 insertions(+), 87 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 06e33c132..4af0e6205 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,7 +55,7 @@ The analyzer must use `IOperation` or `ISymbol` to analyze the content. Only fal ### Generated code -The analyzers analyze generated code, so they must call `context.ConfigureAnalysisOfGeneratedCode(...)` in `Initialize` instead of `ConfigureGeneratedCodeAnalysis`, which `BannedSymbols.txt` makes a compilation error: the flags passed to it are the minimum, and `Analyze | ReportDiagnostics` is added to them unless the `MEZIANTOU_ANALYZER_GENERATED_CODE` environment variable opts out, as analyzing generated code has a cost. Pass `GeneratedCodeAnalysisFlags.None` unless the rule needs generated code to be correct even when a user opts out (`Analyze`), or reports in generated code by default (`Analyze | ReportDiagnostics`). The diagnostics located in generated code are filtered when they are reported. The filtering is done by `GeneratedCodeReporting`, which sets the `DiagnosticReporter.CanReportDiagnostic` filter of `Meziantou.Framework.Roslyn` from a module initializer, so the rules must report their diagnostics with the `ReportDiagnostic` extension methods of the analysis contexts or with a `DiagnosticReporter`; `BannedSymbols.txt` makes reporting directly on a Roslyn context a compilation error, as it would bypass the filter. Add `customTags: [GeneratedCodeReporting.ReportInGeneratedCodeTag]` to the descriptor of the rules that must report in generated code by default, such as the rules whose subject is the generated file itself, use `GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics` in their analyzer, and run `dotnet run --project src/DocumentationGenerator` to update the list in [`docs/generated-code.md`](/docs/generated-code.md). +The analyzers skip generated code, so they must call `context.ConfigureAnalysisOfGeneratedCode(...)` in `Initialize` instead of `ConfigureGeneratedCodeAnalysis`, which `BannedSymbols.txt` makes a compilation error: the flags passed to it are the minimum, and `Analyze | ReportDiagnostics` is added to them only when the `MEZIANTOU_ANALYZER_GENERATED_CODE` environment variable opts in, as analyzing generated code has a cost. Pass `GeneratedCodeAnalysisFlags.None` unless the rule needs generated code to be correct even when nobody opts in (`Analyze`), or reports in generated code by default (`Analyze | ReportDiagnostics`). The diagnostics located in generated code are filtered when they are reported. The filtering is done by `GeneratedCodeReporting`, which sets the `DiagnosticReporter.CanReportDiagnostic` filter of `Meziantou.Framework.Roslyn` from a module initializer, so the rules must report their diagnostics with the `ReportDiagnostic` extension methods of the analysis contexts or with a `DiagnosticReporter`; `BannedSymbols.txt` makes reporting directly on a Roslyn context a compilation error, as it would bypass the filter. Add `customTags: [GeneratedCodeReporting.ReportInGeneratedCodeTag]` to the descriptor of the rules that must report in generated code by default, such as the rules whose subject is the generated file itself, use `GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics` in their analyzer, and run `dotnet run --project src/DocumentationGenerator` to update the list in [`docs/generated-code.md`](/docs/generated-code.md). Code snippets in tests must use raw string literals (`"""`) and must be minimized to only include the necessary code to reproduce the issue. Avoid including unnecessary code that does not contribute to the test case. When reporting a diagnostic, the snippet must use the `[|code|]` syntax or `{|id:code|}` syntax. Do not explicitly indicates lines or columns. diff --git a/README.md b/README.md index bb35c6e35..04d1d4db9 100755 --- a/README.md +++ b/README.md @@ -298,8 +298,19 @@ Supported values are: ## Analyzing generated code -The rules analyze generated code, but most of them do not report the diagnostics located in generated code. Set -`report_generated_code` in the `.editorconfig` file to change it, for a single rule or for all of them at once: +The rules skip generated code, as analyzing it costs build time and you cannot fix code you do not own. Set the +`MEZIANTOU_ANALYZER_GENERATED_CODE` environment variable to `true` (or `1`) to opt in, which makes the rules analyze +generated code: + +```bash +MEZIANTOU_ANALYZER_GENERATED_CODE=true +``` + +Note that the analyzers run in a long-lived process. After changing the variable, run `dotnet build-server shutdown` +and rebuild, or restart your IDE. + +Opting in does not report anything by itself: `report_generated_code` decides whether a rule reports the diagnostics +located in the generated code it analyzes, for a single rule or for all of them at once: ```ini [*.cs] @@ -310,18 +321,8 @@ MA.report_generated_code = true MA0051.report_generated_code = false ``` -A few rules report in generated code by default, such as the Blazor rules that work on the code generated from the -`.razor` files. - -Analyzing generated code costs build time. Set the `MEZIANTOU_ANALYZER_GENERATED_CODE` environment variable to -`false` (or `0`) to opt out, which makes the rules skip generated code entirely, except the few that need it: - -```bash -MEZIANTOU_ANALYZER_GENERATED_CODE=false -``` - -Note that the analyzers run in a long-lived process. After changing the variable, run `dotnet build-server shutdown` -and rebuild, or restart your IDE. +A few rules analyze generated code without the variable, and report in it by default, such as the Blazor rules that +work on the code generated from the `.razor` files. See [Analyzing generated code](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/generated-code.md) for the list of those rules and for the detection of the generated files. diff --git a/docs/generated-code.md b/docs/generated-code.md index 048759cf6..ba79e018d 100644 --- a/docs/generated-code.md +++ b/docs/generated-code.md @@ -1,14 +1,15 @@ # Analyzing generated code -The rules analyze generated code, so they see the whole compilation, but most of them do not report the diagnostics -located in generated code, which is the expected behavior for the vast majority of projects: you cannot fix code you -do not own. Some rules are the exception and report in generated code by default, as the generated file is the -subject of the rule, such as the Blazor rules that work on the code generated from the `.razor` files. +The rules skip generated code, which is the expected behavior for the vast majority of projects: you cannot fix code +you do not own, and analyzing code nobody looks at costs build time. Some rules are the exception and analyze it +anyway: the ones whose subject is the generated file itself, such as the Blazor rules that work on the code generated +from the `.razor` files, and the ones that need to see the whole compilation to be correct. Set the +`MEZIANTOU_ANALYZER_GENERATED_CODE` environment variable to opt in to analyzing generated code with every rule. ## Configuration -The `report_generated_code` option indicates whether a rule reports the diagnostics located in generated code. It can -be set for a single rule, or for all of them at once with the `MA` prefix: +The `report_generated_code` option indicates whether a rule reports the diagnostics located in the generated code it +analyzes. It can be set for a single rule, or for all of them at once with the `MA` prefix: ```ini [*.cs] @@ -35,24 +36,23 @@ MA0051.report_generated_code = true When neither is set, each rule uses its own default, which is to not report in generated code, except for the rules below. To turn off a rule entirely, set its severity to `none` instead. -## Opting out with the environment variable +This option only decides what a rule reports, not what it analyzes, so `report_generated_code = true` has no effect on +a rule that does not analyze generated code: it needs the environment variable below. Setting it to `false` always +works, as a rule can only report what it is allowed to report. -Analyzing generated code costs build time, and a project where nothing reports in generated code pays it for nothing. -Set the `MEZIANTOU_ANALYZER_GENERATED_CODE` environment variable to skip generated code entirely: +## Opting in with the environment variable + +Set the `MEZIANTOU_ANALYZER_GENERATED_CODE` environment variable to analyze generated code with every rule: | Value | Behavior | |-------|----------| -| not set, empty, or any other value | The rules analyze generated code, and `report_generated_code` decides what they report | -| `false` (case-insensitive) or `0` | The rules skip generated code, except the ones that need it | - -The variable can only remove analysis from the rules that do not need it. Two kinds of rules are unaffected: the ones -that report in generated code by default, listed below, and the ones that need to see the whole compilation to be -correct, such as MA0053, which reports a class that no other class inherits from and would report a false positive if -the deriving class were declared in a generated file. +| `true` (case-insensitive) or `1` | The rules analyze generated code, and `report_generated_code` decides what they report | +| not set, empty, or any other value | The rules skip generated code, except the ones that need it | -The option above only decides what the rules report, not what they analyze, so `report_generated_code = true` has no -effect on a rule that skips generated code because of the variable. Setting it to `false` always works, as a rule can -only report what it is allowed to report. +Two kinds of rules analyze generated code without the variable: the ones that report in generated code by default, +listed below, and the ones that need to see the whole compilation to be correct, such as MA0053, which reports a class +that no other class inherits from and would report a false positive if the deriving class were declared in a generated +file. `report_generated_code` works on those rules whether the variable is set or not. ## Rules reporting in generated code by default @@ -99,8 +99,9 @@ written file does not make the code generated, and a partial type declared in a one reports only in the hand written file. Use the `generated_code` option below for the files the detection does not recognize. -The rules that skip generated code because of the environment variable never see it, so they follow the detection of -Roslyn instead, which also considers the symbols marked with `[GeneratedCode]` or `[DebuggerNonUserCode]` generated. +The rules that do not analyze generated code never see it, so they follow the detection of Roslyn instead, which also +considers the symbols marked with `[GeneratedCode]` or `[DebuggerNonUserCode]` generated. The two detections only +differ for a rule the variable opted in. ## Using the `generated_code` option @@ -121,7 +122,7 @@ generated, and `generated_code` when you want all the analyzers to treat a speci An analyzer must declare how it handles generated code from `Initialize(AnalysisContext)`, and the options of the `.editorconfig` files are not available at that point: they can only be read from the analysis callbacks, which run later. An environment variable is the only configuration that can be read early enough, which is why the global -opt-out is not an `.editorconfig` option. +opt-in is not an `.editorconfig` option. This has consequences that are worth knowing: diff --git a/src/Meziantou.Analyzer/BannedSymbols.txt b/src/Meziantou.Analyzer/BannedSymbols.txt index 3ba2cee74..d3e53c5c7 100644 --- a/src/Meziantou.Analyzer/BannedSymbols.txt +++ b/src/Meziantou.Analyzer/BannedSymbols.txt @@ -1,5 +1,5 @@ M:Microsoft.CodeAnalysis.Compilation.GetTypeByMetadataName(System.String); Use GetBestTypeByMetadataName instead -M:Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.ConfigureGeneratedCodeAnalysis(Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags); Use ConfigureAnalysisOfGeneratedCode instead, so users can opt out of analyzing generated code +M:Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.ConfigureGeneratedCodeAnalysis(Microsoft.CodeAnalysis.Diagnostics.GeneratedCodeAnalysisFlags); Use ConfigureAnalysisOfGeneratedCode instead, so users can opt in to analyzing generated code M:Microsoft.CodeAnalysis.Diagnostics.SymbolAnalysisContext.ReportDiagnostic(Microsoft.CodeAnalysis.Diagnostic); Use the ReportDiagnostic methods of ContextExtensions instead, so the diagnostics located in generated code are filtered by GeneratedCodeReporting M:Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext.ReportDiagnostic(Microsoft.CodeAnalysis.Diagnostic); Use the ReportDiagnostic methods of ContextExtensions instead, so the diagnostics located in generated code are filtered by GeneratedCodeReporting M:Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext.ReportDiagnostic(Microsoft.CodeAnalysis.Diagnostic); Use the ReportDiagnostic methods of ContextExtensions instead, so the diagnostics located in generated code are filtered by GeneratedCodeReporting diff --git a/src/Meziantou.Analyzer/Internals/AnalysisContextExtensions.cs b/src/Meziantou.Analyzer/Internals/AnalysisContextExtensions.cs index c0ac95169..b31e72228 100644 --- a/src/Meziantou.Analyzer/Internals/AnalysisContextExtensions.cs +++ b/src/Meziantou.Analyzer/Internals/AnalysisContextExtensions.cs @@ -14,11 +14,11 @@ internal static class AnalysisContextExtensions private static readonly GeneratedCodeAnalysisFlags AdditionalFlags = GetAdditionalFlags(ReadEnvironmentVariable()); /// - /// Configures how the analyzer handles generated code. The rules analyze generated code and report the - /// diagnostics located in it, which then filters, unless the - /// MEZIANTOU_ANALYZER_GENERATED_CODE environment variable opts out, as analyzing generated code has a - /// cost. is the minimum, which opting out cannot remove: it is what the rule - /// needs to be correct, or to report in generated code by default. + /// Configures how the analyzer handles generated code. The rules skip generated code, as analyzing it has a + /// cost, unless the MEZIANTOU_ANALYZER_GENERATED_CODE environment variable opts in, in which case they + /// analyze it and report the diagnostics located in it, which then + /// filters. is the minimum, which not opting in cannot remove: it is what the + /// rule needs to be correct, or to report in generated code by default. /// public static void ConfigureAnalysisOfGeneratedCode(this AnalysisContext context, GeneratedCodeAnalysisFlags defaultFlags) { @@ -29,9 +29,9 @@ public static void ConfigureAnalysisOfGeneratedCode(this AnalysisContext context internal static GeneratedCodeAnalysisFlags GetAdditionalFlags(string? value) => value?.Trim() switch { - "0" => GeneratedCodeAnalysisFlags.None, - var trimmed when string.Equals(trimmed, "false", StringComparison.OrdinalIgnoreCase) => GeneratedCodeAnalysisFlags.None, - _ => GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics, + "1" => GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics, + var trimmed when string.Equals(trimmed, "true", StringComparison.OrdinalIgnoreCase) => GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics, + _ => GeneratedCodeAnalysisFlags.None, }; private static string? ReadEnvironmentVariable() diff --git a/src/Meziantou.Analyzer/Internals/GeneratedCodeReporting.cs b/src/Meziantou.Analyzer/Internals/GeneratedCodeReporting.cs index b9984bf41..74d2f6875 100644 --- a/src/Meziantou.Analyzer/Internals/GeneratedCodeReporting.cs +++ b/src/Meziantou.Analyzer/Internals/GeneratedCodeReporting.cs @@ -4,12 +4,12 @@ namespace Meziantou.Analyzer.Internals; /// -/// Decides whether a rule reports the diagnostics located in generated code. The analyzers configure Roslyn to -/// analyze generated code and to report the diagnostics located in it, as ConfigureGeneratedCodeAnalysis +/// Decides whether a rule reports the diagnostics located in generated code. The analyzers that analyze generated +/// code also configure Roslyn to report the diagnostics located in it, as ConfigureGeneratedCodeAnalysis /// cannot read the .editorconfig options, so the decision is taken when a diagnostic is reported. The -/// options below only apply to what the analyzers actually analyze: a rule stops reporting in generated code when -/// the MEZIANTOU_ANALYZER_GENERATED_CODE environment variable opts out of analyzing it, unless the rule -/// needs it (see ). +/// options below only apply to what the analyzers actually analyze: a rule only reports in generated code when the +/// MEZIANTOU_ANALYZER_GENERATED_CODE environment variable opts in to analyzing it, unless the rule analyzes +/// it anyway (see ). /// internal static class GeneratedCodeReporting { diff --git a/tests/Meziantou.Analyzer.Test/Internals/AnalysisContextExtensionsTests.cs b/tests/Meziantou.Analyzer.Test/Internals/AnalysisContextExtensionsTests.cs index 906847ea9..e56a6d3d3 100644 --- a/tests/Meziantou.Analyzer.Test/Internals/AnalysisContextExtensionsTests.cs +++ b/tests/Meziantou.Analyzer.Test/Internals/AnalysisContextExtensionsTests.cs @@ -6,28 +6,28 @@ namespace Meziantou.Analyzer.Test.Internals; public sealed class AnalysisContextExtensionsTests { [Theory] - [InlineData("0")] - [InlineData(" 0 ")] - [InlineData("false")] - [InlineData("False")] - [InlineData("FALSE")] - [InlineData(" false ")] - public void GetAdditionalFlags_OptedOut(string value) + [InlineData("1")] + [InlineData(" 1 ")] + [InlineData("true")] + [InlineData("True")] + [InlineData("TRUE")] + [InlineData(" true ")] + public void GetAdditionalFlags_OptedIn(string value) { - Assert.Equal(GeneratedCodeAnalysisFlags.None, AnalysisContextExtensions.GetAdditionalFlags(value)); + Assert.Equal(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics, AnalysisContextExtensions.GetAdditionalFlags(value)); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] - [InlineData("1")] - [InlineData("true")] - [InlineData("True")] + [InlineData("0")] + [InlineData("false")] + [InlineData("False")] [InlineData("dummy")] - [InlineData("falsy")] - public void GetAdditionalFlags_NotOptedOut(string? value) + [InlineData("truthy")] + public void GetAdditionalFlags_NotOptedIn(string? value) { - Assert.Equal(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics, AnalysisContextExtensions.GetAdditionalFlags(value)); + Assert.Equal(GeneratedCodeAnalysisFlags.None, AnalysisContextExtensions.GetAdditionalFlags(value)); } } diff --git a/tests/Meziantou.Analyzer.Test/Rules/GeneratedCodeAnalysisTests.cs b/tests/Meziantou.Analyzer.Test/Rules/GeneratedCodeAnalysisTests.cs index 52a70d9fc..af4247e63 100644 --- a/tests/Meziantou.Analyzer.Test/Rules/GeneratedCodeAnalysisTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/GeneratedCodeAnalysisTests.cs @@ -14,14 +14,15 @@ namespace Meziantou.Analyzer.Test.Rules; /// -/// The analyzers analyze generated code, but only the rules configured with report_generated_code, or -/// whose default is to report in generated code such as MA0070, report the diagnostics located in it. The -/// MEZIANTOU_ANALYZER_GENERATED_CODE environment variable opts out of analyzing generated code, which the -/// tests simulate by setting GeneratedCodeAnalysisFlags to the flags of the rule, as the rules read the -/// variable once per process. +/// The analyzers skip generated code, except the rules that need it to be correct and the rules that report in it +/// by default such as MA0070. The MEZIANTOU_ANALYZER_GENERATED_CODE environment variable opts in to +/// analyzing it, and report_generated_code then decides what the rules report, which the tests simulate by +/// setting GeneratedCodeAnalysisFlags, as the rules read the variable once per process. /// public sealed class GeneratedCodeAnalysisTests { + private const GeneratedCodeAnalysisFlags OptedIn = GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics; + private const string GeneratedCodeWithDiagnostic = """ // abstract class Test @@ -47,10 +48,33 @@ public Task AutoGeneratedHeader_NoDiagnosticByDefault() return test.RunAsync(); } + [Fact] + public Task AutoGeneratedHeader_NoDiagnosticWhenTheRuleIsConfiguredButNotOptedIn() + { + // MA0017 does not analyze generated code, so configuring it to report in generated code is not enough + var test = new AbstractTypesTest(); + test.TestState.SetConfiguration("MA0017.report_generated_code", "true"); + test.TestCode = GeneratedCodeWithDiagnostic; + + return test.RunAsync(); + } + + [Fact] + public Task AutoGeneratedHeader_NoDiagnosticWhenOptedInWithoutConfiguration() + { + // Opting in makes the rule analyze generated code, but its default is still to not report in it + var test = new AbstractTypesTest(); + test.GeneratedCodeAnalysisFlags = OptedIn; + test.TestCode = GeneratedCodeWithDiagnostic; + + return test.RunAsync(); + } + [Fact] public Task AutoGeneratedHeader_DiagnosticWhenTheRuleIsConfigured() { var test = new AbstractTypesTest(); + test.GeneratedCodeAnalysisFlags = OptedIn; test.TestState.SetConfiguration("MA0017.report_generated_code", "true"); test.TestCode = GeneratedCodeWithDiagnosticReported; @@ -61,6 +85,7 @@ public Task AutoGeneratedHeader_DiagnosticWhenTheRuleIsConfigured() public Task AutoGeneratedHeader_DiagnosticWhenAllTheRulesAreConfigured() { var test = new AbstractTypesTest(); + test.GeneratedCodeAnalysisFlags = OptedIn; test.TestState.SetConfiguration("MA.report_generated_code", "true"); test.TestCode = GeneratedCodeWithDiagnosticReported; @@ -71,6 +96,7 @@ public Task AutoGeneratedHeader_DiagnosticWhenAllTheRulesAreConfigured() public Task AutoGeneratedHeader_RuleConfigurationWinsOverTheGlobalOne() { var test = new AbstractTypesTest(); + test.GeneratedCodeAnalysisFlags = OptedIn; test.TestState.SetConfiguration(("MA.report_generated_code", "true"), ("MA0017.report_generated_code", "false")); test.TestCode = GeneratedCodeWithDiagnostic; @@ -81,24 +107,13 @@ public Task AutoGeneratedHeader_RuleConfigurationWinsOverTheGlobalOne() public Task AutoGeneratedHeader_GlobalConfigurationDoesNotApplyToAnotherRule() { var test = new AbstractTypesTest(); + test.GeneratedCodeAnalysisFlags = OptedIn; test.TestState.SetConfiguration(("MA.report_generated_code", "false"), ("MA0053.report_generated_code", "true")); test.TestCode = GeneratedCodeWithDiagnostic; return test.RunAsync(); } - [Fact] - public Task AutoGeneratedHeader_NoDiagnosticWhenTheRuleIsConfiguredButOptedOut() - { - // MA0017 does not need generated code, so opting out prevents it from reporting in it - var test = new AbstractTypesTest(); - test.GeneratedCodeAnalysisFlags = GeneratedCodeAnalysisFlags.None; - test.TestState.SetConfiguration("MA0017.report_generated_code", "true"); - test.TestCode = GeneratedCodeWithDiagnostic; - - return test.RunAsync(); - } - [Fact] public Task GeneratedFileName_NoDiagnosticByDefault() { @@ -117,6 +132,7 @@ public Test() { } public Task GeneratedFileName_DiagnosticWhenTheRuleIsConfigured() { var test = new AbstractTypesTest(); + test.GeneratedCodeAnalysisFlags = OptedIn; test.TestState.SetConfiguration("MA0017.report_generated_code", "true"); test.TestState.Sources.Add(("/0/Test.g.cs", """ abstract class Test @@ -129,10 +145,12 @@ abstract class Test } [Fact] - public Task GeneratedCodeAttribute_TheFileIsWhatMatters() + public Task GeneratedCodeAttribute_TheFileIsWhatMattersWhenOptedIn() { // Only the file of the diagnostic is considered, so the attribute does not hide the code of a hand written file var test = new AbstractTypesTest(); + test.GeneratedCodeAnalysisFlags = OptedIn; + test.TestState.SetConfiguration("MA0017.report_generated_code", "true"); test.TestCode = """ [System.CodeDom.Compiler.GeneratedCode("tool", "1.0")] abstract class Test @@ -144,6 +162,23 @@ abstract class Test return test.RunAsync(); } + [Fact] + public Task GeneratedCodeAttribute_NoDiagnosticWhenNotOptedIn() + { + // A rule that does not analyze generated code follows the detection of Roslyn, which also considers the + // symbols marked with [GeneratedCode] generated + var test = new AbstractTypesTest(); + test.TestCode = """ + [System.CodeDom.Compiler.GeneratedCode("tool", "1.0")] + abstract class Test + { + public Test() { } + } + """; + + return test.RunAsync(); + } + [Fact] public Task GeneratedCodeOption_TheFileIsNotGeneratedCode() { @@ -223,6 +258,7 @@ public Task SyntaxTreeRule_NoDiagnosticByDefault() public Task SyntaxTreeRule_DiagnosticWhenTheRuleIsConfigured() { var test = new FixToDoTest(); + test.GeneratedCodeAnalysisFlags = OptedIn; test.TestState.SetConfiguration("MA0026.report_generated_code", "true"); test.TestCode = """ // @@ -234,11 +270,10 @@ public Task SyntaxTreeRule_DiagnosticWhenTheRuleIsConfigured() } [Fact] - public Task GeneratedCodeIsAnalyzedWhenOptedOut() + public Task GeneratedCodeIsAnalyzedWhenNotOptedIn() { - // MA0053 needs generated code to be correct, so opting out cannot stop it from analyzing it + // MA0053 needs generated code to be correct, so it analyzes it even when nobody opts in var test = new ClassMustBeSealedTest(); - test.GeneratedCodeAnalysisFlags = GeneratedCodeAnalysisFlags.Analyze; test.TestState.Sources.Add(("/0/Test.cs", """ class Test { @@ -254,10 +289,11 @@ class Test2 : Test } [Fact] - public Task GeneratedCodeIsAnalyzed() + public Task GeneratedCodeIsAnalyzedWhenOptedIn() { // The class declared in the generated file inherits from the other one, which cannot be sealed var test = new ClassMustBeSealedTest(); + test.GeneratedCodeAnalysisFlags = OptedIn; test.TestState.Sources.Add(("/0/Test.cs", """ class Test {