diff --git a/docs/Rules/MA0042.md b/docs/Rules/MA0042.md index 7de13383..0f0b8195 100644 --- a/docs/Rules/MA0042.md +++ b/docs/Rules/MA0042.md @@ -56,6 +56,7 @@ public sealed class Sample The rule does not report a diagnostic for `IDbContextFactory.CreateDbContext()`. The `CreateDbContextAsync()` overload was introduced only for specific edge-case scenarios where the factory itself must perform asynchronous initialization, and is not intended as a general-purpose replacement. See [dotnet/efcore#26630](https://github.com/dotnet/efcore/issues/26630) for more details. The rule does not report a diagnostic for the following SQLite APIs by default: +- `SqliteConnection.Open()` - `SqliteConnection.CreateCommand()` - `SqliteCommand.ExecuteNonQuery()` - `SqliteCommand.ExecuteScalar()` diff --git a/docs/Rules/MA0045.md b/docs/Rules/MA0045.md index 2366c8da..71772277 100644 --- a/docs/Rules/MA0045.md +++ b/docs/Rules/MA0045.md @@ -31,6 +31,7 @@ public async Task Sample() ```` This rule shares the same SQLite special-cases as [MA0042](MA0042.md) by default: +- `SqliteConnection.Open()` - `SqliteConnection.CreateCommand()` - `SqliteCommand.ExecuteNonQuery()` - `SqliteCommand.ExecuteScalar()` diff --git a/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs b/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs index 1b69203a..677d0a1a 100755 --- a/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer.cs @@ -270,8 +270,9 @@ private bool HasAsyncEquivalent(IInvocationOperation operation, bool sqliteSpeci return false; } - // SqliteConnection.CreateCommand() always returns SqliteCommand. - // SqliteCommand does not override DisposeAsync, so there is no async alternative to require. + // SqliteConnection.Open() and CreateCommand() are synchronous by design in Microsoft.Data.Sqlite. + // SqliteConnection.CreateCommand() always returns SqliteCommand and SqliteCommand does not override + // DisposeAsync, so there is no async alternative to require. // https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/async else if (sqliteSpecialCasesEnabled && IsSqliteSpecialCaseMethod(targetMethod)) { @@ -329,6 +330,16 @@ private bool IsSqliteConnectionCreateCommand(IMethodSymbol targetMethod) targetMethod.ReturnType.IsEqualTo(SqliteCommandSymbol); } + private bool IsSqliteConnectionOpen(IMethodSymbol targetMethod) + { + if (SqliteConnectionSymbol is null) + return false; + + return targetMethod.Name is "Open" && + targetMethod.ContainingType.IsEqualTo(SqliteConnectionSymbol) && + targetMethod.Parameters.Length == 0; + } + private bool IsSqliteCommandMethod(IMethodSymbol targetMethod) { if (SqliteCommandSymbol is null) @@ -340,7 +351,7 @@ private bool IsSqliteCommandMethod(IMethodSymbol targetMethod) private bool IsSqliteSpecialCaseMethod(IMethodSymbol targetMethod) { - return IsSqliteConnectionCreateCommand(targetMethod) || IsSqliteCommandMethod(targetMethod); + return IsSqliteConnectionOpen(targetMethod) || IsSqliteConnectionCreateCommand(targetMethod) || IsSqliteCommandMethod(targetMethod); } private IMethodSymbol? FindPotentialAsyncEquivalent(IInvocationOperation operation, IMethodSymbol targetMethod, string methodName) diff --git a/tests/Meziantou.Analyzer.Test/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer_AsyncContextTests.cs b/tests/Meziantou.Analyzer.Test/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer_AsyncContextTests.cs index a955c048..4ef39595 100644 --- a/tests/Meziantou.Analyzer.Test/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer_AsyncContextTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer_AsyncContextTests.cs @@ -2024,6 +2024,28 @@ public async Task A(SqliteConnection connection) .ValidateAsync(); } + [Fact] + [Trait("Issue", "https://github.com/meziantou/Meziantou.Analyzer/issues/1121")] + public async Task SqliteConnection_Open_NoDiagnostic() + { + await CreateProjectBuilder() + .WithTargetFramework(TargetFramework.Net8_0) + .AddNuGetReference("Microsoft.Data.Sqlite.Core", "8.0.0", "lib/net8.0/") + .WithSourceCode(""" + using System.Threading.Tasks; + using Microsoft.Data.Sqlite; + + class Test + { + public async Task A(SqliteConnection connection) + { + connection.Open(); + } + } + """) + .ValidateAsync(); + } + [Fact] [Trait("Issue", "https://github.com/meziantou/Meziantou.Analyzer/issues/1121")] public async Task SqliteCommand_ExecuteMethods_NoDiagnostic() @@ -2071,6 +2093,29 @@ public async Task A(SqliteConnection connection) .ValidateAsync(); } + [Fact] + [Trait("Issue", "https://github.com/meziantou/Meziantou.Analyzer/issues/1121")] + public async Task SqliteConnection_Open_OptionDisabled_Diagnostic() + { + await CreateProjectBuilder() + .WithTargetFramework(TargetFramework.Net8_0) + .AddNuGetReference("Microsoft.Data.Sqlite.Core", "8.0.0", "lib/net8.0/") + .AddAnalyzerConfiguration("MA0042.enable_sqlite_special_cases", "false") + .WithSourceCode(""" + using System.Threading.Tasks; + using Microsoft.Data.Sqlite; + + class Test + { + public async Task A(SqliteConnection connection) + { + [|connection.Open()|]; + } + } + """) + .ValidateAsync(); + } + [Fact] [Trait("Issue", "https://github.com/meziantou/Meziantou.Analyzer/issues/1121")] public async Task SqliteCommand_ExecuteMethods_OptionDisabled_Diagnostic() diff --git a/tests/Meziantou.Analyzer.Test/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer_NonAsyncContextTests.cs b/tests/Meziantou.Analyzer.Test/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer_NonAsyncContextTests.cs index cb0a30f3..fe66628b 100644 --- a/tests/Meziantou.Analyzer.Test/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer_NonAsyncContextTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/DoNotUseBlockingCallInAsyncContextAnalyzer_NonAsyncContextTests.cs @@ -161,6 +161,49 @@ private void A(SqliteCommand command) .ValidateAsync(); } + [Fact] + [Trait("Issue", "https://github.com/meziantou/Meziantou.Analyzer/issues/1121")] + public async Task PrivateNonAsync_SqliteConnection_Open_NoDiagnostic() + { + await CreateProjectBuilder() + .WithTargetFramework(TargetFramework.Net8_0) + .AddNuGetReference("Microsoft.Data.Sqlite.Core", "8.0.0", "lib/net8.0/") + .WithSourceCode(""" + using Microsoft.Data.Sqlite; + + class Test + { + private void A(SqliteConnection connection) + { + connection.Open(); + } + } + """) + .ValidateAsync(); + } + + [Fact] + [Trait("Issue", "https://github.com/meziantou/Meziantou.Analyzer/issues/1121")] + public async Task PrivateNonAsync_SqliteConnection_Open_OptionDisabled_Diagnostic() + { + await CreateProjectBuilder() + .WithTargetFramework(TargetFramework.Net8_0) + .AddNuGetReference("Microsoft.Data.Sqlite.Core", "8.0.0", "lib/net8.0/") + .AddAnalyzerConfiguration("MA0042.enable_sqlite_special_cases", "false") + .WithSourceCode(""" + using Microsoft.Data.Sqlite; + + class Test + { + private void A(SqliteConnection connection) + { + [|connection.Open()|]; + } + } + """) + .ValidateAsync(); + } + [Fact] [Trait("Issue", "https://github.com/meziantou/Meziantou.Analyzer/issues/1121")] public async Task PrivateNonAsync_SqliteCommand_ExecuteMethods_OptionDisabled_Diagnostic()