Skip to content

Commit 1f0c390

Browse files
authored
Don't put breakpoints on component start tags, end tags or attributes (#12422)
2 parents 49b30fd + cc6bf75 commit 1f0c390

File tree

2 files changed

+138
-19
lines changed

2 files changed

+138
-19
lines changed

src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Debugging/RemoteDebugInfoService.cs

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
using System.Threading;
66
using System.Threading.Tasks;
77
using Microsoft.AspNetCore.Razor.Language;
8+
using Microsoft.AspNetCore.Razor.Language.Syntax;
89
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
910
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
10-
using Microsoft.CodeAnalysis.Razor.Protocol;
1111
using Microsoft.CodeAnalysis.Razor.Remote;
1212
using Microsoft.CodeAnalysis.Remote.Razor.ProjectSystem;
1313
using Microsoft.CodeAnalysis.Text;
@@ -113,27 +113,48 @@ protected override IRemoteDebugInfoService CreateService(in ServiceArgs args)
113113

114114
private bool TryGetUsableProjectedIndex(RazorCodeDocument codeDocument, LinePosition hostDocumentPosition, out int projectedIndex)
115115
{
116-
var hostDocumentIndex = codeDocument.Source.Text.GetPosition(hostDocumentPosition);
117-
118116
projectedIndex = 0;
119-
var languageKind = codeDocument.GetLanguageKind(hostDocumentIndex, rightAssociative: false);
120-
// For C#, we just map
121-
if (languageKind == RazorLanguageKind.CSharp &&
122-
!_documentMappingService.TryMapToCSharpDocumentPosition(codeDocument.GetRequiredCSharpDocument(), hostDocumentIndex, out _, out projectedIndex))
123-
{
124-
return false;
125-
}
126-
// Otherwise see if there is more C# on the line to map to. This is for situations like "$$<p>@DateTime.Now</p>"
127-
else if (languageKind == RazorLanguageKind.Html &&
128-
!_documentMappingService.TryMapToCSharpPositionOrNext(codeDocument.GetRequiredCSharpDocument(), hostDocumentIndex, out _, out projectedIndex))
129-
{
130-
return false;
131-
}
132-
else if (languageKind == RazorLanguageKind.Razor)
117+
118+
var sourceText = codeDocument.Source.Text;
119+
var hostDocumentIndex = sourceText.GetPosition(hostDocumentPosition);
120+
var syntaxRoot = codeDocument.GetRequiredSyntaxRoot();
121+
var csharpDocument = codeDocument.GetRequiredCSharpDocument();
122+
123+
// We want to find a position that maps to C# on the same line as the original request, but we might have to skip over
124+
// some Razor/HTML nodes to find valid C#.
125+
while (sourceText.GetLinePosition(hostDocumentIndex).Line == hostDocumentPosition.Line)
133126
{
134-
return false;
127+
if (_documentMappingService.TryMapToCSharpPositionOrNext(csharpDocument, hostDocumentIndex, out _, out projectedIndex))
128+
{
129+
if (syntaxRoot.FindInnermostNode(hostDocumentIndex) is not { } node)
130+
{
131+
return false;
132+
}
133+
134+
// We want to avoid component tags and component attributes, where we map to C#, but they're not valid breakpoint locations
135+
if (!node.IsAnyAttributeSyntax() && node is not (MarkupTagHelperStartTagSyntax or MarkupEndTagSyntax))
136+
{
137+
// Found something valid!
138+
return true;
139+
}
140+
141+
// It's C#, but not valid, so skip past it so we can try to find more C#
142+
hostDocumentIndex = node.Span.End + 1;
143+
}
144+
145+
// See if there is more C# on the line to map to, for example "$$<p>@DateTime.Now</p>"
146+
if (!_documentMappingService.TryMapToCSharpPositionOrNext(csharpDocument, hostDocumentIndex, out _, out projectedIndex))
147+
{
148+
return false;
149+
}
150+
151+
// We found some C# later on the line, so map that back to Razor so we can loop around and check the node type
152+
if (!_documentMappingService.TryMapToRazorDocumentPosition(csharpDocument, projectedIndex, out _, out hostDocumentIndex))
153+
{
154+
return false;
155+
}
135156
}
136157

137-
return true;
158+
return false;
138159
}
139160
}

src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Cohost/RemoteDebugInfoServiceTest.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,104 @@ public async Task ResolveBreakpointRangeAsync_OutsideImplicitExpression()
174174
await VerifyBreakpointRangeAsync(input);
175175
}
176176

177+
[Fact]
178+
public async Task ResolveBreakpointRangeAsync_ComponentStartTag()
179+
{
180+
var input = """
181+
<div></div>
182+
183+
<Page$$Title>Hello</PageTitle>
184+
185+
""";
186+
187+
await VerifyBreakpointRangeAsync(input);
188+
}
189+
190+
[Fact]
191+
public async Task ResolveBreakpointRangeAsync_ComponentAttribute()
192+
{
193+
var input = """
194+
<div></div>
195+
196+
@{
197+
var caption = "Hello";
198+
}
199+
200+
<InputText Val$$ue="@caption" />
201+
202+
""";
203+
204+
await VerifyBreakpointRangeAsync(input);
205+
}
206+
207+
[Fact]
208+
public async Task ResolveBreakpointRangeAsync_ComponentContent()
209+
{
210+
var input = """
211+
<div></div>
212+
213+
@{
214+
var caption = "Hello";
215+
}
216+
217+
<PageTitle>@[|cap$$tion|]</PageTitle>
218+
219+
""";
220+
221+
await VerifyBreakpointRangeAsync(input);
222+
}
223+
224+
[Fact]
225+
public async Task ResolveBreakpointRangeAsync_ComponentContent_FromStartOfLine()
226+
{
227+
var input = """
228+
<div></div>
229+
230+
@{
231+
var caption = "Hello";
232+
}
233+
234+
$$<PageTitle>@[|caption|]</PageTitle>
235+
236+
""";
237+
238+
await VerifyBreakpointRangeAsync(input);
239+
}
240+
241+
[Fact]
242+
public async Task ResolveBreakpointRangeAsync_ComponentContent_FromStartOfLine_WithAttribute()
243+
{
244+
var input = """
245+
<div></div>
246+
247+
@{
248+
var caption = "Hello";
249+
}
250+
251+
$$<InputText Value="@caption" />@[|caption|]
252+
253+
""";
254+
255+
await VerifyBreakpointRangeAsync(input);
256+
}
257+
258+
[Fact]
259+
public async Task ResolveBreakpointRangeAsync_ComponentContent_FromStartTag()
260+
{
261+
var input = """
262+
<div></div>
263+
264+
@{
265+
var caption = "Hello";
266+
}
267+
268+
<PageT$$itle>@[|caption|]</PageTitle>
269+
270+
""";
271+
272+
await VerifyBreakpointRangeAsync(input);
273+
}
274+
177275
private async Task VerifyProximityExpressionsAsync(TestCode input, string[] extraExpressions)
178276
{
179277
var document = CreateProjectAndRazorDocument(input.Text);

0 commit comments

Comments
 (0)