diff --git a/.editorconfig b/.editorconfig
index 1d0da3b9..05778cd0 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,4 +1,4 @@
[*.cs]
-# DAP000: Interceptors generated
-dotnet_diagnostic.DAP000.severity = suggestion
+# RS2008: Enable analyzer release tracking
+dotnet_diagnostic.RS2008.severity = none
diff --git a/Directory.Build.props b/Directory.Build.props
index 6cfe30d0..db609fb0 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -26,6 +26,7 @@
en-US
false
latest-Recommended
+ ($Features);strict
diff --git a/docs/docs.csproj b/docs/docs.csproj
index 33e77764..0c37a108 100644
--- a/docs/docs.csproj
+++ b/docs/docs.csproj
@@ -5,4 +5,7 @@
false
false
+
+
+
diff --git a/docs/rules/DAP003.md b/docs/rules/DAP003.md
new file mode 100644
index 00000000..70c75e63
--- /dev/null
+++ b/docs/rules/DAP003.md
@@ -0,0 +1,12 @@
+# DAP003
+
+Interceptors are a new feature, and require some project configuration.
+
+The exact shape of this has changed repeatedly, so this guidance is not complete yet.
+
+Things that might be involved, depending on what preview build SDK you're using:
+
+- `($Features);InterceptorsPreview`
+- `$(InterceptorsPreviewNamespaces);Dapper.AOT`
+
+This will be clarified for release.
\ No newline at end of file
diff --git a/docs/rules/DAP004.md b/docs/rules/DAP004.md
new file mode 100644
index 00000000..d00f9d1b
--- /dev/null
+++ b/docs/rules/DAP004.md
@@ -0,0 +1,10 @@
+# DAP004
+
+Dapper.AOT runs on interceptors, which requires at least C# 11. The language version is implied by
+your target .NET version, but can be changed manually - it is fine to use a different language
+version than the default for that target runtime, especially when going "higher". For example, you
+can use C# 12 on a project that targets .NET Framework (as long as you have up-to-date build tools).
+
+The easiest way to do this is via `latest`.
+
+For more information, see https://learn.microsoft.com/dotnet/csharp/language-reference/configure-language-version
\ No newline at end of file
diff --git a/docs/rules/DAP005.md b/docs/rules/DAP005.md
new file mode 100644
index 00000000..269f0af0
--- /dev/null
+++ b/docs/rules/DAP005.md
@@ -0,0 +1,22 @@
+# DAP005
+
+You're seeing this message because Dapper.AOT has found at least one place where it *could* help, and it
+isn't explicit whether you want it to do so.
+
+Dapper.AOT doesn't change any behaviours without your permission. You can *enable* (or disable) Dapper.AOT
+at any level by adding a `[DapperAot]` (or `[DapperAot(false)]`) attribute.
+
+To enable Dapper.AOT globally, add (usually to `AssemblyInfo.cs`, although it doesn't matter where):
+
+
+``` csharp
+[module: DapperAot]
+```
+
+Alternatively, if you only want it in a few places, use:
+
+``` csharp
+[module: DapperAot(false)]
+```
+
+and add `[DapperAot]` at more specific levels - types, individual methods, etc.
\ No newline at end of file
diff --git a/src/Dapper.AOT.Analyzers/AnalyzerReleases.Shipped.md b/src/Dapper.AOT.Analyzers/AnalyzerReleases.Shipped.md
deleted file mode 100644
index a898a733..00000000
--- a/src/Dapper.AOT.Analyzers/AnalyzerReleases.Shipped.md
+++ /dev/null
@@ -1,72 +0,0 @@
-; Shipped analyzer releases
-; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
-
-Rule ID | Category | Severity | Notes
---------|----------|----------|-------
-DAP000 | Library | Hidden | Diagnostics
-DAP001 | Library | Info | Diagnostics
-; DAP002 | Library | Info | Diagnostics
-DAP003 | Library | Warning | Diagnostics
-DAP004 | Library | Warning | Diagnostics
-DAP005 | Library | Info | Diagnostics
-DAP006 | Library | Warning | Diagnostics
-DAP007 | Library | Info | Diagnostics
-; DAP008 | Library | Info | Diagnostics
-DAP009 | Library | Info | Diagnostics
-; DAP010 | Library | Info | Diagnostics
-DAP011 | Library | Warning | Diagnostics
-DAP012 | Library | Warning | Diagnostics
-DAP013 | Library | Info | Diagnostics
-DAP014 | Library | Info | Diagnostics
-DAP015 | Library | Info | Diagnostics
-DAP016 | Library | Info | Diagnostics
-DAP017 | Library | Info | Diagnostics
-DAP018 | Sql | Warning | Diagnostics
-DAP019 | Sql | Error | Diagnostics
-DAP020 | Sql | Error | Diagnostics
-DAP021 | Sql | Warning | Diagnostics
-DAP022 | Sql | Warning | Diagnostics
-DAP023 | Sql | Warning | Diagnostics
-DAP024 | Sql | Warning | Diagnostics
-DAP025 | Sql | Warning | Diagnostics
-DAP026 | Sql | Error | Diagnostics
-DAP027 | Performance | Warning | Diagnostics
-DAP028 | Performance | Warning | Diagnostics
-DAP029 | Library | Info | Diagnostics
-DAP030 | Library | Error | Diagnostics
-DAP031 | Library | Error | Diagnostics
-DAP032 | Library | Error | Diagnostics
-DAP033 | Library | Warning | Diagnostics
-DAP034 | Library | Warning | Diagnostics
-DAP035 | Library | Error | Diagnostics
-DAP036 | Library | Error | Diagnostics
-DAP037 | Library | Error | Diagnostics
-DAP038 | Library | Warning | Diagnostics
-DAP100 | Library | Error | Diagnostics
-DAP101 | Library | Error | Diagnostics
-DAP102 | Library | Error | Diagnostics
-DAP200 | Sql | Warning | Diagnostics
-DAP201 | Sql | Error | Diagnostics
-DAP202 | Sql | Error | Diagnostics
-DAP203 | Sql | Error | Diagnostics
-DAP204 | Sql | Info | Diagnostics
-DAP205 | Sql | Warning | Diagnostics
-DAP206 | Sql | Error | Diagnostics
-DAP207 | Sql | Error | Diagnostics
-DAP208 | Sql | Error | Diagnostics
-DAP209 | Sql | Error | Diagnostics
-DAP210 | Sql | Error | Diagnostics
-DAP211 | Sql | Error | Diagnostics
-DAP212 | Sql | Warning | Diagnostics
-DAP213 | Sql | Warning | Diagnostics
-DAP214 | Sql | Error | Diagnostics
-DAP215 | Sql | Warning | Diagnostics
-DAP216 | Sql | Warning | Diagnostics
-DAP217 | Sql | Error | Diagnostics
-DAP218 | Sql | Error | Diagnostics
-DAP219 | Sql | Warning | Diagnostics
-DAP220 | Sql | Warning | Diagnostics
-DAP221 | Sql | Warning | Diagnostics
-DAP222 | Sql | Warning | Diagnostics
-DAP223 | Sql | Warning | Diagnostics
-DAP224 | Sql | Warning | Diagnostics
diff --git a/src/Dapper.AOT.Analyzers/AnalyzerReleases.Unshipped.md b/src/Dapper.AOT.Analyzers/AnalyzerReleases.Unshipped.md
deleted file mode 100644
index cc67b67f..00000000
--- a/src/Dapper.AOT.Analyzers/AnalyzerReleases.Unshipped.md
+++ /dev/null
@@ -1,7 +0,0 @@
-; Unshipped analyzer release
-; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
-
-### New Rules
-
-Rule ID | Category | Severity | Notes
---------|----------|----------|-------
diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.Diagnostics.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.Diagnostics.cs
index 16ba84dd..ad4fc38c 100644
--- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.Diagnostics.cs
+++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.Diagnostics.cs
@@ -6,149 +6,94 @@ partial class DapperInterceptorGenerator
{
internal sealed class Diagnostics : DiagnosticsBase
{
+
internal static readonly DiagnosticDescriptor
- InterceptorsGenerated = new("DAP000", "Interceptors generated",
- "Dapper.AOT handled {0} of {1} enabled call-sites using {2} interceptors, {3} commands and {4} readers", Category.Library, DiagnosticSeverity.Hidden, true),
- UnsupportedMethod = new("DAP001", "Unsupported method",
- "The Dapper method '{0}' is not currently supported by Dapper.AOT", Category.Library, DiagnosticSeverity.Info, true),
+ InterceptorsGenerated = LibraryHidden("DAP000", "Interceptors generated", "Dapper.AOT handled {0} of {1} enabled call-sites using {2} interceptors, {3} commands and {4} readers"),
+ UnsupportedMethod = LibraryInfo("DAP001", "Unsupported method", "The Dapper method '{0}' is not currently supported by Dapper.AOT"),
//UntypedResults = new("DAP002", "Untyped result types",
// "Dapper.AOT does not currently support untyped/dynamic results", Category.Library, DiagnosticSeverity.Info, true),
- InterceptorsNotEnabled = new("DAP003", "Interceptors not enabled",
- "Interceptors are an experimental feature, and requires that 'InterceptorsPreview' be added to the project file", Category.Library, DiagnosticSeverity.Warning, true),
- LanguageVersionTooLow = new("DAP004", "Language version too low",
- "Interceptors require at least C# version 11", Category.Library, DiagnosticSeverity.Warning, true),
- DapperAotNotEnabled = new("DAP005", "Dapper.AOT not enabled",
- "Candidate Dapper methods were detected, but none have Dapper.AOT enabled; [DapperAot] can be added at the method, type, module or assembly level (for example '[module:DapperAot]')", Category.Library, DiagnosticSeverity.Info, true),
- DapperLegacyTupleParameter = new("DAP006", "Dapper tuple-type parameter",
- "Dapper (original) does not work well with tuple-type parameters as name information is inaccessible", Category.Library, DiagnosticSeverity.Warning, true),
- UnexpectedCommandType = new("DAP007", "Unexpected command type",
- "The command type specified is not understood", Category.Library, DiagnosticSeverity.Info, true),
+ InterceptorsNotEnabled = LibraryWarning("DAP003", "Interceptors not enabled",
+ "Interceptors need to be enabled (see help-link)", true),
+ LanguageVersionTooLow = LibraryWarning("DAP004", "Language version too low", "Interceptors require at least C# version 11", true),
+ DapperAotNotEnabled = LibraryInfo("DAP005", "Dapper.AOT not enabled",
+ "Candidate Dapper methods were detected, but none have Dapper.AOT enabled; [DapperAot] can be added at the method, type, module or assembly level (for example '[module:DapperAot]')", true),
+ DapperLegacyTupleParameter = LibraryWarning("DAP006", "Dapper tuple-type parameter", "Dapper (original) does not work well with tuple-type parameters as name information is inaccessible"),
+ UnexpectedCommandType = LibraryInfo("DAP007", "Unexpected command type", "The command type specified is not understood"),
// space
- UnexpectedArgument = new("DAP009", "Unexpected parameter",
- "The parameter '{0}' is not understood", Category.Library, DiagnosticSeverity.Info, true),
+ UnexpectedArgument = LibraryInfo("DAP009", "Unexpected parameter", "The parameter '{0}' is not understood"),
// space
- DapperLegacyBindNameTupleResults = new("DAP011", "Named-tuple results",
- "Dapper (original) does not support tuple results with bind-by-name semantics", Category.Library, DiagnosticSeverity.Warning, true),
- DapperAotAddBindTupleByName = new("DAP012", "Add BindTupleByName",
- "Because of differences in how Dapper and Dapper.AOT can process tuple-types, please add '[BindTupleByName({true|false})]' to clarify your intent", Category.Library, DiagnosticSeverity.Warning, true),
- DapperAotTupleResults = new("DAP013", "Tuple-type results",
- "Tuple-type results are not currently supported", Category.Library, DiagnosticSeverity.Info, true),
- DapperAotTupleParameter = new("DAP014", "Tuple-type parameter",
- "Tuple-type parameters are not currently supported", Category.Library, DiagnosticSeverity.Info, true),
- UntypedParameter = new("DAP015", "Untyped parameter",
- "The parameter type could not be resolved", Category.Library, DiagnosticSeverity.Info, true),
- GenericTypeParameter = new("DAP016", "Generic type parameter",
- "Generic type parameters ({0}) are not currently supported", Category.Library, DiagnosticSeverity.Info, true),
- NonPublicType = new("DAP017", "Non-accessible type",
- "Type '{0}' is not accessible; {1} types are not currently supported", Category.Library, DiagnosticSeverity.Info, true),
- SqlParametersNotDetected = new("DAP018", "SQL parameters not detected",
- "Parameters are being supplied, but no parameters were detected in the command", Category.Sql, DiagnosticSeverity.Warning, true),
- NoParametersSupplied = new("DAP019", "No parameters supplied",
- "SQL parameters were detected, but no parameters are being supplied", Category.Sql, DiagnosticSeverity.Error, true, helpLinkUri: RulesRoot + "DAP019"),
- SqlParameterNotBound = new("DAP020", "SQL parameter not bound",
- "No member could be found for the SQL parameter '{0}' from type '{1}'", Category.Sql, DiagnosticSeverity.Error, true),
- DuplicateParameter = new("DAP021", "Duplicate parameter",
- "Members '{0}' and '{1}' both have the database name '{2}'; '{0}' will be ignored", Category.Sql, DiagnosticSeverity.Warning, true),
- DuplicateReturn = new("DAP022", "Duplicate return parameter",
- "Members '{0}' and '{1}' are both designated as return values; '{0}' will be ignored", Category.Sql, DiagnosticSeverity.Warning, true),
- DuplicateRowCount = new("DAP023", "Duplicate row-count member",
- "Members '{0}' and '{1}' are both marked [RowCount]", Category.Sql, DiagnosticSeverity.Warning, true),
- RowCountDbValue = new("DAP024", "Member is both row-count and mapped value",
- "Member '{0}' is marked both [RowCount] and [DbValue]; [DbValue] will be ignored", Category.Sql, DiagnosticSeverity.Warning, true),
- ExecuteCommandWithQuery = new("DAP025", "Execute command with query",
- "The command has a query that will be ignored", Category.Sql, DiagnosticSeverity.Warning, true),
- QueryCommandMissingQuery = new("DAP026", "Query/scalar command lacks query",
- "The command lacks a query", Category.Sql, DiagnosticSeverity.Error, true),
- UseSingleRowQuery = new("DAP027", "Use single-row query",
- "Use {0}() instead of Query(...).{1}()", Category.Performance, DiagnosticSeverity.Warning, true),
- UseQueryAsList = new("DAP028", "Use AsList instead of ToList",
- "Use Query(...).AsList() instead of Query(...).ToList()", Category.Performance, DiagnosticSeverity.Warning, true),
- MethodRowCountHintRedundant = new("DAP029", "Method-level row-count hint redundant",
- "The [EstimatedRowCount] will be ignored due to parameter member '{0}'", Category.Library, DiagnosticSeverity.Info, true),
- MethodRowCountHintInvalid = new("DAP030", "Method-level row-count hint invalid",
- "The [EstimatedRowCount] parameters are invalid; a positive integer must be supplied", Category.Library, DiagnosticSeverity.Error, true),
- MemberRowCountHintInvalid = new("DAP031", "Member-level row-count hint invalid",
- "The [EstimatedRowCount] parameters are invalid; no parameter should be supplied", Category.Library, DiagnosticSeverity.Error, true),
- MemberRowCountHintDuplicated = new("DAP032", "Member-level row-count hint duplicated",
- "Only a single member should be marked [EstimatedRowCount]", Category.Library, DiagnosticSeverity.Error, true),
- CommandPropertyNotFound = new("DAP033", "Command property not found",
- "Command property {0}.{1} was not found or was not valid; attribute will be ignored", Category.Library, DiagnosticSeverity.Warning, true),
- CommandPropertyReserved = new("DAP034", "Command property reserved",
- "Command property {1} is reserved for internal usage; attribute will be ignored", Category.Library, DiagnosticSeverity.Warning, true),
- TooManyDapperAotEnabledConstructors = new("DAP035", "Too many Dapper.AOT enabled constructors",
- "Only one constructor can be Dapper.AOT enabled per type '{0}'", Category.Library, DiagnosticSeverity.Error, true),
- TooManyStandardConstructors = new("DAP036", "Type has more than 1 constructor to choose for creating an instance",
- "Type has more than 1 constructor, please, either mark one constructor with [DapperAot] or reduce amount of constructors", Category.Library, DiagnosticSeverity.Error, true),
- UserTypeNoSettableMembersFound = new("DAP037", "No settable members exist for user type",
- "Type '{0}' has no settable members (fields or properties)", Category.Library, DiagnosticSeverity.Error, true),
- ValueTypeSingleFirstOrDefaultUsage = new("DAP038", "Value-type single row 'OrDefault' usage",
- "Type '{0}' is a value-type; it will not be trivial to identify missing rows from {1}", Category.Library, DiagnosticSeverity.Warning, true),
+ DapperLegacyBindNameTupleResults = LibraryWarning("DAP011", "Named-tuple results", "Dapper (original) does not support tuple results with bind-by-name semantics"),
+ DapperAotAddBindTupleByName = LibraryWarning("DAP012", "Add BindTupleByName", "Because of differences in how Dapper and Dapper.AOT can process tuple-types, please add '[BindTupleByName({true|false})]' to clarify your intent"),
+ DapperAotTupleResults = LibraryInfo("DAP013", "Tuple-type results", "Tuple-type results are not currently supported"),
+ DapperAotTupleParameter = LibraryInfo("DAP014", "Tuple-type parameter", "Tuple-type parameters are not currently supported"),
+ UntypedParameter = LibraryInfo("DAP015", "Untyped parameter", "The parameter type could not be resolved"),
+ GenericTypeParameter = LibraryInfo("DAP016", "Generic type parameter", "Generic type parameters ({0}) are not currently supported"),
+ NonPublicType = LibraryInfo("DAP017", "Non-accessible type", "Type '{0}' is not accessible; {1} types are not currently supported"),
+ SqlParametersNotDetected = SqlWarning("DAP018", "SQL parameters not detected", "Parameters are being supplied, but no parameters were detected in the command"),
+ NoParametersSupplied = SqlWarning("DAP019", "No parameters supplied", "SQL parameters were detected, but no parameters are being supplied", true),
+ SqlParameterNotBound = SqlWarning("DAP020", "SQL parameter not bound", "No member could be found for the SQL parameter '{0}' from type '{1}'"),
+ DuplicateParameter = LibraryWarning("DAP021", "Duplicate parameter", "Members '{0}' and '{1}' both have the database name '{2}'; '{0}' will be ignored"),
+ DuplicateReturn = LibraryWarning("DAP022", "Duplicate return parameter", "Members '{0}' and '{1}' are both designated as return values; '{0}' will be ignored"),
+ DuplicateRowCount = LibraryWarning("DAP023", "Duplicate row-count member",
+ "Members '{0}' and '{1}' are both marked [RowCount]"),
+ RowCountDbValue = LibraryWarning("DAP024", "Member is both row-count and mapped value",
+ "Member '{0}' is marked both [RowCount] and [DbValue]; [DbValue] will be ignored"),
+ ExecuteCommandWithQuery = SqlWarning("DAP025", "Execute command with query", "The command has a query that will be ignored"),
+ QueryCommandMissingQuery = SqlError("DAP026", "Query/scalar command lacks query", "The command lacks a query"),
+ UseSingleRowQuery = PerformanceWarning("DAP027", "Use single-row query", "Use {0}() instead of Query(...).{1}()"),
+ UseQueryAsList = PerformanceWarning("DAP028", "Use AsList instead of ToList", "Use Query(...).AsList() instead of Query(...).ToList()"),
+ MethodRowCountHintRedundant = LibraryInfo("DAP029", "Method-level row-count hint redundant", "The [EstimatedRowCount] will be ignored due to parameter member '{0}'"),
+ MethodRowCountHintInvalid = LibraryError("DAP030", "Method-level row-count hint invalid",
+ "The [EstimatedRowCount] parameters are invalid; a positive integer must be supplied"),
+ MemberRowCountHintInvalid = LibraryError("DAP031", "Member-level row-count hint invalid",
+ "The [EstimatedRowCount] parameters are invalid; no parameter should be supplied"),
+ MemberRowCountHintDuplicated = LibraryError("DAP032", "Member-level row-count hint duplicated",
+ "Only a single member should be marked [EstimatedRowCount]"),
+ CommandPropertyNotFound = LibraryWarning("DAP033", "Command property not found",
+ "Command property {0}.{1} was not found or was not valid; attribute will be ignored"),
+ CommandPropertyReserved = LibraryWarning("DAP034", "Command property reserved",
+ "Command property {1} is reserved for internal usage; attribute will be ignored"),
+ TooManyDapperAotEnabledConstructors = LibraryError("DAP035", "Too many Dapper.AOT enabled constructors",
+ "Only one constructor can be Dapper.AOT enabled per type '{0}'"),
+ TooManyStandardConstructors = LibraryError("DAP036", "Type has more than 1 constructor to choose for creating an instance",
+ "Type has more than 1 constructor, please, either mark one constructor with [DapperAot] or reduce amount of constructors"),
+ UserTypeNoSettableMembersFound = LibraryError("DAP037", "No settable members exist for user type",
+ "Type '{0}' has no settable members (fields or properties)"),
+ ValueTypeSingleFirstOrDefaultUsage = LibraryWarning("DAP038", "Value-type single row 'OrDefault' usage",
+ "Type '{0}' is a value-type; it will not be trivial to identify missing rows from {1}"),
// SQL parse specific
- SqlError = new("DAP200", "SQL error",
- "SQL error: {0}", Category.Sql, DiagnosticSeverity.Warning, true),
- MultipleBatches = new("DAP201", "Multiple batches",
- "Multiple batches are not permitted (L{0} C{1})", Category.Sql, DiagnosticSeverity.Error, true),
- DuplicateVariableDeclaration = new("DAP202", "Duplicate variable declaration",
- "The variable {0} is declared multiple times (L{1} C{2})", Category.Sql, DiagnosticSeverity.Error, true),
- GlobalIdentity = new("DAP203", "Do not use @@identity",
- "@@identity should not be used; prefer SCOPE_IDENTITY() or OUTPUT INSERTED.yourid (L{0} C{1})", Category.Sql, DiagnosticSeverity.Error, true),
- SelectScopeIdentity = new("DAP204", "Prefer OUTPUT over SELECT",
- "Consider using OUTPUT INSERTED.yourid in the INSERT instead of SELECT SCOPE_IDENTITY() (L{0} C{1})", Category.Sql, DiagnosticSeverity.Info, true),
- NullLiteralComparison = new("DAP205", "Null comparison",
- "Literal null used in comparison; 'is null' or 'is not null' should be preferred (L{0} C{1})", Category.Sql, DiagnosticSeverity.Warning, true),
- ParseError = new("DAP206", "SQL parse error",
- "{0} (#{1} L{2} C{3})", Category.Sql, DiagnosticSeverity.Error, true),
- ScalarVariableUsedAsTable = new("DAP207", "Scalar used like table",
- "Scalar variable {0} is used like a table (L{1} C{2})", Category.Sql, DiagnosticSeverity.Error, true),
- TableVariableUsedAsScalar = new("DAP208", "Table used like scalar",
- "Table-variable {0} is used like a scalar (L{1} C{2})", Category.Sql, DiagnosticSeverity.Error, true),
- TableVariableAccessedBeforePopulate = new("DAP209", "Table used before populate",
- "Table-variable {0} is accessed before it populated (L{1} C{2})", Category.Sql, DiagnosticSeverity.Error, true),
- VariableAccessedBeforeAssignment = new("DAP210", "Variable used before assigned",
- "Variable {0} is accessed before it is assigned a value (L{1} C{2})", Category.Sql, DiagnosticSeverity.Error, true),
- VariableAccessedBeforeDeclaration = new("DAP211", "Variable used before declared",
- "Variable {0} is accessed before it is declared (L{1} C{2})", Category.Sql, DiagnosticSeverity.Error, true),
- ExecVariable = new("DAP212", "EXEC with composed SQL",
- "EXEC with composed SQL may be susceptible to SQL injection; consider EXEC sp_executesql, taking care to fully parameterize the composed query (L{0} C{1})", Category.Sql, DiagnosticSeverity.Warning, true),
- VariableValueNotConsumed = new("DAP213", "Variable used before declared",
- "Variable {0} has a value that is not consumed (L{1} C{2})", Category.Sql, DiagnosticSeverity.Warning, true),
- VariableNotDeclared = new("DAP214", "Variable not declared",
- "Variable {0} is not declared and no corresponding parameter exists (L{1} C{2})", Category.Sql, DiagnosticSeverity.Error, true),
- TableVariableOutputParameter = new("DAP215", "Variable used before declared",
- "Table variable {0} cannot be used as an output parameter (L{1} C{2})", Category.Sql, DiagnosticSeverity.Warning, true),
- InsertColumnsNotSpecified = new("DAP216", "INSERT without target columns",
- "INSERT should explicitly specify target columns (L{0} C{1})", Category.Sql, DiagnosticSeverity.Warning, true),
- InsertColumnsMismatch = new("DAP217", "INSERT with mismatched columns",
- "The INSERT values do not match the target columns (L{0} C{1})", Category.Sql, DiagnosticSeverity.Error, true),
- InsertColumnsUnbalanced = new("DAP218", "INSERT with unbalanced rows",
- "The INSERT rows have different widths (L{0} C{1})", Category.Sql, DiagnosticSeverity.Error, true),
- SelectStar = new("DAP219", "SELECT with wildcard columns",
- "SELECT columns should be specified explicitly (L{0} C{1})", Category.Sql, DiagnosticSeverity.Warning, true),
- SelectEmptyColumnName = new("DAP220", "SELECT with missing column name",
- "SELECT column name is missing: {0} (L{1} C{2})", Category.Sql, DiagnosticSeverity.Warning, true),
- SelectDuplicateColumnName = new("DAP221", "SELECT with duplicate column name",
- "SELECT column name is duplicated: '{0}' (L{1} C{2})", Category.Sql, DiagnosticSeverity.Warning, true),
- SelectAssignAndRead = new("DAP222", "SELECT with assignment and reads",
- "SELECT statement assigns variable and performs reads (L{0} C{1})", Category.Sql, DiagnosticSeverity.Warning, true),
- DeleteWithoutWhere = new("DAP223", "DELETE without WHERE",
- "DELETE statement lacks WHERE clause (L{0} C{1})", Category.Sql, DiagnosticSeverity.Warning, true),
- UpdateWithoutWhere = new("DAP224", "UPDATE without WHERE",
- "UPDATE statement lacks WHERE clause (L{0} C{1})", Category.Sql, DiagnosticSeverity.Warning, true),
-
- FromMultiTableMissingAlias = new("DAP225", "Multi-element FROM missing alias",
- "FROM expressions with multiple elements should use aliases (L{0} C{1})", Category.Sql, DiagnosticSeverity.Warning, true),
- FromMultiTableUnqualifiedColumn = new("DAP226", "Multi-element FROM with unqualified column",
- "FROM expressions with multiple elements should qualify all columns; it is unclear where '{0}' is located (L{1} C{2})", Category.Sql, DiagnosticSeverity.Warning, true),
- NonIntegerTop = new("DAP227", "Non-integer TOP",
- "TOP literals should be integers (L{0} C{1})", Category.Sql, DiagnosticSeverity.Error, true),
- NonPositiveTop = new("DAP228", "Non-positive TOP",
- "TOP literals should be positive (L{0} C{1})", Category.Sql, DiagnosticSeverity.Error, true),
- SelectFirstTopError = new("DAP229", "SELECT for First* with invalid TOP",
- "SELECT for First* should use TOP 1 (L{0} C{1})", Category.Sql, DiagnosticSeverity.Warning, true),
- SelectSingleTopError = new("DAP230", "SELECT for Single* with invalid TOP",
- "SELECT for Single* should use TOP 2; if you do not need to test over-read, use First* (L{0} C{1})", Category.Sql, DiagnosticSeverity.Warning, true),
- SelectSingleRowWithoutWhere = new("DAP231", "SELECT for single row without WHERE",
- "SELECT for single row without WHERE or (TOP and ORDER BY) (L{0} C{1})", Category.Sql, DiagnosticSeverity.Warning, true);
+ GeneralSqlError = SqlWarning("DAP200", "SQL error", "SQL error: {0}"),
+ MultipleBatches = SqlError("DAP201", "Multiple batches", "Multiple batches are not permitted (L{0} C{1})"),
+ DuplicateVariableDeclaration = SqlError("DAP202", "Duplicate variable declaration", "The variable {0} is declared multiple times (L{1} C{2})"),
+ GlobalIdentity = SqlError("DAP203", "Do not use @@identity", "@@identity should not be used; prefer SCOPE_IDENTITY() or OUTPUT INSERTED.yourid (L{0} C{1})"),
+ SelectScopeIdentity = SqlInfo("DAP204", "Prefer OUTPUT over SELECT", "Consider using OUTPUT INSERTED.yourid in the INSERT instead of SELECT SCOPE_IDENTITY() (L{0} C{1})"),
+ NullLiteralComparison = SqlWarning("DAP205", "Null comparison", "Literal null used in comparison; 'is null' or 'is not null' should be preferred (L{0} C{1})"),
+ ParseError = SqlError("DAP206", "SQL parse error", "{0} (#{1} L{2} C{3})"),
+ ScalarVariableUsedAsTable = SqlError("DAP207", "Scalar used like table", "Scalar variable {0} is used like a table (L{1} C{2})"),
+ TableVariableUsedAsScalar = SqlError("DAP208", "Table used like scalar", "Table-variable {0} is used like a scalar (L{1} C{2})"),
+ TableVariableAccessedBeforePopulate = SqlError("DAP209", "Table used before populate", "Table-variable {0} is accessed before it populated (L{1} C{2})"),
+ VariableAccessedBeforeAssignment = SqlError("DAP210", "Variable used before assigned", "Variable {0} is accessed before it is assigned a value (L{1} C{2})"),
+ VariableAccessedBeforeDeclaration = SqlError("DAP211", "Variable used before declared", "Variable {0} is accessed before it is declared (L{1} C{2})"),
+ ExecVariable = SqlWarning("DAP212", "EXEC with composed SQL", "EXEC with composed SQL may be susceptible to SQL injection; consider EXEC sp_executesql, taking care to fully parameterize the composed query (L{0} C{1})"),
+ VariableValueNotConsumed = SqlWarning("DAP213", "Variable used before declared", "Variable {0} has a value that is not consumed (L{1} C{2})"),
+ VariableNotDeclared = SqlError("DAP214", "Variable not declared", "Variable {0} is not declared and no corresponding parameter exists (L{1} C{2})"),
+ TableVariableOutputParameter = SqlWarning("DAP215", "Variable used before declared", "Table variable {0} cannot be used as an output parameter (L{1} C{2})"),
+ InsertColumnsNotSpecified = SqlWarning("DAP216", "INSERT without target columns", "INSERT should explicitly specify target columns (L{0} C{1})"),
+ InsertColumnsMismatch = SqlError("DAP217", "INSERT with mismatched columns", "The INSERT values do not match the target columns (L{0} C{1})"),
+ InsertColumnsUnbalanced = SqlError("DAP218", "INSERT with unbalanced rows", "The INSERT rows have different widths (L{0} C{1})"),
+ SelectStar = SqlWarning("DAP219", "SELECT with wildcard columns", "SELECT columns should be specified explicitly (L{0} C{1})"),
+ SelectEmptyColumnName = SqlWarning("DAP220", "SELECT with missing column name", "SELECT column name is missing: {0} (L{1} C{2})"),
+ SelectDuplicateColumnName = SqlWarning("DAP221", "SELECT with duplicate column name", "SELECT column name is duplicated: '{0}' (L{1} C{2})"),
+ SelectAssignAndRead = SqlWarning("DAP222", "SELECT with assignment and reads", "SELECT statement assigns variable and performs reads (L{0} C{1})"),
+ DeleteWithoutWhere = SqlWarning("DAP223", "DELETE without WHERE", "DELETE statement lacks WHERE clause (L{0} C{1})"),
+ UpdateWithoutWhere = SqlWarning("DAP224", "UPDATE without WHERE", "UPDATE statement lacks WHERE clause (L{0} C{1})"),
+ FromMultiTableMissingAlias = SqlWarning("DAP225", "Multi-element FROM missing alias", "FROM expressions with multiple elements should use aliases (L{0} C{1})"),
+ FromMultiTableUnqualifiedColumn = SqlWarning("DAP226", "Multi-element FROM with unqualified column", "FROM expressions with multiple elements should qualify all columns; it is unclear where '{0}' is located (L{1} C{2})"),
+ NonIntegerTop = SqlError("DAP227", "Non-integer TOP", "TOP literals should be integers (L{0} C{1})"),
+ NonPositiveTop = SqlError("DAP228", "Non-positive TOP", "TOP literals should be positive (L{0} C{1})"),
+ SelectFirstTopError = SqlWarning("DAP229", "SELECT for First* with invalid TOP", "SELECT for First* should use TOP 1 (L{0} C{1})"),
+ SelectSingleTopError = SqlWarning("DAP230", "SELECT for Single* with invalid TOP", "SELECT for Single* should use TOP 2; if you do not need to test over-read, use First* (L{0} C{1})"),
+ SelectSingleRowWithoutWhere = SqlWarning("DAP231", "SELECT for single row without WHERE", "SELECT for single row without WHERE or (TOP and ORDER BY) (L{0} C{1})");
}
}
diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs
index f05f0372..4f6b2f58 100644
--- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs
+++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs
@@ -481,7 +481,7 @@ select var.Name.StartsWith("@") ? var.Name.Substring(1) : var.Name
}
catch (Exception ex)
{
- Diagnostics.Add(ref diagnostics, Diagnostic.Create(Diagnostics.SqlError, loc, ex.Message));
+ Diagnostics.Add(ref diagnostics, Diagnostic.Create(Diagnostics.GeneralSqlError, loc, ex.Message));
goto default; // some internal failure
}
break;
diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/Diagnostics.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/Diagnostics.cs
index 0b413ddf..eb245be8 100644
--- a/src/Dapper.AOT.Analyzers/CodeAnalysis/Diagnostics.cs
+++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/Diagnostics.cs
@@ -11,6 +11,30 @@ internal abstract class DiagnosticsBase
{
protected const string DocsRoot = "https://aot.dapperlib.dev/", RulesRoot = DocsRoot + "rules/";
+ private static DiagnosticDescriptor Create(string id, string title, string messageFormat, string? category, DiagnosticSeverity severity, bool docs) =>
+ new(id, title,
+ messageFormat, category, severity, true, helpLinkUri: docs ? (RulesRoot + id) : null);
+
+ protected static DiagnosticDescriptor LibraryWarning(string id, string title, string messageFormat, bool docs = false) => Create(id, title, messageFormat, Category.Library, DiagnosticSeverity.Warning, docs);
+
+ protected static DiagnosticDescriptor LibraryError(string id, string title, string messageFormat, bool docs = false) => Create(id, title, messageFormat, Category.Library, DiagnosticSeverity.Error, docs);
+
+ protected static DiagnosticDescriptor LibraryHidden(string id, string title, string messageFormat, bool docs = false) => Create(id, title, messageFormat, Category.Library, DiagnosticSeverity.Hidden, docs);
+
+ protected static DiagnosticDescriptor LibraryInfo(string id, string title, string messageFormat, bool docs = false) => Create(id, title, messageFormat, Category.Library, DiagnosticSeverity.Info, docs);
+
+ protected static DiagnosticDescriptor SqlWarning(string id, string title, string messageFormat, bool docs = false) => Create(id, title, messageFormat, Category.Sql, DiagnosticSeverity.Warning, docs);
+
+ protected static DiagnosticDescriptor SqlError(string id, string title, string messageFormat, bool docs = false) => Create(id, title, messageFormat, Category.Sql, DiagnosticSeverity.Error, docs);
+
+ protected static DiagnosticDescriptor SqlInfo(string id, string title, string messageFormat, bool docs = false) => Create(id, title, messageFormat, Category.Sql, DiagnosticSeverity.Info, docs);
+
+ protected static DiagnosticDescriptor PerformanceWarning(string id, string title, string messageFormat, bool docs = false) => Create(id, title, messageFormat, Category.Performance, DiagnosticSeverity.Warning, docs);
+
+ protected static DiagnosticDescriptor PerformanceError(string id, string title, string messageFormat, bool docs = false) => Create(id, title, messageFormat, Category.Performance, DiagnosticSeverity.Error, docs);
+
+ protected static DiagnosticDescriptor PerformanceInfo(string id, string title, string messageFormat, bool docs = false) => Create(id, title, messageFormat, Category.Performance, DiagnosticSeverity.Info, docs);
+
private static ImmutableDictionary? _idsToFieldNames;
public static bool TryGetFieldName(string id, out string field)
{
@@ -44,7 +68,7 @@ public static readonly ImmutableArray All
}
- protected static class Category
+ private static class Category
{
public const string Library = nameof(Library);
public const string Sql = nameof(Sql);
diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/TypeAccessorInterceptorGenerator.Diagnostics.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/TypeAccessorInterceptorGenerator.Diagnostics.cs
index 5e4c35ec..550b8483 100644
--- a/src/Dapper.AOT.Analyzers/CodeAnalysis/TypeAccessorInterceptorGenerator.Diagnostics.cs
+++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/TypeAccessorInterceptorGenerator.Diagnostics.cs
@@ -8,12 +8,12 @@ internal sealed class Diagnostics : DiagnosticsBase
{
internal static readonly DiagnosticDescriptor
// TypeAccessor
- TypeAccessorCollectionTypeNotAllowed = new("DAP100", "TypeAccessors does not allow collection types",
- "TypeAccessors does not allow collection types", Category.Library, DiagnosticSeverity.Error, true),
- TypeAccessorPrimitiveTypeNotAllowed = new("DAP101", "TypeAccessors does not allow primitive types",
- "TypeAccessors does not allow primitive types", Category.Library, DiagnosticSeverity.Error, true),
- TypeAccessorMembersNotParsed = new("DAP102", "TypeAccessor members can not be parsed",
- "At least one gettable and settable member must be defined for type '{0}'", Category.Library, DiagnosticSeverity.Error, true);
+ TypeAccessorCollectionTypeNotAllowed = LibraryError("DAP100", "TypeAccessors does not allow collection types",
+ "TypeAccessors does not allow collection types"),
+ TypeAccessorPrimitiveTypeNotAllowed = LibraryError("DAP101", "TypeAccessors does not allow primitive types",
+ "TypeAccessors does not allow primitive types"),
+ TypeAccessorMembersNotParsed = LibraryError("DAP102", "TypeAccessor members can not be parsed",
+ "At least one gettable and settable member must be defined for type '{0}'");
}
}
diff --git a/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj b/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj
index 2f3a5a89..3341a58d 100644
--- a/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj
+++ b/src/Dapper.AOT.Analyzers/Dapper.AOT.Analyzers.csproj
@@ -10,6 +10,8 @@
true
true
true
+
diff --git a/src/Dapper.AOT.Analyzers/Internal/DiagnosticTSqlProcessor.cs b/src/Dapper.AOT.Analyzers/Internal/DiagnosticTSqlProcessor.cs
index 32ebbb27..c63f8da7 100644
--- a/src/Dapper.AOT.Analyzers/Internal/DiagnosticTSqlProcessor.cs
+++ b/src/Dapper.AOT.Analyzers/Internal/DiagnosticTSqlProcessor.cs
@@ -73,7 +73,7 @@ private void AddDiagnostic(DiagnosticDescriptor diagnostic, in Location location
protected override void OnError(string error, in Location location)
{
Debug.Fail("unhandled error: " + error);
- AddDiagnostic(DapperInterceptorGenerator.Diagnostics.SqlError, location, error, location.Line, location.Column);
+ AddDiagnostic(DapperInterceptorGenerator.Diagnostics.GeneralSqlError, location, error, location.Line, location.Column);
}
protected override void OnAdditionalBatch(Location location)
diff --git a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj
index dad11f4b..10beaa55 100644
--- a/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj
+++ b/test/Dapper.AOT.Test/Dapper.AOT.Test.csproj
@@ -1,6 +1,6 @@
- net6.0;net7.0;net8.0;net48
+ net6.0;net48
$(NoWarn);IDE0042;CS8002
Dapper.AOT.Test
$(DefineConstants);DAPPERAOT_INTERNAL
diff --git a/test/Dapper.AOT.Test/Helpers/AnalyzerAndCodeFixVerifier.cs b/test/Dapper.AOT.Test/Helpers/AnalyzerAndCodeFixVerifier.cs
deleted file mode 100644
index 3fce5aee..00000000
--- a/test/Dapper.AOT.Test/Helpers/AnalyzerAndCodeFixVerifier.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using Microsoft.CodeAnalysis.CodeFixes;
-using Microsoft.CodeAnalysis.CSharp.Testing;
-using Microsoft.CodeAnalysis.Diagnostics;
-using Microsoft.CodeAnalysis.Testing;
-using Microsoft.CodeAnalysis.Testing.Verifiers;
-using System.Data.SqlClient;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-// credit: https://www.thinktecture.com/en/net/roslyn-source-generators-analyzers-code-fixes-testing/
-
-namespace Dapper.AOT.Test;
-
-public static class AnalyzerAndCodeFixVerifier
- where TAnalyzer : DiagnosticAnalyzer, new()
- where TCodeFix : CodeFixProvider, new()
-{
- public static DiagnosticResult Diagnostic(string diagnosticId)
- {
- return CSharpCodeFixVerifier
- .Diagnostic(diagnosticId);
- }
-
- public static async Task VerifyCodeFixAsync(
- string source,
- string fixedSource,
- params DiagnosticResult[] expected)
- {
- var test = new CodeFixTest(source, fixedSource, expected);
- await test.RunAsync(CancellationToken.None);
- }
-
- private class CodeFixTest : CSharpCodeFixTest
- {
- public CodeFixTest(
- string source,
- string fixedSource,
- params DiagnosticResult[] expected)
- {
- TestCode = source;
- FixedCode = fixedSource;
- ExpectedDiagnostics.AddRange(expected);
-#if NETFRAMEWORK
- ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net472.Default;
-#else
- ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
-#endif
-
- TestState.AdditionalReferences.Add(typeof(SqlMapper).Assembly);
- TestState.AdditionalReferences.Add(typeof(DapperAotAttribute).Assembly);
- }
- }
-}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test/Helpers/AnalyzerVerifier.cs b/test/Dapper.AOT.Test/Helpers/AnalyzerVerifier.cs
deleted file mode 100644
index 8c999b4a..00000000
--- a/test/Dapper.AOT.Test/Helpers/AnalyzerVerifier.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using Microsoft.CodeAnalysis.CSharp.Testing;
-using Microsoft.CodeAnalysis.Diagnostics;
-using Microsoft.CodeAnalysis.Testing;
-using Microsoft.CodeAnalysis.Testing.Verifiers;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-// credit: https://www.thinktecture.com/en/net/roslyn-source-generators-analyzers-code-fixes-testing/
-
-namespace Dapper.AOT.Test;
-
-public static class AnalyzerVerifier
- where TAnalyzer : DiagnosticAnalyzer, new()
-{
- public static DiagnosticResult Diagnostic(string diagnosticId)
- {
- return CSharpAnalyzerVerifier.Diagnostic(diagnosticId);
- }
-
- public static async Task VerifyAnalyzerAsync(
- string source,
- params DiagnosticResult[] expected)
- {
- var test = new AnalyzerTest(source, expected);
- await test.RunAsync(CancellationToken.None);
- }
-
- private class AnalyzerTest : CSharpAnalyzerTest
- {
- public AnalyzerTest(
- string source,
- params DiagnosticResult[] expected)
- {
- TestCode = source;
- ExpectedDiagnostics.AddRange(expected);
-#if NETFRAMEWORK
- ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net472.Default;
-#else
- ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
-#endif
-
- TestState.AdditionalReferences.Add(typeof(SqlMapper).Assembly);
- TestState.AdditionalReferences.Add(typeof(DapperAotAttribute).Assembly);
- }
- }
-}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test/Interceptors/SqlDetection.output.netfx.txt b/test/Dapper.AOT.Test/Interceptors/SqlDetection.output.netfx.txt
index 457a6072..5856fae6 100644
--- a/test/Dapper.AOT.Test/Interceptors/SqlDetection.output.netfx.txt
+++ b/test/Dapper.AOT.Test/Interceptors/SqlDetection.output.netfx.txt
@@ -3,7 +3,7 @@ Generator produced 4 diagnostics:
Hidden DAP000 L1 C1
Dapper.AOT handled 7 of 7 enabled call-sites using 5 interceptors, 2 commands and 0 readers
-Error DAP019 Interceptors/SqlDetection.input.cs L20 C20
+Warning DAP019 Interceptors/SqlDetection.input.cs L20 C20
SQL parameters were detected, but no parameters are being supplied
Warning DAP018 Interceptors/SqlDetection.input.cs L23 C20
diff --git a/test/Dapper.AOT.Test/Interceptors/SqlDetection.output.txt b/test/Dapper.AOT.Test/Interceptors/SqlDetection.output.txt
index b9fe470f..c2092697 100644
--- a/test/Dapper.AOT.Test/Interceptors/SqlDetection.output.txt
+++ b/test/Dapper.AOT.Test/Interceptors/SqlDetection.output.txt
@@ -3,7 +3,7 @@ Generator produced 4 diagnostics:
Hidden DAP000 L1 C1
Dapper.AOT handled 7 of 7 enabled call-sites using 5 interceptors, 2 commands and 0 readers
-Error DAP019 Interceptors/SqlDetection.input.cs L20 C20
+Warning DAP019 Interceptors/SqlDetection.input.cs L20 C20
SQL parameters were detected, but no parameters are being supplied
Warning DAP018 Interceptors/SqlDetection.input.cs L23 C20
diff --git a/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.netfx.txt b/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.netfx.txt
index 3b274d01..ffff23f7 100644
--- a/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.netfx.txt
+++ b/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.netfx.txt
@@ -141,5 +141,5 @@ SELECT for single row without WHERE or (TOP and ORDER BY) (L1 C1)
Warning DAP231 Interceptors/TsqlTips.input.cs L163 C43
SELECT for single row without WHERE or (TOP and ORDER BY) (L1 C1)
-Error DAP019 Interceptors/TsqlTips.input.cs L173 C17
+Warning DAP019 Interceptors/TsqlTips.input.cs L173 C17
SQL parameters were detected, but no parameters are being supplied
diff --git a/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.txt b/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.txt
index 3b274d01..ffff23f7 100644
--- a/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.txt
+++ b/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.txt
@@ -141,5 +141,5 @@ SELECT for single row without WHERE or (TOP and ORDER BY) (L1 C1)
Warning DAP231 Interceptors/TsqlTips.input.cs L163 C43
SELECT for single row without WHERE or (TOP and ORDER BY) (L1 C1)
-Error DAP019 Interceptors/TsqlTips.input.cs L173 C17
+Warning DAP019 Interceptors/TsqlTips.input.cs L173 C17
SQL parameters were detected, but no parameters are being supplied
diff --git a/test/Dapper.AOT.Test/TestCommon/GeneratorWrapper.cs b/test/Dapper.AOT.Test/TestCommon/GeneratorWrapper.cs
index ac521d07..a450405d 100644
--- a/test/Dapper.AOT.Test/TestCommon/GeneratorWrapper.cs
+++ b/test/Dapper.AOT.Test/TestCommon/GeneratorWrapper.cs
@@ -1,17 +1,14 @@
using Dapper.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
-using System;
using System.Collections.Concurrent;
using System.Collections.Immutable;
-using System.Diagnostics;
-using System.Linq;
using static Dapper.CodeAnalysis.DapperInterceptorGenerator;
namespace Dapper.AOT.Test.TestCommon;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
-internal sealed class WrappedDapperInterceptorAnalyzer : DiagnosticAnalyzer
+public sealed class WrappedDapperInterceptorAnalyzer : DiagnosticAnalyzer
{
private readonly DapperInterceptorGenerator inner = new();
diff --git a/test/Dapper.AOT.Test/Verifiers/DAP004.cs b/test/Dapper.AOT.Test/Verifiers/DAP004.cs
new file mode 100644
index 00000000..2ea0046b
--- /dev/null
+++ b/test/Dapper.AOT.Test/Verifiers/DAP004.cs
@@ -0,0 +1,42 @@
+using Dapper.AOT.Test.TestCommon;
+using Dapper.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Dapper.AOT.Test.Verifiers;
+
+public class DAP004 : Verifier
+{
+ [Fact]
+ public Task LanguageTooLow() => VerifyAsync("""
+using Dapper;
+using System.Data.Common;
+
+[DapperAot(true)]
+class SomeCode
+{
+ public void Foo(DbConnection conn) => conn.Execute("some sql");
+}
+""", new[]
+ {
+ InterceptorsEnabled, WithLanguageVersion(LanguageVersion.CSharp10)
+ }, Diagnostic(DapperInterceptorGenerator.Diagnostics.LanguageVersionTooLow));
+
+ [Fact]
+ public Task FineIfInactive() => VerifyAsync("""
+using Dapper;
+using System.Data.Common;
+
+[DapperAot(false)]
+class SomeCode
+{
+ public void Foo(DbConnection conn) => conn.Execute("some sql");
+}
+""", new[]
+ {
+ InterceptorsEnabled, WithLanguageVersion(LanguageVersion.CSharp10)
+ });
+
+ // we don't need to test higher-level language versions: *every other test does that!*
+}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test/Verifiers/DAP005.cs b/test/Dapper.AOT.Test/Verifiers/DAP005.cs
index 25e5b3d6..e76d54ab 100644
--- a/test/Dapper.AOT.Test/Verifiers/DAP005.cs
+++ b/test/Dapper.AOT.Test/Verifiers/DAP005.cs
@@ -1,17 +1,14 @@
-using Dapper.CodeAnalysis;
+using Dapper.AOT.Test.TestCommon;
+using Dapper.CodeAnalysis;
using System.Threading.Tasks;
using Xunit;
-// using Verifier = Dapper.AOT.Test.AnalyzerAndCodeFixVerifier;
-using Verifier = Dapper.AOT.Test.AnalyzerVerifier;
namespace Dapper.AOT.Test.Verifiers;
-public class DAP005
+public class DAP005 : Verifier
{
[Fact]
- public async Task ShouldDetectNotEnabledWhenUsed()
- {
- var input = """
+ public Task ShouldFlagWhenUsedAndNotAttrib() => VerifyAsync("""
using Dapper;
using System.Data.Common;
@@ -19,9 +16,40 @@ class SomeCode
{
public void Foo(DbConnection conn) => conn.Execute("some sql");
}
-""";
+""", Diagnostic(DapperInterceptorGenerator.Diagnostics.DapperAotNotEnabled));
- var expectedError = Verifier.Diagnostic(DapperInterceptorGenerator.Diagnostics.DapperAotNotEnabled.Id);
- await Verifier.VerifyAnalyzerAsync(input, expectedError);
- }
+ [Fact]
+ public Task ShouldNotFlagWhenNotUsedAndNoAttrib() => VerifyAsync("""
+using Dapper;
+using System.Data.Common;
+
+class SomeCode
+{
+ public void Foo(DbConnection conn) {}
+}
+""");
+
+ [Fact]
+ public Task ShouldNotFlagWhenUsedAndOptedOut() => VerifyAsync("""
+using Dapper;
+using System.Data.Common;
+
+[DapperAot(false)]
+class SomeCode
+{
+ public void Foo(DbConnection conn) => conn.Execute("some sql");
+}
+""");
+
+ [Fact]
+ public Task ShouldNotFlagWhenUsedAndOptedIn() => VerifyAsync("""
+using Dapper;
+using System.Data.Common;
+
+[DapperAot(true)]
+class SomeCode
+{
+ public void Foo(DbConnection conn) => conn.Execute("some sql");
+}
+""", InterceptorsGenerated(1, 1, 1, 0, 0));
}
\ No newline at end of file
diff --git a/test/Dapper.AOT.Test/Verifiers/Verifier.cs b/test/Dapper.AOT.Test/Verifiers/Verifier.cs
new file mode 100644
index 00000000..ce9023b0
--- /dev/null
+++ b/test/Dapper.AOT.Test/Verifiers/Verifier.cs
@@ -0,0 +1,123 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.CodeAnalysis.Testing.Verifiers;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Dapper.AOT.Test.Verifiers;
+// inspiration: https://www.thinktecture.com/en/net/roslyn-source-generators-analyzers-code-fixes-testing/
+public abstract class Verifier
+{
+ public CancellationToken CancellationToken { get; private set; }
+
+ protected static DiagnosticResult Diagnostic(DiagnosticDescriptor diagnostic)
+ => new DiagnosticResult(diagnostic);
+ protected static DiagnosticResult InterceptorsNotEnabled = Diagnostic(CodeAnalysis.DapperInterceptorGenerator.Diagnostics.InterceptorsNotEnabled);
+
+ protected static DiagnosticResult InterceptorsGenerated(int handled, int total,
+ int interceptors, int commands, int readers)
+ => new DiagnosticResult(CodeAnalysis.DapperInterceptorGenerator.Diagnostics.InterceptorsGenerated)
+ .WithArguments(handled, total, interceptors, commands, readers);
+
+ public Task VerifyAsync(string source,
+ Func[] transforms,
+ params DiagnosticResult[] expected)
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ {
+ var test = new CSharpAnalyzerTest();
+ return ExecuteAsync(test, source, transforms, expected);
+ }
+ public Task VerifyAsync(string source,
+ Func[] transforms,
+ params DiagnosticResult[] expected)
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ where TCodeFixProvider : CodeFixProvider, new()
+ {
+ var test = new CSharpAnalyzerTest();
+ return ExecuteAsync(test, source, transforms, expected);
+ }
+
+ protected Task ExecuteAsync(AnalyzerTest test, string source,
+ Func[] transforms,
+ DiagnosticResult[] expected)
+ {
+ test.TestCode = source;
+ test.ExpectedDiagnostics.AddRange(expected);
+#if NETFRAMEWORK
+ test.ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net472.Default;
+#elif NET8_0_OR_GREATER
+ test.ReferenceAssemblies = new ReferenceAssemblies("net8.0",
+ new PackageIdentity("Microsoft.NETCore.App.Ref", "8.0.0"),
+ Path.Combine("ref", "net8.0"));
+#elif NET7_0_OR_GREATER
+ test.ReferenceAssemblies = new ReferenceAssemblies("net7.0",
+ new PackageIdentity("Microsoft.NETCore.App.Ref", "7.0.0"),
+ Path.Combine("ref", "net7.0"));
+#else
+ test.ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
+#endif
+ test.TestState.AdditionalReferences.Add(typeof(SqlMapper).Assembly);
+ test.TestState.AdditionalReferences.Add(typeof(DapperAotAttribute).Assembly);
+ if (transforms is not null) test.SolutionTransforms.AddRange(transforms);
+ return test.RunAsync(CancellationToken);
+ }
+ protected static Func InterceptorsEnabled = WithFeatures(
+ new("InterceptorsPreview", "true"), // rc 1
+ new("InterceptorsPreviewNamespaces", "Dapper.AOT") // rc2 ?
+ );
+ protected static Func CSharpPreview = WithLanguageVersion(LanguageVersion.Preview);
+
+ protected static Func[] DefaultConfig = new[] { InterceptorsEnabled, CSharpPreview };
+
+ protected static Func WithLanguageVersion(LanguageVersion version)
+ => WithParseOptions(options => options.WithLanguageVersion(version));
+ protected static Func WithFeatures(params KeyValuePair[] features)
+ => WithParseOptions(options => options.WithFeatures(features));
+ protected static Func WithParseOptions(Func func)
+ {
+ if (func is null) return static (solution, _) => solution;
+ return (solution, projectId) =>
+ {
+ var options = solution.GetProject(projectId)?.ParseOptions as CSharpParseOptions;
+ if (options is null) return solution;
+ return solution.WithProjectParseOptions(projectId, func(options));
+ };
+ }
+
+}
+
+public class Verifier : Verifier where TAnalyzer : DiagnosticAnalyzer, new()
+{
+ protected Task VerifyAsync(string source,
+ Func[] transforms,
+ params DiagnosticResult[] expected)
+ => base.VerifyAsync(source, transforms, expected);
+ protected Task VerifyAsync(string source, params DiagnosticResult[] expected)
+ => base.VerifyAsync(source, DefaultConfig, expected);
+
+ new protected Task VerifyAsync(string source,
+ Func[] transforms,
+ params DiagnosticResult[] expected)
+ where TCodeFixProvider : CodeFixProvider, new()
+ => VerifyAsync(source, transforms, expected);
+
+ protected Task VerifyAsync(string source, params DiagnosticResult[] expected)
+ where TCodeFixProvider : CodeFixProvider, new()
+ => VerifyAsync(source, DefaultConfig, expected);
+}
+public class Verifier : Verifier
+ where TAnalyzer : DiagnosticAnalyzer, new()
+ where TCodeFixProvider : CodeFixProvider, new()
+{
+ protected Task VerifyAsync(string source, params DiagnosticResult[] expected)
+ => VerifyAsync(source, DefaultConfig, expected);
+ protected Task VerifyAsync(string source, Func[] transforms,
+ params DiagnosticResult[] expected)
+ => VerifyAsync(source, transforms, expected);
+}