Update GetLanguageKind(...) tests and move to Workspaces.Test

This commit is contained in:
Dustin Campbell 2024-09-06 09:55:44 -07:00
Родитель ed8e62b920
Коммит 5105d1b960
4 изменённых файлов: 478 добавлений и 480 удалений

Просмотреть файл

@ -1,143 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.CodeAnalysis.Razor.Protocol;
namespace Microsoft.CodeAnalysis.Razor.DocumentMapping;
internal static class LanguageKindHelper
{
public static ImmutableArray<ClassifiedSpanInternal> GetClassifiedSpans(RazorCodeDocument document)
{
// Since this service is called so often, we get a good performance improvement by caching these values
// for this code document. If the document changes, as the user types, then the document instance will be
// different, so we don't need to worry about invalidating the cache.
if (!document.Items.TryGetValue(typeof(ClassifiedSpanInternal), out ImmutableArray<ClassifiedSpanInternal> classifiedSpans))
{
var syntaxTree = document.GetSyntaxTree();
classifiedSpans = ClassifiedSpanVisitor.VisitRoot(syntaxTree);
document.Items[typeof(ClassifiedSpanInternal)] = classifiedSpans;
}
return classifiedSpans;
}
public static ImmutableArray<TagHelperSpanInternal> GetTagHelperSpans(RazorCodeDocument document)
{
// Since this service is called so often, we get a good performance improvement by caching these values
// for this code document. If the document changes, as the user types, then the document instance will be
// different, so we don't need to worry about invalidating the cache.
if (!document.Items.TryGetValue(typeof(TagHelperSpanInternal), out ImmutableArray<TagHelperSpanInternal> tagHelperSpans))
{
var syntaxTree = document.GetSyntaxTree();
tagHelperSpans = TagHelperSpanVisitor.VisitRoot(syntaxTree);
document.Items[typeof(TagHelperSpanInternal)] = tagHelperSpans;
}
return tagHelperSpans;
}
// Internal for testing
internal static RazorLanguageKind GetLanguageKindCore(
ImmutableArray<ClassifiedSpanInternal> classifiedSpans,
ImmutableArray<TagHelperSpanInternal> tagHelperSpans,
int hostDocumentIndex,
int hostDocumentLength,
bool rightAssociative)
{
var length = classifiedSpans.Length;
for (var i = 0; i < length; i++)
{
var classifiedSpan = classifiedSpans[i];
var span = classifiedSpan.Span;
if (span.AbsoluteIndex <= hostDocumentIndex)
{
var end = span.AbsoluteIndex + span.Length;
if (end >= hostDocumentIndex)
{
if (end == hostDocumentIndex)
{
// We're at an edge.
if (classifiedSpan.SpanKind is SpanKindInternal.MetaCode or SpanKindInternal.Transition)
{
// If we're on an edge of a transition of some kind (MetaCode representing an open or closing piece of syntax such as <|,
// and Transition representing an explicit transition to/from razor syntax, such as @|), prefer to classify to the span
// to the right to better represent where the user clicks
continue;
}
// If we're right associative, then we don't want to use the classification that we're at the end
// of, if we're also at the start of the next one
if (rightAssociative)
{
if (i < classifiedSpans.Length - 1 && classifiedSpans[i + 1].Span.AbsoluteIndex == hostDocumentIndex)
{
// If we're at the start of the next span, then use that span
return GetLanguageFromClassifiedSpan(classifiedSpans[i + 1]);
}
// Otherwise, we did not find a match using right associativity, so check for tag helpers
break;
}
}
return GetLanguageFromClassifiedSpan(classifiedSpan);
}
}
}
foreach (var tagHelperSpan in tagHelperSpans)
{
var span = tagHelperSpan.Span;
if (span.AbsoluteIndex <= hostDocumentIndex)
{
var end = span.AbsoluteIndex + span.Length;
if (end >= hostDocumentIndex)
{
if (end == hostDocumentIndex)
{
// We're at an edge. TagHelper spans never own their edge and aren't represented by marker spans
continue;
}
// Found intersection
return RazorLanguageKind.Html;
}
}
}
// Use the language of the last classified span if we're at the end
// of the document.
if (classifiedSpans.Length != 0 && hostDocumentIndex == hostDocumentLength)
{
var lastClassifiedSpan = classifiedSpans.Last();
return GetLanguageFromClassifiedSpan(lastClassifiedSpan);
}
// Default to Razor
return RazorLanguageKind.Razor;
static RazorLanguageKind GetLanguageFromClassifiedSpan(ClassifiedSpanInternal classifiedSpan)
{
// Overlaps with request
return classifiedSpan.SpanKind switch
{
SpanKindInternal.Markup => RazorLanguageKind.Html,
SpanKindInternal.Code => RazorLanguageKind.CSharp,
// Content type was non-C# or Html or we couldn't find a classified span overlapping the request position.
// All other classified span kinds default back to Razor
_ => RazorLanguageKind.Razor,
};
}
}
}

Просмотреть файл

@ -2,12 +2,13 @@
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.CodeAnalysis.Text;
@ -143,10 +144,139 @@ internal static class RazorCodeDocumentExtensions
public static RazorLanguageKind GetLanguageKind(this RazorCodeDocument codeDocument, int hostDocumentIndex, bool rightAssociative)
{
var classifiedSpans = LanguageKindHelper.GetClassifiedSpans(codeDocument);
var tagHelperSpans = LanguageKindHelper.GetTagHelperSpans(codeDocument);
var classifiedSpans = GetClassifiedSpans(codeDocument);
var tagHelperSpans = GetTagHelperSpans(codeDocument);
var documentLength = codeDocument.Source.Text.Length;
return LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, hostDocumentIndex, documentLength, rightAssociative);
return GetLanguageKindCore(classifiedSpans, tagHelperSpans, hostDocumentIndex, documentLength, rightAssociative);
}
private static ImmutableArray<ClassifiedSpanInternal> GetClassifiedSpans(RazorCodeDocument document)
{
// Since this service is called so often, we get a good performance improvement by caching these values
// for this code document. If the document changes, as the user types, then the document instance will be
// different, so we don't need to worry about invalidating the cache.
if (!document.Items.TryGetValue(typeof(ClassifiedSpanInternal), out ImmutableArray<ClassifiedSpanInternal> classifiedSpans))
{
var syntaxTree = document.GetSyntaxTree();
classifiedSpans = syntaxTree.GetClassifiedSpans();
document.Items[typeof(ClassifiedSpanInternal)] = classifiedSpans;
}
return classifiedSpans;
}
private static ImmutableArray<TagHelperSpanInternal> GetTagHelperSpans(RazorCodeDocument document)
{
// Since this service is called so often, we get a good performance improvement by caching these values
// for this code document. If the document changes, as the user types, then the document instance will be
// different, so we don't need to worry about invalidating the cache.
if (!document.Items.TryGetValue(typeof(TagHelperSpanInternal), out ImmutableArray<TagHelperSpanInternal> tagHelperSpans))
{
var syntaxTree = document.GetSyntaxTree();
tagHelperSpans = syntaxTree.GetTagHelperSpans();
document.Items[typeof(TagHelperSpanInternal)] = tagHelperSpans;
}
return tagHelperSpans;
}
private static RazorLanguageKind GetLanguageKindCore(
ImmutableArray<ClassifiedSpanInternal> classifiedSpans,
ImmutableArray<TagHelperSpanInternal> tagHelperSpans,
int hostDocumentIndex,
int hostDocumentLength,
bool rightAssociative)
{
var length = classifiedSpans.Length;
for (var i = 0; i < length; i++)
{
var classifiedSpan = classifiedSpans[i];
var span = classifiedSpan.Span;
if (span.AbsoluteIndex <= hostDocumentIndex)
{
var end = span.AbsoluteIndex + span.Length;
if (end >= hostDocumentIndex)
{
if (end == hostDocumentIndex)
{
// We're at an edge.
if (classifiedSpan.SpanKind is SpanKindInternal.MetaCode or SpanKindInternal.Transition)
{
// If we're on an edge of a transition of some kind (MetaCode representing an open or closing piece of syntax such as <|,
// and Transition representing an explicit transition to/from razor syntax, such as @|), prefer to classify to the span
// to the right to better represent where the user clicks
continue;
}
// If we're right associative, then we don't want to use the classification that we're at the end
// of, if we're also at the start of the next one
if (rightAssociative)
{
if (i < classifiedSpans.Length - 1 && classifiedSpans[i + 1].Span.AbsoluteIndex == hostDocumentIndex)
{
// If we're at the start of the next span, then use that span
return GetLanguageFromClassifiedSpan(classifiedSpans[i + 1]);
}
// Otherwise, we did not find a match using right associativity, so check for tag helpers
break;
}
}
return GetLanguageFromClassifiedSpan(classifiedSpan);
}
}
}
foreach (var tagHelperSpan in tagHelperSpans)
{
var span = tagHelperSpan.Span;
if (span.AbsoluteIndex <= hostDocumentIndex)
{
var end = span.AbsoluteIndex + span.Length;
if (end >= hostDocumentIndex)
{
if (end == hostDocumentIndex)
{
// We're at an edge. TagHelper spans never own their edge and aren't represented by marker spans
continue;
}
// Found intersection
return RazorLanguageKind.Html;
}
}
}
// Use the language of the last classified span if we're at the end
// of the document.
if (classifiedSpans.Length != 0 && hostDocumentIndex == hostDocumentLength)
{
var lastClassifiedSpan = classifiedSpans.Last();
return GetLanguageFromClassifiedSpan(lastClassifiedSpan);
}
// Default to Razor
return RazorLanguageKind.Razor;
static RazorLanguageKind GetLanguageFromClassifiedSpan(ClassifiedSpanInternal classifiedSpan)
{
// Overlaps with request
return classifiedSpan.SpanKind switch
{
SpanKindInternal.Markup => RazorLanguageKind.Html,
SpanKindInternal.Code => RazorLanguageKind.CSharp,
// Content type was non-C# or Html or we couldn't find a classified span overlapping the request position.
// All other classified span kinds default back to Razor
_ => RazorLanguageKind.Razor,
};
}
}
}

Просмотреть файл

@ -2,10 +2,8 @@
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer;
using Microsoft.AspNetCore.Razor.Test.Common.Workspaces;
@ -699,339 +697,12 @@ public class RazorDocumentMappingServiceTest(ITestOutputHelper testOutput) : Too
Assert.False(result);
}
[Fact]
public void GetLanguageKindCore_TagHelperElementOwnsName()
{
// Arrange
var descriptor = TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly");
descriptor.TagMatchingRule(rule => rule.TagName = "test");
descriptor.SetMetadata(TypeName("TestTagHelper"));
var text = """
@addTagHelper *, TestAssembly
<test>@Name</test>
""";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text, new[] { descriptor.Build() });
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 32 + Environment.NewLine.Length, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
[Fact]
public void GetLanguageKindCore_TagHelpersDoNotOwnTrailingEdge()
{
// Arrange
var descriptor = TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly");
descriptor.TagMatchingRule(rule => rule.TagName = "test");
descriptor.SetMetadata(TypeName("TestTagHelper"));
var text = """
@addTagHelper *, TestAssembly
<test></test>@DateTime.Now
""";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text, new[] { descriptor.Build() });
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 42 + Environment.NewLine.Length, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Razor, languageKind);
}
[Fact]
public void GetLanguageKindCore_TagHelperNestedCSharpAttribute()
{
// Arrange
var descriptor = TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly");
descriptor.TagMatchingRule(rule => rule.TagName = "test");
descriptor.BindAttribute(builder =>
{
builder.Name = "asp-int";
builder.TypeName = typeof(int).FullName;
builder.SetMetadata(PropertyName("AspInt"));
});
descriptor.SetMetadata(TypeName("TestTagHelper"));
var text = """
@addTagHelper *, TestAssembly
<test asp-int='123'></test>
""";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text, new[] { descriptor.Build() });
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 46 + Environment.NewLine.Length, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKindCore_CSharp()
{
// Arrange
var text = "<p>@Name</p>";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 5, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKindCore_Html()
{
// Arrange
var text = "<p>Hello World</p>";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 5, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
[Fact]
public void GetLanguageKindCore_DefaultsToRazorLanguageIfCannotLocateOwner()
{
// Arrange
var text = "<p>Hello World</p>";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, text.Length + 1, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Razor, languageKind);
}
[Fact]
public void GetLanguageKindCore_GetsLastClassifiedSpanLanguageIfAtEndOfDocument()
{
// Arrange
var text = """
<strong>Something</strong>
<App>
""";
var classifiedSpans = ImmutableArray.Create<ClassifiedSpanInternal>(
new(new SourceSpan(0, 0),
blockSpan: new SourceSpan(absoluteIndex: 0, lineIndex: 0, characterIndex: 0, length: text.Length),
SpanKindInternal.Transition,
blockKind: default,
acceptedCharacters: default),
new(new SourceSpan(0, 26),
blockSpan: default,
SpanKindInternal.Markup,
blockKind: default,
acceptedCharacters: default));
var tagHelperSpans = ImmutableArray<TagHelperSpanInternal>.Empty;
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, text.Length, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
[Fact]
public void GetLanguageKindCore_HtmlEdgeEnd()
{
// Arrange
var text = "Hello World";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, text.Length, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
[Fact]
public void GetLanguageKindCore_CSharpEdgeEnd()
{
// Arrange
var text = "@Name";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, text.Length, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKindCore_RazorEdgeWithCSharp()
{
// Arrange
var text = "@{}";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 2, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKindCore_CSharpEdgeWithCSharpMarker()
{
// Arrange
var text = "@{var x = 1;}";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 12, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKindCore_ExplicitExpressionStartCSharp()
{
// Arrange
var text = "@()";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 2, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKindCore_ExplicitExpressionInProgressCSharp()
{
// Arrange
var text = "@(Da)";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 4, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKindCore_ImplicitExpressionStartCSharp()
{
// Arrange
var text = "@";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 1, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKindCore_ImplicitExpressionInProgressCSharp()
{
// Arrange
var text = "@Da";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 3, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKindCore_RazorEdgeWithHtml()
{
// Arrange
var text = "@{<br />}";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 2, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
[Fact]
public void GetLanguageKindCore_HtmlInCSharpLeftAssociative()
{
// Arrange
var text = "@if (true) { <br /> }";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 13, text.Length, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKindCore_HtmlInCSharpRightAssociative()
{
// Arrange
var text = "@if (true) { <br /> }";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text);
// Act\
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 13, text.Length, rightAssociative: true);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
[Fact]
public void GetLanguageKindCore_TagHelperInCSharpRightAssociative()
{
// Arrange
var descriptor = TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly");
descriptor.TagMatchingRule(rule => rule.TagName = "test");
descriptor.SetMetadata(TypeName("TestTagHelper"));
var text = """
@addTagHelper *, TestAssembly
@if {
<test>@Name</test>
}
""";
var (classifiedSpans, tagHelperSpans) = GetClassifiedSpans(text, new[] { descriptor.Build() });
// Act\
var languageKind = LanguageKindHelper.GetLanguageKindCore(classifiedSpans, tagHelperSpans, 40, text.Length, rightAssociative: true);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
private static (ImmutableArray<ClassifiedSpanInternal> classifiedSpans, ImmutableArray<TagHelperSpanInternal> tagHelperSpans) GetClassifiedSpans(string text, IReadOnlyList<TagHelperDescriptor>? tagHelpers = null)
{
var codeDocument = CreateCodeDocument(text, tagHelpers);
var syntaxTree = codeDocument.GetSyntaxTree();
var classifiedSpans = syntaxTree.GetClassifiedSpans();
var tagHelperSpans = syntaxTree.GetTagHelperSpans();
return (classifiedSpans, tagHelperSpans);
}
private static RazorCodeDocument CreateCodeDocument(string text, IReadOnlyList<TagHelperDescriptor>? tagHelpers = null)
{
tagHelpers ??= Array.Empty<TagHelperDescriptor>();
var sourceDocument = TestRazorSourceDocument.Create(text);
var projectEngine = RazorProjectEngine.Create(builder => { });
var codeDocument = projectEngine.ProcessDesignTime(sourceDocument, "mvc", importSources: default, tagHelpers);
return codeDocument;
}
private static RazorCodeDocument CreateCodeDocumentWithCSharpProjection(string razorSource, string projectedCSharpSource, ImmutableArray<SourceMapping> sourceMappings)
{
var codeDocument = CreateCodeDocument(razorSource, tagHelpers: []);
var sourceDocument = TestRazorSourceDocument.Create(razorSource);
var projectEngine = RazorProjectEngine.Create(builder => { });
var codeDocument = projectEngine.ProcessDesignTime(sourceDocument, "mvc", importSources: default, tagHelpers: []);
var csharpDocument = new RazorCSharpDocument(
codeDocument,
projectedCSharpSource,

Просмотреть файл

@ -0,0 +1,340 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Xunit;
using Xunit.Abstractions;
using static Microsoft.AspNetCore.Razor.Language.CommonMetadata;
namespace Microsoft.CodeAnalysis.Razor.Workspaces.Test.Extensions;
public class RazorCodeDocumentExtensionsTest(ITestOutputHelper testOutput) : ToolingTestBase(testOutput)
{
[Fact]
public void GetLanguageKind_TagHelperElementOwnsName()
{
// Arrange
var descriptor = TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly");
descriptor.TagMatchingRule(rule => rule.TagName = "test");
descriptor.SetMetadata(TypeName("TestTagHelper"));
TestCode code = """
@addTagHelper *, TestAssembly
<te$$st>@Name</test>
""";
var codeDocument = CreateCodeDocument(code, descriptor.Build());
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
[Fact]
public void GetLanguageKind_TagHelpersDoNotOwnTrailingEdge()
{
// Arrange
var descriptor = TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly");
descriptor.TagMatchingRule(rule => rule.TagName = "test");
descriptor.SetMetadata(TypeName("TestTagHelper"));
TestCode code = """
@addTagHelper *, TestAssembly
<test></test>$$@DateTime.Now
""";
var codeDocument = CreateCodeDocument(code, descriptor.Build());
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Razor, languageKind);
}
[Fact]
public void GetLanguageKind_TagHelperNestedCSharpAttribute()
{
// Arrange
var descriptor = TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly");
descriptor.TagMatchingRule(rule => rule.TagName = "test");
descriptor.BindAttribute(builder =>
{
builder.Name = "asp-int";
builder.TypeName = typeof(int).FullName;
builder.SetMetadata(PropertyName("AspInt"));
});
descriptor.SetMetadata(TypeName("TestTagHelper"));
TestCode code = """
@addTagHelper *, TestAssembly
<test asp-int='12$$3'></test>
""";
var codeDocument = CreateCodeDocument(code, descriptor.Build());
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKind_CSharp()
{
// Arrange
TestCode code = "<p>@N$$ame</p>";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKind_Html()
{
// Arrange
TestCode code = "<p>He$$llo World</p>";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
[Fact]
public void GetLanguageKind_DefaultsToRazorLanguageIfCannotLocateOwner()
{
// Arrange
TestCode code = "<p>Hello World</p>$$";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position + 1, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Razor, languageKind);
}
[Fact]
public void GetLanguageKind_GetsLastClassifiedSpanLanguageIfAtEndOfDocument()
{
// Arrange
TestCode code = """
<strong>Something</strong>
<App>$$
""";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
[Fact]
public void GetLanguageKind_HtmlEdgeEnd()
{
// Arrange
TestCode code = "Hello World$$";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
[Fact]
public void GetLanguageKind_CSharpEdgeEnd()
{
// Arrange
TestCode code = "@Name$$";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKind_RazorEdgeWithCSharp()
{
// Arrange
TestCode code = "@{$$}";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKind_CSharpEdgeWithCSharpMarker()
{
// Arrange
TestCode code = "@{var x = 1;$$}";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKind_ExplicitExpressionStartCSharp()
{
// Arrange
TestCode code = "@($$)";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKind_ExplicitExpressionInProgressCSharp()
{
// Arrange
TestCode code = "@(Da$$)";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKind_ImplicitExpressionStartCSharp()
{
// Arrange
TestCode code = "@$$";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKind_ImplicitExpressionInProgressCSharp()
{
// Arrange
TestCode code = "@Da$$";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKind_RazorEdgeWithHtml()
{
// Arrange
TestCode code = "@{$$<br />}";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
[Fact]
public void GetLanguageKind_HtmlInCSharpLeftAssociative()
{
// Arrange
TestCode code = "@if (true) { $$<br /> }";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: false);
// Assert
Assert.Equal(RazorLanguageKind.CSharp, languageKind);
}
[Fact]
public void GetLanguageKind_HtmlInCSharpRightAssociative()
{
// Arrange
TestCode code = "@if (true) { $$<br /> }";
var codeDocument = CreateCodeDocument(code);
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: true);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
[Fact]
public void GetLanguageKind_TagHelperInCSharpRightAssociative()
{
// Arrange
var descriptor = TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly");
descriptor.TagMatchingRule(rule => rule.TagName = "test");
descriptor.SetMetadata(TypeName("TestTagHelper"));
TestCode code = """
@addTagHelper *, TestAssembly
@if {
$$<test>@Name</test>
}
""";
var codeDocument = CreateCodeDocument(code, descriptor.Build());
// Act
var languageKind = codeDocument.GetLanguageKind(code.Position, rightAssociative: true);
// Assert
Assert.Equal(RazorLanguageKind.Html, languageKind);
}
private static RazorCodeDocument CreateCodeDocument(TestCode code, params ImmutableArray<TagHelperDescriptor> tagHelpers)
{
tagHelpers = tagHelpers.NullToEmpty();
var sourceDocument = TestRazorSourceDocument.Create(code.Text);
var projectEngine = RazorProjectEngine.Create(builder => { });
return projectEngine.ProcessDesignTime(sourceDocument, "mvc", importSources: default, tagHelpers);
}
}