Skip to content

Commit de40873

Browse files
Merge pull request #35 from Chris-Wolfgang/copilot/add-banned-api-analyzers
Add BannedApiAnalyzers to enforce async-first best practices
2 parents 1aad9c8 + e7b81d8 commit de40873

File tree

3 files changed

+116
-0
lines changed

3 files changed

+116
-0
lines changed

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ dotnet_diagnostic.CA2007.severity = none # ConfigureAwait - not needed
192192
dotnet_diagnostic.CA2012.severity = error # Use ValueTasks correctly
193193
dotnet_diagnostic.CA2016.severity = warning # Forward CancellationToken parameter
194194

195+
# Banned API Analyzer (RS0030) - Enforce async-first best practices
196+
dotnet_diagnostic.RS0030.severity = error # Using banned API - treat as error
197+
195198
# New line preferences
196199
csharp_new_line_before_open_brace = all
197200
csharp_new_line_before_else = true
@@ -377,6 +380,9 @@ dotnet_diagnostic.VSTHRD104.severity = none # Allow missing async options
377380
dotnet_diagnostic.VSTHRD107.severity = none # Allow Task in using without await in tests
378381
dotnet_diagnostic.VSTHRD114.severity = none # Allow returning null from Task methods in tests
379382

383+
# Banned API Analyzer - Just warn in tests (allow for testing purposes)
384+
dotnet_diagnostic.RS0030.severity = warning # Using banned API - warn instead of error in tests
385+
380386
# Meziantou - Relax in tests
381387
dotnet_diagnostic.MA0004.severity = none # ConfigureAwait not needed in tests
382388
dotnet_diagnostic.MA0011.severity = none # IFormatProvider not critical in tests
@@ -409,6 +415,9 @@ dotnet_diagnostic.CA1707.severity = none
409415
# Relax async/await analyzer rules for benchmarks
410416
dotnet_diagnostic.AsyncFixer01.severity = none # Allow unnecessary async/await in benchmarks
411417

418+
# Banned API Analyzer - Just warn in benchmarks (allow for benchmarking purposes)
419+
dotnet_diagnostic.RS0030.severity = warning # Using banned API - warn instead of error in benchmarks
420+
412421
# No documentation required for benchmarks
413422
dotnet_diagnostic.SA1600.severity = none
414423
dotnet_diagnostic.SA1601.severity = none
@@ -425,3 +434,6 @@ dotnet_diagnostic.CA1707.severity = none
425434
dotnet_diagnostic.SA1600.severity = suggestion
426435
dotnet_diagnostic.SA1601.severity = suggestion
427436
dotnet_diagnostic.SA1602.severity = suggestion
437+
438+
# Banned API Analyzer - Allow in examples for demonstration purposes
439+
dotnet_diagnostic.RS0030.severity = none # Allow banned APIs in examples for demonstration

BannedSymbols.txt

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# BannedSymbols.txt - Async-First Enforcement for IAsyncEnumerable Extension Library
2+
# Format: <API Documentation ID>; <Reason/Alternative>
3+
# T: = Type, M: = Method, P: = Property, F: = Field
4+
# Task.Wait() - All overloads - Absolutely NOT allowed in async code
5+
M:System.Threading.Tasks.Task.Wait(); Use 'await' instead - this blocks the calling thread!
6+
M:System.Threading.Tasks.Task.Wait(System.Int32); Use 'await' with CancellationToken timeout instead
7+
M:System.Threading.Tasks.Task.Wait(System.TimeSpan); Use 'await' with CancellationToken timeout instead
8+
M:System.Threading.Tasks.Task.Wait(System.Int32,System.Threading.CancellationToken); Use 'await' instead
9+
M:System.Threading.Tasks.Task.Wait(System.Threading.CancellationToken); Use 'await' instead
10+
# Task.WaitAll/WaitAny - Use async alternatives
11+
M:System.Threading.Tasks.Task.WaitAll(System.Threading.Tasks.Task[]); Use 'await Task.WhenAll()' instead
12+
M:System.Threading.Tasks.Task.WaitAll(System.Threading.Tasks.Task[],System.Int32); Use 'await Task.WhenAll()' with CancellationToken instead
13+
M:System.Threading.Tasks.Task.WaitAll(System.Threading.Tasks.Task[],System.TimeSpan); Use 'await Task.WhenAll()' with CancellationToken instead
14+
M:System.Threading.Tasks.Task.WaitAll(System.Threading.Tasks.Task[],System.Int32,System.Threading.CancellationToken); Use 'await Task.WhenAll()' instead
15+
M:System.Threading.Tasks.Task.WaitAll(System.Threading.Tasks.Task[],System.Threading.CancellationToken); Use 'await Task.WhenAll()' instead
16+
M:System.Threading.Tasks.Task.WaitAny(System.Threading.Tasks.Task[]); Use 'await Task.WhenAny()' instead
17+
M:System.Threading.Tasks.Task.WaitAny(System.Threading.Tasks.Task[],System.Int32); Use 'await Task.WhenAny()' with CancellationToken instead
18+
M:System.Threading.Tasks.Task.WaitAny(System.Threading.Tasks.Task[],System.TimeSpan); Use 'await Task.WhenAny()' with CancellationToken instead
19+
M:System.Threading.Tasks.Task.WaitAny(System.Threading.Tasks.Task[],System.Int32,System.Threading.CancellationToken); Use 'await Task.WhenAny()' instead
20+
M:System.Threading.Tasks.Task.WaitAny(System.Threading.Tasks.Task[],System.Threading.CancellationToken); Use 'await Task.WhenAny()' instead
21+
# Task<T>.Result - Blocking property access
22+
P:System.Threading.Tasks.Task`1.Result; Blocking! Use 'await' instead to get the result asynchronously
23+
# GetAwaiter().GetResult() - Also blocking
24+
M:System.Runtime.CompilerServices.TaskAwaiter.GetResult(); Blocking! Use 'await' instead
25+
M:System.Runtime.CompilerServices.TaskAwaiter`1.GetResult(); Blocking! Use 'await' instead
26+
# Thread.Sleep - Use Task.Delay for async delays
27+
M:System.Threading.Thread.Sleep(System.Int32); Use 'await Task.Delay()' instead for async-friendly delays
28+
M:System.Threading.Thread.Sleep(System.TimeSpan); Use 'await Task.Delay()' instead for async-friendly delays
29+
# Obsolete/Deprecated Threading APIs
30+
M:System.Threading.Thread.Suspend(); Deprecated and dangerous
31+
M:System.Threading.Thread.Resume(); Deprecated and dangerous
32+
T:System.ComponentModel.BackgroundWorker; Use async/await patterns instead of BackgroundWorker
33+
# Synchronous File I/O - Use async versions
34+
M:System.IO.File.ReadAllText(System.String); Use 'File.ReadAllTextAsync()' instead
35+
M:System.IO.File.ReadAllText(System.String,System.Text.Encoding); Use 'File.ReadAllTextAsync()' instead
36+
M:System.IO.File.ReadAllLines(System.String); Use 'File.ReadAllLinesAsync()' instead
37+
M:System.IO.File.ReadAllLines(System.String,System.Text.Encoding); Use 'File.ReadAllLinesAsync()' instead
38+
M:System.IO.File.ReadAllBytes(System.String); Use 'File.ReadAllBytesAsync()' instead
39+
M:System.IO.File.WriteAllText(System.String,System.String); Use 'File.WriteAllTextAsync()' instead
40+
M:System.IO.File.WriteAllText(System.String,System.String,System.Text.Encoding); Use 'File.WriteAllTextAsync()' instead
41+
M:System.IO.File.WriteAllLines(System.String,System.Collections.Generic.IEnumerable{System.String}); Use 'File.WriteAllLinesAsync()' instead
42+
M:System.IO.File.WriteAllLines(System.String,System.Collections.Generic.IEnumerable{System.String},System.Text.Encoding); Use 'File.WriteAllLinesAsync()' instead
43+
M:System.IO.File.WriteAllLines(System.String,System.String[]); Use 'File.WriteAllLinesAsync()' instead
44+
M:System.IO.File.WriteAllLines(System.String,System.String[],System.Text.Encoding); Use 'File.WriteAllLinesAsync()' instead
45+
M:System.IO.File.WriteAllBytes(System.String,System.Byte[]); Use 'File.WriteAllBytesAsync()' instead
46+
M:System.IO.File.AppendAllText(System.String,System.String); Use 'File.AppendAllTextAsync()' instead
47+
M:System.IO.File.AppendAllText(System.String,System.String,System.Text.Encoding); Use 'File.AppendAllTextAsync()' instead
48+
M:System.IO.File.AppendAllLines(System.String,System.Collections.Generic.IEnumerable{System.String}); Use 'File.AppendAllLinesAsync()' instead
49+
M:System.IO.File.AppendAllLines(System.String,System.Collections.Generic.IEnumerable{System.String},System.Text.Encoding); Use 'File.AppendAllLinesAsync()' instead
50+
# Synchronous Stream operations - Use async versions for file I/O
51+
M:System.IO.FileStream.Read(System.Byte[],System.Int32,System.Int32); Use 'ReadAsync()' instead
52+
M:System.IO.FileStream.Write(System.Byte[],System.Int32,System.Int32); Use 'WriteAsync()' instead
53+
M:System.IO.Stream.CopyTo(System.IO.Stream); Use 'CopyToAsync()' instead
54+
M:System.IO.Stream.CopyTo(System.IO.Stream,System.Int32); Use 'CopyToAsync()' instead
55+
M:System.IO.Stream.Flush(); Use 'FlushAsync()' instead
56+
# StreamReader
57+
M:System.IO.StreamReader.ReadToEnd(); Use 'ReadToEndAsync()' instead
58+
M:System.IO.StreamReader.ReadLine(); Use 'ReadLineAsync()' instead
59+
M:System.IO.StreamReader.Read(); Use 'ReadAsync()' instead
60+
M:System.IO.StreamReader.Read(System.Char[],System.Int32,System.Int32); Use 'ReadAsync()' instead
61+
M:System.IO.StreamReader.ReadBlock(System.Char[],System.Int32,System.Int32); Use 'ReadBlockAsync()' instead
62+
# StreamWriter
63+
M:System.IO.StreamWriter.Write(System.String); Use 'WriteAsync()' instead
64+
M:System.IO.StreamWriter.Write(System.Char); Use 'WriteAsync()' instead
65+
M:System.IO.StreamWriter.Write(System.Char[]); Use 'WriteAsync()' instead
66+
M:System.IO.StreamWriter.Write(System.Char[],System.Int32,System.Int32); Use 'WriteAsync()' instead
67+
M:System.IO.StreamWriter.WriteLine(); Use 'WriteLineAsync()' instead
68+
M:System.IO.StreamWriter.WriteLine(System.String); Use 'WriteLineAsync()' instead
69+
M:System.IO.StreamWriter.Flush(); Use 'FlushAsync()' instead
70+
# Obsolete Network APIs - Use HttpClient
71+
T:System.Net.WebClient; Obsolete - use HttpClient instead
72+
T:System.Net.WebRequest; Obsolete - use HttpClient instead
73+
T:System.Net.HttpWebRequest; Obsolete - use HttpClient instead
74+
T:System.Net.HttpWebResponse; Obsolete - use HttpClient instead
75+
M:System.Net.WebClient.DownloadString(System.String); Use 'HttpClient.GetStringAsync()' instead
76+
M:System.Net.WebClient.DownloadData(System.String); Use 'HttpClient.GetByteArrayAsync()' instead
77+
M:System.Net.WebClient.UploadString(System.String,System.String); Use 'HttpClient.PostAsync()' instead
78+
# Obsolete/Insecure Serialization
79+
T:System.Runtime.Serialization.Formatters.Binary.BinaryFormatter; Insecure and deprecated - use System.Text.Json.JsonSerializer instead
80+
T:System.Runtime.Serialization.Formatters.Soap.SoapFormatter; Insecure and deprecated - use System.Text.Json.JsonSerializer instead
81+
# DateTime Anti-patterns - Prefer DateTimeOffset for timezone safety
82+
P:System.DateTime.Now; Use 'DateTimeOffset.UtcNow' or 'DateTimeOffset.Now' for timezone-aware operations
83+
# Synchronous Parallel operations - Use async alternatives
84+
M:System.Threading.Tasks.Parallel.For(System.Int32,System.Int32,System.Action{System.Int32}); Synchronous - prefer async concurrency patterns (e.g., 'Task.WhenAll()', dataflow) or 'Parallel.ForEachAsync()' on modern .NET versions
85+
M:System.Threading.Tasks.Parallel.For(System.Int32,System.Int32,System.Threading.Tasks.ParallelOptions,System.Action{System.Int32}); Synchronous - prefer async concurrency patterns (e.g., 'Task.WhenAll()', dataflow) or 'Parallel.ForEachAsync()' on modern .NET versions
86+
M:System.Threading.Tasks.Parallel.ForEach``1(System.Collections.Generic.IEnumerable{``0},System.Action{``0}); Synchronous - prefer async concurrency patterns (e.g., 'Task.WhenAll()', dataflow) or 'Parallel.ForEachAsync()' on modern .NET versions
87+
M:System.Threading.Tasks.Parallel.ForEach``1(System.Collections.Generic.IEnumerable{``0},System.Threading.Tasks.ParallelOptions,System.Action{``0}); Synchronous - prefer async concurrency patterns (e.g., 'Task.WhenAll()', dataflow) or 'Parallel.ForEachAsync()' on modern .NET versions
88+
M:System.Threading.Tasks.Parallel.Invoke(System.Action[]); Synchronous - use 'Task.WhenAll()' with async delegates instead
89+
# Console Blocking operations - Avoid in async code
90+
M:System.Console.ReadLine(); Blocking - avoid in async code paths
91+
M:System.Console.Read(); Blocking - avoid in async code paths
92+
M:System.Console.ReadKey(); Blocking - avoid in async code paths
93+
M:System.Console.ReadKey(System.Boolean); Blocking - avoid in async code paths

Directory.Build.props

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
<TreatWarningsAsErrors Condition="'$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
1313
</PropertyGroup>
1414

15+
<ItemGroup>
16+
<!-- Include BannedSymbols.txt as AdditionalFiles for BannedApiAnalyzers -->
17+
<AdditionalFiles Include="$(MSBuildThisFileDirectory)BannedSymbols.txt" />
18+
</ItemGroup>
19+
1520
<ItemGroup>
1621
<!-- Roslynator - Code quality and refactoring -->
1722
<PackageReference Include="Roslynator.Analyzers" Version="4.12.0">
@@ -31,6 +36,12 @@
3136
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
3237
</PackageReference>
3338

39+
<!-- BannedApiAnalyzers - Prevent usage of specific APIs -->
40+
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0">
41+
<PrivateAssets>all</PrivateAssets>
42+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
43+
</PackageReference>
44+
3445
<!-- NEW: Meziantou - Comprehensive code quality -->
3546
<PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
3647
<PrivateAssets>all</PrivateAssets>

0 commit comments

Comments
 (0)