From 572b55690dd23e9df3239681dba5fc3c46022e6e Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Fri, 10 Aug 2018 11:41:11 -0700 Subject: [PATCH] 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 --- .../DefaultRazorCompletionFactsService.cs | 98 +++++++ .../RazorCompletionFactsService.cs | 13 + .../RazorCompletionItem.cs | 45 ++++ .../RazorCompletionItemKind.cs | 10 + .../RazorDirectiveCompletionProvider.cs | 73 ++---- .../RazorDirectiveCompletionSource.cs | 126 +++------ .../RazorDirectiveCompletionSourceProvider.cs | 13 +- .../DefaultRazorCompletionFactsServiceTest.cs | 239 ++++++++++++++++++ .../RazorDirectiveCompletionProviderTest.cs | 179 +------------ ...orDirectiveCompletionSourceProviderTest.cs | 10 +- .../RazorDirectiveCompletionSourceTest.cs | 213 +--------------- 11 files changed, 501 insertions(+), 518 deletions(-) create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/DefaultRazorCompletionFactsService.cs create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/RazorCompletionFactsService.cs create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/RazorCompletionItem.cs create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/RazorCompletionItemKind.cs create mode 100644 test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorCompletionFactsServiceTest.cs diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultRazorCompletionFactsService.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultRazorCompletionFactsService.cs new file mode 100644 index 0000000000..57ac8ddb8d --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultRazorCompletionFactsService.cs @@ -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 DefaultDirectives = new[] + { + CSharpCodeParser.AddTagHelperDirectiveDescriptor, + CSharpCodeParser.RemoveTagHelperDirectiveDescriptor, + CSharpCodeParser.TagHelperPrefixDirectiveDescriptor, + }; + + public override IReadOnlyList GetCompletionItems(RazorSyntaxTree syntaxTree, SourceSpan location) + { + var completionItems = new List(); + + if (AtDirectiveCompletionPoint(syntaxTree, location)) + { + var directiveCompletions = GetDirectiveCompletionItems(syntaxTree); + completionItems.AddRange(directiveCompletions); + } + + return completionItems; + } + + // Internal for testing + internal static List GetDirectiveCompletionItems(RazorSyntaxTree syntaxTree) + { + var directives = syntaxTree.Options.Directives.Concat(DefaultDirectives); + var completionItems = new List(); + 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; + } + } +} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/RazorCompletionFactsService.cs b/src/Microsoft.VisualStudio.Editor.Razor/RazorCompletionFactsService.cs new file mode 100644 index 0000000000..5009002d13 --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/RazorCompletionFactsService.cs @@ -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 GetCompletionItems(RazorSyntaxTree syntaxTree, SourceSpan location); + } +} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/RazorCompletionItem.cs b/src/Microsoft.VisualStudio.Editor.Razor/RazorCompletionItem.cs new file mode 100644 index 0000000000..94a7bdb769 --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/RazorCompletionItem.cs @@ -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; } + } +} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/RazorCompletionItemKind.cs b/src/Microsoft.VisualStudio.Editor.Razor/RazorCompletionItemKind.cs new file mode 100644 index 0000000000..9925cb134a --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/RazorCompletionItemKind.cs @@ -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 + } +} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionProvider.cs b/src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionProvider.cs index fc3a4b9ed3..607270c978 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionProvider.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionProvider.cs @@ -36,12 +36,14 @@ namespace Microsoft.VisualStudio.Editor.Razor CSharpCodeParser.TagHelperPrefixDirectiveDescriptor, }; private readonly Lazy _codeDocumentProvider; + private readonly Lazy _completionFactsService; private readonly IAsyncCompletionBroker _asyncCompletionBroker; private readonly RazorTextBufferProvider _textBufferProvider; [ImportingConstructor] public RazorDirectiveCompletionProvider( [Import(typeof(RazorCodeDocumentProvider))] Lazy codeDocumentProvider, + [Import(typeof(RazorCompletionFactsService))] Lazy 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 GetCompletionItems(RazorSyntaxTree syntaxTree) - { - var directives = syntaxTree.Options.Directives.Concat(DefaultDirectives); - var completionItems = new List(); - foreach (var directive in directives) + foreach (var razorCompletionItem in razorCompletionItems) { - var propertyDictionary = new Dictionary(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(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) diff --git a/src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionSource.cs b/src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionSource.cs index 06677a843c..6224fc43d4 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionSource.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionSource.cs @@ -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 DirectiveCompletionFilters = new[] { new CompletionFilter("Razor Directive", "r", DirectiveImageGlyph) }.ToImmutableArray(); - private static readonly IEnumerable 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 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(); + 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.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 GetCompletionItems(RazorSyntaxTree syntaxTree) - { - var directives = syntaxTree.Options.Directives.Concat(DefaultDirectives); - var completionItems = new List(); - 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.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; - } } } diff --git a/src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionSourceProvider.cs b/src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionSourceProvider.cs index 343bfba36f..0c9e40a38f 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionSourceProvider.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionSourceProvider.cs @@ -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; } } diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorCompletionFactsServiceTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorCompletionFactsServiceTest.cs new file mode 100644 index 0000000000..a5f095be0d --- /dev/null +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorCompletionFactsServiceTest.cs @@ -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 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(); + + // 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; + } + } +} diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/RazorDirectiveCompletionProviderTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/RazorDirectiveCompletionProviderTest.cs index d6e5ff747d..cd35510b36 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/RazorDirectiveCompletionProviderTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/RazorDirectiveCompletionProviderTest.cs @@ -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 DefaultDirectives = new[] - { - CSharpCodeParser.AddTagHelperDirectiveDescriptor, - CSharpCodeParser.RemoveTagHelperDirectiveDescriptor, - CSharpCodeParser.TagHelperPrefixDirectiveDescriptor, - }; - public RazorDirectiveCompletionProviderTest() { CompletionBroker = Mock.Of(broker => broker.IsCompletionSupported(It.IsAny()) == true); var razorBuffer = Mock.Of(buffer => buffer.ContentType == Mock.Of()); TextBufferProvider = Mock.Of(provider => provider.TryGetFromDocument(It.IsAny(), 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()); - 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(); var completionProvider = new RazorDirectiveCompletionProvider( new Lazy(() => codeDocumentProvider.Object), + new Lazy(() => CompletionFactsService), CompletionBroker, TextBufferProvider); @@ -98,6 +74,7 @@ namespace Microsoft.VisualStudio.Editor.Razor var codeDocumentProvider = new Mock(); var completionProvider = new RazorDirectiveCompletionProvider( new Lazy(() => codeDocumentProvider.Object), + new Lazy(() => 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()); - 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()); - 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()); - 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(); - var codeDocument = TestRazorCodeDocument.CreateEmpty(); - codeDocumentProvider.Setup(provider => provider.TryGetFromDocument(It.IsAny(), out codeDocument)) - .Returns(true); - var completionProvider = new FailOnGetCompletionsProvider( - new Lazy(() => 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()); - 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 CreateCodeDocumentProvider(string text, IEnumerable directives) { var codeDocumentProvider = new Mock(); @@ -388,17 +225,11 @@ namespace Microsoft.VisualStudio.Editor.Razor IAsyncCompletionBroker asyncCompletionBroker, RazorTextBufferProvider textBufferProvider, bool canGetSnapshotPoint = true) - : base(codeDocumentProvider, asyncCompletionBroker, textBufferProvider) + : base(codeDocumentProvider, new Lazy(() => new DefaultRazorCompletionFactsService()), asyncCompletionBroker, textBufferProvider) { _canGetSnapshotPoint = canGetSnapshotPoint; } - internal override IEnumerable 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) diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/RazorDirectiveCompletionSourceProviderTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/RazorDirectiveCompletionSourceProviderTest.cs index 5cffff529b..ac8dba7e3c 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/RazorDirectiveCompletionSourceProviderTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/RazorDirectiveCompletionSourceProviderTest.cs @@ -19,6 +19,8 @@ namespace Microsoft.VisualStudio.Editor.Razor private IContentType NonRazorContentType { get; } = Mock.Of(c => c.IsOfType(It.IsAny()) == false); + private RazorCompletionFactsService CompletionFactsService { get; } = Mock.Of(); + [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(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(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); diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/RazorDirectiveCompletionSourceTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/RazorDirectiveCompletionSourceTest.cs index 3e51fbf0fe..1c9516fa8a 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/RazorDirectiveCompletionSourceTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/RazorDirectiveCompletionSourceTest.cs @@ -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(); // 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(), 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(), 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(), 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()); var expectedDescription = "The expected description"; completionItem.Properties.AddProperty(RazorDirectiveCompletionSource.DescriptionKey, expectedDescription); - var completionSource = new RazorDirectiveCompletionSource(Mock.Of(), Dispatcher); + var completionSource = new RazorDirectiveCompletionSource(Dispatcher, Mock.Of(), 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()); - var completionSource = new RazorDirectiveCompletionSource(Mock.Of(), Dispatcher); + var completionSource = new RazorDirectiveCompletionSource(Dispatcher, Mock.Of(), 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(); - - // 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);