Skip to content
Open
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
13 changes: 12 additions & 1 deletion src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,17 @@ StringSyntaxKind.ConcatenatedString or StringSyntaxKind.FormatString

location ??= ctx.Operation.Syntax.GetLocation();

// a constant declared in generated code would have its SQL diagnostics dropped by the driver;
// re-home them onto the call-site argument so they stay visible (inline/same-file keep the token)
// https://github.com/DapperLib/DapperAOT/issues/177
var sqlLocation = location;
if (sqlSyntax is not null && sqlSyntax.SyntaxTree != sqlSource.Syntax.SyntaxTree
&& IsGeneratedDocument(sqlSyntax.SyntaxTree, ctx.Compilation, ctx.CancellationToken))
{
sqlLocation = sqlSource.Syntax.GetLocation();
sqlSyntax = null;
}

if (DebugSqlFlags is not null)
{
var debugModeFlags = DebugSqlFlags.Value;
Expand Down Expand Up @@ -458,7 +469,7 @@ StringSyntaxKind.ConcatenatedString or StringSyntaxKind.FormatString
forgiveSyntaxErrors = true; // we're just taking a punt, honestly
goto case SqlSyntax.SqlServer;
case SqlSyntax.SqlServer:
var proc = new OperationAnalysisContextTSqlProcessor(ctx, null, flags, location, sqlSyntax);
var proc = new OperationAnalysisContextTSqlProcessor(ctx, null, flags, sqlLocation, sqlSyntax);
proc.Execute(sql!, parameters);
parseFlags = proc.Flags;
// paramMembers);
Expand Down
36 changes: 36 additions & 0 deletions src/Dapper.AOT.Analyzers/Internal/Inspection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,42 @@ internal static bool IsCommand(INamedTypeSymbol type)
return false;
}

public static bool IsGeneratedDocument(SyntaxTree tree, Compilation compilation, CancellationToken cancellationToken)
{
// explicit editorconfig `generated_code`
switch (compilation.Options.SyntaxTreeOptionsProvider?.IsGenerated(tree, cancellationToken))
{
case GeneratedKind.MarkedGenerated: return true;
case GeneratedKind.NotGenerated: return false;
}

// file-name convention
var name = System.IO.Path.GetFileNameWithoutExtension(tree.FilePath);
if (name.StartsWith("TemporaryGeneratedFile_", StringComparison.OrdinalIgnoreCase)
|| name.EndsWith(".designer", StringComparison.OrdinalIgnoreCase)
|| name.EndsWith(".generated", StringComparison.OrdinalIgnoreCase)
|| name.EndsWith(".g", StringComparison.OrdinalIgnoreCase)
|| name.EndsWith(".g.i", StringComparison.OrdinalIgnoreCase))
{
return true;
}

// `<auto-generated>` header (text scan; only comment trivia contains it, so language-agnostic)
foreach (var trivia in tree.GetRoot(cancellationToken).GetLeadingTrivia())
{
if (trivia.Span.Length > 0)
{
var text = trivia.ToString();
if (text.IndexOf("<auto-generated", StringComparison.OrdinalIgnoreCase) >= 0
|| text.IndexOf("<autogenerated", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
}
}
return false;
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0042:Deconstruct variable declaration", Justification = "Fine as is; let's not pay the unwrap cost")]
public static SqlSyntax? IdentifySqlSyntax(in ParseState ctx, IOperation op, out bool caseSensitive)
{
Expand Down
67 changes: 64 additions & 3 deletions test/Dapper.AOT.Test/Verifiers/DAP214.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Dapper.CodeAnalysis;
using Dapper.CodeAnalysis;
using System.Threading.Tasks;
using Xunit;
using Diagnostics = Dapper.CodeAnalysis.DapperAnalyzer.Diagnostics;
Expand All @@ -10,5 +10,66 @@ public class DAP214 : Verifier<DapperAnalyzer>
public Task VariableNotDeclared() => SqlVerifyAsync("""
select {|#0:@i|};
""", SqlAnalysis.SqlParseInputFlags.KnownParameters, Diagnostic(Diagnostics.VariableNotDeclared).WithLocation(0).WithArguments("@i"));

}

private const string SqlClass = """
static class Sql
{
public const string GetUsers =
"select Id from Users where LoginCount >= {|#2:@MinLogins|}";
}
""";

// same class behind an <auto-generated> header, as a source generator would emit
private const string GeneratedSqlClass = "// <auto-generated/>\n" + SqlClass;

[Fact] // const sql in a generated doc re-homes to the call-site, https://github.com/DapperLib/DapperAOT/issues/177
public Task ConstSqlInGeneratedDocument() => CSVerifyAsync(""""
using Dapper;
using System.Data.Common;

[DapperAot]
static class Q
{
public static void ViaConst(DbConnection c) =>
c.Query<int>({|#0:Sql.GetUsers|}, {|#1:new { Wrong = 1 }|});
}
"""", DefaultConfig, [
Diagnostic(Diagnostics.VariableNotDeclared).WithLocation(0).WithArguments("@MinLogins"),
Diagnostic(Diagnostics.SqlParametersNotDetected).WithLocation(1),
], additionalSources: [GeneratedSqlClass]);

[Fact] // const sql in a visible sibling doc keeps its declaration token, https://github.com/DapperLib/DapperAOT/issues/177
public Task ConstSqlInVisibleSiblingDocument() => CSVerifyAsync(""""
using Dapper;
using System.Data.Common;

[DapperAot]
static class Q
{
public static void ViaConst(DbConnection c) =>
c.Query<int>(Sql.GetUsers, {|#1:new { Wrong = 1 }|});
}
"""", DefaultConfig, [
Diagnostic(Diagnostics.VariableNotDeclared).WithLocation(2).WithArguments("@MinLogins"),
Diagnostic(Diagnostics.SqlParametersNotDetected).WithLocation(1),
], additionalSources: [SqlClass]);

[Fact] // const sql in the same doc keeps its declaration token, https://github.com/DapperLib/DapperAOT/issues/177
public Task ConstSqlInSameDocument() => CSVerifyAsync(""""
using Dapper;
using System.Data.Common;

[DapperAot]
static class Q
{
public const string GetUsers =
"select Id from Users where LoginCount >= {|#0:@MinLogins|}";

public static void ViaConst(DbConnection c) =>
c.Query<int>(GetUsers, {|#1:new { Wrong = 1 }|});
}
"""", DefaultConfig, [
Diagnostic(Diagnostics.VariableNotDeclared).WithLocation(0).WithArguments("@MinLogins"),
Diagnostic(Diagnostics.SqlParametersNotDetected).WithLocation(1),
]);
}
20 changes: 15 additions & 5 deletions test/Dapper.AOT.Test/Verifiers/Verifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ protected static DiagnosticResult InterceptorsGenerated(int handled, int total,

internal Task CSVerifyAsync<TAnalyzer>(string source,
Func<Solution, ProjectId, Solution>[] transforms,
DiagnosticResult[] expected, SqlSyntax sqlSyntax, SqlParseInputFlags sqlParseInputFlags = SqlParseInputFlags.None, bool refDapperAot = true)
DiagnosticResult[] expected, SqlSyntax sqlSyntax, SqlParseInputFlags sqlParseInputFlags = SqlParseInputFlags.None, bool refDapperAot = true,
string[]? additionalSources = null)
where TAnalyzer : DiagnosticAnalyzer, new()
{
var test = new CSharpAnalyzerTest<TAnalyzer, DefaultVerifier>();
return ExecuteAsync(test, source, transforms, expected, sqlSyntax, sqlParseInputFlags, refDapperAot);
return ExecuteAsync(test, source, transforms, expected, sqlSyntax, sqlParseInputFlags, refDapperAot, additionalSources);
}
internal Task CSVerifyAsync<TAnalyzer, TCodeFix>(string source,
Func<Solution, ProjectId, Solution>[] transforms,
Expand Down Expand Up @@ -68,9 +69,17 @@ internal Task VBVerifyAsync<TAnalyzer, TCodeFix>(string source,

internal Task ExecuteAsync(AnalyzerTest<DefaultVerifier> test, string source,
Func<Solution, ProjectId, Solution>[] transforms,
DiagnosticResult[] expected, SqlSyntax sqlSyntax, SqlParseInputFlags sqlParseInputFlags, bool refDapperAot)
DiagnosticResult[] expected, SqlSyntax sqlSyntax, SqlParseInputFlags sqlParseInputFlags, bool refDapperAot,
string[]? additionalSources = null)
{
test.TestCode = source;
if (additionalSources is not null)
{
foreach (var additionalSource in additionalSources)
{
test.TestState.Sources.Add(additionalSource);
}
}

// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (expected is not null)
Expand Down Expand Up @@ -199,8 +208,9 @@ class SomeCode

internal Task CSVerifyAsync(string source,
Func<Solution, ProjectId, Solution>[] transforms,
DiagnosticResult[] expected, SqlSyntax sqlSyntax = SqlSyntax.SqlServer, SqlParseInputFlags sqlParseInputFlags = SqlParseInputFlags.None, bool refDapperAot = true)
=> base.CSVerifyAsync<TAnalyzer>(source, transforms, expected, sqlSyntax, sqlParseInputFlags, refDapperAot);
DiagnosticResult[] expected, SqlSyntax sqlSyntax = SqlSyntax.SqlServer, SqlParseInputFlags sqlParseInputFlags = SqlParseInputFlags.None, bool refDapperAot = true,
string[]? additionalSources = null)
=> base.CSVerifyAsync<TAnalyzer>(source, transforms, expected, sqlSyntax, sqlParseInputFlags, refDapperAot, additionalSources);

new internal Task CSVerifyAsync<TCodeFix>(string source,
Func<Solution, ProjectId, Solution>[] transforms,
Expand Down