Remove IDocumentMappingService.GetLanguageKind(...) and make it an extension method on RazorCodeDocument (#10851)

For a long while, the `GetLanguageKind(...)` method that determines
whether an index into a document falls within Razor, C# or HTML has been
a bit of a wart on the `IDocumentMappingService`. It really isn't part
of document mapping, and its implementation is completely distinct. In
fact, making the actual change is quite simple, so why hadn't it been
done yet? The answer is mocking.

There are several tests that mock
`IDocumentMappingService.GetLanguageKind(...)` to lie about test inputs.
In my not-so-humble opinion, this represents an abuse of mocking.
Instead of setting up tests to have the necessary inputs that ensure
`GetLanguageKind(...)` would return a real and correct result, the
inputs would often be garbage and an `IDocumentMappingService` mock
would lie about the `GetLanguageKind(...)` result at a particular
location. This makes moving `GetLanguageKind(...)` off of
`IDocumentMappingService` a much larger change than it needs to be. This
is why there are substantial test changes in this PR.

Don't misunderstand me as a mocking hater! Mocking libraries are
definitely useful! In fact, there are new mocks used in this very PR!
However, mocks should be used judiciously and thoughtfully, and in this
case, a mock was used to write lazy tests.

Fixes https://github.com/dotnet/razor/issues/8774
This commit is contained in:
Dustin Campbell 2024-09-09 16:21:28 -07:00 коммит произвёл GitHub
Родитель 308833d8b8 5105d1b960
Коммит 29c73023e7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
25 изменённых файлов: 1385 добавлений и 1668 удалений

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

@ -177,7 +177,7 @@ internal sealed class CodeActionEndpoint(
private async Task<ImmutableArray<RazorVSInternalCodeAction>> GetDelegatedCodeActionsAsync(DocumentContext documentContext, RazorCodeActionContext context, Guid correlationId, CancellationToken cancellationToken)
{
var languageKind = _documentMappingService.GetLanguageKind(context.CodeDocument, context.Location.AbsoluteIndex, rightAssociative: false);
var languageKind = context.CodeDocument.GetLanguageKind(context.Location.AbsoluteIndex, rightAssociative: false);
// No point delegating if we're in a Razor context
if (languageKind == RazorLanguageKind.Razor)

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

@ -55,7 +55,7 @@ internal class RazorBreakpointSpanEndpoint(
}
var projectedIndex = hostDocumentIndex;
var languageKind = _documentMappingService.GetLanguageKind(codeDocument, hostDocumentIndex, rightAssociative: false);
var languageKind = codeDocument.GetLanguageKind(hostDocumentIndex, rightAssociative: false);
// If we're in C#, then map to the right position in the generated document
if (languageKind == RazorLanguageKind.CSharp &&
!_documentMappingService.TryMapToGeneratedDocumentPosition(codeDocument.GetCSharpDocument(), hostDocumentIndex, out _, out projectedIndex))

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

@ -56,7 +56,7 @@ internal class RazorProximityExpressionsEndpoint(
}
var projectedIndex = hostDocumentIndex;
var languageKind = _documentMappingService.GetLanguageKind(codeDocument, hostDocumentIndex, rightAssociative: false);
var languageKind = codeDocument.GetLanguageKind(hostDocumentIndex, rightAssociative: false);
// If we're in C#, then map to the right position in the generated document
if (languageKind == RazorLanguageKind.CSharp &&
!_documentMappingService.TryMapToGeneratedDocumentPosition(codeDocument.GetCSharpDocument(), hostDocumentIndex, out _, out projectedIndex))

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

@ -3,8 +3,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
@ -71,7 +69,7 @@ internal abstract class AbstractTextDocumentPresentationEndpointBase<TParams>(
return null;
}
var languageKind = _documentMappingService.GetLanguageKind(codeDocument, hostDocumentIndex, rightAssociative: false);
var languageKind = codeDocument.GetLanguageKind(hostDocumentIndex, rightAssociative: false);
// See if we can handle this directly in Razor. If not, we'll let things flow to the below delegated handling.
var result = await TryGetRazorWorkspaceEditAsync(languageKind, request, cancellationToken).ConfigureAwait(false);
if (result is not null)

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

@ -3,16 +3,13 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Formatting;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;

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

@ -11,7 +11,6 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
using Microsoft.AspNetCore.Razor.LanguageServer.Formatting;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
@ -86,7 +85,7 @@ internal sealed class InlineCompletionEndpoint(
var sourceText = await documentContext.GetSourceTextAsync(cancellationToken).ConfigureAwait(false);
var hostDocumentIndex = sourceText.GetPosition(request.Position);
var languageKind = _documentMappingService.GetLanguageKind(codeDocument, hostDocumentIndex, rightAssociative: false);
var languageKind = codeDocument.GetLanguageKind(hostDocumentIndex, rightAssociative: false);
// Map to the location in the C# document.
if (languageKind != RazorLanguageKind.CSharp ||

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

@ -61,7 +61,7 @@ internal sealed class RazorLanguageQueryEndpoint(IDocumentMappingService documen
var responsePositionIndex = hostDocumentIndex;
var languageKind = _documentMappingService.GetLanguageKind(codeDocument, hostDocumentIndex, rightAssociative: false);
var languageKind = codeDocument.GetLanguageKind(hostDocumentIndex, rightAssociative: false);
if (languageKind == RazorLanguageKind.CSharp)
{
if (_documentMappingService.TryMapToGeneratedDocumentPosition(codeDocument.GetCSharpDocument(), hostDocumentIndex, out Position? projectedPosition, out var projectedIndex))

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

@ -1,7 +1,6 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
@ -9,7 +8,6 @@ using Microsoft.AspNetCore.Razor.Language.Syntax;
using Microsoft.AspNetCore.Razor.LanguageServer.EndpointContracts;
using Microsoft.AspNetCore.Razor.LanguageServer.Formatting;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Text;
@ -18,14 +16,9 @@ using Microsoft.VisualStudio.LanguageServer.Protocol;
namespace Microsoft.AspNetCore.Razor.LanguageServer.WrapWithTag;
[RazorLanguageServerEndpoint(LanguageServerConstants.RazorWrapWithTagEndpoint)]
internal class WrapWithTagEndpoint(
IClientConnection clientConnection,
IDocumentMappingService documentMappingService,
ILoggerFactory loggerFactory)
: IRazorRequestHandler<WrapWithTagParams, WrapWithTagResponse?>
internal class WrapWithTagEndpoint(IClientConnection clientConnection, ILoggerFactory loggerFactory) : IRazorRequestHandler<WrapWithTagParams, WrapWithTagResponse?>
{
private readonly IClientConnection _clientConnection = clientConnection ?? throw new ArgumentNullException(nameof(clientConnection));
private readonly IDocumentMappingService _documentMappingService = documentMappingService ?? throw new ArgumentNullException(nameof(documentMappingService));
private readonly IClientConnection _clientConnection = clientConnection;
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<WrapWithTagEndpoint>();
public bool MutatesSolutionState => false;
@ -69,7 +62,7 @@ internal class WrapWithTagEndpoint(
//
// Instead of C#, which certainly would be expected to go in an if statement, we'll see HTML, which obviously
// is the better choice for this operation.
var languageKind = _documentMappingService.GetLanguageKind(codeDocument, hostDocumentIndex, rightAssociative: true);
var languageKind = codeDocument.GetLanguageKind(hostDocumentIndex, rightAssociative: true);
if (languageKind is not RazorLanguageKind.Html)
{
// In general, we don't support C# for obvious reasons, but we can support implicit expressions. ie

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

@ -8,11 +8,9 @@ using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
@ -341,114 +339,6 @@ internal abstract class AbstractDocumentMappingService(IFilePathService filePath
}
}
public RazorLanguageKind GetLanguageKind(RazorCodeDocument codeDocument, int hostDocumentIndex, bool rightAssociative)
{
var classifiedSpans = GetClassifiedSpans(codeDocument);
var tagHelperSpans = GetTagHelperSpans(codeDocument);
var documentLength = codeDocument.Source.Text.Length;
var languageKind = GetLanguageKindCore(classifiedSpans, tagHelperSpans, hostDocumentIndex, documentLength, rightAssociative);
return languageKind;
}
// 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,
};
}
}
private bool TryMapToHostDocumentRangeStrict(IRazorGeneratedDocument generatedDocument, LinePositionSpan generatedDocumentRange, out LinePositionSpan hostDocumentRange)
{
hostDocumentRange = default;
@ -666,36 +556,4 @@ internal abstract class AbstractDocumentMappingService(IFilePathService filePath
return sourceText.TryGetAbsoluteIndex(linePosition, out _);
}
}
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 = ClassifiedSpanVisitor.VisitRoot(syntaxTree);
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 = TagHelperSpanVisitor.VisitRoot(syntaxTree);
document.Items[typeof(TagHelperSpanInternal)] = tagHelperSpans;
}
return tagHelperSpans;
}
}

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

@ -3,7 +3,6 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Razor.DocumentMapping;
@ -21,6 +20,4 @@ internal interface IDocumentMappingService
bool TryMapToGeneratedDocumentPosition(IRazorGeneratedDocument generatedDocument, int hostDocumentIndex, out LinePosition generatedPosition, out int generatedIndex);
bool TryMapToGeneratedDocumentOrNextCSharpPosition(IRazorGeneratedDocument generatedDocument, int hostDocumentIndex, out LinePosition generatedPosition, out int generatedIndex);
RazorLanguageKind GetLanguageKind(RazorCodeDocument codeDocument, int hostDocumentIndex, bool rightAssociative);
}

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

@ -48,7 +48,7 @@ internal static class IDocumentMappingServiceExtensions
var sourceText = codeDocument.Source.Text;
var position = sourceText.GetPosition(hostDocumentIndex);
var languageKind = service.GetLanguageKind(codeDocument, hostDocumentIndex, rightAssociative: false);
var languageKind = codeDocument.GetLanguageKind(hostDocumentIndex, rightAssociative: false);
if (languageKind is not RazorLanguageKind.Razor)
{
var generatedDocument = languageKind is RazorLanguageKind.CSharp

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

@ -2,10 +2,12 @@
// 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.Protocol;
using Microsoft.CodeAnalysis.Razor.Workspaces;
@ -139,4 +141,142 @@ internal static class RazorCodeDocumentExtensions
return namespaceNode.Content == fullyQualifiedNamespace;
}
public static RazorLanguageKind GetLanguageKind(this RazorCodeDocument codeDocument, int hostDocumentIndex, bool rightAssociative)
{
var classifiedSpans = GetClassifiedSpans(codeDocument);
var tagHelperSpans = GetTagHelperSpans(codeDocument);
var documentLength = codeDocument.Source.Text.Length;
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,
};
}
}
}

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

@ -31,7 +31,6 @@ internal class RazorFormattingService : IRazorFormattingService
private static readonly FrozenSet<string> s_htmlTriggerCharacterSet = FrozenSet.ToFrozenSet(["\n", "{", "}", ";"], StringComparer.Ordinal);
private readonly IFormattingCodeDocumentProvider _codeDocumentProvider;
private readonly IDocumentMappingService _documentMappingService;
private readonly IAdhocWorkspaceFactory _workspaceFactory;
private readonly ImmutableArray<IFormattingPass> _documentFormattingPasses;
@ -46,7 +45,6 @@ internal class RazorFormattingService : IRazorFormattingService
ILoggerFactory loggerFactory)
{
_codeDocumentProvider = codeDocumentProvider;
_documentMappingService = documentMappingService;
_workspaceFactory = workspaceFactory;
_htmlOnTypeFormattingPass = new HtmlOnTypeFormattingPass(loggerFactory);
@ -200,7 +198,7 @@ internal class RazorFormattingService : IRazorFormattingService
public bool TryGetOnTypeFormattingTriggerKind(RazorCodeDocument codeDocument, int hostDocumentIndex, string triggerCharacter, out RazorLanguageKind triggerCharacterKind)
{
triggerCharacterKind = _documentMappingService.GetLanguageKind(codeDocument, hostDocumentIndex, rightAssociative: false);
triggerCharacterKind = codeDocument.GetLanguageKind(hostDocumentIndex, rightAssociative: false);
return triggerCharacterKind switch
{

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

@ -53,7 +53,7 @@ internal sealed partial class RemoteDocumentHighlightService(in ServiceArgs args
var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
var languageKind = _documentMappingService.GetLanguageKind(codeDocument, index, rightAssociative: true);
var languageKind = codeDocument.GetLanguageKind(index, rightAssociative: true);
if (languageKind is RazorLanguageKind.Html)
{
return Response.CallHtml;

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

@ -4,6 +4,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.DocumentPresentation;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
@ -51,7 +52,7 @@ internal sealed partial class RemoteUriPresentationService(in ServiceArgs args)
var codeDocument = await context.GetCodeDocumentAsync(cancellationToken).ConfigureAwait(false);
var languageKind = DocumentMappingService.GetLanguageKind(codeDocument, index, rightAssociative: true);
var languageKind = codeDocument.GetLanguageKind(index, rightAssociative: true);
if (languageKind is not RazorLanguageKind.Html)
{
// Roslyn doesn't currently support Uri presentation, and whilst it might seem counter intuitive,

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

@ -12,8 +12,8 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.CodeActions.Models;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer;
using Microsoft.AspNetCore.Razor.Threading;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Protocol.CodeActions;
@ -26,49 +26,17 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Razor.LanguageServer.CodeActions;
public class CodeActionEndpointTest : LanguageServerTestBase
public class CodeActionEndpointTest(ITestOutputHelper testOutput) : LanguageServerTestBase(testOutput)
{
private readonly IDocumentMappingService _documentMappingService;
private readonly LanguageServerFeatureOptions _languageServerFeatureOptions;
private readonly IClientConnection _clientConnection;
public CodeActionEndpointTest(ITestOutputHelper testOutput)
: base(testOutput)
{
_documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.TryMapToGeneratedDocumentRange(
It.IsAny<IRazorGeneratedDocument>(),
It.IsAny<LinePositionSpan>(),
out It.Ref<LinePositionSpan>.IsAny) == false &&
s.GetLanguageKind(It.IsAny<RazorCodeDocument>(), It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.CSharp,
MockBehavior.Strict);
_languageServerFeatureOptions = Mock.Of<LanguageServerFeatureOptions>(
l => l.SupportsFileManipulation == true,
MockBehavior.Strict);
_clientConnection = Mock.Of<IClientConnection>(MockBehavior.Strict);
}
private static readonly LinePositionSpan s_defaultRange = new(new(5, 2), new(5, 2));
[Fact]
public async Task Handle_NoDocument()
{
// Arrange
var documentPath = new Uri("C:/path/to/Page.razor");
var codeActionEndpoint = new CodeActionEndpoint(
_documentMappingService,
Array.Empty<IRazorCodeActionProvider>(),
Array.Empty<ICSharpCodeActionProvider>(),
Array.Empty<IHtmlCodeActionProvider>(),
_clientConnection,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint();
var request = new VSCodeActionParams()
{
TextDocument = new VSTextDocumentIdentifier { Uri = documentPath },
@ -79,7 +47,7 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var requestContext = CreateRazorRequestContext(documentContext: null);
// Act
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, default);
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, DisposalToken);
// Assert
Assert.Null(commandOrCodeActionContainer);
@ -93,28 +61,19 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
codeDocument.SetUnsupported();
var codeActionEndpoint = new CodeActionEndpoint(
_documentMappingService,
Array.Empty<IRazorCodeActionProvider>(),
Array.Empty<ICSharpCodeActionProvider>(),
Array.Empty<IHtmlCodeActionProvider>(),
_clientConnection,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint();
var request = new VSCodeActionParams()
{
TextDocument = new VSTextDocumentIdentifier { Uri = documentPath },
Range = VsLspFactory.CreateZeroWidthRange(0, 1),
Context = new VSInternalCodeActionContext()
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, default);
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, DisposalToken);
// Assert
Assert.Null(commandOrCodeActionContainer);
@ -127,31 +86,23 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var codeActionEndpoint = new CodeActionEndpoint(
_documentMappingService,
Array.Empty<IRazorCodeActionProvider>(),
Array.Empty<ICSharpCodeActionProvider>(),
Array.Empty<IHtmlCodeActionProvider>(),
_clientConnection,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint();
var request = new VSCodeActionParams()
{
TextDocument = new VSTextDocumentIdentifier { Uri = documentPath },
Range = VsLspFactory.CreateZeroWidthRange(0, 1),
Context = new VSInternalCodeActionContext()
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, default);
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, DisposalToken);
// Assert
Assert.Empty(commandOrCodeActionContainer!);
Assert.NotNull(commandOrCodeActionContainer);
Assert.Empty(commandOrCodeActionContainer);
}
[Fact]
@ -161,20 +112,7 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var codeActionEndpoint = new CodeActionEndpoint(
_documentMappingService,
new IRazorCodeActionProvider[] {
new MockRazorCodeActionProvider()
},
Array.Empty<ICSharpCodeActionProvider>(),
Array.Empty<IHtmlCodeActionProvider>(),
_clientConnection,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint(razorCodeActionProviders: [CreateRazorCodeActionProvider()]);
var request = new VSCodeActionParams()
{
@ -182,10 +120,11 @@ public class CodeActionEndpointTest : LanguageServerTestBase
Range = VsLspFactory.CreateZeroWidthRange(0, 1),
Context = new VSInternalCodeActionContext()
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, default);
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, DisposalToken);
// Assert
Assert.NotNull(commandOrCodeActionContainer);
@ -199,22 +138,11 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var documentMappingService = CreateDocumentMappingService();
var languageServer = CreateLanguageServer();
var codeActionEndpoint = new CodeActionEndpoint(
documentMappingService,
Array.Empty<IRazorCodeActionProvider>(),
new ICSharpCodeActionProvider[] {
new MockCSharpCodeActionProvider()
},
Array.Empty<IHtmlCodeActionProvider>(),
languageServer,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint(
documentMappingService: CreateDocumentMappingService(s_defaultRange),
csharpCodeActionProviders: [CreateCSharpCodeActionProvider()],
clientConnection: TestClientConnection.Instance);
var request = new VSCodeActionParams()
{
@ -222,10 +150,11 @@ public class CodeActionEndpointTest : LanguageServerTestBase
Range = VsLspFactory.CreateZeroWidthRange(0, 1),
Context = new VSInternalCodeActionContext()
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, default);
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, DisposalToken);
// Assert
Assert.NotNull(commandOrCodeActionContainer);
@ -239,20 +168,7 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var codeActionEndpoint = new CodeActionEndpoint(
_documentMappingService,
new IRazorCodeActionProvider[] {
new MockMultipleRazorCodeActionProvider(),
},
Array.Empty<ICSharpCodeActionProvider>(),
Array.Empty<IHtmlCodeActionProvider>(),
_clientConnection,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint(razorCodeActionProviders: [CreateMultipleRazorCodeActionProvider()]);
var request = new VSCodeActionParams()
{
@ -260,10 +176,11 @@ public class CodeActionEndpointTest : LanguageServerTestBase
Range = VsLspFactory.CreateZeroWidthRange(0, 1),
Context = new VSInternalCodeActionContext()
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, default);
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, DisposalToken);
// Assert
Assert.NotNull(commandOrCodeActionContainer);
@ -277,27 +194,17 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var documentMappingService = CreateDocumentMappingService();
var languageServer = CreateLanguageServer();
var codeActionEndpoint = new CodeActionEndpoint(
documentMappingService,
new IRazorCodeActionProvider[] {
new MockMultipleRazorCodeActionProvider(),
new MockMultipleRazorCodeActionProvider(),
new MockRazorCodeActionProvider(),
},
new ICSharpCodeActionProvider[] {
new MockCSharpCodeActionProvider(),
new MockCSharpCodeActionProvider()
},
Array.Empty<IHtmlCodeActionProvider>(),
languageServer,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint(
documentMappingService: CreateDocumentMappingService(s_defaultRange),
razorCodeActionProviders: [
CreateMultipleRazorCodeActionProvider(),
CreateMultipleRazorCodeActionProvider(),
CreateRazorCodeActionProvider()],
csharpCodeActionProviders: [
CreateCSharpCodeActionProvider(),
CreateCSharpCodeActionProvider()],
clientConnection: TestClientConnection.Instance);
var request = new VSCodeActionParams()
{
@ -322,27 +229,17 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var documentMappingService = CreateDocumentMappingService();
var languageServer = CreateLanguageServer();
var codeActionEndpoint = new CodeActionEndpoint(
documentMappingService,
new IRazorCodeActionProvider[] {
new MockRazorCodeActionProvider(),
new MockRazorCodeActionProvider(),
new MockRazorCodeActionProvider(),
},
new ICSharpCodeActionProvider[] {
new MockCSharpCodeActionProvider(),
new MockCSharpCodeActionProvider()
},
Array.Empty<IHtmlCodeActionProvider>(),
languageServer,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint(
documentMappingService: CreateDocumentMappingService(s_defaultRange),
razorCodeActionProviders: [
CreateRazorCodeActionProvider(),
CreateRazorCodeActionProvider(),
CreateRazorCodeActionProvider()],
csharpCodeActionProviders: [
CreateCSharpCodeActionProvider(),
CreateCSharpCodeActionProvider()],
clientConnection: TestClientConnection.Instance);
var request = new VSCodeActionParams()
{
@ -350,6 +247,7 @@ public class CodeActionEndpointTest : LanguageServerTestBase
Range = VsLspFactory.CreateZeroWidthRange(0, 1),
Context = new VSInternalCodeActionContext()
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -367,20 +265,7 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var codeActionEndpoint = new CodeActionEndpoint(
_documentMappingService,
new IRazorCodeActionProvider[] {
new MockEmptyRazorCodeActionProvider()
},
Array.Empty<ICSharpCodeActionProvider>(),
Array.Empty<IHtmlCodeActionProvider>(),
_clientConnection,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint(razorCodeActionProviders: [CreateEmptyRazorCodeActionProvider()]);
var request = new VSCodeActionParams()
{
@ -388,13 +273,15 @@ public class CodeActionEndpointTest : LanguageServerTestBase
Range = VsLspFactory.CreateZeroWidthRange(0, 1),
Context = new VSInternalCodeActionContext()
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
var commandOrCodeActionContainer = await codeActionEndpoint.HandleRequestAsync(request, requestContext, default);
// Assert
Assert.Empty(commandOrCodeActionContainer!);
Assert.NotNull(commandOrCodeActionContainer);
Assert.Empty(commandOrCodeActionContainer);
}
[Fact]
@ -404,28 +291,18 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var documentMappingService = CreateDocumentMappingService();
var languageServer = CreateLanguageServer();
var codeActionEndpoint = new CodeActionEndpoint(
documentMappingService,
new IRazorCodeActionProvider[] {
new MockRazorCodeActionProvider(),
new MockEmptyRazorCodeActionProvider(),
new MockRazorCodeActionProvider(),
new MockEmptyRazorCodeActionProvider(),
},
new ICSharpCodeActionProvider[] {
new MockCSharpCodeActionProvider(),
new MockCSharpCodeActionProvider()
},
Array.Empty<IHtmlCodeActionProvider>(),
languageServer,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint(
documentMappingService: CreateDocumentMappingService(s_defaultRange),
razorCodeActionProviders: [
CreateRazorCodeActionProvider(),
CreateEmptyRazorCodeActionProvider(),
CreateRazorCodeActionProvider(),
CreateEmptyRazorCodeActionProvider()],
csharpCodeActionProviders: [
CreateCSharpCodeActionProvider(),
CreateCSharpCodeActionProvider()],
clientConnection: TestClientConnection.Instance);
var request = new VSCodeActionParams()
{
@ -433,6 +310,7 @@ public class CodeActionEndpointTest : LanguageServerTestBase
Range = VsLspFactory.CreateZeroWidthRange(0, 1),
Context = new VSInternalCodeActionContext()
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -450,22 +328,13 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var codeActionEndpoint = new CodeActionEndpoint(
_documentMappingService,
new IRazorCodeActionProvider[] {
new MockRazorCodeActionProvider(),
new MockRazorCommandProvider(),
new MockEmptyRazorCodeActionProvider()
},
Array.Empty<ICSharpCodeActionProvider>(),
Array.Empty<IHtmlCodeActionProvider>(),
_clientConnection,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = true
};
var codeActionEndpoint = CreateEndpoint(
razorCodeActionProviders: [
CreateRazorCodeActionProvider(),
CreateRazorCommandProvider(),
CreateEmptyRazorCodeActionProvider()],
supportsCodeActionResolve: true);
var request = new VSCodeActionParams()
{
@ -473,6 +342,7 @@ public class CodeActionEndpointTest : LanguageServerTestBase
Range = VsLspFactory.CreateZeroWidthRange(0, 1),
Context = new VSInternalCodeActionContext()
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -500,24 +370,13 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var documentMappingService = CreateDocumentMappingService();
var languageServer = CreateLanguageServer();
var codeActionEndpoint = new CodeActionEndpoint(
documentMappingService,
new IRazorCodeActionProvider[] {
new MockRazorCodeActionProvider(),
},
new ICSharpCodeActionProvider[] {
new MockCSharpCodeActionProvider()
},
Array.Empty<IHtmlCodeActionProvider>(),
languageServer,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = true
};
var codeActionEndpoint = CreateEndpoint(
documentMappingService: CreateDocumentMappingService(s_defaultRange),
razorCodeActionProviders: [CreateRazorCodeActionProvider()],
csharpCodeActionProviders: [CreateCSharpCodeActionProvider()],
clientConnection: TestClientConnection.Instance,
supportsCodeActionResolve: true);
var request = new VSCodeActionParams()
{
@ -525,6 +384,7 @@ public class CodeActionEndpointTest : LanguageServerTestBase
Range = VsLspFactory.CreateZeroWidthRange(0, 1),
Context = new VSInternalCodeActionContext()
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -554,22 +414,12 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var codeActionEndpoint = new CodeActionEndpoint(
_documentMappingService,
new IRazorCodeActionProvider[] {
new MockRazorCodeActionProvider(),
new MockRazorCommandProvider(),
new MockEmptyRazorCodeActionProvider()
},
Array.Empty<ICSharpCodeActionProvider>(),
Array.Empty<IHtmlCodeActionProvider>(),
_clientConnection,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint(
razorCodeActionProviders: [
CreateRazorCodeActionProvider(),
CreateRazorCommandProvider(),
CreateEmptyRazorCodeActionProvider()]);
var request = new VSCodeActionParams()
{
@ -577,6 +427,7 @@ public class CodeActionEndpointTest : LanguageServerTestBase
Range = VsLspFactory.CreateZeroWidthRange(0, 1),
Context = new VSInternalCodeActionContext()
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -587,14 +438,15 @@ public class CodeActionEndpointTest : LanguageServerTestBase
Assert.Collection(commandOrCodeActionContainer,
c =>
{
Assert.True(c.TryGetFirst(out var command1));
var command = Assert.IsType<Command>(command1);
var codeActionParamsToken = (JsonObject)command.Arguments!.First();
Assert.True(c.TryGetFirst(out var first));
var command = Assert.IsType<Command>(first);
Assert.NotNull(command.Arguments);
var codeActionParamsToken = (JsonObject)command.Arguments.First();
var codeActionParams = codeActionParamsToken.Deserialize<RazorCodeActionResolutionParams>();
Assert.NotNull(codeActionParams);
Assert.Equal(LanguageServerConstants.CodeActions.EditBasedCodeActionCommand, codeActionParams.Action);
},
c => Assert.True(c.TryGetFirst(out var _)));
c => Assert.True(c.TryGetFirst(out _)));
}
[Fact]
@ -604,20 +456,7 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var codeActionEndpoint = new CodeActionEndpoint(
_documentMappingService,
new IRazorCodeActionProvider[] {
new MockRazorCodeActionProvider()
},
Array.Empty<ICSharpCodeActionProvider>(),
Array.Empty<IHtmlCodeActionProvider>(),
_clientConnection,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint(razorCodeActionProviders: [CreateRazorCodeActionProvider()]);
var initialRange = VsLspFactory.CreateZeroWidthRange(0, 1);
var selectionRange = VsLspFactory.CreateZeroWidthRange(0, 5);
@ -646,20 +485,7 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var codeActionEndpoint = new CodeActionEndpoint(
_documentMappingService,
new IRazorCodeActionProvider[] {
new MockRazorCodeActionProvider()
},
Array.Empty<ICSharpCodeActionProvider>(),
Array.Empty<IHtmlCodeActionProvider>(),
_clientConnection,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint(razorCodeActionProviders: [CreateRazorCodeActionProvider()]);
var initialRange = VsLspFactory.CreateZeroWidthRange(0, 1);
var request = new VSCodeActionParams()
@ -687,24 +513,8 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var documentPath = new Uri("C:/path/to/Page.razor");
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
LinePositionSpan projectedRange = default;
var documentMappingService = Mock.Of<IDocumentMappingService>(
d => d.TryMapToGeneratedDocumentRange(It.IsAny<IRazorGeneratedDocument>(), It.IsAny<LinePositionSpan>(), out projectedRange) == false
, MockBehavior.Strict);
var codeActionEndpoint = new CodeActionEndpoint(
documentMappingService,
Array.Empty<IRazorCodeActionProvider>(),
new ICSharpCodeActionProvider[] {
new MockCSharpCodeActionProvider()
},
Array.Empty<IHtmlCodeActionProvider>(),
_clientConnection,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint(csharpCodeActionProviders: [CreateCSharpCodeActionProvider()]);
var initialRange = VsLspFactory.CreateZeroWidthRange(0, 1);
var request = new VSCodeActionParams()
@ -733,22 +543,11 @@ public class CodeActionEndpointTest : LanguageServerTestBase
var codeDocument = CreateCodeDocument("@code {}");
var documentContext = CreateDocumentContext(documentPath, codeDocument);
var projectedRange = VsLspFactory.CreateZeroWidthRange(15, 2);
var documentMappingService = CreateDocumentMappingService(projectedRange.ToLinePositionSpan());
var languageServer = CreateLanguageServer();
var codeActionEndpoint = new CodeActionEndpoint(
documentMappingService,
Array.Empty<IRazorCodeActionProvider>(),
new ICSharpCodeActionProvider[] {
new MockCSharpCodeActionProvider()
},
Array.Empty<IHtmlCodeActionProvider>(),
languageServer,
_languageServerFeatureOptions,
LoggerFactory,
telemetryReporter: null)
{
_supportsCodeActionResolve = false
};
var codeActionEndpoint = CreateEndpoint(
documentMappingService: CreateDocumentMappingService(projectedRange.ToLinePositionSpan()),
csharpCodeActionProviders: [CreateCSharpCodeActionProvider()],
clientConnection: TestClientConnection.Instance);
var initialRange = VsLspFactory.CreateZeroWidthRange(0, 1);
var request = new VSCodeActionParams()
@ -765,11 +564,12 @@ public class CodeActionEndpointTest : LanguageServerTestBase
Assert.NotNull(context);
// Act
var results = await codeActionEndpoint.GetCodeActionsFromLanguageServerAsync(RazorLanguageKind.CSharp, documentContext, context, Guid.Empty, cancellationToken: default);
var results = await codeActionEndpoint.GetCodeActionsFromLanguageServerAsync(RazorLanguageKind.CSharp, documentContext, context, Guid.Empty, cancellationToken: DisposalToken);
// Assert
var result = Assert.Single(results);
var diagnostics = result.Diagnostics!.ToArray();
Assert.NotNull(result.Diagnostics);
var diagnostics = result.Diagnostics.ToArray();
Assert.Equal(2, diagnostics.Length);
// Diagnostic ranges contain the projected range for
@ -782,136 +582,142 @@ public class CodeActionEndpointTest : LanguageServerTestBase
Assert.Equal(projectedRange, diagnostics[1].Range);
}
private static IDocumentMappingService CreateDocumentMappingService(LinePositionSpan projectedRange = default)
private CodeActionEndpoint CreateEndpoint(
IDocumentMappingService? documentMappingService = null,
ImmutableArray<IRazorCodeActionProvider> razorCodeActionProviders = default,
ImmutableArray<ICSharpCodeActionProvider> csharpCodeActionProviders = default,
ImmutableArray<IHtmlCodeActionProvider> htmlCodeActionProviders = default,
IClientConnection? clientConnection = null,
LanguageServerFeatureOptions? languageServerFeatureOptions = null,
bool supportsCodeActionResolve = false)
{
if (projectedRange == default)
return new CodeActionEndpoint(
documentMappingService ?? CreateDocumentMappingService(),
razorCodeActionProviders.NullToEmpty(),
csharpCodeActionProviders.NullToEmpty(),
htmlCodeActionProviders.NullToEmpty(),
clientConnection ?? StrictMock.Of<IClientConnection>(),
languageServerFeatureOptions ?? StrictMock.Of<LanguageServerFeatureOptions>(x => x.SupportsFileManipulation == true),
LoggerFactory,
telemetryReporter: null)
{
projectedRange = new LinePositionSpan(new(5, 2), new(5, 2));
_supportsCodeActionResolve = supportsCodeActionResolve
};
}
var documentMappingService = Mock.Of<IDocumentMappingService>(
d => d.TryMapToGeneratedDocumentRange(It.IsAny<IRazorGeneratedDocument>(), It.IsAny<LinePositionSpan>(), out projectedRange) == true &&
d.GetLanguageKind(It.IsAny<RazorCodeDocument>(), It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.CSharp
, MockBehavior.Strict);
return documentMappingService;
}
private static IClientConnection CreateLanguageServer()
private static IDocumentMappingService CreateDocumentMappingService(LinePositionSpan? projectedRange = null)
{
return new TestLanguageServer();
var mock = new StrictMock<IDocumentMappingService>();
// If a range was provided, use that and return true; otherwise, return false.
var (outRange, result) = projectedRange is LinePositionSpan
? (projectedRange.GetValueOrDefault(), true)
: (It.Ref<LinePositionSpan>.IsAny, false);
mock.Setup(x => x.TryMapToGeneratedDocumentRange(It.IsAny<IRazorGeneratedDocument>(), It.IsAny<LinePositionSpan>(), out outRange))
.Returns(result);
return mock.Object;
}
private static RazorCodeDocument CreateCodeDocument(string text)
{
var codeDocument = TestRazorCodeDocument.Create(text);
var sourceDocument = TestRazorSourceDocument.Create(text);
var syntaxTree = RazorSyntaxTree.Parse(sourceDocument);
codeDocument.SetSyntaxTree(syntaxTree);
return codeDocument;
var projectEngine = RazorProjectEngine.Create(builder => { });
return projectEngine.ProcessDesignTime(sourceDocument, "mvc", importSources: [], tagHelpers: []);
}
private class MockRazorCodeActionProvider : IRazorCodeActionProvider
{
public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeActionContext context, CancellationToken cancellationToken)
{
return Task.FromResult<ImmutableArray<RazorVSInternalCodeAction>>([new RazorVSInternalCodeAction()]);
}
}
private static IRazorCodeActionProvider CreateEmptyRazorCodeActionProvider()
=> CreateRazorCodeActionProvider([]);
private class MockMultipleRazorCodeActionProvider : IRazorCodeActionProvider
{
public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeActionContext context, CancellationToken cancellationToken)
{
return Task.FromResult<ImmutableArray<RazorVSInternalCodeAction>>(
[
private static IRazorCodeActionProvider CreateRazorCodeActionProvider()
=> CreateRazorCodeActionProvider(new RazorVSInternalCodeAction());
private static IRazorCodeActionProvider CreateMultipleRazorCodeActionProvider()
=> CreateRazorCodeActionProvider(
new RazorVSInternalCodeAction(),
new RazorVSInternalCodeAction()
]);
}
}
new RazorVSInternalCodeAction());
private class MockCSharpCodeActionProvider : ICSharpCodeActionProvider
{
public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeActionContext _1, ImmutableArray<RazorVSInternalCodeAction> _2, CancellationToken _3)
{
return Task.FromResult<ImmutableArray<RazorVSInternalCodeAction>>(
[
private static IRazorCodeActionProvider CreateRazorCommandProvider()
=> CreateRazorCodeActionProvider(
new RazorVSInternalCodeAction()
]);
}
}
private class MockRazorCommandProvider : IRazorCodeActionProvider
{
public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeActionContext _1, CancellationToken _2)
{
// O# Code Actions don't have `Data`, but `Commands` do
return Task.FromResult<ImmutableArray<RazorVSInternalCodeAction>>(
[
new RazorVSInternalCodeAction() {
Title = "SomeTitle",
Data = JsonSerializer.SerializeToElement(new AddUsingsCodeActionParams()
{
Namespace="Test",
Namespace = "Test",
Uri = new Uri("C:/path/to/Page.razor")
})
}
]);
}
});
private static IRazorCodeActionProvider CreateRazorCodeActionProvider(params ImmutableArray<RazorVSInternalCodeAction> codeActions)
{
var mock = new StrictMock<IRazorCodeActionProvider>();
mock.Setup(x => x.ProvideAsync(It.IsAny<RazorCodeActionContext>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(() => codeActions);
return mock.Object;
}
private class MockEmptyRazorCodeActionProvider : IRazorCodeActionProvider
private static ICSharpCodeActionProvider CreateCSharpCodeActionProvider()
=> CreateCSharpCodeActionProvider([new RazorVSInternalCodeAction()]);
private static ICSharpCodeActionProvider CreateCSharpCodeActionProvider(params ImmutableArray<RazorVSInternalCodeAction> codeActions)
{
public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeActionContext _1, CancellationToken _2)
{
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>();
}
var mock = new StrictMock<ICSharpCodeActionProvider>();
mock.Setup(x => x.ProvideAsync(It.IsAny<RazorCodeActionContext>(), It.IsAny<ImmutableArray<RazorVSInternalCodeAction>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(() => codeActions);
return mock.Object;
}
private class TestLanguageServer : IClientConnection
private sealed class TestClientConnection : IClientConnection
{
public static readonly IClientConnection Instance = new TestClientConnection();
private static readonly string[] s_customTags = ["CodeActionName"];
private TestClientConnection()
{
}
public Task SendNotificationAsync<TParams>(string method, TParams @params, CancellationToken cancellationToken)
{
if (method != CustomMessageNames.RazorProvideCodeActionsEndpoint)
{
throw new InvalidOperationException($"Unexpected method {method}");
}
Assert.Equal(CustomMessageNames.RazorProvideCodeActionsEndpoint, method);
return Task.CompletedTask;
}
public Task SendNotificationAsync(string method, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
=> throw new NotImplementedException();
public Task<TResponse> SendRequestAsync<TParams, TResponse>(string method, TParams @params, CancellationToken cancellationToken)
{
if (method != CustomMessageNames.RazorProvideCodeActionsEndpoint)
{
throw new InvalidOperationException($"Unexpected method {method}");
}
Assert.Equal(CustomMessageNames.RazorProvideCodeActionsEndpoint, method);
if (@params is not DelegatedCodeActionParams delegatedCodeActionParams ||
delegatedCodeActionParams.CodeActionParams is not VSCodeActionParams codeActionParams ||
codeActionParams.Context is not VSInternalCodeActionContext codeActionContext)
{
throw new InvalidOperationException(@params!.GetType().FullName);
}
Assert.NotNull(@params);
var delegatedCodeActionParams = Assert.IsType<DelegatedCodeActionParams>(@params);
Assert.NotNull(delegatedCodeActionParams.CodeActionParams);
Assert.NotNull(delegatedCodeActionParams.CodeActionParams.Context);
var diagnostics = new List<Diagnostic>
{
new Diagnostic()
new()
{
Range = codeActionParams.Range,
Range = delegatedCodeActionParams.CodeActionParams.Range,
Message = "Range"
}
};
if (codeActionContext.SelectionRange is not null)
if (delegatedCodeActionParams.CodeActionParams.Context.SelectionRange is { } selectionRange)
{
diagnostics.Add(new Diagnostic()
diagnostics.Add(new()
{
Range = codeActionContext.SelectionRange,
Range = selectionRange,
Message = "Selection Range"
});
}
@ -924,8 +730,8 @@ public class CodeActionEndpointTest : LanguageServerTestBase
{
new RazorVSInternalCodeAction()
{
Data = JsonSerializer.SerializeToElement(new { CustomTags = new object[] { "CodeActionName" } }),
Diagnostics = diagnostics.ToArray()
Data = JsonSerializer.SerializeToElement(new { CustomTags = s_customTags }),
Diagnostics = [.. diagnostics]
}
};

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

@ -2,15 +2,14 @@
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Protocol.DocumentPresentation;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Moq;
using Xunit;
@ -24,116 +23,83 @@ public class TextDocumentTextPresentationEndpointTests(ITestOutputHelper testOut
public async Task Handle_Html_MakesRequest()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("<div></div>");
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
TestCode code = "<[|d|]iv></div>";
var codeDocument = CreateCodeDocument(code.Text);
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var response = (WorkspaceEdit?)null;
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorTextPresentationEndpoint, It.IsAny<IRazorPresentationParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response)
.Verifiable();
var endpoint = new TextDocumentTextPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
LoggerFactory);
var clientConnection = CreateClientConnection(response: null, verifiable: true);
var endpoint = CreateEndpoint(clientConnection);
var parameters = new TextPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1),
TextDocument = new() { Uri = uri },
Range = codeDocument.Source.Text.GetRange(code.Span),
Text = "Hi there"
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
var result = await endpoint.HandleRequestAsync(parameters, requestContext, DisposalToken);
_ = await endpoint.HandleRequestAsync(parameters, requestContext, DisposalToken);
// Assert
clientConnection.Verify();
Mock.Get(clientConnection).Verify();
}
[Fact]
public async Task Handle_CSharp_DoesNotMakeRequest()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("@counter");
var csharpDocument = codeDocument.GetCSharpDocument();
TestCode code = "@[|c|]ounter";
var codeDocument = CreateCodeDocument(code.Text);
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var projectedRange = It.IsAny<LinePositionSpan>();
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.CSharp &&
s.TryMapToGeneratedDocumentRange(csharpDocument, It.IsAny<LinePositionSpan>(), out projectedRange) == true, MockBehavior.Strict);
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
var endpoint = new TextDocumentTextPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
LoggerFactory);
var clientConnection = StrictMock.Of<IClientConnection>();
var endpoint = CreateEndpoint(clientConnection);
var parameters = new TextPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1),
TextDocument = new() { Uri = uri },
Range = codeDocument.Source.Text.GetRange(code.Span),
Text = "Hi there"
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
var result = await endpoint.HandleRequestAsync(parameters, requestContext, DisposalToken);
_ = await endpoint.HandleRequestAsync(parameters, requestContext, DisposalToken);
// Assert
clientConnection.Verify();
Mock.Get(clientConnection)
.VerifySendRequest<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorTextPresentationEndpoint, Times.Never);
}
[Fact]
public async Task Handle_DocumentNotFound_ReturnsNull()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("<div></div>");
TestCode code = "<[|d|]iv></div>";
var codeDocument = CreateCodeDocument(code.Text);
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var response = (WorkspaceEdit?)null;
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorTextPresentationEndpoint, It.IsAny<IRazorPresentationParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var endpoint = new TextDocumentTextPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
LoggerFactory);
var clientConnection = CreateClientConnection(response: null);
var endpoint = CreateEndpoint(clientConnection);
var parameters = new TextPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1),
TextDocument = new() { Uri = uri },
Range = codeDocument.Source.Text.GetRange(code.Span),
Text = "Hi there"
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -147,35 +113,24 @@ public class TextDocumentTextPresentationEndpointTests(ITestOutputHelper testOut
public async Task Handle_UnsupportedCodeDocument_ReturnsNull()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("<div></div>");
TestCode code = "<[|d|]iv></div>";
var codeDocument = CreateCodeDocument(code.Text);
codeDocument.SetUnsupported();
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var response = new WorkspaceEdit();
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorTextPresentationEndpoint, It.IsAny<IRazorPresentationParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var endpoint = new TextDocumentTextPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
LoggerFactory);
var clientConnection = CreateClientConnection(response: new WorkspaceEdit());
var endpoint = CreateEndpoint(clientConnection);
var parameters = new TextPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1),
TextDocument = new() { Uri = uri },
Range = codeDocument.Source.Text.GetRange(code.Span),
Text = "Hi there"
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -184,4 +139,13 @@ public class TextDocumentTextPresentationEndpointTests(ITestOutputHelper testOut
// Assert
Assert.Null(result);
}
private TextDocumentTextPresentationEndpoint CreateEndpoint(IClientConnection clientConnection)
=> new(StrictMock.Of<IDocumentMappingService>(), clientConnection, FilePathService, LoggerFactory);
private static IClientConnection CreateClientConnection(WorkspaceEdit? response, bool verifiable = false)
=> TestMocks.CreateClientConnection(builder =>
{
builder.SetupSendRequest<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorTextPresentationEndpoint, response, verifiable);
});
}

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

@ -2,11 +2,11 @@
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.ProjectSystem;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer;
using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Protocol.DocumentPresentation;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Moq;
using Xunit;
using Xunit.Abstractions;
using static Microsoft.AspNetCore.Razor.Language.CommonMetadata;
@ -30,46 +29,43 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
// Arrange
var projectManager = CreateProjectSnapshotManager();
var project = await projectManager.UpdateAsync(updater => updater.CreateAndAddProject("c:/path/project.csproj"));
var project = await projectManager.UpdateAsync(updater =>
{
return updater.CreateAndAddProject("c:/path/project.csproj");
});
await projectManager.CreateAndAddDocumentAsync(project, "c:/path/index.razor");
await projectManager.CreateAndAddDocumentAsync(project, "c:/path/MyTagHelper.razor");
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(It.IsAny<RazorCodeDocument>(), It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var droppedUri = new Uri("file:///c:/path/MyTagHelper.razor");
var builder = TagHelperDescriptorBuilder.Create("MyTagHelper", "MyAssembly");
builder.SetMetadata(TypeNameIdentifier("MyTagHelper"), TypeNamespace("TestRootNamespace"));
var tagHelperDescriptor = builder.Build();
await projectManager.UpdateAsync(updater => updater.ProjectWorkspaceStateChanged(project.Key, ProjectWorkspaceState.Create([tagHelperDescriptor])));
await projectManager.UpdateAsync(updater =>
{
updater.ProjectWorkspaceStateChanged(project.Key, ProjectWorkspaceState.Create([builder.Build()]));
});
var razorFilePath = "c:/path/index.razor";
var uri = new Uri(razorFilePath);
await projectManager.UpdateAsync(updater => updater.DocumentOpened(project.Key, razorFilePath, SourceText.From("<div></div>")));
var documentSnapshot = projectManager.GetLoadedProject(project.Key).GetDocument(razorFilePath).AssumeNotNull();
await projectManager.UpdateAsync(updater =>
{
updater.DocumentOpened(project.Key, razorFilePath, SourceText.From("<div></div>"));
});
var documentContextFactory = new DocumentContextFactory(projectManager, LoggerFactory);
Assert.True(documentContextFactory.TryCreate(uri, null, out var documentContext));
Assert.True(documentContextFactory.TryCreate(uri, projectContext: null, out var documentContext));
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
var endpoint = new TextDocumentUriPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
documentContextFactory,
LoggerFactory);
var endpoint = CreateEndpoint(documentContextFactory);
var parameters = new UriPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
TextDocument = new() { Uri = uri },
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1),
Uris = [droppedUri]
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -77,7 +73,12 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
// Assert
Assert.NotNull(result);
Assert.Equal("<MyTagHelper />", result.DocumentChanges!.Value.First[0].Edits[0].NewText);
Assert.NotNull(result.DocumentChanges);
var documentChanges = result.DocumentChanges.GetValueOrDefault();
Assert.True(documentChanges.TryGetFirst(out var documentEdits));
Assert.Equal("<MyTagHelper />", documentEdits[0].Edits[0].NewText);
}
[OSSkipConditionFact(["OSX", "Linux"])]
@ -86,43 +87,39 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
// Arrange
var projectManager = CreateProjectSnapshotManager();
var project = await projectManager.UpdateAsync(updater => updater.CreateAndAddProject("c:/path/project.csproj"));
var project = await projectManager.UpdateAsync(updater =>
{
return updater.CreateAndAddProject("c:/path/project.csproj");
});
await projectManager.CreateAndAddDocumentAsync(project, "c:/path/index.razor");
await projectManager.CreateAndAddDocumentAsync(project, "c:/path/MyTagHelper.razor");
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(It.IsAny<RazorCodeDocument>(), It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var droppedUri = new Uri("file:///c:/path/MyTagHelper.razor");
var builder = TagHelperDescriptorBuilder.Create("MyTagHelper", "MyAssembly");
builder.SetMetadata(TypeNameIdentifier("MyTagHelper"), TypeNamespace("TestRootNamespace"));
var tagHelperDescriptor = builder.Build();
await projectManager.UpdateAsync(updater => updater.ProjectWorkspaceStateChanged(project.Key, ProjectWorkspaceState.Create([tagHelperDescriptor])));
await projectManager.UpdateAsync(updater =>
{
updater.ProjectWorkspaceStateChanged(project.Key, ProjectWorkspaceState.Create([builder.Build()]));
});
var razorFilePath = "c:/path/index.razor";
var uri = new Uri(razorFilePath);
await projectManager.UpdateAsync(updater => updater.DocumentOpened(project.Key, razorFilePath, SourceText.From("<div></div>")));
var documentSnapshot = projectManager.GetLoadedProject(project.Key).GetDocument(razorFilePath).AssumeNotNull();
await projectManager.UpdateAsync(updater =>
{
updater.DocumentOpened(project.Key, razorFilePath, SourceText.From("<div></div>"));
});
var documentContextFactory = new DocumentContextFactory(projectManager, LoggerFactory);
Assert.True(documentContextFactory.TryCreate(uri, null, out var documentContext));
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
var endpoint = new TextDocumentUriPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
documentContextFactory,
LoggerFactory);
var endpoint = CreateEndpoint(documentContextFactory);
var parameters = new UriPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
TextDocument = new() { Uri = uri },
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1),
Uris =
[
@ -131,6 +128,7 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
droppedUri,
]
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -138,7 +136,12 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
// Assert
Assert.NotNull(result);
Assert.Equal("<MyTagHelper />", result!.DocumentChanges!.Value.First[0].Edits[0].NewText);
Assert.NotNull(result.DocumentChanges);
var documentChanges = result.DocumentChanges.GetValueOrDefault();
Assert.True(documentChanges.TryGetFirst(out var documentEdits));
Assert.Equal("<MyTagHelper />", documentEdits[0].Edits[0].NewText);
}
[OSSkipConditionFact(["OSX", "Linux"])]
@ -147,13 +150,14 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
// Arrange
var projectManager = CreateProjectSnapshotManager();
var project = await projectManager.UpdateAsync(updater => updater.CreateAndAddProject("c:/path/project.csproj"));
var project = await projectManager.UpdateAsync(updater =>
{
return updater.CreateAndAddProject("c:/path/project.csproj");
});
await projectManager.CreateAndAddDocumentAsync(project, "c:/path/index.razor");
await projectManager.CreateAndAddDocumentAsync(project, "c:/path/fetchdata.razor");
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(It.IsAny<RazorCodeDocument>(), It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var droppedUri = new Uri("file:///c:/path/fetchdata.razor");
var builder = TagHelperDescriptorBuilder.Create("FetchData", "MyAssembly");
builder.SetMetadata(TypeNameIdentifier("FetchData"), TypeNamespace("TestRootNamespace"));
@ -163,36 +167,32 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
b.Name = "MyAttribute";
});
builder.BindAttribute(b => b.Name = "MyNonRequiredAttribute");
var tagHelperDescriptor = builder.Build();
await projectManager.UpdateAsync(updater => updater.ProjectWorkspaceStateChanged(project.Key, ProjectWorkspaceState.Create([tagHelperDescriptor])));
await projectManager.UpdateAsync(updater =>
{
updater.ProjectWorkspaceStateChanged(project.Key, ProjectWorkspaceState.Create([builder.Build()]));
});
var razorFilePath = "c:/path/index.razor";
var uri = new Uri(razorFilePath);
await projectManager.UpdateAsync(updater => updater.DocumentOpened(project.Key, razorFilePath, SourceText.From("<div></div>")));
var documentSnapshot = projectManager.GetLoadedProject(project.Key).GetDocument(razorFilePath).AssumeNotNull();
await projectManager.UpdateAsync(updater =>
{
updater.DocumentOpened(project.Key, razorFilePath, SourceText.From("<div></div>"));
});
var documentContextFactory = new DocumentContextFactory(projectManager, LoggerFactory);
Assert.True(documentContextFactory.TryCreate(uri, null, out var documentContext));
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
var endpoint = new TextDocumentUriPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
documentContextFactory,
LoggerFactory);
var endpoint = CreateEndpoint(documentContextFactory);
var parameters = new UriPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
TextDocument = new() { Uri = uri },
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1),
Uris = [droppedUri]
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -200,51 +200,35 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
// Assert
Assert.NotNull(result);
Assert.Equal("<FetchData MyAttribute=\"\" />", result.DocumentChanges!.Value.First[0].Edits[0].NewText);
Assert.NotNull(result.DocumentChanges);
var documentChanges = result.DocumentChanges.GetValueOrDefault();
Assert.True(documentChanges.TryGetFirst(out var documentEdits));
Assert.Equal("<FetchData MyAttribute=\"\" />", documentEdits[0].Edits[0].NewText);
}
[Fact]
public async Task Handle_NoTypeNameIdentifier_ReturnsNull()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("<div></div>");
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var componentCodeDocument = TestRazorCodeDocument.Create("<div></div>");
var droppedUri = new Uri("file:///c:/path/MyTagHelper.razor");
var builder = TagHelperDescriptorBuilder.Create("MyTagHelper", "MyAssembly");
var tagHelperDescriptor = builder.Build();
var documentSnapshot = Mock.Of<IDocumentSnapshot>(s => s.GetGeneratedOutputAsync(It.IsAny<bool>()) == Task.FromResult(componentCodeDocument), MockBehavior.Strict);
var codeDocument = CreateCodeDocument("<div></div>");
var uri = new Uri("file://path/test.razor");
var droppedUri = new Uri("file:///c:/path/MyTagHelper.razor");
var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument);
var documentContext = CreateDocumentContext(uri, codeDocument);
var response = (WorkspaceEdit?)null;
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorUriPresentationEndpoint, It.IsAny<IRazorPresentationParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var endpoint = new TextDocumentUriPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
documentContextFactory,
LoggerFactory);
var clientConnection = CreateClientConnection(response: null);
var endpoint = CreateEndpoint(documentContextFactory, clientConnection);
var parameters = new UriPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
TextDocument = new() { Uri = uri },
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1),
Uris = [droppedUri]
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -258,36 +242,18 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
public async Task Handle_MultipleUris_ReturnsNull()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("<div></div>");
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var documentSnapshot = Mock.Of<IDocumentSnapshot>(s => s.GetGeneratedOutputAsync(false) == Task.FromResult(codeDocument), MockBehavior.Strict);
var codeDocument = CreateCodeDocument("<div></div>");
var uri = new Uri("file://path/test.razor");
var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument);
var documentContext = CreateDocumentContext(uri, codeDocument);
var response = (WorkspaceEdit?)null;
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorUriPresentationEndpoint, It.IsAny<IRazorPresentationParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var endpoint = new TextDocumentUriPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
documentContextFactory,
LoggerFactory);
var clientConnection = CreateClientConnection(response: null);
var endpoint = CreateEndpoint(documentContextFactory, clientConnection);
var parameters = new UriPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
TextDocument = new() { Uri = uri },
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1),
Uris =
[
@ -296,6 +262,7 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
new Uri("file:///c:/path/MyTagHelper.razor"),
]
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -309,40 +276,23 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
public async Task Handle_NotComponent_ReturnsNull()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("<div></div>");
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var documentSnapshot = Mock.Of<IDocumentSnapshot>(s => s.GetGeneratedOutputAsync(false) == Task.FromResult(codeDocument), MockBehavior.Strict);
var codeDocument = CreateCodeDocument("<div></div>");
var droppedUri = new Uri("file:///c:/path/MyTagHelper.cshtml");
var uri = new Uri("file://path/test.razor");
var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument);
var documentContext = CreateDocumentContext(uri, codeDocument);
var response = (WorkspaceEdit?)null;
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorUriPresentationEndpoint, It.IsAny<IRazorPresentationParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var endpoint = new TextDocumentUriPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
documentContextFactory,
LoggerFactory);
var clientConnection = CreateClientConnection(response: null);
var endpoint = CreateEndpoint(documentContextFactory, clientConnection);
var parameters = new UriPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
TextDocument = new() { Uri = uri },
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1),
Uris = [droppedUri]
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -358,47 +308,49 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
// Arrange
var projectManager = CreateProjectSnapshotManager();
var project = await projectManager.UpdateAsync(updater => updater.CreateAndAddProject("c:/path/project.csproj"));
var project = await projectManager.UpdateAsync(updater =>
{
return updater.CreateAndAddProject("c:/path/project.csproj");
});
await projectManager.CreateAndAddDocumentAsync(project, "c:/path/index.razor");
await projectManager.CreateAndAddDocumentAsync(project, "c:/path/fetchdata.razor");
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(It.IsAny<RazorCodeDocument>(), It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var droppedUri1 = new Uri("file:///c:/path/fetchdata.razor.cs");
var droppedUri2 = new Uri("file:///c:/path/fetchdata.razor");
var builder = TagHelperDescriptorBuilder.Create("FetchData", "MyAssembly");
builder.SetMetadata(TypeNameIdentifier("FetchData"), TypeNamespace("TestRootNamespace"));
var tagHelperDescriptor = builder.Build();
await projectManager.UpdateAsync(updater => updater.ProjectWorkspaceStateChanged(project.Key, ProjectWorkspaceState.Create([tagHelperDescriptor])));
await projectManager.UpdateAsync(updater =>
{
updater.ProjectWorkspaceStateChanged(project.Key, ProjectWorkspaceState.Create([builder.Build()]));
});
var razorFilePath = "c:/path/index.razor";
var uri = new Uri(razorFilePath);
await projectManager.UpdateAsync(updater => updater.DocumentOpened(project.Key, razorFilePath, SourceText.From("<div></div>")));
var documentSnapshot = projectManager.GetLoadedProject(project.Key).GetDocument(razorFilePath).AssumeNotNull();
await projectManager.UpdateAsync(updater =>
{
updater.DocumentOpened(project.Key, razorFilePath, SourceText.From("<div></div>"));
});
var documentSnapshot = projectManager
.GetLoadedProject(project.Key)
.GetDocument(razorFilePath);
Assert.NotNull(documentSnapshot);
var documentContextFactory = new DocumentContextFactory(projectManager, LoggerFactory);
Assert.True(documentContextFactory.TryCreate(uri, null, out var documentContext));
Assert.True(documentContextFactory.TryCreate(uri, projectContext: null, out var documentContext));
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
var endpoint = new TextDocumentUriPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
documentContextFactory,
LoggerFactory);
var endpoint = CreateEndpoint(documentContextFactory);
var parameters = new UriPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
TextDocument = new() { Uri = uri },
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1),
Uris = [droppedUri1, droppedUri2]
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -406,47 +358,32 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
// Assert
Assert.NotNull(result);
Assert.Equal("<FetchData />", result!.DocumentChanges!.Value.First[0].Edits[0].NewText);
Assert.NotNull(result.DocumentChanges);
var documentChanges = result.DocumentChanges.GetValueOrDefault();
Assert.True(documentChanges.TryGetFirst(out var documentEdits));
Assert.Equal("<FetchData />", documentEdits[0].Edits[0].NewText);
}
[Fact]
public async Task Handle_CSharp_ReturnsNull()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("@counter");
var csharpDocument = codeDocument.GetCSharpDocument();
var codeDocument = CreateCodeDocument("@counter");
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var projectedRange = It.IsAny<LinePositionSpan>();
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.CSharp &&
s.TryMapToGeneratedDocumentRange(csharpDocument, It.IsAny<LinePositionSpan>(), out projectedRange) == true, MockBehavior.Strict);
var documentSnapshot = Mock.Of<IDocumentSnapshot>(s => s.GetGeneratedOutputAsync(false) == Task.FromResult(codeDocument), MockBehavior.Strict);
var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument);
var response = (WorkspaceEdit?)null;
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorUriPresentationEndpoint, It.IsAny<IRazorPresentationParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var endpoint = new TextDocumentUriPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
documentContextFactory,
LoggerFactory);
var clientConnection = CreateClientConnection(response: null);
var endpoint = CreateEndpoint(documentContextFactory, clientConnection);
var parameters = new UriPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
TextDocument = new() { Uri = uri },
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1)
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -460,37 +397,21 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
public async Task Handle_DocumentNotFound_ReturnsNull()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("<div></div>");
var codeDocument = CreateCodeDocument("<div></div>");
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var documentSnapshot = Mock.Of<IDocumentSnapshot>(s => s.GetGeneratedOutputAsync(false) == Task.FromResult(codeDocument), MockBehavior.Strict);
var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument);
var response = (WorkspaceEdit?)null;
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorUriPresentationEndpoint, It.IsAny<IRazorPresentationParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var endpoint = new TextDocumentUriPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
documentContextFactory,
LoggerFactory);
var clientConnection = CreateClientConnection(response: null);
var endpoint = CreateEndpoint(documentContextFactory, clientConnection);
var parameters = new UriPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
TextDocument = new() { Uri = uri },
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1)
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -504,38 +425,22 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
public async Task Handle_UnsupportedCodeDocument_ReturnsNull()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("<div></div>");
var codeDocument = CreateCodeDocument("<div></div>");
codeDocument.SetUnsupported();
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var documentSnapshot = Mock.Of<IDocumentSnapshot>(s => s.GetGeneratedOutputAsync(false) == Task.FromResult(codeDocument), MockBehavior.Strict);
var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument);
var response = new WorkspaceEdit();
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorUriPresentationEndpoint, It.IsAny<IRazorPresentationParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var endpoint = new TextDocumentUriPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
documentContextFactory,
LoggerFactory);
var clientConnection = CreateClientConnection(response: new WorkspaceEdit());
var endpoint = CreateEndpoint(documentContextFactory, clientConnection);
var parameters = new UriPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
TextDocument = new() { Uri = uri },
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1)
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -549,37 +454,21 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
public async Task Handle_NoUris_ReturnsNull()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("<div></div>");
var codeDocument = CreateCodeDocument("<div></div>");
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var documentSnapshot = Mock.Of<IDocumentSnapshot>(s => s.GetGeneratedOutputAsync(false) == Task.FromResult(codeDocument), MockBehavior.Strict);
var documentContextFactory = CreateDocumentContextFactory(uri, codeDocument);
var response = (WorkspaceEdit?)null;
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorUriPresentationEndpoint, It.IsAny<IRazorPresentationParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var endpoint = new TextDocumentUriPresentationEndpoint(
documentMappingService,
clientConnection.Object,
FilePathService,
documentContextFactory,
LoggerFactory);
var clientConnection = CreateClientConnection(response: null);
var endpoint = CreateEndpoint(documentContextFactory, clientConnection);
var parameters = new UriPresentationParams()
{
TextDocument = new TextDocumentIdentifier
{
Uri = uri
},
TextDocument = new() { Uri = uri },
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 1)
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -588,4 +477,22 @@ public class TextDocumentUriPresentationEndpointTests(ITestOutputHelper testOutp
// Assert
Assert.Null(result);
}
private TextDocumentUriPresentationEndpoint CreateEndpoint(
IDocumentContextFactory documentContextFactory,
IClientConnection? clientConnection = null)
{
return new TextDocumentUriPresentationEndpoint(
StrictMock.Of<IDocumentMappingService>(),
clientConnection ?? StrictMock.Of<IClientConnection>(),
FilePathService,
documentContextFactory,
LoggerFactory);
}
private static IClientConnection CreateClientConnection(WorkspaceEdit? response)
=> TestMocks.CreateClientConnection(builder =>
{
builder.SetupSendRequest<IRazorPresentationParams, WorkspaceEdit?>(CustomMessageNames.RazorUriPresentationEndpoint, response);
});
}

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

@ -139,7 +139,7 @@ public class FormattingTestBase : RazorToolingIntegrationTestBase
var filePathService = new LSPFilePathService(TestLanguageServerFeatureOptions.Instance);
var mappingService = new LspDocumentMappingService(
filePathService, new TestDocumentContextFactory(), LoggerFactory);
var languageKind = mappingService.GetLanguageKind(codeDocument, positionAfterTrigger, rightAssociative: false);
var languageKind = codeDocument.GetLanguageKind(positionAfterTrigger, rightAssociative: false);
var formattingService = await TestRazorFormattingService.CreateWithFullSupportAsync(LoggerFactory, codeDocument, razorLSPOptions);
var options = new FormattingOptions()
@ -213,7 +213,7 @@ public class FormattingTestBase : RazorToolingIntegrationTestBase
var filePathService = new LSPFilePathService(TestLanguageServerFeatureOptions.Instance);
var mappingService = new LspDocumentMappingService(filePathService, new TestDocumentContextFactory(), LoggerFactory);
var languageKind = mappingService.GetLanguageKind(codeDocument, positionAfterTrigger, rightAssociative: false);
var languageKind = codeDocument.GetLanguageKind(positionAfterTrigger, rightAssociative: false);
if (languageKind == RazorLanguageKind.Html)
{
throw new NotImplementedException("Code action formatting is not yet supported for HTML in Razor.");

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

@ -1,8 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
#nullable disable
using System;
using System.Linq;
using System.Threading;
@ -13,6 +11,7 @@ using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.LanguageServer.Hover;
using Microsoft.AspNetCore.Razor.LanguageServer.Tooltip;
using Microsoft.AspNetCore.Razor.ProjectSystem;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer;
using Microsoft.AspNetCore.Razor.Test.Common.Mef;
using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem;
@ -20,7 +19,6 @@ using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Workspaces;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.Text.Adornments;
@ -56,21 +54,22 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_Element()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<$$test1></test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("**Test1TagHelper**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 1, length: 5);
Assert.Equal(expectedRange, hover.Range);
@ -80,23 +79,24 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_Element_WithParent()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1>
<Som$$eChild></SomeChild>
</test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("**SomeChild**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 2, character: 5, length: 9);
Assert.Equal(expectedRange, hover.Range);
@ -106,25 +106,26 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_Attribute_WithParent()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1>
<SomeChild [|att$$ribute|]="test"></SomeChild>
</test1>
""";
TestFileMarkupParser.GetPositionAndSpan(txt, out txt, out var cursorPosition, out var span);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("**Attribute**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
var expectedRange = codeDocument.Source.Text.GetRange(span);
var expectedRange = codeDocument.Source.Text.GetRange(code.Span);
Assert.Equal(expectedRange, hover.Range);
}
@ -132,21 +133,22 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_Element_EndTag()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1></$$test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("**Test1TagHelper**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 9, length: 5);
Assert.Equal(expectedRange, hover.Range);
@ -156,21 +158,22 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_Attribute()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1 $$bool-val='true'></test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("**BoolVal**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
Assert.DoesNotContain("**IntVal**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 7, length: 8);
@ -181,22 +184,23 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_AttributeTrailingEdge()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1 bool-val$$ minimized></test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var edgeLocation = cursorPosition;
var location = new SourceLocation(edgeLocation, 0, edgeLocation);
var edgeLocation = code.Position;
var location = new SourceLocation(edgeLocation, lineIndex: 0, edgeLocation);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("**BoolVal**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
Assert.DoesNotContain("**IntVal**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 7, length: 8);
@ -207,16 +211,15 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_AttributeValue_ReturnsNull()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1 bool-val='$$true'></test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
@ -229,16 +232,15 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_AfterAttributeEquals_ReturnsNull()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1 bool-val=$$'true'></test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
@ -251,16 +253,15 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_AttributeEnd_ReturnsNull()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1 bool-val='true'$$></test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
@ -273,21 +274,22 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_MinimizedAttribute()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1 $$bool-val></test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("**BoolVal**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
Assert.DoesNotContain("**IntVal**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 7, length: 8);
@ -298,7 +300,7 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_DirectiveAttribute_HasResult()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<any @t$$est="Increment" />
@code{
@ -306,18 +308,18 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
}
}
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, "text.razor", DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, "text.razor", DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("**Test**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 5, length: 5);
Assert.Equal(expectedRange, hover.Range);
@ -327,21 +329,22 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_MalformedElement()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<$$test1<hello
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("**Test1TagHelper**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 1, length: 5);
Assert.Equal(expectedRange, hover.Range);
@ -351,21 +354,22 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_MalformedAttribute()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1 $$bool-val=\"aslj alsk<strong>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("**BoolVal**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
Assert.DoesNotContain("**IntVal**", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 7, length: 8);
@ -376,16 +380,15 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_HTML_MarkupElement()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<p><$$strong></strong></p>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreateMarkDownCapabilities(), DisposalToken);
@ -398,22 +401,23 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_PlainTextElement()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<$$test1></test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreatePlainTextCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("Test1TagHelper", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
Assert.Equal(MarkupKind.PlainText, ((MarkupContent)hover.Contents).Kind);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 1, length: 5);
@ -424,22 +428,23 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_PlainTextElement_EndTag()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1></$$test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreatePlainTextCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("Test1TagHelper", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
Assert.Equal(MarkupKind.PlainText, ((MarkupContent)hover.Contents).Kind);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 9, length: 5);
@ -450,21 +455,22 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_TextComponent()
{
// Arrange
var txt = """
TestCode code = """
<$$Text></Text>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: true, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: true, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.razor", codeDocument, location, CreatePlainTextCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("Text", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
Assert.Equal(MarkupKind.PlainText, ((MarkupContent)hover.Contents).Kind);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 0, character: 1, length: 4);
@ -475,23 +481,24 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_TextComponent_NestedInHtml()
{
// Arrange
var txt = """
TestCode code = """
<div>
<$$Text></Text>
</div>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: true, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: true, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.razor", codeDocument, location, CreatePlainTextCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("Text", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
Assert.Equal(MarkupKind.PlainText, ((MarkupContent)hover.Contents).Kind);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 5, length: 4);
@ -502,19 +509,18 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_TextComponent_NestedInCSharp()
{
// Arrange
var txt = """
TestCode code = """
@if (true)
{
<$$Text></Text>
}
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: true, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: true, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.razor", codeDocument, location, CreatePlainTextCapabilities(), DisposalToken);
@ -527,7 +533,7 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_TextComponent_NestedInCSharpAndText()
{
// Arrange
var txt = """
TestCode code = """
@if (true)
{
<text>
@ -535,18 +541,19 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
</text>
}
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: true, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: true, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.razor", codeDocument, location, CreatePlainTextCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("Text", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
Assert.Equal(MarkupKind.PlainText, ((MarkupContent)hover.Contents).Kind);
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 3, character: 9, length: 4);
@ -557,22 +564,23 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_PlainTextAttribute()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1 $$bool-val></test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreatePlainTextCapabilities(), DisposalToken);
// Assert
Assert.NotNull(hover);
Assert.NotNull(hover.Contents);
Assert.Contains("BoolVal", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
Assert.DoesNotContain("IntVal", ((MarkupContent)hover.Contents).Value, StringComparison.Ordinal);
Assert.Equal(MarkupKind.PlainText, ((MarkupContent)hover.Contents).Kind);
@ -584,17 +592,16 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_HTML_PlainTextElement()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<p><$$strong></strong></p>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreatePlainTextCapabilities(), DisposalToken);
@ -607,18 +614,17 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_HTML_PlainTextAttribute()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<p><strong class="$$weak"></strong></p>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
// Act
var hover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, CreatePlainTextCapabilities(), DisposalToken);
@ -631,16 +637,15 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_Element_VSClient_ReturnVSHover()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<$$test1></test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
var clientCapabilities = CreateMarkDownCapabilities();
clientCapabilities.SupportsVisualStudioExtensions = true;
@ -648,11 +653,14 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
var vsHover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, clientCapabilities, DisposalToken);
// Assert
Assert.NotNull(vsHover);
Assert.NotNull(vsHover.Contents);
Assert.False(vsHover.Contents.Value.TryGetFourth(out var _));
Assert.True(vsHover.Contents.Value.TryGetThird(out var _) && !vsHover.Contents.Value.Third.Any());
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 1, length: 5);
Assert.Equal(expectedRange, vsHover.Range);
Assert.NotNull(vsHover.RawContent);
var container = (ContainerElement)vsHover.RawContent;
var containerElements = container.Elements.ToList();
Assert.Equal(ContainerElementStyle.Stacked, container.Style);
@ -671,17 +679,16 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task GetHoverInfo_TagHelper_Attribute_VSClient_ReturnVSHover()
{
// Arrange
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<test1 $$bool-val='true'></test1>
""";
TestFileMarkupParser.GetPosition(txt, out txt, out var cursorPosition);
var codeDocument = CreateCodeDocument(txt, isRazorFile: false, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, isRazorFile: false, DefaultTagHelpers);
var service = GetHoverService();
var serviceAccessor = service.GetTestAccessor();
var location = new SourceLocation(cursorPosition, -1, -1);
var location = new SourceLocation(code.Position, lineIndex: -1, characterIndex: -1);
var clientCapabilities = CreateMarkDownCapabilities();
clientCapabilities.SupportsVisualStudioExtensions = true;
@ -689,11 +696,14 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
var vsHover = await serviceAccessor.GetHoverInfoAsync("file.cshtml", codeDocument, location, clientCapabilities, DisposalToken);
// Assert
Assert.False(vsHover.Contents.Value.TryGetFourth(out var _));
Assert.NotNull(vsHover);
Assert.NotNull(vsHover.Contents);
Assert.False(vsHover.Contents.Value.TryGetFourth(out _));
Assert.True(vsHover.Contents.Value.TryGetThird(out var markedStrings) && !markedStrings.Any());
var expectedRange = VsLspFactory.CreateSingleLineRange(line: 1, character: 7, length: 8);
Assert.Equal(expectedRange, vsHover.Range);
Assert.NotNull(vsHover.RawContent);
var container = (ContainerElement)vsHover.RawContent;
var containerElements = container.Elements.ToList();
Assert.Equal(ContainerElementStyle.Stacked, container.Style);
@ -716,43 +726,40 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task Handle_Hover_SingleServer_CallsDelegatedLanguageServer()
{
// Arrange
var languageServerFeatureOptions = Mock.Of<LanguageServerFeatureOptions>(options => options.SingleServerSupport == true && options.UseRazorCohostServer == false, MockBehavior.Strict);
var languageServerFeatureOptions = StrictMock.Of<LanguageServerFeatureOptions>(options =>
options.SingleServerSupport == true &&
options.UseRazorCohostServer == false);
var delegatedHover = new VSInternalHover();
var clientConnectionMock = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnectionMock
.Setup(c => c.SendRequestAsync<IDelegatedParams, VSInternalHover>(CustomMessageNames.RazorHoverEndpointName, It.IsAny<DelegatedPositionParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(delegatedHover);
var clientConnection = TestMocks.CreateClientConnection(builder =>
{
builder.SetupSendRequest<IDelegatedParams, VSInternalHover>(CustomMessageNames.RazorHoverEndpointName, response: delegatedHover);
});
var documentMappingServiceMock = new Mock<IDocumentMappingService>(MockBehavior.Strict);
documentMappingServiceMock
.Setup(c => c.GetLanguageKind(It.IsAny<RazorCodeDocument>(), It.IsAny<int>(), It.IsAny<bool>()))
.Returns(RazorLanguageKind.CSharp);
var documentMappingServiceMock = new StrictMock<IDocumentMappingService>();
var outRange = new LinePositionSpan();
documentMappingServiceMock
.Setup(c => c.TryMapToGeneratedDocumentRange(It.IsAny<IRazorGeneratedDocument>(), It.IsAny<LinePositionSpan>(), out outRange))
.Setup(x => x.TryMapToGeneratedDocumentRange(It.IsAny<IRazorGeneratedDocument>(), It.IsAny<LinePositionSpan>(), out outRange))
.Returns(true);
var projectedPosition = new LinePosition(1, 1);
var projectedIndex = 1;
documentMappingServiceMock.Setup(
c => c.TryMapToGeneratedDocumentPosition(It.IsAny<IRazorGeneratedDocument>(), It.IsAny<int>(), out projectedPosition, out projectedIndex))
documentMappingServiceMock
.Setup(x => x.TryMapToGeneratedDocumentPosition(It.IsAny<IRazorGeneratedDocument>(), It.IsAny<int>(), out projectedPosition, out projectedIndex))
.Returns(true);
var endpoint = CreateEndpoint(languageServerFeatureOptions, documentMappingServiceMock.Object, clientConnectionMock.Object);
var endpoint = CreateEndpoint(languageServerFeatureOptions, documentMappingServiceMock.Object, clientConnection);
var (documentContext, position) = CreateDefaultDocumentContext();
var requestContext = CreateRazorRequestContext(documentContext);
var request = new TextDocumentPositionParams
{
TextDocument = new TextDocumentIdentifier
{
Uri = new Uri("C:/text.razor")
},
Position = VsLspFactory.CreatePosition(1, 0),
TextDocument = new() { Uri = new Uri("C:/text.razor") },
Position = position,
};
var documentContext = CreateDefaultDocumentContext();
var requestContext = CreateRazorRequestContext(documentContext: documentContext);
// Act
var result = await endpoint.HandleRequestAsync(request, requestContext, DisposalToken);
@ -765,7 +772,7 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task Handle_Hover_SingleServer_CSharpVariable()
{
// Arrange
var input = """
TestCode code = """
<div></div>
@{
@ -776,14 +783,16 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
""";
// Act
var result = await GetResultFromSingleServerEndpointAsync(input);
var result = await GetResultFromSingleServerEndpointAsync(code);
// Assert
Assert.NotNull(result);
var range = result.Range;
var expected = VsLspFactory.CreateSingleLineRange(line: 3, character: 8, length: 10);
Assert.Equal(expected, range);
Assert.NotNull(result.RawContent);
var rawContainer = (ContainerElement)result.RawContent;
var embeddedContainerElement = (ContainerElement)rawContainer.Elements.Single();
@ -799,21 +808,23 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task Handle_Hover_SingleServer_Component()
{
// Arrange
var input = """
TestCode code = """
@addTagHelper *, TestAssembly
<$$test1></test1>
""";
// Act
var result = await GetResultFromSingleServerEndpointAsync(input);
var result = await GetResultFromSingleServerEndpointAsync(code);
// Assert
Assert.NotNull(result);
var range = result.Range;
var expected = VsLspFactory.CreateSingleLineRange(line: 2, character: 1, length: 5);
Assert.Equal(expected, range);
Assert.NotNull(result.RawContent);
var rawContainer = (ContainerElement)result.RawContent;
var embeddedContainerElement = (ContainerElement)rawContainer.Elements.Single();
@ -826,21 +837,23 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
public async Task Handle_Hover_SingleServer_AddTagHelper()
{
// Arrange
var input = """
TestCode code = """
@addTagHelper *, Test$$Assembly
<test1></test1>
""";
// Act
var result = await GetResultFromSingleServerEndpointAsync(input);
var result = await GetResultFromSingleServerEndpointAsync(code);
// Assert
// Roslyn returns us a range that is outside of our source mappings, so we expect the endpoint
// to return null, so as not to confuse the client
Assert.NotNull(result);
Assert.Null(result.Range);
Assert.NotNull(result.RawContent);
var rawContainer = (ContainerElement)result.RawContent;
var embeddedContainerElement = (ContainerElement)rawContainer.Elements.Single();
@ -855,10 +868,9 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
Assert.StartsWith("class System.String", text);
}
private async Task<VSInternalHover> GetResultFromSingleServerEndpointAsync(string input)
private async Task<VSInternalHover?> GetResultFromSingleServerEndpointAsync(TestCode code)
{
TestFileMarkupParser.GetPosition(input, out var output, out var cursorPosition);
var codeDocument = CreateCodeDocument(output, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, DefaultTagHelpers);
var csharpSourceText = codeDocument.GetCSharpSourceText();
var csharpDocumentUri = new Uri("C:/path/to/file.razor__virtual.g.cs");
var serverCapabilities = new VSInternalServerCapabilities()
@ -872,13 +884,12 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
var razorFilePath = "C:/path/to/file.razor";
var documentContextFactory = new TestDocumentContextFactory(razorFilePath, codeDocument);
var languageServerFeatureOptions = Mock.Of<LanguageServerFeatureOptions>(options =>
var languageServerFeatureOptions = StrictMock.Of<LanguageServerFeatureOptions>(options =>
options.SupportsFileManipulation == true &&
options.SingleServerSupport == true &&
options.CSharpVirtualDocumentSuffix == ".g.cs" &&
options.HtmlVirtualDocumentSuffix == ".g.html" &&
options.UseRazorCohostServer == false
, MockBehavior.Strict);
options.UseRazorCohostServer == false);
var languageServer = new HoverLanguageServer(csharpServer, csharpDocumentUri, DisposalToken);
var documentMappingService = new LspDocumentMappingService(FilePathService, documentContextFactory, LoggerFactory);
@ -894,61 +905,70 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
var razorFileUri = new Uri(razorFilePath);
var request = new TextDocumentPositionParams
{
TextDocument = new TextDocumentIdentifier
{
Uri = razorFileUri,
},
Position = codeDocument.Source.Text.GetPosition(cursorPosition)
TextDocument = new() { Uri = razorFileUri, },
Position = codeDocument.Source.Text.GetPosition(code.Position)
};
var documentContext = CreateDocumentContext(razorFileUri, codeDocument);
var requestContext = CreateRazorRequestContext(documentContext: documentContext);
return await endpoint.HandleRequestAsync(request, requestContext, DisposalToken);
}
private DocumentContext CreateDefaultDocumentContext()
private (DocumentContext, Position) CreateDefaultDocumentContext()
{
var txt = """
TestCode code = """
@addTagHelper *, TestAssembly
<any @test="Increment" />
@code{
public void Increment(){
public void $$Increment(){
}
}
""";
var path = "C:/text.razor";
var codeDocument = CreateCodeDocument(txt, path, DefaultTagHelpers);
var codeDocument = CreateCodeDocument(code.Text, path, DefaultTagHelpers);
var projectWorkspaceState = ProjectWorkspaceState.Create(DefaultTagHelpers);
var projectSnapshot = TestProjectSnapshot.Create("C:/project.csproj", projectWorkspaceState);
var sourceText = SourceText.From(txt);
var snapshot = Mock.Of<IDocumentSnapshot>(d =>
d.GetGeneratedOutputAsync(It.IsAny<bool>()) == Task.FromResult(codeDocument) &&
d.FilePath == path &&
d.FileKind == FileKinds.Component &&
d.GetTextAsync() == Task.FromResult(sourceText) &&
d.Version == 0 &&
d.Project == projectSnapshot, MockBehavior.Strict);
var documentSnapshotMock = new StrictMock<IDocumentSnapshot>();
documentSnapshotMock
.Setup(x => x.GetGeneratedOutputAsync(It.IsAny<bool>()))
.ReturnsAsync(codeDocument);
documentSnapshotMock
.Setup(x => x.GetTextAsync())
.ReturnsAsync(codeDocument.Source.Text);
documentSnapshotMock
.SetupGet(x => x.FilePath)
.Returns(path);
documentSnapshotMock
.SetupGet(x => x.FileKind)
.Returns(FileKinds.Component);
documentSnapshotMock
.SetupGet(x => x.Version)
.Returns(0);
documentSnapshotMock
.SetupGet(x => x.Project)
.Returns(projectSnapshot);
var documentContext = new DocumentContext(new Uri(path), snapshot, projectContext: null);
var documentContext = new DocumentContext(new Uri(path), documentSnapshotMock.Object, projectContext: null);
var position = codeDocument.Source.Text.GetPosition(code.Position);
return documentContext;
return (documentContext, position);
}
private HoverEndpoint CreateEndpoint(
LanguageServerFeatureOptions languageServerFeatureOptions = null,
IDocumentMappingService documentMappingService = null,
IClientConnection clientConnection = null)
LanguageServerFeatureOptions? languageServerFeatureOptions = null,
IDocumentMappingService? documentMappingService = null,
IClientConnection? clientConnection = null)
{
languageServerFeatureOptions ??= Mock.Of<LanguageServerFeatureOptions>(options => options.SupportsFileManipulation == true && options.SingleServerSupport == false, MockBehavior.Strict);
languageServerFeatureOptions ??= StrictMock.Of<LanguageServerFeatureOptions>(options =>
options.SupportsFileManipulation == true &&
options.SingleServerSupport == false);
var documentMappingServiceMock = new Mock<IDocumentMappingService>(MockBehavior.Strict);
documentMappingServiceMock
.Setup(c => c.GetLanguageKind(It.IsAny<RazorCodeDocument>(), It.IsAny<int>(), It.IsAny<bool>()))
.Returns(RazorLanguageKind.Html);
documentMappingService ??= documentMappingServiceMock.Object;
documentMappingService ??= StrictMock.Of<IDocumentMappingService>();
clientConnection ??= Mock.Of<IClientConnection>(MockBehavior.Strict);
clientConnection ??= StrictMock.Of<IClientConnection>();
var service = GetHoverService();
@ -962,7 +982,7 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
return endpoint;
}
private HoverService GetHoverService(IDocumentMappingService mappingService = null)
private HoverService GetHoverService(IDocumentMappingService? mappingService = null)
{
var projectManager = CreateProjectSnapshotManager();
var lspTagHelperTooltipFactory = new DefaultLSPTagHelperTooltipFactory(projectManager);
@ -971,6 +991,9 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
var clientCapabilities = CreateMarkDownCapabilities();
clientCapabilities.SupportsVisualStudioExtensions = true;
var clientCapabilitiesService = new TestClientCapabilitiesService(clientCapabilities);
mappingService ??= StrictMock.Of<IDocumentMappingService>();
return new HoverService(lspTagHelperTooltipFactory, vsLspTagHelperTooltipFactory, mappingService, clientCapabilitiesService);
}
@ -1007,14 +1030,11 @@ public class HoverServiceTest(ITestOutputHelper testOutput) : TagHelperServiceTe
var hoverRequest = new TextDocumentPositionParams()
{
TextDocument = new VSTextDocumentIdentifier()
{
Uri = _csharpDocumentUri,
},
TextDocument = new() { Uri = _csharpDocumentUri, },
Position = hoverParams.ProjectedPosition
};
var result = await _csharpServer.ExecuteRequestAsync<VisualStudio.LanguageServer.Protocol.TextDocumentPositionParams, TResponse>(
var result = await _csharpServer.ExecuteRequestAsync<TextDocumentPositionParams, TResponse>(
Methods.TextDocumentHoverName, hoverRequest, _cancellationToken);
return result;

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

@ -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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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 = LspDocumentMappingService.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,

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

@ -110,7 +110,7 @@ public class RenameEndpointTest(ITestOutputHelper testOutput) : LanguageServerTe
public async Task Handle_Rename_FileManipulationNotSupported_ReturnsNull()
{
// Arrange
var options = StrictMock.Of<LanguageServerFeatureOptions>(o =>
var options = StrictMock.Of<LanguageServerFeatureOptions>(static o =>
o.SupportsFileManipulation == false &&
o.ReturnCodeActionAndRenamePathsWithPrefixedSlash == false);
var (endpoint, documentContextFactory) = await CreateEndpointAndDocumentContextFactoryAsync(options);
@ -538,39 +538,36 @@ public class RenameEndpointTest(ITestOutputHelper testOutput) : LanguageServerTe
public async Task Handle_Rename_SingleServer_CallsDelegatedLanguageServer()
{
// Arrange
var options = StrictMock.Of<LanguageServerFeatureOptions>(o =>
var options = StrictMock.Of<LanguageServerFeatureOptions>(static o =>
o.SupportsFileManipulation == true &&
o.SingleServerSupport == true &&
o.ReturnCodeActionAndRenamePathsWithPrefixedSlash == false);
var delegatedEdit = new WorkspaceEdit();
var clientConnectionMock = new StrictMock<IClientConnection>();
clientConnectionMock
.Setup(c => c.SendRequestAsync<IDelegatedParams, WorkspaceEdit>(CustomMessageNames.RazorRenameEndpointName, It.IsAny<DelegatedRenameParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(delegatedEdit);
var clientConnection = TestMocks.CreateClientConnection(builder =>
{
builder.SetupSendRequest<IDelegatedParams, WorkspaceEdit>(CustomMessageNames.RazorRenameEndpointName, response: delegatedEdit);
});
var documentMappingServiceMock = new StrictMock<IDocumentMappingService>();
documentMappingServiceMock
.Setup(c => c.GetLanguageKind(It.IsAny<RazorCodeDocument>(), It.IsAny<int>(), It.IsAny<bool>()))
.Returns(RazorLanguageKind.CSharp);
var projectedPosition = new LinePosition(1, 1);
var projectedIndex = 1;
documentMappingServiceMock
.Setup(c => c.TryMapToGeneratedDocumentPosition(It.IsAny<IRazorGeneratedDocument>(), It.IsAny<int>(), out projectedPosition, out projectedIndex))
.Setup(x => x.TryMapToGeneratedDocumentPosition(It.IsAny<IRazorGeneratedDocument>(), It.IsAny<int>(), out projectedPosition, out projectedIndex))
.Returns(true);
var editMappingServiceMock = new StrictMock<IEditMappingService>();
editMappingServiceMock
.Setup(c => c.RemapWorkspaceEditAsync(It.IsAny<IDocumentSnapshot>(), It.IsAny<WorkspaceEdit>(), It.IsAny<CancellationToken>()))
.Setup(x => x.RemapWorkspaceEditAsync(It.IsAny<IDocumentSnapshot>(), It.IsAny<WorkspaceEdit>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(delegatedEdit);
var (endpoint, documentContextFactory) = await CreateEndpointAndDocumentContextFactoryAsync(
options,
documentMappingServiceMock.Object,
editMappingServiceMock.Object,
clientConnectionMock.Object);
clientConnection);
var uri = PathUtilities.GetUri(s_componentWithParamFilePath);
var request = new RenameParams
@ -594,29 +591,19 @@ public class RenameEndpointTest(ITestOutputHelper testOutput) : LanguageServerTe
public async Task Handle_Rename_SingleServer_DoesNotDelegateForRazor()
{
// Arrange
var options = StrictMock.Of<LanguageServerFeatureOptions>(o =>
var options = StrictMock.Of<LanguageServerFeatureOptions>(static o =>
o.SupportsFileManipulation == true &&
o.SingleServerSupport == true &&
o.ReturnCodeActionAndRenamePathsWithPrefixedSlash == false);
var clientConnection = StrictMock.Of<IClientConnection>();
var documentMappingServiceMock = new StrictMock<IDocumentMappingService>();
documentMappingServiceMock
.Setup(c => c.GetLanguageKind(It.IsAny<RazorCodeDocument>(), It.IsAny<int>(), It.IsAny<bool>()))
.Returns(RazorLanguageKind.Razor);
var documentMappingService = StrictMock.Of<IDocumentMappingService>();
var editMappingService = StrictMock.Of<IEditMappingService>();
var (endpoint, documentContextFactory) = await CreateEndpointAndDocumentContextFactoryAsync(
options,
documentMappingServiceMock.Object,
editMappingService,
clientConnection);
var (endpoint, documentContextFactory) = await CreateEndpointAndDocumentContextFactoryAsync(options, documentMappingService);
var request = new RenameParams
{
TextDocument = new() { Uri = PathUtilities.GetUri(s_componentWithParamFilePath) },
Position = VsLspFactory.CreatePosition(1, 0),
Position = VsLspFactory.CreatePosition(0, 1), // This is right after the '@' in '@namespace'
NewName = "Test2"
};
@ -663,10 +650,11 @@ public class RenameEndpointTest(ITestOutputHelper testOutput) : LanguageServerTe
return textLoaderMock.Object;
});
var projectService = new TestRazorProjectService(
var projectService = AddDisposable(
new TestRazorProjectService(
remoteTextLoaderFactoryMock.Object,
projectManager,
LoggerFactory);
LoggerFactory));
var projectKey1 = await projectService.AddProjectAsync(
s_projectFilePath1, s_intermediateOutputPath1, RazorConfiguration.Default, RootNamespace1, displayName: null, DisposalToken);
@ -707,7 +695,7 @@ public class RenameEndpointTest(ITestOutputHelper testOutput) : LanguageServerTe
await projectService.UpdateDocumentAsync(s_componentWithParamFilePath, SourceText.From(ComponentWithParamText), DisposalToken);
var searchEngine = new RazorComponentSearchEngine(projectManager, LoggerFactory);
options ??= StrictMock.Of<LanguageServerFeatureOptions>(o =>
options ??= StrictMock.Of<LanguageServerFeatureOptions>(static o =>
o.SupportsFileManipulation == true &&
o.SingleServerSupport == false &&
o.ReturnCodeActionAndRenamePathsWithPrefixedSlash == false);
@ -715,9 +703,7 @@ public class RenameEndpointTest(ITestOutputHelper testOutput) : LanguageServerTe
if (documentMappingService == null)
{
var documentMappingServiceMock = new StrictMock<IDocumentMappingService>();
documentMappingServiceMock
.Setup(c => c.GetLanguageKind(It.IsAny<RazorCodeDocument>(), It.IsAny<int>(), It.IsAny<bool>()))
.Returns(RazorLanguageKind.Html);
var projectedPosition = new LinePosition(1, 1);
var projectedIndex = 1;
documentMappingServiceMock

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

@ -8,8 +8,8 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.Formatting;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.AspNetCore.Razor.Test.Common.LanguageServer;
using Microsoft.CodeAnalysis.Razor.DocumentMapping;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.VisualStudio.LanguageServer.Protocol;
@ -28,24 +28,19 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
var codeDocument = CreateCodeDocument("<div></div>");
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var response = new WrapWithTagResponse();
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, It.IsAny<WrapWithTagParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var clientConnection = TestMocks.CreateClientConnection(builder =>
{
builder.SetupSendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, response: new(), verifiable: true);
});
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var endpoint = new WrapWithTagEndpoint(
clientConnection.Object,
documentMappingService,
LoggerFactory);
var endpoint = new WrapWithTagEndpoint(clientConnection, LoggerFactory);
var wrapWithDivParams = new WrapWithTagParams(new TextDocumentIdentifier { Uri = uri })
var wrapWithDivParams = new WrapWithTagParams(new() { Uri = uri })
{
Range = VsLspFactory.CreateSingleLineRange(start: (0, 0), length: 2),
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -53,7 +48,7 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
// Assert
Assert.NotNull(result);
clientConnection.Verify();
Mock.Get(clientConnection).Verify();
}
[Fact]
@ -63,24 +58,19 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
var codeDocument = CreateCodeDocument("@(counter)");
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var response = new WrapWithTagResponse();
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, It.IsAny<WrapWithTagParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var clientConnection = TestMocks.CreateClientConnection(builder =>
{
builder.SetupSendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, response: new(), verifiable: true);
});
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.CSharp, MockBehavior.Strict);
var endpoint = new WrapWithTagEndpoint(
clientConnection.Object,
documentMappingService,
LoggerFactory);
var endpoint = new WrapWithTagEndpoint(clientConnection, LoggerFactory);
var wrapWithDivParams = new WrapWithTagParams(new TextDocumentIdentifier { Uri = uri })
var wrapWithDivParams = new WrapWithTagParams(new() { Uri = uri })
{
Range = VsLspFactory.CreateSingleLineRange(start: (0, 0), length: 2),
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -88,7 +78,8 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
// Assert
Assert.Null(result);
clientConnection.Verify();
Mock.Get(clientConnection)
.VerifySendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, Times.Never);
}
[Fact]
@ -98,24 +89,19 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
var codeDocument = CreateCodeDocument("@counter");
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var response = new WrapWithTagResponse();
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, It.IsAny<WrapWithTagParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var clientConnection = TestMocks.CreateClientConnection(builder =>
{
builder.SetupSendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, response: new(), verifiable: true);
});
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.CSharp, MockBehavior.Strict);
var endpoint = new WrapWithTagEndpoint(
clientConnection.Object,
documentMappingService,
LoggerFactory);
var endpoint = new WrapWithTagEndpoint(clientConnection, LoggerFactory);
var wrapWithDivParams = new WrapWithTagParams(new TextDocumentIdentifier { Uri = uri })
var wrapWithDivParams = new WrapWithTagParams(new() { Uri = uri })
{
Range = VsLspFactory.CreateSingleLineRange(start: (0, 0), length: 8),
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -123,7 +109,7 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
// Assert
Assert.NotNull(result);
clientConnection.Verify();
Mock.Get(clientConnection).Verify();
}
[Fact]
@ -133,24 +119,19 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
var codeDocument = CreateCodeDocument("@counter");
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var response = new WrapWithTagResponse();
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, It.IsAny<WrapWithTagParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var clientConnection = TestMocks.CreateClientConnection(builder =>
{
builder.SetupSendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, response: new(), verifiable: true);
});
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.CSharp, MockBehavior.Strict);
var endpoint = new WrapWithTagEndpoint(
clientConnection.Object,
documentMappingService,
LoggerFactory);
var endpoint = new WrapWithTagEndpoint(clientConnection, LoggerFactory);
var wrapWithDivParams = new WrapWithTagParams(new TextDocumentIdentifier { Uri = uri })
var wrapWithDivParams = new WrapWithTagParams(new() { Uri = uri })
{
Range = VsLspFactory.CreateSingleLineRange(line: 0, character: 2, length: 2),
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -158,7 +139,8 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
// Assert
Assert.Null(result);
clientConnection.Verify();
Mock.Get(clientConnection)
.VerifySendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, Times.Never);
}
[Fact]
@ -168,24 +150,19 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
var codeDocument = CreateCodeDocument("@counter");
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var response = new WrapWithTagResponse();
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
clientConnection
.Setup(l => l.SendRequestAsync<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, It.IsAny<WrapWithTagParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
var clientConnection = TestMocks.CreateClientConnection(builder =>
{
builder.SetupSendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, response: new(), verifiable: true);
});
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.CSharp, MockBehavior.Strict);
var endpoint = new WrapWithTagEndpoint(
clientConnection.Object,
documentMappingService,
LoggerFactory);
var endpoint = new WrapWithTagEndpoint(clientConnection, LoggerFactory);
var wrapWithDivParams = new WrapWithTagParams(new TextDocumentIdentifier { Uri = uri })
var wrapWithDivParams = new WrapWithTagParams(new() { Uri = uri })
{
Range = VsLspFactory.CreateZeroWidthRange(0, 4),
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -193,27 +170,27 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
// Assert
Assert.NotNull(result);
clientConnection.Verify();
Mock.Get(clientConnection).Verify();
}
[Fact]
public async Task Handle_DocumentNotFound_ReturnsNull()
{
// Arrange
var codeDocument = CreateCodeDocument("<div></div>");
var realUri = new Uri("file://path/test.razor");
var missingUri = new Uri("file://path/nottest.razor");
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
var clientConnection = TestMocks.CreateClientConnection(builder =>
{
builder.SetupSendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, response: new(), verifiable: true);
});
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var endpoint = new WrapWithTagEndpoint(clientConnection.Object, documentMappingService, LoggerFactory);
var endpoint = new WrapWithTagEndpoint(clientConnection, LoggerFactory);
var wrapWithDivParams = new WrapWithTagParams(new TextDocumentIdentifier { Uri = missingUri })
var wrapWithDivParams = new WrapWithTagParams(new() { Uri = missingUri })
{
Range = VsLspFactory.CreateSingleLineRange(start: (0, 0), length: 2),
};
var requestContext = CreateRazorRequestContext(documentContext: null);
// Act
@ -221,6 +198,8 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
// Assert
Assert.Null(result);
Mock.Get(clientConnection)
.VerifySendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, Times.Never);
}
[Fact]
@ -232,16 +211,18 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
var uri = new Uri("file://path/test.razor");
var documentContext = CreateDocumentContext(uri, codeDocument);
var clientConnection = new Mock<IClientConnection>(MockBehavior.Strict);
var clientConnection = TestMocks.CreateClientConnection(builder =>
{
builder.SetupSendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, response: new(), verifiable: true);
});
var documentMappingService = Mock.Of<IDocumentMappingService>(
s => s.GetLanguageKind(codeDocument, It.IsAny<int>(), It.IsAny<bool>()) == RazorLanguageKind.Html, MockBehavior.Strict);
var endpoint = new WrapWithTagEndpoint(clientConnection.Object, documentMappingService, LoggerFactory);
var endpoint = new WrapWithTagEndpoint(clientConnection, LoggerFactory);
var wrapWithDivParams = new WrapWithTagParams(new TextDocumentIdentifier { Uri = uri })
var wrapWithDivParams = new WrapWithTagParams(new() { Uri = uri })
{
Range = VsLspFactory.CreateSingleLineRange(start: (0, 0), length: 2),
};
var requestContext = CreateRazorRequestContext(documentContext);
// Act
@ -249,6 +230,8 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
// Assert
Assert.Null(result);
Mock.Get(clientConnection)
.VerifySendRequest<WrapWithTagParams, WrapWithTagResponse>(LanguageServerConstants.RazorWrapWithTagEndpoint, Times.Never);
}
[Fact]
@ -259,6 +242,7 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
{
}
""";
var expected = """
<div>
@if (true)
@ -270,7 +254,7 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
var uri = new Uri("file://path.razor");
var factory = CreateDocumentContextFactory(uri, input);
Assert.True(factory.TryCreate(uri, out var context));
var inputSourceText = await context!.GetSourceTextAsync(DisposalToken);
var inputSourceText = await context.GetSourceTextAsync(DisposalToken);
var computedEdits = new TextEdit[]
{
@ -281,7 +265,7 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
newText: " }" + Environment.NewLine + "</div>"),
};
var htmlSourceText = await context!.GetHtmlSourceTextAsync(DisposalToken);
var htmlSourceText = await context.GetHtmlSourceTextAsync(DisposalToken);
var edits = HtmlFormatter.FixHtmlTextEdits(htmlSourceText, computedEdits);
Assert.Same(computedEdits, edits);
@ -309,7 +293,7 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
var uri = new Uri("file://path.razor");
var factory = CreateDocumentContextFactory(uri, input);
Assert.True(factory.TryCreate(uri, out var context));
var inputSourceText = await context!.GetSourceTextAsync(DisposalToken);
var inputSourceText = await context.GetSourceTextAsync(DisposalToken);
var computedEdits = new TextEdit[]
{
@ -321,7 +305,7 @@ public class WrapWithTagEndpointTest(ITestOutputHelper testOutput) : LanguageSer
newText: " ~" + Environment.NewLine + "</div>")
};
var htmlSourceText = await context!.GetHtmlSourceTextAsync(DisposalToken);
var htmlSourceText = await context.GetHtmlSourceTextAsync(DisposalToken);
var edits = HtmlFormatter.FixHtmlTextEdits(htmlSourceText, computedEdits);
Assert.NotSame(computedEdits, edits);

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

@ -1,10 +1,14 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.LanguageServer.Hosting;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Moq;
using Moq.Language.Flow;
namespace Microsoft.AspNetCore.Razor.Test.Common;
@ -26,4 +30,58 @@ internal static class TestMocks
return mock.Object;
}
public interface IClientConnectionBuilder
{
void SetupSendRequest<TParams, TResponse>(string method, TResponse response, bool verifiable = false);
void SetupSendRequest<TParams, TResponse>(string method, TParams @params, TResponse response, bool verifiable = false);
}
private sealed class ClientConnectionBuilder : IClientConnectionBuilder
{
public StrictMock<IClientConnection> Mock { get; } = new();
public void SetupSendRequest<TParams, TResponse>(string method, TResponse response, bool verifiable = false)
{
var returnsResult = Mock
.Setup(x => x.SendRequestAsync<TParams, TResponse>(method, It.IsAny<TParams>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
if (verifiable)
{
returnsResult.Verifiable();
}
}
public void SetupSendRequest<TParams, TResponse>(string method, TParams @params, TResponse response, bool verifiable = false)
{
var returnsResult = Mock
.Setup(x => x.SendRequestAsync<TParams, TResponse>(method, @params, It.IsAny<CancellationToken>()))
.ReturnsAsync(response);
if (verifiable)
{
returnsResult.Verifiable();
}
}
}
public static IClientConnection CreateClientConnection(Action<IClientConnectionBuilder> configure)
{
var builder = new ClientConnectionBuilder();
configure?.Invoke(builder);
return builder.Mock.Object;
}
public static void VerifySendRequest<TParams, TResponse>(this Mock<IClientConnection> mock, string method, Times times)
=> mock.Verify(x => x.SendRequestAsync<TParams, TResponse>(method, It.IsAny<TParams>(), It.IsAny<CancellationToken>()), times);
public static void VerifySendRequest<TParams, TResponse>(this Mock<IClientConnection> mock, string method, Func<Times> times)
=> mock.Verify(x => x.SendRequestAsync<TParams, TResponse>(method, It.IsAny<TParams>(), It.IsAny<CancellationToken>()), times);
public static void VerifySendRequest<TParams, TResponse>(this Mock<IClientConnection> mock, string method, TParams @params, Times times)
=> mock.Verify(x => x.SendRequestAsync<TParams, TResponse>(method, @params, It.IsAny<CancellationToken>()), times);
public static void VerifySendRequest<TParams, TResponse>(this Mock<IClientConnection> mock, string method, TParams @params, Func<Times> times)
=> mock.Verify(x => x.SendRequestAsync<TParams, TResponse>(method, @params, It.IsAny<CancellationToken>()), times);
}

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

@ -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);
}
}