Surface const-sql diagnostics from generated files by gtbuchanan · Pull Request #178 · DapperLib/DapperAOT · GitHub
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
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