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
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@ private static void AppendSanitizedTypeName(StringBuilder sb, string typeName)
continue;
}

// Skip the verbatim identifier prefix '@' — it is a C# syntactic escape for
// reserved keywords (e.g. '@event') and has no meaning at the CLR level.
// The CLR type name is just 'event', so stripping '@' keeps the generated name
// consistent with the runtime resolver's output.
if (typeName[i] == '@')
{
i++;
continue;
}

var c = typeName[i];
sb.Append(IsInvalidIdentifierChar(c) ? '_' : c);
i++;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// <auto-generated/>
#nullable disable
using System;
using EntityFrameworkCore.Projectables;
using Foo;

namespace EntityFrameworkCore.Projectables.Generated
{
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
static class Foo_EventExtensions_GetId_P0_Foo_event
{
static global::System.Linq.Expressions.Expression<global::System.Func<global::Foo.@event, int>> Expression()
{
return (global::Foo.@event e) => e.Id;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,32 @@ static class C {
return Verifier.Verify(result.GeneratedTrees[0].ToString());
}

[Fact]
public Task ProjectableExtensionMethod_WithVerbatimKeywordParameterType()
{
var compilation = CreateCompilation(@"
using System;
using EntityFrameworkCore.Projectables;
namespace Foo {
class @event {
public int Id { get; set; }
}

static class EventExtensions {
[Projectable]
public static int GetId(this @event e) => e.Id;
}
}
");

var result = RunGenerator(compilation);

Assert.Empty(result.Diagnostics);
Assert.Single(result.GeneratedTrees);

return Verifier.Verify(result.GeneratedTrees[0].ToString());
}

[Fact]
public Task ProjectableExtensionMethod_WithExpressionPropertyBody()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,31 @@ public void GenerateName_WithGlobalPrefixInGenericArgs_StripsAllGlobalPrefixes(
Assert.Equal(expected, result);
}

/// <summary>
/// Verifies that the verbatim identifier prefix <c>@</c> is stripped from parameter type
/// names. Roslyn's <c>FullyQualifiedFormat</c> includes it for types whose names are
/// reserved C# keywords (e.g. <c>@event</c>), but the CLR runtime name never includes
/// <c>@</c> — so both sides must agree on the sanitised name.
/// </summary>
[Theory]
[InlineData(
"global::Foo.Storage.@event",
"ns_a_m_P0_Foo_Storage_event")]
[InlineData(
"@event",
"ns_a_m_P0_event")]
[InlineData(
"global::Foo.@delegate",
"ns_a_m_P0_Foo_delegate")]
public void GenerateName_WithVerbatimAtPrefixInParamType_StripsAtSign(
string paramTypeName, string expected)
{
var result = ProjectionExpressionClassNameGenerator.GenerateName(
"ns", new[] { "a" }, "m", new[] { paramTypeName });

Assert.Equal(expected, result);
}

[Fact]
public void GeneratedFullName()
{
Expand Down
Loading