Skip to content
Merged
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
2 changes: 0 additions & 2 deletions src/BLite.SourceGenerators/Helpers/BLiteConventions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ internal static class BLiteConventions
// ── DbContext discovery ────────────────────────────────────────────────
/// <summary>Base class name that marks a user class as a BLite DbContext.</summary>
public const string DocumentDbContextBaseName = "DocumentDbContext";
/// <summary>Suffix expected on DbContext class names to filter candidates early.</summary>
public const string DbContextClassSuffix = "Context";
/// <summary>File suffix used to skip already-generated files.</summary>
public const string GeneratedFileSuffix = ".g.cs";

Expand Down
9 changes: 7 additions & 2 deletions src/BLite.SourceGenerators/MapperGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@
{
// ── Diagnostic descriptors ─────────────────────────────────────────────
private static readonly DiagnosticDescriptor DiagError = new DiagnosticDescriptor(
"BLITE001", "BLite Generator Error", "{0}",

Check warning on line 39 in src/BLite.SourceGenerators/MapperGenerator.cs

View workflow job for this annotation

GitHub Actions / build_and_publish

Enable analyzer release tracking for the analyzer project containing rule 'BLITE001' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 39 in src/BLite.SourceGenerators/MapperGenerator.cs

View workflow job for this annotation

GitHub Actions / build_and_publish

Enable analyzer release tracking for the analyzer project containing rule 'BLITE001' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)
"BLite.SourceGenerators", DiagnosticSeverity.Error, isEnabledByDefault: true);

private static readonly DiagnosticDescriptor DiagWarning = new DiagnosticDescriptor(
"BLITE002", "BLite Generator Warning", "{0}",

Check warning on line 43 in src/BLite.SourceGenerators/MapperGenerator.cs

View workflow job for this annotation

GitHub Actions / build_and_publish

Enable analyzer release tracking for the analyzer project containing rule 'BLITE002' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 43 in src/BLite.SourceGenerators/MapperGenerator.cs

View workflow job for this annotation

GitHub Actions / build_and_publish

Enable analyzer release tracking for the analyzer project containing rule 'BLITE002' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)
"BLite.SourceGenerators", DiagnosticSeverity.Warning, isEnabledByDefault: true);

private static readonly DiagnosticDescriptor DiagDuplicateCollection = new DiagnosticDescriptor(
"BLITE003", "BLite Duplicate Collection Name", "{0}",

Check warning on line 47 in src/BLite.SourceGenerators/MapperGenerator.cs

View workflow job for this annotation

GitHub Actions / build_and_publish

Enable analyzer release tracking for the analyzer project containing rule 'BLITE003' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 47 in src/BLite.SourceGenerators/MapperGenerator.cs

View workflow job for this annotation

GitHub Actions / build_and_publish

Enable analyzer release tracking for the analyzer project containing rule 'BLITE003' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)
"BLite.SourceGenerators", DiagnosticSeverity.Error, isEnabledByDefault: true);

private static readonly DiagnosticDescriptor DiagUnresolvableConstructor = new DiagnosticDescriptor(
"BLITE010", "BLite Unresolvable Constructor", "{0}",

Check warning on line 51 in src/BLite.SourceGenerators/MapperGenerator.cs

View workflow job for this annotation

GitHub Actions / build_and_publish

Enable analyzer release tracking for the analyzer project containing rule 'BLITE010' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)

Check warning on line 51 in src/BLite.SourceGenerators/MapperGenerator.cs

View workflow job for this annotation

GitHub Actions / build_and_publish

Enable analyzer release tracking for the analyzer project containing rule 'BLITE010' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)
"BLite.SourceGenerators", DiagnosticSeverity.Warning, isEnabledByDefault: true);

public void Initialize(IncrementalGeneratorInitializationContext context)
Expand Down Expand Up @@ -489,13 +489,18 @@
}
}

/// <summary>
/// Syntactic pre-filter: any class with a base list. Whether it really derives from
/// <c>DocumentDbContext</c> is decided semantically in <see cref="GetDbContextInfo"/>.
/// The class name is deliberately not part of the filter: a context may be called
/// anything (<c>Shop</c>, <c>AppDb</c>), and a name filter would skip it silently (#300).
/// </summary>
private static bool IsPotentialDbContext(SyntaxNode node)
{
if (node.SyntaxTree.FilePath.EndsWith(BLiteConventions.GeneratedFileSuffix)) return false;

return node is ClassDeclarationSyntax classDecl &&
classDecl.BaseList != null &&
classDecl.Identifier.Text.EndsWith(BLiteConventions.DbContextClassSuffix);
classDecl.BaseList != null;
}

// ── Interceptor helpers ───────────────────────────────────────────────────
Expand Down
44 changes: 44 additions & 0 deletions tests/BLite.Shared/UnsuffixedNameFixtures.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using BLite.Bson;
using BLite.Core;
using BLite.Core.Collections;

namespace BLite.Shared;

// Regression fixtures for EntglDb/BLite#300: the mapper generator only looked at classes
// whose name ends in "Context", so a DocumentDbContext called anything else got no generated
// code and no diagnostic, and its collections stayed null. These classes compiling at all
// (InitializeCollections is generated) already proves the generator found them.

/// <summary>A context whose name does not end in "Context".</summary>
public partial class Shop : DocumentDbContext
{
public DocumentCollection<ObjectId, ShopCustomer> Customers { get; set; } = null!;

public Shop(string databasePath) : base(databasePath)
{
InitializeCollections();
}
}

/// <summary>A context that derives from another context, neither named with the suffix.</summary>
public partial class OnlineShop : Shop
{
public DocumentCollection<ObjectId, ShopOrder> Orders { get; set; } = null!;

public OnlineShop(string databasePath) : base(databasePath)
{
InitializeCollections();
}
}

public class ShopCustomer
{
public ObjectId Id { get; set; }
public string Name { get; set; } = "";
}

public class ShopOrder
{
public ObjectId Id { get; set; }
public decimal Total { get; set; }
}
69 changes: 69 additions & 0 deletions tests/BLite.Tests/UnsuffixedNameGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using BLite.Bson;
using BLite.Shared;

namespace BLite.Tests;

/// <summary>
/// Regression coverage for a <c>DocumentDbContext</c> whose class name does not end in
/// "Context" (see <c>UnsuffixedNameFixtures.cs</c> in BLite.Shared). The generator's syntax
/// filter used to require that suffix, so such a context got no generated code and no
/// diagnostic; its collection properties stayed null (EntglDb/BLite#300).
/// </summary>
public class UnsuffixedNameGeneratorTests : IDisposable
{
private readonly string _dbPath = Path.Combine(Path.GetTempPath(), $"blite_unsuffixed_{Guid.NewGuid():N}.db");

public void Dispose()
{
TryDelete(_dbPath);
TryDelete(Path.ChangeExtension(_dbPath, ".wal"));
}

private static void TryDelete(string path)
{
try { if (File.Exists(path)) File.Delete(path); } catch { /* best-effort */ }
}

[Fact]
public async Task Context_NamedWithoutSuffix_HasGeneratedCollections_AndRoundTrips()
{
using var db = new Shop(_dbPath);

Assert.NotNull(db.Customers);

var id = ObjectId.NewObjectId();
await db.Customers.InsertAsync(new ShopCustomer { Id = id, Name = "Ada" });

var stored = await db.Customers.FindByIdAsync(id);
Assert.NotNull(stored);
Assert.Equal("Ada", stored.Name);
}

[Fact]
public async Task DerivedContext_NamedWithoutSuffix_HasBothCollections()
{
using var db = new OnlineShop(_dbPath);

Assert.NotNull(db.Customers);
Assert.NotNull(db.Orders);

var id = ObjectId.NewObjectId();
await db.Orders.InsertAsync(new ShopOrder { Id = id, Total = 12.5m });
var stored = await db.Orders.FindByIdAsync(id);
Assert.NotNull(stored);
Assert.Equal(12.5m, stored.Total);
}

[Fact]
public void Generated_Mappers_Exist_ForTheUnsuffixedContext()
{
var mapperTypes = typeof(Shop).Assembly
.GetTypes()
// "{namespace}.{class}_{file}_Mappers", as for every context (see GlobalNamespaceGeneratorTests).
.Where(t => t.Namespace == $"{typeof(Shop).Namespace}.{nameof(Shop)}_UnsuffixedNameFixtures_Mappers")
.ToArray();

Assert.NotEmpty(mapperTypes);
Assert.Contains(mapperTypes, t => t.Name.Contains(nameof(ShopCustomer)));
}
}
Loading