Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/Rules/MA0048.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,27 @@ class Foo<T> // compliant
{
}

// filename: Foo`1.cs
class Foo<T> // compliant
{
}

// filename: Foo{TKey,TResult}.cs
class Foo<TKey, TResult> // compliant
{
}

// filename: FooOfT.cs
class Foo<TKey, TResult> // 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<TKey, TResult> // compliant
{
}

// filename: Foo{T}.cs
class Foo<TKey, TResult> // non compliant
{
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,45 @@ class Test0<T>
.ValidateAsync();
}

[Fact]
public async Task DoesNotMatchFileName_GenericWithArityGreaterThan1UsingOfT_WithoutConfiguration()
{
await CreateProjectBuilder()
.WithSourceCode(fileName: "Test0OfT.cs", """
class [||]Test0<T1, T2>
{
}
""")
.ValidateAsync();
}

[Fact]
public async Task DoesMatchFileName_GenericWithArityGreaterThan1UsingOfT_WithConfiguration()
{
await CreateProjectBuilder()
.WithSourceCode(fileName: "Test0OfT.cs", """
class Test0<T1, T2>
{
}
""")
.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, T2>(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()
{
Expand Down