Skip to content

Commit 63f2fa0

Browse files
authored
Razor Support ; after @typeparam (#37148)
* Razor Support `;` after `@typeparam` * Update ComponentDiscoveryIntegrationTest.cs
1 parent 6d334ff commit 63f2fa0

File tree

21 files changed

+1159
-1
lines changed

21 files changed

+1159
-1
lines changed

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CSharpCodeParser.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,7 @@ private void ParseExtensibleDirective(in SyntaxListBuilder<RazorSyntaxNode> buil
13131313
{
13141314
if (!At(SyntaxKind.Whitespace) &&
13151315
!At(SyntaxKind.NewLine) &&
1316+
!At(SyntaxKind.Semicolon) &&
13161317
!EndOfFile)
13171318
{
13181319
// This case should never happen in a real scenario. We're just being defensive.
@@ -1487,6 +1488,13 @@ private void ParseExtensibleDirective(in SyntaxListBuilder<RazorSyntaxNode> buil
14871488
{
14881489
while (!At(SyntaxKind.NewLine))
14891490
{
1491+
if (At(SyntaxKind.Semicolon))
1492+
{
1493+
// Consume the ending ';'
1494+
EatCurrentToken();
1495+
break;
1496+
}
1497+
14901498
AcceptAndMoveNext();
14911499
if (EndOfFile)
14921500
{
@@ -1496,6 +1504,11 @@ private void ParseExtensibleDirective(in SyntaxListBuilder<RazorSyntaxNode> buil
14961504
}
14971505
}
14981506
}
1507+
else if (At(SyntaxKind.Semicolon))
1508+
{
1509+
// Consume the ending ';'
1510+
EatCurrentToken();
1511+
}
14991512
else
15001513
{
15011514
Context.ErrorSink.OnError(

src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentCodeGenerationTestBase.cs

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Linq;
55
using Microsoft.AspNetCore.Razor.Language.Components;
6-
using Microsoft.AspNetCore.Testing;
76
using Xunit;
87

98
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
@@ -212,6 +211,36 @@ public void ComponentWithTypeParameters()
212211
@typeparam TItem1
213212
@typeparam TItem2
214213
214+
<h1>Item1</h1>
215+
@foreach (var item2 in Items2)
216+
{
217+
<p>
218+
@ChildContent(item2);
219+
</p>
220+
}
221+
@code {
222+
[Parameter] public TItem1 Item1 { get; set; }
223+
[Parameter] public List<TItem2> Items2 { get; set; }
224+
[Parameter] public RenderFragment<TItem2> ChildContent { get; set; }
225+
}");
226+
227+
// Assert
228+
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
229+
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
230+
CompileToAssembly(generated);
231+
}
232+
233+
[Fact]
234+
public void ComponentWithTypeParameters_WithSemicolon()
235+
{
236+
// Arrange
237+
238+
// Act
239+
var generated = CompileToCSharp(@"
240+
@using Microsoft.AspNetCore.Components;
241+
@typeparam TItem1;
242+
@typeparam TItem2;
243+
215244
<h1>Item1</h1>
216245
@foreach (var item2 in Items2)
217246
{
@@ -356,6 +385,83 @@ @using Test
356385
<p>@context</p>
357386
</TestComponent>
358387
388+
@code {
389+
Image item1 = new Image() { id = 1, url=""https://example.com""};
390+
static Tag tag1 = new Tag() { description = ""A description.""};
391+
static Tag tag2 = new Tag() { description = ""Another description.""};
392+
List<Tag> items = new List<Tag>() { tag1, tag2 };
393+
}");
394+
AssertDocumentNodeMatchesBaseline(useGenerated.CodeDocument);
395+
AssertCSharpDocumentMatchesBaseline(useGenerated.CodeDocument);
396+
CompileToAssembly(useGenerated);
397+
}
398+
399+
[Fact]
400+
public void ComponentWithConstrainedTypeParameters_WithSemicolon()
401+
{
402+
// Arrange
403+
var classes = @"
404+
public class Image
405+
{
406+
public string url { get; set; }
407+
public int id { get; set; }
408+
409+
public Image()
410+
{
411+
url = ""https://example.com/default.png"";
412+
id = 1;
413+
}
414+
}
415+
416+
public interface ITag
417+
{
418+
string description { get; set; }
419+
}
420+
421+
public class Tag : ITag
422+
{
423+
public string description { get; set; }
424+
}
425+
";
426+
427+
AdditionalSyntaxTrees.Add(Parse(classes));
428+
429+
// Act
430+
var generated = CompileToCSharp(@"
431+
@using Microsoft.AspNetCore.Components;
432+
@typeparam TItem1 where TItem1 : Image;
433+
@typeparam TItem2 where TItem2 : ITag;
434+
@typeparam TItem3 where TItem3 : Image, new();
435+
436+
<h1>Item1</h1>
437+
@foreach (var item2 in Items2)
438+
{
439+
<p>
440+
@ChildContent(item2);
441+
</p>
442+
}
443+
444+
<p>Item3</p>
445+
446+
@code {
447+
[Parameter] public TItem1 Item1 { get; set; }
448+
[Parameter] public List<TItem2> Items2 { get; set; }
449+
[Parameter] public TItem3 Item3 { get; set; }
450+
[Parameter] public RenderFragment<TItem2> ChildContent { get; set; }
451+
}");
452+
453+
// Assert
454+
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
455+
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
456+
CompileToAssembly(generated);
457+
458+
AdditionalSyntaxTrees.Add(Parse(generated.CodeDocument.GetCSharpDocument().GeneratedCode));
459+
var useGenerated = CompileToCSharp("UseTestComponent.cshtml", @"
460+
@using Test
461+
<TestComponent Item1=@item1 Items2=@items Item3=@item1>
462+
<p>@context</p>
463+
</TestComponent>
464+
359465
@code {
360466
Image item1 = new Image() { id = 1, url=""https://example.com""};
361467
static Tag tag1 = new Tag() { description = ""A description.""};

src/Razor/Microsoft.AspNetCore.Razor.Language/test/IntegrationTests/ComponentDiscoveryIntegrationTest.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,23 @@ @typeparam TItem
100100
Assert.Contains(bindings.TagHelpers, t => t.Name == "Test.UniqueName<TItem>");
101101
}
102102

103+
[Fact]
104+
public void ComponentDiscovery_CanFindComponent_WithTypeParameterAndSemicolon()
105+
{
106+
// Arrange
107+
108+
// Act
109+
var result = CompileToCSharp("UniqueName.cshtml", @"
110+
@typeparam TItem;
111+
@functions {
112+
[Parameter] public TItem Item { get; set; }
113+
}");
114+
115+
// Assert
116+
var bindings = result.CodeDocument.GetTagHelperContext();
117+
Assert.Contains(bindings.TagHelpers, t => t.Name == "Test.UniqueName<TItem>");
118+
}
119+
103120
[Fact]
104121
public void ComponentDiscovery_CanFindComponent_WithMultipleTypeParameters()
105122
{
@@ -110,6 +127,25 @@ public void ComponentDiscovery_CanFindComponent_WithMultipleTypeParameters()
110127
@typeparam TItem1
111128
@typeparam TItem2
112129
@typeparam TItem3
130+
@functions {
131+
[Parameter] public TItem1 Item { get; set; }
132+
}");
133+
134+
// Assert
135+
var bindings = result.CodeDocument.GetTagHelperContext();
136+
Assert.Contains(bindings.TagHelpers, t => t.Name == "Test.UniqueName<TItem1, TItem2, TItem3>");
137+
}
138+
139+
[Fact]
140+
public void ComponentDiscovery_CanFindComponent_WithMultipleTypeParametersAndMixedSemicolons()
141+
{
142+
// Arrange
143+
144+
// Act
145+
var result = CompileToCSharp("UniqueName.cshtml", @"
146+
@typeparam TItem1
147+
@typeparam TItem2;
148+
@typeparam TItem3
113149
@functions {
114150
[Parameter] public TItem1 Item { get; set; }
115151
}");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// <auto-generated/>
2+
#pragma warning disable 1591
3+
namespace Test
4+
{
5+
#line hidden
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Threading.Tasks;
10+
#nullable restore
11+
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
12+
using Microsoft.AspNetCore.Components;
13+
14+
#line default
15+
#line hidden
16+
#nullable disable
17+
public partial class TestComponent<TItem1, TItem2, TItem3> : Microsoft.AspNetCore.Components.ComponentBase
18+
where TItem1 : Image
19+
where TItem2 : ITag
20+
where TItem3 : Image, new()
21+
{
22+
#pragma warning disable 219
23+
private void __RazorDirectiveTokenHelpers__() {
24+
((System.Action)(() => {
25+
#nullable restore
26+
#line 2 "x:\dir\subdir\Test\TestComponent.cshtml"
27+
global::System.Object TItem1 = null!;
28+
29+
#line default
30+
#line hidden
31+
#nullable disable
32+
}
33+
))();
34+
((System.Action)(() => {
35+
#pragma warning disable CS0693
36+
#pragma warning disable CS8321
37+
#nullable restore
38+
#line 2 "x:\dir\subdir\Test\TestComponent.cshtml"
39+
void __TypeConstraints_TItem1<TItem1>() where TItem1 : Image
40+
{
41+
}
42+
#pragma warning restore CS0693
43+
#pragma warning restore CS8321
44+
45+
#line default
46+
#line hidden
47+
#nullable disable
48+
}
49+
))();
50+
((System.Action)(() => {
51+
#nullable restore
52+
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
53+
global::System.Object TItem2 = null!;
54+
55+
#line default
56+
#line hidden
57+
#nullable disable
58+
}
59+
))();
60+
((System.Action)(() => {
61+
#pragma warning disable CS0693
62+
#pragma warning disable CS8321
63+
#nullable restore
64+
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
65+
void __TypeConstraints_TItem2<TItem2>() where TItem2 : ITag
66+
{
67+
}
68+
#pragma warning restore CS0693
69+
#pragma warning restore CS8321
70+
71+
#line default
72+
#line hidden
73+
#nullable disable
74+
}
75+
))();
76+
((System.Action)(() => {
77+
#nullable restore
78+
#line 4 "x:\dir\subdir\Test\TestComponent.cshtml"
79+
global::System.Object TItem3 = null!;
80+
81+
#line default
82+
#line hidden
83+
#nullable disable
84+
}
85+
))();
86+
((System.Action)(() => {
87+
#pragma warning disable CS0693
88+
#pragma warning disable CS8321
89+
#nullable restore
90+
#line 4 "x:\dir\subdir\Test\TestComponent.cshtml"
91+
void __TypeConstraints_TItem3<TItem3>() where TItem3 : Image, new()
92+
{
93+
}
94+
#pragma warning restore CS0693
95+
#pragma warning restore CS8321
96+
97+
#line default
98+
#line hidden
99+
#nullable disable
100+
}
101+
))();
102+
}
103+
#pragma warning restore 219
104+
#pragma warning disable 0414
105+
private static System.Object __o = null;
106+
#pragma warning restore 0414
107+
#pragma warning disable 1998
108+
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
109+
{
110+
#nullable restore
111+
#line 6 "x:\dir\subdir\Test\TestComponent.cshtml"
112+
foreach (var item2 in Items2)
113+
{
114+
115+
116+
#line default
117+
#line hidden
118+
#nullable disable
119+
#nullable restore
120+
#line 10 "x:\dir\subdir\Test\TestComponent.cshtml"
121+
__o = ChildContent(item2);
122+
123+
#line default
124+
#line hidden
125+
#nullable disable
126+
#nullable restore
127+
#line 11 "x:\dir\subdir\Test\TestComponent.cshtml"
128+
129+
}
130+
131+
#line default
132+
#line hidden
133+
#nullable disable
134+
}
135+
#pragma warning restore 1998
136+
#nullable restore
137+
#line 16 "x:\dir\subdir\Test\TestComponent.cshtml"
138+
139+
[Parameter] public TItem1 Item1 { get; set; }
140+
[Parameter] public List<TItem2> Items2 { get; set; }
141+
[Parameter] public TItem3 Item3 { get; set; }
142+
[Parameter] public RenderFragment<TItem2> ChildContent { get; set; }
143+
144+
#line default
145+
#line hidden
146+
#nullable disable
147+
}
148+
}
149+
#pragma warning restore 1591

0 commit comments

Comments
 (0)