Skip to content

Commit 98b5141

Browse files
Merge pull request #8860 from DustinCampbell/use-immutable-array
Return ImmutableArray<T> in legacy RazorSyntaxTreeExtensions
2 parents 0250104 + c5e3570 commit 98b5141

File tree

9 files changed

+135
-88
lines changed

9 files changed

+135
-88
lines changed

src/Compiler/Microsoft.AspNetCore.Razor.Language/src/ClassifiedSpanVisitor.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Collections.Immutable;
67
using System.Linq;
78
using Microsoft.AspNetCore.Razor.Language.Legacy;
89
using Microsoft.AspNetCore.Razor.Language.Syntax;
10+
using Microsoft.AspNetCore.Razor.PooledObjects;
911

1012
namespace Microsoft.AspNetCore.Razor.Language;
1113

1214
internal class ClassifiedSpanVisitor : SyntaxWalker
1315
{
1416
private readonly RazorSourceDocument _source;
15-
private readonly List<ClassifiedSpanInternal> _spans;
17+
private readonly ImmutableArray<ClassifiedSpanInternal>.Builder _spans;
1618

1719
private readonly Action<CSharpCodeBlockSyntax> _baseVisitCSharpCodeBlock;
1820
private readonly Action<CSharpStatementSyntax> _baseVisitCSharpStatement;
@@ -29,15 +31,10 @@ internal class ClassifiedSpanVisitor : SyntaxWalker
2931
private BlockKindInternal _currentBlockKind;
3032
private SyntaxNode? _currentBlock;
3133

32-
public ClassifiedSpanVisitor(RazorSourceDocument source)
34+
private ClassifiedSpanVisitor(RazorSourceDocument source, ImmutableArray<ClassifiedSpanInternal>.Builder spans)
3335
{
34-
if (source is null)
35-
{
36-
throw new ArgumentNullException(nameof(source));
37-
}
38-
3936
_source = source;
40-
_spans = new List<ClassifiedSpanInternal>();
37+
_spans = spans;
4138

4239
_baseVisitCSharpCodeBlock = base.VisitCSharpCodeBlock;
4340
_baseVisitCSharpStatement = base.VisitCSharpStatement;
@@ -54,7 +51,15 @@ public ClassifiedSpanVisitor(RazorSourceDocument source)
5451
_currentBlockKind = BlockKindInternal.Markup;
5552
}
5653

57-
public IReadOnlyList<ClassifiedSpanInternal> ClassifiedSpans => _spans;
54+
public static ImmutableArray<ClassifiedSpanInternal> VisitRoot(RazorSyntaxTree syntaxTree)
55+
{
56+
using var _ = ArrayBuilderPool<ClassifiedSpanInternal>.GetPooledObject(out var builder);
57+
58+
var visitor = new ClassifiedSpanVisitor(syntaxTree.Source, builder);
59+
visitor.Visit(syntaxTree.Root);
60+
61+
return builder.DrainToImmutable();
62+
}
5863

5964
public override void VisitRazorCommentBlock(RazorCommentBlockSyntax node)
6065
{

src/Compiler/Microsoft.AspNetCore.Razor.Language/src/ItemCollection.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Collections;
88
using System.Collections.Concurrent;
99
using System.Collections.Generic;
10+
using System.Diagnostics.CodeAnalysis;
1011

1112
namespace Microsoft.AspNetCore.Razor.Language;
1213

@@ -128,4 +129,17 @@ void ICollection.CopyTo(Array array, int index)
128129
{
129130
((ICollection)_inner).CopyTo(array, index);
130131
}
132+
133+
internal bool TryGetValue<TKey, TValue>(TKey key, [MaybeNullWhen(false)] out TValue value)
134+
where TKey : notnull
135+
{
136+
if (!_inner.TryGetValue(key, out var objValue))
137+
{
138+
value = default;
139+
return false;
140+
}
141+
142+
value = (TValue)objValue;
143+
return true;
144+
}
131145
}
Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,30 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
#nullable disable
5-
64
using System;
7-
using System.Collections.Generic;
5+
using System.Collections.Immutable;
86

97
namespace Microsoft.AspNetCore.Razor.Language.Legacy;
108

119
internal static class RazorSyntaxTreeExtensions
1210
{
13-
public static IReadOnlyList<ClassifiedSpanInternal> GetClassifiedSpans(this RazorSyntaxTree syntaxTree)
11+
public static ImmutableArray<ClassifiedSpanInternal> GetClassifiedSpans(this RazorSyntaxTree syntaxTree)
1412
{
1513
if (syntaxTree == null)
1614
{
1715
throw new ArgumentNullException(nameof(syntaxTree));
1816
}
1917

20-
var visitor = new ClassifiedSpanVisitor(syntaxTree.Source);
21-
visitor.Visit(syntaxTree.Root);
22-
23-
return visitor.ClassifiedSpans;
18+
return ClassifiedSpanVisitor.VisitRoot(syntaxTree);
2419
}
2520

26-
public static IReadOnlyList<TagHelperSpanInternal> GetTagHelperSpans(this RazorSyntaxTree syntaxTree)
21+
public static ImmutableArray<TagHelperSpanInternal> GetTagHelperSpans(this RazorSyntaxTree syntaxTree)
2722
{
2823
if (syntaxTree == null)
2924
{
3025
throw new ArgumentNullException(nameof(syntaxTree));
3126
}
3227

33-
var visitor = new TagHelperSpanVisitor(syntaxTree.Source);
34-
visitor.Visit(syntaxTree.Root);
35-
36-
return visitor.TagHelperSpans;
28+
return TagHelperSpanVisitor.VisitRoot(syntaxTree);
3729
}
3830
}

src/Compiler/Microsoft.AspNetCore.Razor.Language/src/TagHelperSpanVisitor.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,33 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
#nullable disable
5-
6-
using System.Collections.Generic;
4+
using System.Collections.Immutable;
75
using Microsoft.AspNetCore.Razor.Language.Legacy;
86
using Microsoft.AspNetCore.Razor.Language.Syntax;
7+
using Microsoft.AspNetCore.Razor.PooledObjects;
98

109
namespace Microsoft.AspNetCore.Razor.Language;
1110

1211
internal class TagHelperSpanVisitor : SyntaxWalker
1312
{
1413
private readonly RazorSourceDocument _source;
15-
private readonly List<TagHelperSpanInternal> _spans;
14+
private readonly ImmutableArray<TagHelperSpanInternal>.Builder _spans;
1615

17-
public TagHelperSpanVisitor(RazorSourceDocument source)
16+
private TagHelperSpanVisitor(RazorSourceDocument source, ImmutableArray<TagHelperSpanInternal>.Builder spans)
1817
{
1918
_source = source;
20-
_spans = new List<TagHelperSpanInternal>();
19+
_spans = spans;
2120
}
2221

23-
public IReadOnlyList<TagHelperSpanInternal> TagHelperSpans => _spans;
22+
public static ImmutableArray<TagHelperSpanInternal> VisitRoot(RazorSyntaxTree syntaxTree)
23+
{
24+
using var _ = ArrayBuilderPool<TagHelperSpanInternal>.GetPooledObject(out var builder);
25+
26+
var visitor = new TagHelperSpanVisitor(syntaxTree.Source, builder);
27+
visitor.Visit(syntaxTree.Root);
28+
29+
return builder.DrainToImmutable();
30+
}
2431

2532
public override void VisitMarkupTagHelperElement(MarkupTagHelperElementSyntax node)
2633
{

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/RazorDocumentMappingService.cs

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Collections.Immutable;
67
using System.Diagnostics;
78
using System.Diagnostics.CodeAnalysis;
89
using System.Linq;
@@ -12,6 +13,7 @@
1213
using Microsoft.AspNetCore.Razor.Language.Legacy;
1314
using Microsoft.AspNetCore.Razor.LanguageServer.Extensions;
1415
using Microsoft.AspNetCore.Razor.LanguageServer.Protocol;
16+
using Microsoft.AspNetCore.Razor.PooledObjects;
1517
using Microsoft.CodeAnalysis.Razor.Workspaces;
1618
using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions;
1719
using Microsoft.CodeAnalysis.Text;
@@ -491,13 +493,14 @@ public async Task<WorkspaceEdit> RemapWorkspaceEditAsync(WorkspaceEdit workspace
491493

492494
// Internal for testing
493495
internal static RazorLanguageKind GetLanguageKindCore(
494-
IReadOnlyList<ClassifiedSpanInternal> classifiedSpans,
495-
IReadOnlyList<TagHelperSpanInternal> tagHelperSpans,
496+
ImmutableArray<ClassifiedSpanInternal> classifiedSpans,
497+
ImmutableArray<TagHelperSpanInternal> tagHelperSpans,
496498
int hostDocumentIndex,
497499
int hostDocumentLength,
498500
bool rightAssociative)
499501
{
500-
for (var i = 0; i < classifiedSpans.Count; i++)
502+
var length = classifiedSpans.Length;
503+
for (var i = 0; i < length; i++)
501504
{
502505
var classifiedSpan = classifiedSpans[i];
503506
var span = classifiedSpan.Span;
@@ -522,7 +525,7 @@ internal static RazorLanguageKind GetLanguageKindCore(
522525
// of, if we're also at the start of the next one
523526
if (rightAssociative)
524527
{
525-
if (i < classifiedSpans.Count - 1 && classifiedSpans[i + 1].Span.AbsoluteIndex == hostDocumentIndex)
528+
if (i < classifiedSpans.Length - 1 && classifiedSpans[i + 1].Span.AbsoluteIndex == hostDocumentIndex)
526529
{
527530
// If we're at the start of the next span, then use that span
528531
return GetLanguageFromClassifiedSpan(classifiedSpans[i + 1]);
@@ -538,9 +541,8 @@ internal static RazorLanguageKind GetLanguageKindCore(
538541
}
539542
}
540543

541-
for (var i = 0; i < tagHelperSpans.Count; i++)
544+
foreach (var tagHelperSpan in tagHelperSpans)
542545
{
543-
var tagHelperSpan = tagHelperSpans[i];
544546
var span = tagHelperSpan.Span;
545547

546548
if (span.AbsoluteIndex <= hostDocumentIndex)
@@ -562,7 +564,7 @@ internal static RazorLanguageKind GetLanguageKindCore(
562564

563565
// Use the language of the last classified span if we're at the end
564566
// of the document.
565-
if (classifiedSpans.Count != 0 && hostDocumentIndex == hostDocumentLength)
567+
if (classifiedSpans.Length != 0 && hostDocumentIndex == hostDocumentLength)
566568
{
567569
var lastClassifiedSpan = classifiedSpans.Last();
568570
return GetLanguageFromClassifiedSpan(lastClassifiedSpan);
@@ -952,39 +954,31 @@ private static SourceText GetGeneratedSourceText(IRazorGeneratedDocument generat
952954
return codeDocument.GetGeneratedSourceText(generatedDocument);
953955
}
954956

955-
private static IReadOnlyList<ClassifiedSpanInternal> GetClassifiedSpans(RazorCodeDocument document)
957+
private static ImmutableArray<ClassifiedSpanInternal> GetClassifiedSpans(RazorCodeDocument document)
956958
{
957959
// Since this service is called so often, we get a good performance improvement by caching these values
958960
// for this code document. If the document changes, as the user types, then the document instance will be
959961
// different, so we don't need to worry about invalidating the cache.
960-
var classifiedSpans = (IReadOnlyList<ClassifiedSpanInternal>)document.Items[typeof(ClassifiedSpanInternal)];
961-
if (classifiedSpans is null)
962+
if (!document.Items.TryGetValue(typeof(ClassifiedSpanInternal), out ImmutableArray<ClassifiedSpanInternal> classifiedSpans))
962963
{
963964
var syntaxTree = document.GetSyntaxTree();
964-
965-
var visitor = new ClassifiedSpanVisitor(syntaxTree.Source);
966-
visitor.Visit(syntaxTree.Root);
967-
classifiedSpans = visitor.ClassifiedSpans;
965+
classifiedSpans = ClassifiedSpanVisitor.VisitRoot(syntaxTree);
968966

969967
document.Items[typeof(ClassifiedSpanInternal)] = classifiedSpans;
970968
}
971969

972970
return classifiedSpans;
973971
}
974972

975-
private static IReadOnlyList<TagHelperSpanInternal> GetTagHelperSpans(RazorCodeDocument document)
973+
private static ImmutableArray<TagHelperSpanInternal> GetTagHelperSpans(RazorCodeDocument document)
976974
{
977975
// Since this service is called so often, we get a good performance improvement by caching these values
978976
// for this code document. If the document changes, as the user types, then the document instance will be
979977
// different, so we don't need to worry about invalidating the cache.
980-
var tagHelperSpans = (IReadOnlyList<TagHelperSpanInternal>)document.Items[typeof(TagHelperSpanInternal)];
981-
if (tagHelperSpans is null)
978+
if (!document.Items.TryGetValue(typeof(TagHelperSpanInternal), out ImmutableArray<TagHelperSpanInternal> tagHelperSpans))
982979
{
983980
var syntaxTree = document.GetSyntaxTree();
984-
985-
var visitor = new TagHelperSpanVisitor(syntaxTree.Source);
986-
visitor.Visit(syntaxTree.Root);
987-
tagHelperSpans = visitor.TagHelperSpans;
981+
tagHelperSpans = TagHelperSpanVisitor.VisitRoot(syntaxTree);
988982

989983
document.Items[typeof(TagHelperSpanInternal)] = tagHelperSpans;
990984
}

src/Razor/src/Microsoft.AspNetCore.Razor.ProjectEngineHost/Serialization/TagHelperDeltaResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public ImmutableArray<TagHelperDescriptor> Apply(ImmutableArray<TagHelperDescrip
2626
// 1. This TagHelperDeltaResult.Apply where we don't iterate / Contains check the "base" collection.
2727
// 2. The rest of the Razor project system. Everything there is always indexed / iterated as a list.
2828
using var _ = ArrayBuilderPool<TagHelperDescriptor>.GetPooledObject(out var newTagHelpers);
29-
newTagHelpers.SetCapacityIfNeeded(baseTagHelpers.Length + Added.Length - Removed.Length);
29+
newTagHelpers.SetCapacityIfLarger(baseTagHelpers.Length + Added.Length - Removed.Length);
3030
newTagHelpers.AddRange(Added);
3131

3232
foreach (var existingTagHelper in baseTagHelpers)

src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/RazorDocumentMappingServiceTest.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Collections.Immutable;
67
using System.Linq;
78
using Microsoft.AspNetCore.Razor.Language;
89
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
@@ -868,22 +869,18 @@ public void GetLanguageKindCore_GetsLastClassifiedSpanLanguageIfAtEndOfDocument(
868869
{
869870
// Arrange
870871
var text = $"<strong>Something</strong>{Environment.NewLine}<App>";
871-
var classifiedSpans = new List<ClassifiedSpanInternal>()
872-
{
873-
new ClassifiedSpanInternal(
874-
new SourceSpan(0, 0),
872+
var classifiedSpans = ImmutableArray.Create<ClassifiedSpanInternal>(
873+
new(new SourceSpan(0, 0),
875874
blockSpan: new SourceSpan(absoluteIndex: 0, lineIndex: 0, characterIndex: 0, length: text.Length),
876875
SpanKindInternal.Transition,
877876
blockKind: default,
878877
acceptedCharacters: default),
879-
new ClassifiedSpanInternal(
880-
new SourceSpan(0, 26),
878+
new(new SourceSpan(0, 26),
881879
blockSpan: default,
882880
SpanKindInternal.Markup,
883881
blockKind: default,
884-
acceptedCharacters: default)
885-
};
886-
var tagHelperSpans = Array.Empty<TagHelperSpanInternal>();
882+
acceptedCharacters: default));
883+
var tagHelperSpans = ImmutableArray<TagHelperSpanInternal>.Empty;
887884

888885
// Act
889886
var languageKind = RazorDocumentMappingService.GetLanguageKindCore(classifiedSpans, tagHelperSpans, text.Length, text.Length, rightAssociative: false);
@@ -1068,7 +1065,7 @@ public void GetLanguageKindCore_TagHelperInCSharpRightAssociative()
10681065
Assert.Equal(RazorLanguageKind.Html, languageKind);
10691066
}
10701067

1071-
private static (IReadOnlyList<ClassifiedSpanInternal> classifiedSpans, IReadOnlyList<TagHelperSpanInternal> tagHelperSpans) GetClassifiedSpans(string text, IReadOnlyList<TagHelperDescriptor>? tagHelpers = null)
1068+
private static (ImmutableArray<ClassifiedSpanInternal> classifiedSpans, ImmutableArray<TagHelperSpanInternal> tagHelperSpans) GetClassifiedSpans(string text, IReadOnlyList<TagHelperDescriptor>? tagHelpers = null)
10721069
{
10731070
var codeDocument = CreateCodeDocument(text, tagHelpers);
10741071
var syntaxTree = codeDocument.GetSyntaxTree();

0 commit comments

Comments
 (0)