Refactor completion logic into standalone service.
- Migrated the completion item source provider and the legacy directive completion provider to use the new service. - Cleaned up duplicate tests that were both verifying common completion functionality. - Ensured that the legacy `RazorDirectiveCompletionProvider` did not result in additional Razor assembly loads when in C# scenarios. #2530
This commit is contained in:
parent
aec88e3eba
commit
572b55690d
|
|
@ -0,0 +1,98 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
[System.Composition.Shared]
|
||||
[Export(typeof(RazorCompletionFactsService))]
|
||||
internal class DefaultRazorCompletionFactsService : RazorCompletionFactsService
|
||||
{
|
||||
private static readonly IEnumerable<DirectiveDescriptor> DefaultDirectives = new[]
|
||||
{
|
||||
CSharpCodeParser.AddTagHelperDirectiveDescriptor,
|
||||
CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
|
||||
CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
|
||||
};
|
||||
|
||||
public override IReadOnlyList<RazorCompletionItem> GetCompletionItems(RazorSyntaxTree syntaxTree, SourceSpan location)
|
||||
{
|
||||
var completionItems = new List<RazorCompletionItem>();
|
||||
|
||||
if (AtDirectiveCompletionPoint(syntaxTree, location))
|
||||
{
|
||||
var directiveCompletions = GetDirectiveCompletionItems(syntaxTree);
|
||||
completionItems.AddRange(directiveCompletions);
|
||||
}
|
||||
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static List<RazorCompletionItem> GetDirectiveCompletionItems(RazorSyntaxTree syntaxTree)
|
||||
{
|
||||
var directives = syntaxTree.Options.Directives.Concat(DefaultDirectives);
|
||||
var completionItems = new List<RazorCompletionItem>();
|
||||
foreach (var directive in directives)
|
||||
{
|
||||
var completionDisplayText = directive.DisplayName ?? directive.Directive;
|
||||
var completionItem = new RazorCompletionItem(
|
||||
completionDisplayText,
|
||||
directive.Directive,
|
||||
directive.Description,
|
||||
RazorCompletionItemKind.Directive);
|
||||
completionItems.Add(completionItem);
|
||||
}
|
||||
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static bool AtDirectiveCompletionPoint(RazorSyntaxTree syntaxTree, SourceSpan location)
|
||||
{
|
||||
if (syntaxTree == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var change = new SourceChange(location, string.Empty);
|
||||
var owner = syntaxTree.Root.LocateOwner(change);
|
||||
|
||||
if (owner == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (owner.ChunkGenerator is ExpressionChunkGenerator &&
|
||||
owner.Tokens.All(IsDirectiveCompletableToken) &&
|
||||
// Do not provide IntelliSense for explicit expressions. Explicit expressions will usually look like:
|
||||
// [@] [(] [DateTime.Now] [)]
|
||||
owner.Parent?.Children.Count > 1 &&
|
||||
owner.Parent.Children[1] == owner)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static bool IsDirectiveCompletableToken(IToken token)
|
||||
{
|
||||
if (!(token is CSharpToken csharpToken))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return csharpToken.Type == CSharpTokenType.Identifier ||
|
||||
// Marker symbol
|
||||
csharpToken.Type == CSharpTokenType.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
internal abstract class RazorCompletionFactsService
|
||||
{
|
||||
public abstract IReadOnlyList<RazorCompletionItem> GetCompletionItems(RazorSyntaxTree syntaxTree, SourceSpan location);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
internal sealed class RazorCompletionItem
|
||||
{
|
||||
public RazorCompletionItem(
|
||||
string displayText,
|
||||
string insertText,
|
||||
string description,
|
||||
RazorCompletionItemKind kind)
|
||||
{
|
||||
if (displayText == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayText));
|
||||
}
|
||||
|
||||
if (insertText == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(insertText));
|
||||
}
|
||||
|
||||
if (description == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(description));
|
||||
}
|
||||
|
||||
DisplayText = displayText;
|
||||
InsertText = insertText;
|
||||
Description = description;
|
||||
Kind = kind;
|
||||
}
|
||||
|
||||
public string DisplayText { get; }
|
||||
|
||||
public string InsertText { get; }
|
||||
|
||||
public string Description { get; }
|
||||
|
||||
public RazorCompletionItemKind Kind { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
internal enum RazorCompletionItemKind
|
||||
{
|
||||
Directive
|
||||
}
|
||||
}
|
||||
|
|
@ -36,12 +36,14 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
|
||||
};
|
||||
private readonly Lazy<RazorCodeDocumentProvider> _codeDocumentProvider;
|
||||
private readonly Lazy<RazorCompletionFactsService> _completionFactsService;
|
||||
private readonly IAsyncCompletionBroker _asyncCompletionBroker;
|
||||
private readonly RazorTextBufferProvider _textBufferProvider;
|
||||
|
||||
[ImportingConstructor]
|
||||
public RazorDirectiveCompletionProvider(
|
||||
[Import(typeof(RazorCodeDocumentProvider))] Lazy<RazorCodeDocumentProvider> codeDocumentProvider,
|
||||
[Import(typeof(RazorCompletionFactsService))] Lazy<RazorCompletionFactsService> completionFactsService,
|
||||
IAsyncCompletionBroker asyncCompletionBroker,
|
||||
RazorTextBufferProvider textBufferProvider)
|
||||
{
|
||||
|
|
@ -50,6 +52,11 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
throw new ArgumentNullException(nameof(codeDocumentProvider));
|
||||
}
|
||||
|
||||
if (completionFactsService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(completionFactsService));
|
||||
}
|
||||
|
||||
if (asyncCompletionBroker == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(asyncCompletionBroker));
|
||||
|
|
@ -61,6 +68,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
|
||||
_codeDocumentProvider = codeDocumentProvider;
|
||||
_completionFactsService = completionFactsService;
|
||||
_asyncCompletionBroker = asyncCompletionBroker;
|
||||
_textBufferProvider = textBufferProvider;
|
||||
}
|
||||
|
|
@ -133,70 +141,41 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
if (!AtDirectiveCompletionPoint(syntaxTree, context))
|
||||
if (!TryGetRazorSnapshotPoint(context, out var razorSnapshotPoint))
|
||||
{
|
||||
// Can't have a valid directive at the current location.
|
||||
// Could not find associated Razor location.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var completionItems = GetCompletionItems(syntaxTree);
|
||||
context.AddItems(completionItems);
|
||||
var location = new SourceSpan(razorSnapshotPoint.Position, 0);
|
||||
var razorCompletionItems = _completionFactsService.Value.GetCompletionItems(syntaxTree, location);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Internal virtual for testing
|
||||
internal virtual IEnumerable<CompletionItem> GetCompletionItems(RazorSyntaxTree syntaxTree)
|
||||
{
|
||||
var directives = syntaxTree.Options.Directives.Concat(DefaultDirectives);
|
||||
var completionItems = new List<CompletionItem>();
|
||||
foreach (var directive in directives)
|
||||
foreach (var razorCompletionItem in razorCompletionItems)
|
||||
{
|
||||
var propertyDictionary = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
|
||||
if (!string.IsNullOrEmpty(directive.Description))
|
||||
if (razorCompletionItem.Kind != RazorCompletionItemKind.Directive)
|
||||
{
|
||||
propertyDictionary[DescriptionKey] = directive.Description;
|
||||
// Don't support any other types of completion kinds other than directives.
|
||||
continue;
|
||||
}
|
||||
|
||||
var propertyDictionary = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
if (!string.IsNullOrEmpty(razorCompletionItem.Description))
|
||||
{
|
||||
propertyDictionary[DescriptionKey] = razorCompletionItem.Description;
|
||||
}
|
||||
|
||||
var completionItem = CompletionItem.Create(
|
||||
directive.Directive,
|
||||
razorCompletionItem.InsertText,
|
||||
// This groups all Razor directives together
|
||||
sortText: "_RazorDirective_",
|
||||
rules: CompletionItemRules.Create(formatOnCommit: false),
|
||||
tags: ImmutableArray.Create(WellKnownTags.Intrinsic),
|
||||
properties: propertyDictionary.ToImmutableDictionary());
|
||||
completionItems.Add(completionItem);
|
||||
|
||||
context.AddItem(completionItem);
|
||||
}
|
||||
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal bool AtDirectiveCompletionPoint(RazorSyntaxTree syntaxTree, CompletionContext context)
|
||||
{
|
||||
if (TryGetRazorSnapshotPoint(context, out var razorSnapshotPoint))
|
||||
{
|
||||
var change = new SourceChange(razorSnapshotPoint.Position, 0, string.Empty);
|
||||
var owner = syntaxTree.Root.LocateOwner(change);
|
||||
|
||||
if (owner == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (owner.ChunkGenerator is ExpressionChunkGenerator &&
|
||||
owner.Tokens.All(IsDirectiveCompletableSymbol) &&
|
||||
// Do not provide IntelliSense for explicit expressions. Explicit expressions will usually look like:
|
||||
// [@] [(] [DateTime.Now] [)]
|
||||
owner.Parent?.Children.Count > 1 &&
|
||||
owner.Parent.Children[1] == owner)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual bool TryGetRazorSnapshotPoint(CompletionContext context, out SnapshotPoint snapshotPoint)
|
||||
|
|
|
|||
|
|
@ -4,11 +4,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Core.Imaging;
|
||||
using Microsoft.VisualStudio.Imaging;
|
||||
|
|
@ -29,33 +27,35 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
internal static readonly ImmutableArray<CompletionFilter> DirectiveCompletionFilters = new[] {
|
||||
new CompletionFilter("Razor Directive", "r", DirectiveImageGlyph)
|
||||
}.ToImmutableArray();
|
||||
private static readonly IEnumerable<DirectiveDescriptor> DefaultDirectives = new[]
|
||||
{
|
||||
CSharpCodeParser.AddTagHelperDirectiveDescriptor,
|
||||
CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
|
||||
CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
|
||||
};
|
||||
|
||||
// Internal for testing
|
||||
internal readonly VisualStudioRazorParser _parser;
|
||||
private readonly RazorCompletionFactsService _completionFactsService;
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
|
||||
public RazorDirectiveCompletionSource(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
VisualStudioRazorParser parser,
|
||||
ForegroundDispatcher foregroundDispatcher)
|
||||
RazorCompletionFactsService completionFactsService)
|
||||
{
|
||||
if (parser == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parser));
|
||||
}
|
||||
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
_parser = parser;
|
||||
if (parser == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parser));
|
||||
}
|
||||
|
||||
if (completionFactsService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(completionFactsService));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_parser = parser;
|
||||
_completionFactsService = completionFactsService;
|
||||
}
|
||||
|
||||
public Task<CompletionContext> GetCompletionContextAsync(
|
||||
|
|
@ -67,12 +67,31 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
_foregroundDispatcher.AssertBackgroundThread();
|
||||
|
||||
var syntaxTree = _parser.CodeDocument?.GetSyntaxTree();
|
||||
if (!AtDirectiveCompletionPoint(syntaxTree, triggerLocation))
|
||||
{
|
||||
return Task.FromResult(CompletionContext.Empty);
|
||||
}
|
||||
var location = new SourceSpan(applicableSpan.Start.Position, applicableSpan.Length);
|
||||
var razorCompletionItems = _completionFactsService.GetCompletionItems(syntaxTree, location);
|
||||
|
||||
var completionItems = GetCompletionItems(syntaxTree);
|
||||
var completionItems = new List<CompletionItem>();
|
||||
foreach (var razorCompletionItem in razorCompletionItems)
|
||||
{
|
||||
if (razorCompletionItem.Kind != RazorCompletionItemKind.Directive)
|
||||
{
|
||||
// Don't support any other types of completion kinds other than directives.
|
||||
continue;
|
||||
}
|
||||
|
||||
var completionItem = new CompletionItem(
|
||||
displayText: razorCompletionItem.DisplayText,
|
||||
filterText: razorCompletionItem.DisplayText,
|
||||
insertText: razorCompletionItem.InsertText,
|
||||
source: this,
|
||||
icon: DirectiveImageGlyph,
|
||||
filters: DirectiveCompletionFilters,
|
||||
suffix: string.Empty,
|
||||
sortText: razorCompletionItem.DisplayText,
|
||||
attributeIcons: ImmutableArray<ImageElement>.Empty);
|
||||
completionItem.Properties.AddProperty(DescriptionKey, razorCompletionItem.Description);
|
||||
completionItems.Add(completionItem);
|
||||
}
|
||||
var context = new CompletionContext(completionItems.ToImmutableArray());
|
||||
return Task.FromResult(context);
|
||||
}
|
||||
|
|
@ -96,72 +115,5 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
applicableToSpan = default(SnapshotSpan);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal List<CompletionItem> GetCompletionItems(RazorSyntaxTree syntaxTree)
|
||||
{
|
||||
var directives = syntaxTree.Options.Directives.Concat(DefaultDirectives);
|
||||
var completionItems = new List<CompletionItem>();
|
||||
foreach (var directive in directives)
|
||||
{
|
||||
var completionDisplayText = directive.DisplayName ?? directive.Directive;
|
||||
var completionItem = new CompletionItem(
|
||||
displayText: completionDisplayText,
|
||||
filterText: completionDisplayText,
|
||||
insertText: directive.Directive,
|
||||
source: this,
|
||||
icon: DirectiveImageGlyph,
|
||||
filters: DirectiveCompletionFilters,
|
||||
suffix: string.Empty,
|
||||
sortText: completionDisplayText,
|
||||
attributeIcons: ImmutableArray<ImageElement>.Empty);
|
||||
completionItem.Properties.AddProperty(DescriptionKey, directive.Description);
|
||||
completionItems.Add(completionItem);
|
||||
}
|
||||
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static bool AtDirectiveCompletionPoint(RazorSyntaxTree syntaxTree, SnapshotPoint location)
|
||||
{
|
||||
if (syntaxTree == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var change = new SourceChange(location.Position, 0, string.Empty);
|
||||
var owner = syntaxTree.Root.LocateOwner(change);
|
||||
|
||||
if (owner == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (owner.ChunkGenerator is ExpressionChunkGenerator &&
|
||||
owner.Tokens.All(IsDirectiveCompletableToken) &&
|
||||
// Do not provide IntelliSense for explicit expressions. Explicit expressions will usually look like:
|
||||
// [@] [(] [DateTime.Now] [)]
|
||||
owner.Parent?.Children.Count > 1 &&
|
||||
owner.Parent.Children[1] == owner)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal static bool IsDirectiveCompletableToken(IToken token)
|
||||
{
|
||||
if (!(token is CSharpToken csharpToken))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return csharpToken.Type == CSharpTokenType.Identifier ||
|
||||
// Marker symbol
|
||||
csharpToken.Type == CSharpTokenType.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,16 +19,25 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
internal class RazorDirectiveCompletionSourceProvider : IAsyncCompletionSourceProvider
|
||||
{
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly RazorCompletionFactsService _completionFactsService;
|
||||
|
||||
[ImportingConstructor]
|
||||
public RazorDirectiveCompletionSourceProvider(ForegroundDispatcher foregroundDispatcher)
|
||||
public RazorDirectiveCompletionSourceProvider(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
RazorCompletionFactsService completionFactsService)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (completionFactsService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(completionFactsService));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_completionFactsService = completionFactsService;
|
||||
}
|
||||
|
||||
public IAsyncCompletionSource GetOrCreate(ITextView textView)
|
||||
|
|
@ -57,7 +66,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
return null;
|
||||
}
|
||||
|
||||
var completionSource = new RazorDirectiveCompletionSource(parser, _foregroundDispatcher);
|
||||
var completionSource = new RazorDirectiveCompletionSource(_foregroundDispatcher, parser, _completionFactsService);
|
||||
return completionSource;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,239 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
public class DefaultRazorCompletionFactsServiceTest
|
||||
{
|
||||
private static readonly IReadOnlyList<DirectiveDescriptor> DefaultDirectives = new[]
|
||||
{
|
||||
CSharpCodeParser.AddTagHelperDirectiveDescriptor,
|
||||
CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
|
||||
CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void GetDirectiveCompletionItems_ReturnsDefaultDirectivesAsCompletionItems()
|
||||
{
|
||||
// Arrange
|
||||
var syntaxTree = CreateSyntaxTree("@addTag");
|
||||
|
||||
// Act
|
||||
var completionItems = DefaultRazorCompletionFactsService.GetDirectiveCompletionItems(syntaxTree);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
completionItems,
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[0], item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[1], item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[2], item));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDirectiveCompletionItems_ReturnsCustomDirectivesAsCompletionItems()
|
||||
{
|
||||
// Arrange
|
||||
var customDirective = DirectiveDescriptor.CreateSingleLineDirective("custom", builder =>
|
||||
{
|
||||
builder.Description = "My Custom Directive.";
|
||||
});
|
||||
var syntaxTree = CreateSyntaxTree("@addTag", customDirective);
|
||||
|
||||
// Act
|
||||
var completionItems = DefaultRazorCompletionFactsService.GetDirectiveCompletionItems(syntaxTree);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
completionItems,
|
||||
item => AssertRazorCompletionItem(customDirective, item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[0], item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[1], item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[2], item));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDirectiveCompletionItems_UsesDisplayNamesWhenNotNull()
|
||||
{
|
||||
// Arrange
|
||||
var customDirective = DirectiveDescriptor.CreateSingleLineDirective("custom", builder =>
|
||||
{
|
||||
builder.DisplayName = "different";
|
||||
builder.Description = "My Custom Directive.";
|
||||
});
|
||||
var syntaxTree = CreateSyntaxTree("@addTag", customDirective);
|
||||
|
||||
// Act
|
||||
var completionItems = DefaultRazorCompletionFactsService.GetDirectiveCompletionItems(syntaxTree);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
completionItems,
|
||||
item => AssertRazorCompletionItem("different", customDirective, item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[0], item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[1], item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[2], item));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsFalseIfSyntaxTreeNull()
|
||||
{
|
||||
// Act
|
||||
var result = DefaultRazorCompletionFactsService.AtDirectiveCompletionPoint(syntaxTree: null, location: new SourceSpan(0, 0));
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsFalseIfNoOwner()
|
||||
{
|
||||
// Arrange
|
||||
var syntaxTree = CreateSyntaxTree("@");
|
||||
var location = new SourceSpan(2, 0);
|
||||
|
||||
// Act
|
||||
var result = DefaultRazorCompletionFactsService.AtDirectiveCompletionPoint(syntaxTree, location);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsFalseWhenOwnerIsNotExpression()
|
||||
{
|
||||
// Arrange
|
||||
var syntaxTree = CreateSyntaxTree("@{");
|
||||
var location = new SourceSpan(2, 0);
|
||||
|
||||
// Act
|
||||
var result = DefaultRazorCompletionFactsService.AtDirectiveCompletionPoint(syntaxTree, location);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsFalseWhenOwnerIsComplexExpression()
|
||||
{
|
||||
// Arrange
|
||||
var syntaxTree = CreateSyntaxTree("@DateTime.Now");
|
||||
var location = new SourceSpan(2, 0);
|
||||
|
||||
// Act
|
||||
var result = DefaultRazorCompletionFactsService.AtDirectiveCompletionPoint(syntaxTree, location);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsFalseWhenOwnerIsExplicitExpression()
|
||||
{
|
||||
// Arrange
|
||||
var syntaxTree = CreateSyntaxTree("@(something)");
|
||||
var location = new SourceSpan(4, 0);
|
||||
|
||||
// Act
|
||||
var result = DefaultRazorCompletionFactsService.AtDirectiveCompletionPoint(syntaxTree, location);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsTrueForSimpleImplicitExpressions()
|
||||
{
|
||||
// Arrange
|
||||
var syntaxTree = CreateSyntaxTree("@mod");
|
||||
var location = new SourceSpan(2, 0);
|
||||
|
||||
// Act
|
||||
var result = DefaultRazorCompletionFactsService.AtDirectiveCompletionPoint(syntaxTree, location);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDirectiveCompletableToken_ReturnsTrueForCSharpIdentifiers()
|
||||
{
|
||||
// Arrange
|
||||
var csharpToken = new CSharpToken("model", CSharpTokenType.Identifier);
|
||||
|
||||
// Act
|
||||
var result = DefaultRazorCompletionFactsService.IsDirectiveCompletableToken(csharpToken);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDirectiveCompletableToken_ReturnsTrueForCSharpMarkerTokens()
|
||||
{
|
||||
// Arrange
|
||||
var csharpToken = new CSharpToken(string.Empty, CSharpTokenType.Unknown);
|
||||
|
||||
// Act
|
||||
var result = DefaultRazorCompletionFactsService.IsDirectiveCompletableToken(csharpToken);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDirectiveCompletableToken_ReturnsFalseForNonCSharpTokens()
|
||||
{
|
||||
// Arrange
|
||||
var token = Mock.Of<IToken>();
|
||||
|
||||
// Act
|
||||
var result = DefaultRazorCompletionFactsService.IsDirectiveCompletableToken(token);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDirectiveCompletableToken_ReturnsFalseForInvalidCSharpTokens()
|
||||
{
|
||||
// Arrange
|
||||
var csharpToken = new CSharpToken("~", CSharpTokenType.Tilde);
|
||||
|
||||
// Act
|
||||
var result = DefaultRazorCompletionFactsService.IsDirectiveCompletableToken(csharpToken);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
private static void AssertRazorCompletionItem(string completionDisplayText, DirectiveDescriptor directive, RazorCompletionItem item)
|
||||
{
|
||||
Assert.Equal(item.DisplayText, completionDisplayText);
|
||||
Assert.Equal(item.InsertText, directive.Directive);
|
||||
Assert.Equal(directive.Description, item.Description);
|
||||
}
|
||||
|
||||
private static void AssertRazorCompletionItem(DirectiveDescriptor directive, RazorCompletionItem item) =>
|
||||
AssertRazorCompletionItem(directive.Directive, directive, item);
|
||||
|
||||
private static RazorSyntaxTree CreateSyntaxTree(string text, params DirectiveDescriptor[] directives)
|
||||
{
|
||||
var sourceDocument = TestRazorSourceDocument.Create(text);
|
||||
var options = RazorParserOptions.Create(builder =>
|
||||
{
|
||||
foreach (var directive in directives)
|
||||
{
|
||||
builder.Directives.Add(directive);
|
||||
}
|
||||
});
|
||||
var syntaxTree = RazorSyntaxTree.Parse(sourceDocument, options);
|
||||
return syntaxTree;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,12 +8,9 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Completion;
|
||||
using Microsoft.CodeAnalysis.Options;
|
||||
using Microsoft.CodeAnalysis.Tags;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
|
|
@ -26,41 +23,19 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
{
|
||||
public class RazorDirectiveCompletionProviderTest
|
||||
{
|
||||
private static readonly IReadOnlyList<DirectiveDescriptor> DefaultDirectives = new[]
|
||||
{
|
||||
CSharpCodeParser.AddTagHelperDirectiveDescriptor,
|
||||
CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
|
||||
CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
|
||||
};
|
||||
|
||||
public RazorDirectiveCompletionProviderTest()
|
||||
{
|
||||
CompletionBroker = Mock.Of<IAsyncCompletionBroker>(broker => broker.IsCompletionSupported(It.IsAny<IContentType>()) == true);
|
||||
var razorBuffer = Mock.Of<ITextBuffer>(buffer => buffer.ContentType == Mock.Of<IContentType>());
|
||||
TextBufferProvider = Mock.Of<RazorTextBufferProvider>(provider => provider.TryGetFromDocument(It.IsAny<TextDocument>(), out razorBuffer) == true);
|
||||
CompletionFactsService = new DefaultRazorCompletionFactsService();
|
||||
}
|
||||
|
||||
private IAsyncCompletionBroker CompletionBroker { get; }
|
||||
|
||||
private RazorTextBufferProvider TextBufferProvider { get; }
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsFalseIfChangeHasNoOwner()
|
||||
{
|
||||
// Arrange
|
||||
var codeDocumentProvider = CreateCodeDocumentProvider("@", Enumerable.Empty<DirectiveDescriptor>());
|
||||
var completionProvider = new FailOnGetCompletionsProvider(codeDocumentProvider, CompletionBroker, TextBufferProvider);
|
||||
var document = CreateDocument();
|
||||
codeDocumentProvider.Value.TryGetFromDocument(document, out var codeDocument);
|
||||
var syntaxTree = codeDocument.GetSyntaxTree();
|
||||
var completionContext = CreateContext(2, completionProvider, document);
|
||||
|
||||
// Act
|
||||
var result = completionProvider.AtDirectiveCompletionPoint(syntaxTree, completionContext);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
private RazorCompletionFactsService CompletionFactsService { get; }
|
||||
|
||||
[Fact]
|
||||
public async Task GetDescriptionAsync_AddsDirectiveDescriptionIfPropertyExists()
|
||||
|
|
@ -76,6 +51,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
var codeDocumentProvider = new Mock<RazorCodeDocumentProvider>();
|
||||
var completionProvider = new RazorDirectiveCompletionProvider(
|
||||
new Lazy<RazorCodeDocumentProvider>(() => codeDocumentProvider.Object),
|
||||
new Lazy<RazorCompletionFactsService>(() => CompletionFactsService),
|
||||
CompletionBroker,
|
||||
TextBufferProvider);
|
||||
|
||||
|
|
@ -98,6 +74,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
var codeDocumentProvider = new Mock<RazorCodeDocumentProvider>();
|
||||
var completionProvider = new RazorDirectiveCompletionProvider(
|
||||
new Lazy<RazorCodeDocumentProvider>(() => codeDocumentProvider.Object),
|
||||
new Lazy<RazorCompletionFactsService>(() => CompletionFactsService),
|
||||
CompletionBroker,
|
||||
TextBufferProvider);
|
||||
|
||||
|
|
@ -126,7 +103,6 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
await completionProvider.ProvideCompletionsAsync(context);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task ProvideCompletionAsync_DoesNotProvideCompletionsForDocumentWithoutPath()
|
||||
{
|
||||
|
|
@ -189,145 +165,6 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
await completionProvider.ProvideCompletionsAsync(context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProvideCompletionAsync_DoesNotProvideCompletionsWhenNotAtCompletionPoint()
|
||||
{
|
||||
// Arrange
|
||||
var codeDocumentProvider = CreateCodeDocumentProvider("@", Enumerable.Empty<DirectiveDescriptor>());
|
||||
var completionProvider = new FailOnGetCompletionsProvider(codeDocumentProvider, CompletionBroker, TextBufferProvider);
|
||||
var document = CreateDocument();
|
||||
var context = CreateContext(0, completionProvider, document);
|
||||
|
||||
// Act & Assert
|
||||
await completionProvider.ProvideCompletionsAsync(context);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("DateTime.Now")]
|
||||
[InlineData("SomeMethod()")]
|
||||
public async Task ProvideCompletionAsync_DoesNotProvideCompletionsWhenAtComplexExpressions(string content)
|
||||
{
|
||||
// Arrange
|
||||
var codeDocumentProvider = CreateCodeDocumentProvider("@" + content, Enumerable.Empty<DirectiveDescriptor>());
|
||||
var completionProvider = new FailOnGetCompletionsProvider(codeDocumentProvider, CompletionBroker, TextBufferProvider);
|
||||
var document = CreateDocument();
|
||||
var context = CreateContext(1, completionProvider, document);
|
||||
|
||||
// Act & Assert
|
||||
await completionProvider.ProvideCompletionsAsync(context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProvideCompletionAsync_DoesNotProvideCompletionsForExplicitExpressions()
|
||||
{
|
||||
// Arrange
|
||||
var codeDocumentProvider = CreateCodeDocumentProvider("@()", Enumerable.Empty<DirectiveDescriptor>());
|
||||
var completionProvider = new FailOnGetCompletionsProvider(codeDocumentProvider, CompletionBroker, TextBufferProvider);
|
||||
var document = CreateDocument();
|
||||
var context = CreateContext(2, completionProvider, document);
|
||||
|
||||
// Act & Assert
|
||||
await completionProvider.ProvideCompletionsAsync(context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProvideCompletionAsync_DoesNotProvideCompletionsForCodeDocumentWithoutSyntaxTree()
|
||||
{
|
||||
// Arrange
|
||||
var codeDocumentProvider = new Mock<RazorCodeDocumentProvider>();
|
||||
var codeDocument = TestRazorCodeDocument.CreateEmpty();
|
||||
codeDocumentProvider.Setup(provider => provider.TryGetFromDocument(It.IsAny<TextDocument>(), out codeDocument))
|
||||
.Returns(true);
|
||||
var completionProvider = new FailOnGetCompletionsProvider(
|
||||
new Lazy<RazorCodeDocumentProvider>(() => codeDocumentProvider.Object),
|
||||
CompletionBroker,
|
||||
TextBufferProvider);
|
||||
var document = CreateDocument();
|
||||
var context = CreateContext(2, completionProvider, document);
|
||||
|
||||
// Act & Assert
|
||||
await completionProvider.ProvideCompletionsAsync(context);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompletionItems_ProvidesCompletionsForDefaultDirectives()
|
||||
{
|
||||
// Arrange
|
||||
var codeDocumentProvider = CreateCodeDocumentProvider("@", Enumerable.Empty<DirectiveDescriptor>());
|
||||
var completionProvider = new RazorDirectiveCompletionProvider(codeDocumentProvider, CompletionBroker, TextBufferProvider);
|
||||
var document = CreateDocument();
|
||||
codeDocumentProvider.Value.TryGetFromDocument(document, out var codeDocument);
|
||||
var syntaxTree = codeDocument.GetSyntaxTree();
|
||||
|
||||
// Act
|
||||
var completionItems = completionProvider.GetCompletionItems(syntaxTree);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
completionItems,
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[0].Description, item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[1].Description, item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[2].Description, item));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompletionItems_ProvidesCompletionsForDefaultAndExtensibleDirectives()
|
||||
{
|
||||
// Arrange
|
||||
var codeDocumentProvider = CreateCodeDocumentProvider("@", new[] { SectionDirective.Directive });
|
||||
var completionProvider = new RazorDirectiveCompletionProvider(codeDocumentProvider, CompletionBroker, TextBufferProvider);
|
||||
var document = CreateDocument();
|
||||
codeDocumentProvider.Value.TryGetFromDocument(document, out var codeDocument);
|
||||
var syntaxTree = codeDocument.GetSyntaxTree();
|
||||
|
||||
// Act
|
||||
var completionItems = completionProvider.GetCompletionItems(syntaxTree);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
completionItems,
|
||||
item => AssertRazorCompletionItem(SectionDirective.Directive.Description, item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[0].Description, item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[1].Description, item),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[2].Description, item));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompletionItems_ProvidesCompletionsForDirectivesWithoutDescription()
|
||||
{
|
||||
// Arrange
|
||||
var customDirective = DirectiveDescriptor.CreateSingleLineDirective("custom");
|
||||
var codeDocumentProvider = CreateCodeDocumentProvider("@", new[] { customDirective });
|
||||
var completionProvider = new RazorDirectiveCompletionProvider(codeDocumentProvider, CompletionBroker, TextBufferProvider);
|
||||
var document = CreateDocument();
|
||||
codeDocumentProvider.Value.TryGetFromDocument(document, out var codeDocument);
|
||||
var syntaxTree = codeDocument.GetSyntaxTree();
|
||||
|
||||
// Act
|
||||
var completionItems = completionProvider.GetCompletionItems(syntaxTree);
|
||||
|
||||
// Assert
|
||||
var customDirectiveCompletion = Assert.Single(completionItems, item => item.DisplayText == customDirective.Directive);
|
||||
AssertRazorCompletionItemDefaults(customDirectiveCompletion);
|
||||
Assert.DoesNotContain(customDirectiveCompletion.Properties, property => property.Key == RazorDirectiveCompletionProvider.DescriptionKey);
|
||||
}
|
||||
|
||||
private static void AssertRazorCompletionItem(string expectedDescription, CompletionItem item)
|
||||
{
|
||||
Assert.True(item.Properties.TryGetValue(RazorDirectiveCompletionProvider.DescriptionKey, out var actualDescription));
|
||||
Assert.Equal(expectedDescription, actualDescription);
|
||||
|
||||
AssertRazorCompletionItemDefaults(item);
|
||||
}
|
||||
|
||||
private static void AssertRazorCompletionItemDefaults(CompletionItem item)
|
||||
{
|
||||
Assert.Equal("_RazorDirective_", item.SortText);
|
||||
Assert.False(item.Rules.FormatOnCommit);
|
||||
var tag = Assert.Single(item.Tags);
|
||||
Assert.Equal(WellKnownTags.Intrinsic, tag);
|
||||
}
|
||||
|
||||
private static Lazy<RazorCodeDocumentProvider> CreateCodeDocumentProvider(string text, IEnumerable<DirectiveDescriptor> directives)
|
||||
{
|
||||
var codeDocumentProvider = new Mock<RazorCodeDocumentProvider>();
|
||||
|
|
@ -388,17 +225,11 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
IAsyncCompletionBroker asyncCompletionBroker,
|
||||
RazorTextBufferProvider textBufferProvider,
|
||||
bool canGetSnapshotPoint = true)
|
||||
: base(codeDocumentProvider, asyncCompletionBroker, textBufferProvider)
|
||||
: base(codeDocumentProvider, new Lazy<RazorCompletionFactsService>(() => new DefaultRazorCompletionFactsService()), asyncCompletionBroker, textBufferProvider)
|
||||
{
|
||||
_canGetSnapshotPoint = canGetSnapshotPoint;
|
||||
}
|
||||
|
||||
internal override IEnumerable<CompletionItem> GetCompletionItems(RazorSyntaxTree syntaxTree)
|
||||
{
|
||||
Assert.False(true, "Completions should not have been attempted.");
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override bool TryGetRazorSnapshotPoint(CompletionContext context, out SnapshotPoint snapshotPoint)
|
||||
{
|
||||
if (!_canGetSnapshotPoint)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
|
||||
private IContentType NonRazorContentType { get; } = Mock.Of<IContentType>(c => c.IsOfType(It.IsAny<string>()) == false);
|
||||
|
||||
private RazorCompletionFactsService CompletionFactsService { get; } = Mock.Of<RazorCompletionFactsService>();
|
||||
|
||||
[Fact]
|
||||
public void CreateCompletionSource_ReturnsNullIfParserHasNotBeenAssocitedWithRazorBuffer()
|
||||
{
|
||||
|
|
@ -27,7 +29,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
var properties = new PropertyCollection();
|
||||
properties.AddProperty(typeof(VisualStudioRazorParser), expectedParser);
|
||||
var razorBuffer = Mock.Of<ITextBuffer>(buffer => buffer.ContentType == RazorContentType && buffer.Properties == properties);
|
||||
var completionSourceProvider = new RazorDirectiveCompletionSourceProvider(Dispatcher);
|
||||
var completionSourceProvider = new RazorDirectiveCompletionSourceProvider(Dispatcher, CompletionFactsService);
|
||||
|
||||
// Act
|
||||
var completionSource = completionSourceProvider.CreateCompletionSource(razorBuffer);
|
||||
|
|
@ -42,7 +44,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
{
|
||||
// Arrange
|
||||
var razorBuffer = Mock.Of<ITextBuffer>(buffer => buffer.ContentType == RazorContentType && buffer.Properties == new PropertyCollection());
|
||||
var completionSourceProvider = new RazorDirectiveCompletionSourceProvider(Dispatcher);
|
||||
var completionSourceProvider = new RazorDirectiveCompletionSourceProvider(Dispatcher, CompletionFactsService);
|
||||
|
||||
// Act
|
||||
var completionSource = completionSourceProvider.CreateCompletionSource(razorBuffer);
|
||||
|
|
@ -56,7 +58,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
{
|
||||
// Arrange
|
||||
var textView = CreateTextView(NonRazorContentType, new PropertyCollection());
|
||||
var completionSourceProvider = new RazorDirectiveCompletionSourceProvider(Dispatcher);
|
||||
var completionSourceProvider = new RazorDirectiveCompletionSourceProvider(Dispatcher, CompletionFactsService);
|
||||
|
||||
// Act
|
||||
var completionSource = completionSourceProvider.GetOrCreate(textView);
|
||||
|
|
@ -73,7 +75,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
var properties = new PropertyCollection();
|
||||
properties.AddProperty(typeof(VisualStudioRazorParser), expectedParser);
|
||||
var textView = CreateTextView(RazorContentType, properties);
|
||||
var completionSourceProvider = new RazorDirectiveCompletionSourceProvider(Dispatcher);
|
||||
var completionSourceProvider = new RazorDirectiveCompletionSourceProvider(Dispatcher, CompletionFactsService);
|
||||
|
||||
// Act
|
||||
var completionSource1 = completionSourceProvider.GetOrCreate(textView);
|
||||
|
|
|
|||
|
|
@ -25,16 +25,18 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
|
||||
};
|
||||
|
||||
private RazorCompletionFactsService CompletionFactsService { get; } = new DefaultRazorCompletionFactsService();
|
||||
|
||||
[ForegroundFact]
|
||||
public async Task GetCompletionContextAsync_DoesNotProvideCompletionsPriorToParseResults()
|
||||
{
|
||||
// Arrange
|
||||
var text = "@validCompletion";
|
||||
var parser = Mock.Of<VisualStudioRazorParser>(); // CodeDocument will be null faking a parser without a parse.
|
||||
var completionSource = new RazorDirectiveCompletionSource(parser, Dispatcher);
|
||||
var completionSource = new RazorDirectiveCompletionSource(Dispatcher, parser, CompletionFactsService);
|
||||
var documentSnapshot = new StringTextSnapshot(text);
|
||||
var triggerLocation = new SnapshotPoint(documentSnapshot, 4);
|
||||
var applicableSpan = new SnapshotSpan(documentSnapshot, new Span(1, text.Length - 1 /* @ */));
|
||||
var applicableSpan = new SnapshotSpan(documentSnapshot, new Span(1, text.Length - 1 /* validCompletion */));
|
||||
|
||||
// Act
|
||||
var completionContext = await Task.Run(
|
||||
|
|
@ -50,7 +52,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
// Arrange
|
||||
var text = "@(NotValidCompletionLocation)";
|
||||
var parser = CreateParser(text);
|
||||
var completionSource = new RazorDirectiveCompletionSource(parser, Dispatcher);
|
||||
var completionSource = new RazorDirectiveCompletionSource(Dispatcher, parser, CompletionFactsService);
|
||||
var documentSnapshot = new StringTextSnapshot(text);
|
||||
var triggerLocation = new SnapshotPoint(documentSnapshot, 4);
|
||||
var applicableSpan = new SnapshotSpan(documentSnapshot, new Span(2, text.Length - 3 /* @() */));
|
||||
|
|
@ -70,10 +72,10 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
// Arrange
|
||||
var text = "@addTag";
|
||||
var parser = CreateParser(text, SectionDirective.Directive);
|
||||
var completionSource = new RazorDirectiveCompletionSource(parser, Dispatcher);
|
||||
var completionSource = new RazorDirectiveCompletionSource(Dispatcher, parser, CompletionFactsService);
|
||||
var documentSnapshot = new StringTextSnapshot(text);
|
||||
var triggerLocation = new SnapshotPoint(documentSnapshot, 4);
|
||||
var applicableSpan = new SnapshotSpan(documentSnapshot, new Span(1, text.Length - 1 /* @ */));
|
||||
var applicableSpan = new SnapshotSpan(documentSnapshot, new Span(1, 6 /* addTag */));
|
||||
|
||||
// Act
|
||||
var completionContext = await Task.Run(
|
||||
|
|
@ -88,71 +90,6 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
item => AssertRazorCompletionItem(DefaultDirectives[2], item, completionSource));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompletionItems_ReturnsDefaultDirectivesAsCompletionItems()
|
||||
{
|
||||
// Arrange
|
||||
var syntaxTree = CreateSyntaxTree("@addTag");
|
||||
var completionSource = new RazorDirectiveCompletionSource(Mock.Of<VisualStudioRazorParser>(), Dispatcher);
|
||||
|
||||
// Act
|
||||
var completionItems = completionSource.GetCompletionItems(syntaxTree);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
completionItems,
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[0], item, completionSource),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[1], item, completionSource),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[2], item, completionSource));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompletionItems_ReturnsCustomDirectivesAsCompletionItems()
|
||||
{
|
||||
// Arrange
|
||||
var customDirective = DirectiveDescriptor.CreateSingleLineDirective("custom", builder =>
|
||||
{
|
||||
builder.Description = "My Custom Directive.";
|
||||
});
|
||||
var syntaxTree = CreateSyntaxTree("@addTag", customDirective);
|
||||
var completionSource = new RazorDirectiveCompletionSource(Mock.Of<VisualStudioRazorParser>(), Dispatcher);
|
||||
|
||||
// Act
|
||||
var completionItems = completionSource.GetCompletionItems(syntaxTree);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
completionItems,
|
||||
item => AssertRazorCompletionItem(customDirective, item, completionSource),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[0], item, completionSource),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[1], item, completionSource),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[2], item, completionSource));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCompletionItems_UsesDisplayNamesWhenNotNull()
|
||||
{
|
||||
// Arrange
|
||||
var customDirective = DirectiveDescriptor.CreateSingleLineDirective("custom", builder =>
|
||||
{
|
||||
builder.DisplayName = "different";
|
||||
builder.Description = "My Custom Directive.";
|
||||
});
|
||||
var syntaxTree = CreateSyntaxTree("@addTag", customDirective);
|
||||
var completionSource = new RazorDirectiveCompletionSource(Mock.Of<VisualStudioRazorParser>(), Dispatcher);
|
||||
|
||||
// Act
|
||||
var completionItems = completionSource.GetCompletionItems(syntaxTree);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
completionItems,
|
||||
item => AssertRazorCompletionItem("different", customDirective, item, completionSource),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[0], item, completionSource),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[1], item, completionSource),
|
||||
item => AssertRazorCompletionItem(DefaultDirectives[2], item, completionSource));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDescriptionAsync_AddsDirectiveDescriptionIfPropertyExists()
|
||||
{
|
||||
|
|
@ -160,7 +97,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
var completionItem = new CompletionItem("TestDirective", Mock.Of<IAsyncCompletionSource>());
|
||||
var expectedDescription = "The expected description";
|
||||
completionItem.Properties.AddProperty(RazorDirectiveCompletionSource.DescriptionKey, expectedDescription);
|
||||
var completionSource = new RazorDirectiveCompletionSource(Mock.Of<VisualStudioRazorParser>(), Dispatcher);
|
||||
var completionSource = new RazorDirectiveCompletionSource(Dispatcher, Mock.Of<VisualStudioRazorParser>(), CompletionFactsService);
|
||||
|
||||
// Act
|
||||
var descriptionObject = await completionSource.GetDescriptionAsync(completionItem, CancellationToken.None);
|
||||
|
|
@ -175,7 +112,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
{
|
||||
// Arrange
|
||||
var completionItem = new CompletionItem("TestDirective", Mock.Of<IAsyncCompletionSource>());
|
||||
var completionSource = new RazorDirectiveCompletionSource(Mock.Of<VisualStudioRazorParser>(), Dispatcher);
|
||||
var completionSource = new RazorDirectiveCompletionSource(Dispatcher, Mock.Of<VisualStudioRazorParser>(), CompletionFactsService);
|
||||
|
||||
// Act
|
||||
var descriptionObject = await completionSource.GetDescriptionAsync(completionItem, CancellationToken.None);
|
||||
|
|
@ -185,138 +122,6 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
Assert.Equal(string.Empty, description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsFalseIfSyntaxTreeNull()
|
||||
{
|
||||
// Act
|
||||
var result = RazorDirectiveCompletionSource.AtDirectiveCompletionPoint(syntaxTree: null, location: new SnapshotPoint());
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsFalseIfNoOwner()
|
||||
{
|
||||
// Arrange
|
||||
var syntaxTree = CreateSyntaxTree("@");
|
||||
var snapshotPoint = new SnapshotPoint(new StringTextSnapshot("@ text"), 2);
|
||||
|
||||
// Act
|
||||
var result = RazorDirectiveCompletionSource.AtDirectiveCompletionPoint(syntaxTree, snapshotPoint);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsFalseWhenOwnerIsNotExpression()
|
||||
{
|
||||
// Arrange
|
||||
var syntaxTree = CreateSyntaxTree("@{");
|
||||
var snapshotPoint = new SnapshotPoint(new StringTextSnapshot("@{"), 2);
|
||||
|
||||
// Act
|
||||
var result = RazorDirectiveCompletionSource.AtDirectiveCompletionPoint(syntaxTree, snapshotPoint);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsFalseWhenOwnerIsComplexExpression()
|
||||
{
|
||||
// Arrange
|
||||
var syntaxTree = CreateSyntaxTree("@DateTime.Now");
|
||||
var snapshotPoint = new SnapshotPoint(new StringTextSnapshot("@DateTime.Now"), 2);
|
||||
|
||||
// Act
|
||||
var result = RazorDirectiveCompletionSource.AtDirectiveCompletionPoint(syntaxTree, snapshotPoint);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsFalseWhenOwnerIsExplicitExpression()
|
||||
{
|
||||
// Arrange
|
||||
var syntaxTree = CreateSyntaxTree("@(something)");
|
||||
var snapshotPoint = new SnapshotPoint(new StringTextSnapshot("@(something)"), 4);
|
||||
|
||||
// Act
|
||||
var result = RazorDirectiveCompletionSource.AtDirectiveCompletionPoint(syntaxTree, snapshotPoint);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AtDirectiveCompletionPoint_ReturnsTrueForSimpleImplicitExpressions()
|
||||
{
|
||||
// Arrange
|
||||
var syntaxTree = CreateSyntaxTree("@mod");
|
||||
var snapshotPoint = new SnapshotPoint(new StringTextSnapshot("@mod"), 2);
|
||||
|
||||
// Act
|
||||
var result = RazorDirectiveCompletionSource.AtDirectiveCompletionPoint(syntaxTree, snapshotPoint);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDirectiveCompletableToken_ReturnsTrueForCSharpIdentifiers()
|
||||
{
|
||||
// Arrange
|
||||
var csharpToken = new CSharpToken("model", CSharpTokenType.Identifier);
|
||||
|
||||
// Act
|
||||
var result = RazorDirectiveCompletionSource.IsDirectiveCompletableToken(csharpToken);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDirectiveCompletableToken_ReturnsTrueForCSharpMarkerTokens()
|
||||
{
|
||||
// Arrange
|
||||
var csharpToken = new CSharpToken(string.Empty, CSharpTokenType.Unknown);
|
||||
|
||||
// Act
|
||||
var result = RazorDirectiveCompletionSource.IsDirectiveCompletableToken(csharpToken);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDirectiveCompletableToken_ReturnsFalseForNonCSharpTokens()
|
||||
{
|
||||
// Arrange
|
||||
var token = Mock.Of<IToken>();
|
||||
|
||||
// Act
|
||||
var result = RazorDirectiveCompletionSource.IsDirectiveCompletableToken(token);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDirectiveCompletableToken_ReturnsFalseForInvalidCSharpTokens()
|
||||
{
|
||||
// Arrange
|
||||
var csharpToken = new CSharpToken("~", CSharpTokenType.Tilde);
|
||||
|
||||
// Act
|
||||
var result = RazorDirectiveCompletionSource.IsDirectiveCompletableToken(csharpToken);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
private static void AssertRazorCompletionItem(string completionDisplayText, DirectiveDescriptor directive, CompletionItem item, IAsyncCompletionSource source)
|
||||
{
|
||||
Assert.Equal(item.DisplayText, completionDisplayText);
|
||||
|
|
|
|||
Loading…
Reference in New Issue