diff --git a/docs/Rules/MA0048.md b/docs/Rules/MA0048.md index 3e220884..8f0e956d 100644 --- a/docs/Rules/MA0048.md +++ b/docs/Rules/MA0048.md @@ -42,11 +42,27 @@ class Foo // compliant { } +// filename: Foo`1.cs +class Foo // compliant +{ +} + // filename: Foo{TKey,TResult}.cs class Foo // compliant { } +// filename: FooOfT.cs +class Foo // non compliant (requires MA0048.allow_oft_for_all_generic_types = true) +{ +} + +// filename: FooOfT.cs +// .editorconfig: MA0048.allow_oft_for_all_generic_types = true +class Foo // compliant +{ +} + // filename: Foo{T}.cs class Foo // non compliant { @@ -76,6 +92,9 @@ MA0048.exclude_file_local_types = true # Only validate the first type in a file. default: false MA0048.only_validate_first_type = false +# Allow "OfT" suffix for generic types with any arity (not just arity 1). default: false +MA0048.allow_oft_for_all_generic_types = false + # Ignore certain symbols. default: none # Pipe-separated list of wildcard patterns dotnet_diagnostic.MA0048.excluded_symbol_names = Foo*|T:MyNamespace.Bar diff --git a/src/Meziantou.Analyzer/Rules/FileNameMustMatchTypeNameAnalyzer.cs b/src/Meziantou.Analyzer/Rules/FileNameMustMatchTypeNameAnalyzer.cs index d4aff689..f78ca27f 100644 --- a/src/Meziantou.Analyzer/Rules/FileNameMustMatchTypeNameAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/FileNameMustMatchTypeNameAnalyzer.cs @@ -114,7 +114,7 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) continue; } - if (symbol.Arity == 1) + if (symbol.Arity == 1 || (symbol.Arity > 1 && context.Options.GetConfigurationValue(location.SourceTree, Rule.Id + ".allow_oft_for_all_generic_types", defaultValue: false))) { // TypeOfT if (fileName.Equals((symbolName + "OfT").AsSpan(), StringComparison.OrdinalIgnoreCase)) diff --git a/tests/Meziantou.Analyzer.Test/Rules/FileNameMustMatchTypeNameAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/FileNameMustMatchTypeNameAnalyzerTests.cs index 132ac8fb..33e7ee55 100755 --- a/tests/Meziantou.Analyzer.Test/Rules/FileNameMustMatchTypeNameAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/FileNameMustMatchTypeNameAnalyzerTests.cs @@ -89,6 +89,45 @@ class Test0 .ValidateAsync(); } + [Fact] + public async Task DoesNotMatchFileName_GenericWithArityGreaterThan1UsingOfT_WithoutConfiguration() + { + await CreateProjectBuilder() + .WithSourceCode(fileName: "Test0OfT.cs", """ + class [||]Test0 + { + } + """) + .ValidateAsync(); + } + + [Fact] + public async Task DoesMatchFileName_GenericWithArityGreaterThan1UsingOfT_WithConfiguration() + { + await CreateProjectBuilder() + .WithSourceCode(fileName: "Test0OfT.cs", """ + class Test0 + { + } + """) + .AddAnalyzerConfiguration("MA0048.allow_oft_for_all_generic_types", "true") + .ValidateAsync(); + } + +#if CSHARP11_OR_GREATER + [Fact] + public async Task DoesMatchFileName_RecordStructWithArityGreaterThan1UsingOfT_WithConfiguration() + { + await CreateProjectBuilder() + .WithSourceCode(fileName: "FooOfT.cs", """ + public record struct Foo(T1 Key, T2 Value); + """) + .WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp11) + .AddAnalyzerConfiguration("MA0048.allow_oft_for_all_generic_types", "true") + .ValidateAsync(); + } +#endif + [Fact] public async Task NestedTypeDoesMatchFileName_Ok() {