public void TryCreateTooltip_Markup_Attribute_MultipleAssociatedAttributes_ReturnsTrue()
        {
            // Arrange
            var descriptionFactory = new DefaultLSPTagHelperTooltipFactory(LanguageServer);
            var associatedAttributeDescriptions = new[]
            {
                new BoundAttributeDescriptionInfo(
                    returnTypeName: "System.String",
                    typeName: "Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName",
                    propertyName: "SomeProperty",
                    documentation: "<summary>Uses <see cref=\"T:System.Collections.List{System.String}\" />s</summary>"),
                new BoundAttributeDescriptionInfo(
                    propertyName: "AnotherProperty",
                    typeName: "Microsoft.AspNetCore.SomeTagHelpers.AnotherTypeName",
                    returnTypeName: "System.Boolean?",
                    documentation: "<summary>\nUses <see cref=\"T:System.Collections.List{System.String}\" />s\n</summary>"),
            };
            var attributeDescription = new AggregateBoundAttributeDescription(associatedAttributeDescriptions);

            // Act
            var result = descriptionFactory.TryCreateTooltip(attributeDescription, out var markdown);

            // Assert
            Assert.True(result);
            Assert.Equal(@"**string** SomeTypeName.**SomeProperty**

Uses `List<System.String>`s
---
**Boolean?** AnotherTypeName.**AnotherProperty**

Uses `List<System.String>`s", markdown.Value);
            Assert.Equal(MarkupKind.Markdown, markdown.Kind);
        }
        public void TryCreateTooltip_Markup_Attribute_PlainText_NoBold()
        {
            // Arrange
            var languageServer = LanguageServer;

            languageServer.ClientSettings.Capabilities.TextDocument.Completion.Value.CompletionItem.DocumentationFormat = new Container <MarkupKind>(MarkupKind.PlainText);
            var descriptionFactory = new DefaultLSPTagHelperTooltipFactory(languageServer);
            var associatedAttributeDescriptions = new[]
            {
                new BoundAttributeDescriptionInfo(
                    returnTypeName: "System.String",
                    typeName: "Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName",
                    propertyName: "SomeProperty",
                    documentation: "<summary>Uses <see cref=\"T:System.Collections.List{System.String}\" />s</summary>")
            };
            var attributeDescription = new AggregateBoundAttributeDescription(associatedAttributeDescriptions);

            // Act
            var result = descriptionFactory.TryCreateTooltip(attributeDescription, out var markdown);

            // Assert
            Assert.True(result);
            Assert.Equal(@"string SomeTypeName.SomeProperty

Uses `List<System.String>`s", markdown.Value);
            Assert.Equal(MarkupKind.PlainText, markdown.Kind);
        }
Example #3
0
        private HoverModel AttributeInfoToHover(IEnumerable <BoundAttributeDescriptor> descriptors, RangeModel range, string attributeName, ClientCapabilities clientCapabilities)
        {
            var descriptionInfos = descriptors.Select(boundAttribute =>
            {
                var indexer         = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, boundAttribute);
                var descriptionInfo = BoundAttributeDescriptionInfo.From(boundAttribute, indexer);
                return(descriptionInfo);
            }).ToList().AsReadOnly();
            var attrDescriptionInfo = new AggregateBoundAttributeDescription(descriptionInfos);

            var isVSClient = clientCapabilities is PlatformAgnosticClientCapabilities platformAgnosticClientCapabilities &&
                             platformAgnosticClientCapabilities.SupportsVisualStudioExtensions;

            if (isVSClient && _vsLspTagHelperTooltipFactory.TryCreateTooltip(attrDescriptionInfo, out ContainerElement classifiedTextElement))
            {
                var vsHover = new OmniSharpVSHover
                {
                    Contents   = new MarkedStringsOrMarkupContent(),
                    Range      = range,
                    RawContent = classifiedTextElement,
                };

                return(vsHover);
            }
            else
            {
                var hoverContentFormat = GetHoverContentFormat(clientCapabilities);

                if (!_lspTagHelperTooltipFactory.TryCreateTooltip(attrDescriptionInfo, hoverContentFormat, out var vsMarkupContent))
                {
                    return(null);
                }

                Enum.TryParse(vsMarkupContent.Kind.Value, out MarkupKind markupKind);

                var markupContent = new MarkupContent()
                {
                    Value = vsMarkupContent.Value,
                    Kind  = markupKind,
                };

                var hover = new HoverModel
                {
                    Contents = new MarkedStringsOrMarkupContent(markupContent),
                    Range    = range
                };

                return(hover);
            }
        }
        public void CreateClassifiedDescription_SingleDescription_NoSeparator()
        {
            // Arrange
            var factory     = new DefaultVisualStudioDescriptionFactory();
            var description = new AggregateBoundAttributeDescription(new[]
            {
                new BoundAttributeDescriptionInfo("TheReturnType", "TheTypeName", "ThePropertyName", "The documentation"),
            });

            // Act
            var result = factory.CreateClassifiedDescription(description);

            // Assert
            Assert.DoesNotContain(DefaultVisualStudioDescriptionFactory.SeparatorElement, result.Elements);
        }
        public void CreateClassifiedDescription_RepresentsPropertyName()
        {
            // Arrange
            var factory     = new DefaultVisualStudioDescriptionFactory();
            var description = new AggregateBoundAttributeDescription(new[]
            {
                new BoundAttributeDescriptionInfo("TheReturnType", "TheTypeName", "ThePropertyName", "The documentation"),
            });

            // Act
            var result = factory.CreateClassifiedDescription(description);

            // Assert
            var flattened = FlattenToStrings(result);

            Assert.Contains(description.DescriptionInfos[0].PropertyName, flattened);
        }
Example #6
0
        public void TryCreateTooltip_ClassifiedTextElement_Attribute_SingleAssociatedAttribute_ReturnsTrue_NestedTypes()
        {
            // Arrange
            var descriptionFactory = new DefaultVSLSPTagHelperTooltipFactory();
            var associatedAttributeDescriptions = new[]
            {
                new BoundAttributeDescriptionInfo(
                    returnTypeName: "System.String",
                    typeName: "Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName",
                    propertyName: "SomeProperty",
                    documentation: "<summary>Uses <see cref=\"T:System.Collections.List{System.Collections.List{System.String}}\" />s</summary>")
            };
            var attributeDescription = new AggregateBoundAttributeDescription(associatedAttributeDescriptions);

            // Act
            var result = descriptionFactory.TryCreateTooltip(attributeDescription, out VSClassifiedTextElement classifiedTextElement);

            // Assert
            Assert.True(result);

            // Expected output:
            //     string Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName.SomeProperty
            //     Uses List<List<string>>s
            Assert.Collection(classifiedTextElement.Runs,
                              run => AssertExpectedClassification(run, "string", VSPredefinedClassificationTypeNames.Keyword),
                              run => AssertExpectedClassification(run, " ", VSPredefinedClassificationTypeNames.WhiteSpace),
                              run => AssertExpectedClassification(run, "Microsoft", VSPredefinedClassificationTypeNames.Text),
                              run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "AspNetCore", VSPredefinedClassificationTypeNames.Text),
                              run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "SomeTagHelpers", VSPredefinedClassificationTypeNames.Text),
                              run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "SomeTypeName", VSPredefinedClassificationTypeNames.Type),
                              run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "SomeProperty", VSPredefinedClassificationTypeNames.Identifier),
                              run => AssertExpectedClassification(run, Environment.NewLine, VSPredefinedClassificationTypeNames.WhiteSpace),
                              run => AssertExpectedClassification(run, "Uses ", VSPredefinedClassificationTypeNames.Text),
                              run => AssertExpectedClassification(run, "List", VSPredefinedClassificationTypeNames.Type),
                              run => AssertExpectedClassification(run, "<", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "List", VSPredefinedClassificationTypeNames.Type),
                              run => AssertExpectedClassification(run, "<", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "string", VSPredefinedClassificationTypeNames.Keyword),
                              run => AssertExpectedClassification(run, ">", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, ">", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "s", VSPredefinedClassificationTypeNames.Text));
        }
        public void CreateClassifiedDescription_CanSimplifyKeywordReturnTypes()
        {
            // Arrange
            var factory     = new DefaultVisualStudioDescriptionFactory();
            var description = new AggregateBoundAttributeDescription(new[]
            {
                new BoundAttributeDescriptionInfo("System.String", "TheTypeName", "ThePropertyName", "The documentation"),
            });

            // Act
            var result = factory.CreateClassifiedDescription(description);

            // Assert
            var flattened = FlattenToStrings(result);

            Assert.DoesNotContain(description.DescriptionInfos[0].ReturnTypeName, flattened);
            Assert.Contains("string", flattened);
        }
        public void CreateClassifiedDescription_CanRepresentMultipleDescriptions()
        {
            // Arrange
            var factory     = new DefaultVisualStudioDescriptionFactory();
            var description = new AggregateBoundAttributeDescription(new[]
            {
                new BoundAttributeDescriptionInfo("System.String", "TheTypeName", "ThePropertyName", "The documentation"),
                new BoundAttributeDescriptionInfo("System.Int32", "TheSecondTypeName", "TheSecondPropertyName", "The second documentation"),
            });

            // Act
            var result = factory.CreateClassifiedDescription(description);

            // Assert
            var flattened = FlattenToStrings(result);

            Assert.Contains(description.DescriptionInfos[0].TypeName, flattened);
            Assert.Contains(description.DescriptionInfos[1].TypeName, flattened);
            Assert.Contains(description.DescriptionInfos[0].Documentation, flattened);
            Assert.Contains(description.DescriptionInfos[1].Documentation, flattened);
        }
Example #9
0
        public void TryConvert_TagHelperAttribute_ForHtml_ReturnsTrue()
        {
            // Arrange
            var completionItem = new RazorCompletionItem("format", "format=\"$0\"", RazorCompletionItemKind.TagHelperAttribute, isSnippet: true);
            var attributeCompletionDescription = new AggregateBoundAttributeDescription(new BoundAttributeDescriptionInfo[] { });

            completionItem.SetAttributeCompletionDescription(attributeCompletionDescription);

            // Act
            var result = RazorCompletionEndpoint.TryConvert(completionItem, ClientCapabilities, out var converted);

            // Assert
            Assert.True(result);
            Assert.Equal(completionItem.DisplayText, converted.Label);
            Assert.Equal("format=\"$0\"", converted.InsertText);
            Assert.Equal(InsertTextFormat.Snippet, converted.InsertTextFormat);
            Assert.Equal(completionItem.DisplayText, converted.FilterText);
            Assert.Equal(completionItem.DisplayText, converted.SortText);
            Assert.Null(converted.Detail);
            Assert.Null(converted.Documentation);
            Assert.Null(converted.Command);
        }
Example #10
0
        private HoverModel AttributeInfoToHover(IEnumerable <BoundAttributeDescriptor> descriptors, RangeModel range, string attributeName)
        {
            var descriptionInfos = descriptors.Select(boundAttribute =>
            {
                var indexer         = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, boundAttribute);
                var descriptionInfo = BoundAttributeDescriptionInfo.From(boundAttribute, indexer);
                return(descriptionInfo);
            }).ToList().AsReadOnly();
            var attrDescriptionInfo = new AggregateBoundAttributeDescription(descriptionInfos);

            if (!_tagHelperTooltipFactory.TryCreateTooltip(attrDescriptionInfo, out var markupContent))
            {
                return(null);
            }

            var hover = new HoverModel
            {
                Contents = new MarkedStringsOrMarkupContent(markupContent),
                Range    = range
            };

            return(hover);
        }
Example #11
0
        public async Task GetDescriptionAsync_DescriptionData_AsksFactoryForDescription()
        {
            // Arrange
            var expectedResult     = new ContainerElement(ContainerElementStyle.Wrapped);
            var description        = new AggregateBoundAttributeDescription(Array.Empty <BoundAttributeDescriptionInfo>());
            var descriptionFactory = Mock.Of <VisualStudioDescriptionFactory>(factory => factory.CreateClassifiedDescription(description) == expectedResult, MockBehavior.Strict);
            var source             = new RazorDirectiveAttributeCompletionSource(
                Dispatcher,
                Mock.Of <VisualStudioRazorParser>(MockBehavior.Strict),
                Mock.Of <RazorCompletionFactsService>(MockBehavior.Strict),
                Mock.Of <ICompletionBroker>(MockBehavior.Strict),
                descriptionFactory);
            var completionSessionSource = Mock.Of <IAsyncCompletionSource>(MockBehavior.Strict);
            var completionItem          = new CompletionItem("@random", completionSessionSource);

            completionItem.Properties.AddProperty(RazorDirectiveAttributeCompletionSource.DescriptionKey, description);

            // Act
            var result = await source.GetDescriptionAsync(session : null, completionItem, CancellationToken.None);

            // Assert
            Assert.Equal(expectedResult, result);
        }
Example #12
0
        public void TryConvert_TagHelperAttribute_ForBool_ReturnsTrue()
        {
            // Arrange
            var completionItem = new RazorCompletionItem("format", "format", RazorCompletionItemKind.TagHelperAttribute);
            var attributeCompletionDescription = new AggregateBoundAttributeDescription(new[] {
                new BoundAttributeDescriptionInfo("System.Boolean", "Stuff", "format", "SomeDocs")
            });

            completionItem.SetAttributeCompletionDescription(attributeCompletionDescription);

            // Act
            var result = RazorCompletionEndpoint.TryConvert(completionItem, ClientCapabilities, out var converted);

            // Assert
            Assert.True(result);
            Assert.Equal(completionItem.DisplayText, converted.Label);
            Assert.Equal("format", converted.InsertText);
            Assert.Equal(InsertTextFormat.Plaintext, converted.InsertTextFormat);
            Assert.Equal(completionItem.InsertText, converted.FilterText);
            Assert.Equal(completionItem.InsertText, converted.SortText);
            Assert.Null(converted.Detail);
            Assert.Null(converted.Documentation);
            Assert.Null(converted.Command);
        }
Example #13
0
 public abstract bool TryCreateTooltip(AggregateBoundAttributeDescription attributeDescriptionInfo, MarkupKind markupKind, [NotNullWhen(true)] out MarkupContent?tooltipContent);
        // Internal for testing
        internal IReadOnlyList <RazorCompletionItem> GetAttributeParameterCompletions(
            string attributeName,
            string parameterName,
            string containingTagName,
            IEnumerable <string> attributes,
            TagHelperDocumentContext tagHelperDocumentContext)
        {
            var descriptorsForTag = _tagHelperFactsService.GetTagHelpersGivenTag(tagHelperDocumentContext, containingTagName, parentTag: null);

            if (descriptorsForTag.Count == 0)
            {
                // If the current tag has no possible descriptors then we can't have any additional attributes.
                return(Array.Empty <RazorCompletionItem>());
            }

            // Attribute parameters are case sensitive when matching
            var attributeCompletions = new Dictionary <string, HashSet <BoundAttributeDescriptionInfo> >(StringComparer.Ordinal);

            foreach (var descriptor in descriptorsForTag)
            {
                for (var i = 0; i < descriptor.BoundAttributes.Count; i++)
                {
                    var attributeDescriptor      = descriptor.BoundAttributes[i];
                    var boundAttributeParameters = attributeDescriptor.BoundAttributeParameters;
                    if (boundAttributeParameters.Count == 0)
                    {
                        continue;
                    }

                    if (TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor))
                    {
                        for (var j = 0; j < boundAttributeParameters.Count; j++)
                        {
                            var parameterDescriptor = boundAttributeParameters[j];

                            if (attributes.Any(name => TagHelperMatchingConventions.SatisfiesBoundAttributeWithParameter(name, attributeDescriptor, parameterDescriptor)))
                            {
                                // There's already an existing attribute that satisfies this parameter, don't show it in the completion list.
                                continue;
                            }

                            if (!attributeCompletions.TryGetValue(parameterDescriptor.Name, out var attributeDescriptionInfos))
                            {
                                attributeDescriptionInfos = new HashSet <BoundAttributeDescriptionInfo>();
                                attributeCompletions[parameterDescriptor.Name] = attributeDescriptionInfos;
                            }

                            var tagHelperTypeName = descriptor.GetTypeName();
                            var descriptionInfo   = BoundAttributeDescriptionInfo.From(parameterDescriptor, tagHelperTypeName);
                            attributeDescriptionInfos.Add(descriptionInfo);
                        }
                    }
                }
            }

            var completionItems = new List <RazorCompletionItem>();

            foreach (var completion in attributeCompletions)
            {
                if (string.Equals(completion.Key, parameterName, StringComparison.Ordinal))
                {
                    // This completion is identical to the selected parameter, don't provide for completions for what's already
                    // present in the document.
                    continue;
                }

                var razorCompletionItem = new RazorCompletionItem(
                    completion.Key,
                    completion.Key,
                    RazorCompletionItemKind.DirectiveAttributeParameter);
                var completionDescription = new AggregateBoundAttributeDescription(completion.Value.ToArray());
                razorCompletionItem.SetAttributeCompletionDescription(completionDescription);

                completionItems.Add(razorCompletionItem);
            }

            return(completionItems);
        }
Example #15
0
        // Internal for testing
        internal IReadOnlyList <RazorCompletionItem> GetAttributeCompletions(
            string selectedAttributeName,
            string containingTagName,
            IEnumerable <string> attributes,
            TagHelperDocumentContext tagHelperDocumentContext)
        {
            var descriptorsForTag = _tagHelperFactsService.GetTagHelpersGivenTag(tagHelperDocumentContext, containingTagName, parentTag: null);

            if (descriptorsForTag.Count == 0)
            {
                // If the current tag has no possible descriptors then we can't have any directive attributes.
                return(Array.Empty <RazorCompletionItem>());
            }

            // Attributes are case sensitive when matching
            var attributeCompletions = new Dictionary <string, (HashSet <BoundAttributeDescriptionInfo>, HashSet <string>)>(StringComparer.Ordinal);

            for (var i = 0; i < descriptorsForTag.Count; i++)
            {
                var descriptor = descriptorsForTag[i];

                foreach (var attributeDescriptor in descriptor.BoundAttributes)
                {
                    if (!attributeDescriptor.IsDirectiveAttribute())
                    {
                        // We don't care about non-directive attributes
                        continue;
                    }

                    if (!TryAddCompletion(attributeDescriptor.Name, attributeDescriptor, descriptor) && attributeDescriptor.BoundAttributeParameters.Count > 0)
                    {
                        // This attribute has parameters and the base attribute name (@bind) is already satisfied. We need to check if there are any valid
                        // parameters left to be provided, if so, we need to still represent the base attribute name in the completion list.

                        for (var j = 0; j < attributeDescriptor.BoundAttributeParameters.Count; j++)
                        {
                            var parameterDescriptor = attributeDescriptor.BoundAttributeParameters[j];
                            if (!attributes.Any(name => TagHelperMatchingConventions.SatisfiesBoundAttributeWithParameter(name, attributeDescriptor, parameterDescriptor)))
                            {
                                // This bound attribute parameter has not had a completion entry added for it, re-represent the base attribute name in the completion list
                                AddCompletion(attributeDescriptor.Name, attributeDescriptor, descriptor);
                                break;
                            }
                        }
                    }

                    if (!string.IsNullOrEmpty(attributeDescriptor.IndexerNamePrefix))
                    {
                        TryAddCompletion(attributeDescriptor.IndexerNamePrefix + "...", attributeDescriptor, descriptor);
                    }
                }
            }

            var completionItems = new List <RazorCompletionItem>();

            foreach (var completion in attributeCompletions)
            {
                var insertText = completion.Key;
                if (insertText.EndsWith("...", StringComparison.Ordinal))
                {
                    // Indexer attribute, we don't want to insert with the triple dot.
                    insertText = insertText.Substring(0, insertText.Length - 3);
                }

                if (insertText.StartsWith("@", StringComparison.Ordinal))
                {
                    // Strip off the @ from the insertion text. This change is here to align the insertion text with the
                    // completion hooks into VS and VSCode. Basically, completion triggers when `@` is typed so we don't
                    // want to insert `@bind` because `@` already exists.
                    insertText = insertText.Substring(1);
                }

                var(attributeDescriptionInfos, commitCharacters) = completion.Value;

                var razorCompletionItem = new RazorCompletionItem(
                    completion.Key,
                    insertText,
                    RazorCompletionItemKind.DirectiveAttribute,
                    commitCharacters);
                var completionDescription = new AggregateBoundAttributeDescription(attributeDescriptionInfos.ToArray());
                razorCompletionItem.SetAttributeCompletionDescription(completionDescription);

                completionItems.Add(razorCompletionItem);
            }

            return(completionItems);

            bool TryAddCompletion(string attributeName, BoundAttributeDescriptor boundAttributeDescriptor, TagHelperDescriptor tagHelperDescriptor)
            {
                if (attributes.Any(name => string.Equals(name, attributeName, StringComparison.Ordinal)) &&
                    !string.Equals(selectedAttributeName, attributeName, StringComparison.Ordinal))
                {
                    // Attribute is already present on this element and it is not the selected attribute.
                    // It shouldn't exist in the completion list.
                    return(false);
                }

                AddCompletion(attributeName, boundAttributeDescriptor, tagHelperDescriptor);
                return(true);
            }

            void AddCompletion(string attributeName, BoundAttributeDescriptor boundAttributeDescriptor, TagHelperDescriptor tagHelperDescriptor)
            {
                if (!attributeCompletions.TryGetValue(attributeName, out var attributeDetails))
                {
                    attributeDetails = (new HashSet <BoundAttributeDescriptionInfo>(), new HashSet <string>());
                    attributeCompletions[attributeName] = attributeDetails;
                }

                (var attributeDescriptionInfos, var commitCharacters) = attributeDetails;

                var indexerCompletion = attributeName.EndsWith("...", StringComparison.Ordinal);
                var tagHelperTypeName = tagHelperDescriptor.GetTypeName();
                var descriptionInfo   = BoundAttributeDescriptionInfo.From(boundAttributeDescriptor, indexer: indexerCompletion, tagHelperTypeName);

                attributeDescriptionInfos.Add(descriptionInfo);

                if (indexerCompletion)
                {
                    // Indexer attribute, we don't want to commit with standard chars
                    return;
                }

                commitCharacters.Add("=");

                if (tagHelperDescriptor.BoundAttributes.Any(b => b.IsBooleanProperty))
                {
                    commitCharacters.Add(" ");
                }

                if (tagHelperDescriptor.BoundAttributes.Any(b => b.BoundAttributeParameters.Count > 0))
                {
                    commitCharacters.Add(":");
                }
            }
        }
Example #16
0
 public abstract bool TryCreateTooltip(AggregateBoundAttributeDescription attributeDescriptionInfo, [NotNullWhen(true)] out ClassifiedTextElement?tooltipContent);
Example #17
0
        public static void SetAttributeCompletionDescription(this RazorCompletionItem completionItem, AggregateBoundAttributeDescription attributeCompletionDescription)
        {
            if (completionItem is null)
            {
                throw new ArgumentNullException(nameof(completionItem));
            }

            completionItem.Items[s_attributeCompletionDescriptionKey] = attributeCompletionDescription;
        }
        private IReadOnlyList <RazorCompletionItem> GetAttributeCompletions(
            SyntaxNode containingAttribute,
            string containingTagName,
            string?selectedAttributeName,
            IEnumerable <KeyValuePair <string, string> > attributes,
            TagHelperDocumentContext tagHelperDocumentContext)
        {
            var ancestors = containingAttribute.Parent.Ancestors();
            var nonDirectiveAttributeTagHelpers = tagHelperDocumentContext.TagHelpers.Where(tagHelper => !tagHelper.BoundAttributes.Any(attribute => attribute.IsDirectiveAttribute()));
            var filteredContext = TagHelperDocumentContext.Create(tagHelperDocumentContext.Prefix, nonDirectiveAttributeTagHelpers);

            var(ancestorTagName, ancestorIsTagHelper) = _tagHelperFactsService.GetNearestAncestorTagInfo(ancestors);
            var attributeCompletionContext = new AttributeCompletionContext(
                filteredContext,
                existingCompletions: Enumerable.Empty <string>(),
                containingTagName,
                selectedAttributeName,
                attributes,
                ancestorTagName,
                ancestorIsTagHelper,
                HtmlFactsService.IsHtmlTagName);

            var completionItems  = new List <RazorCompletionItem>();
            var completionResult = _tagHelperCompletionService.GetAttributeCompletions(attributeCompletionContext);

            foreach (var completion in completionResult.Completions)
            {
                var filterText = completion.Key;

                // This is a little bit of a hack because the information returned by _razorTagHelperCompletionService.GetAttributeCompletions
                // does not have enough information for us to determine if a completion is an indexer completion or not. Therefore we have to
                // jump through a few hoops below to:
                //   1. Determine if this specific completion is an indexer based completion
                //   2. Resolve an appropriate snippet if it is. This is more troublesome because we need to remove the ... suffix to accurately
                //      build a snippet that makes sense for the user to type.
                var indexerCompletion = filterText.EndsWith("...", StringComparison.Ordinal);
                if (indexerCompletion)
                {
                    filterText = filterText.Substring(0, filterText.Length - 3);
                }

                var attributeCommitCharacters = ResolveAttributeCommitCharacters(completion.Value, indexerCompletion);

                var razorCompletionItem = new RazorCompletionItem(
                    displayText: completion.Key,
                    insertText: filterText,
                    RazorCompletionItemKind.TagHelperAttribute,
                    attributeCommitCharacters);

                var attributeDescriptions = completion.Value.Select(boundAttribute =>
                {
                    var descriptionInfo = BoundAttributeDescriptionInfo.From(boundAttribute, indexerCompletion);

                    return(descriptionInfo);
                });
                var attributeDescriptionInfo = new AggregateBoundAttributeDescription(attributeDescriptions.ToList());
                razorCompletionItem.SetAttributeCompletionDescription(attributeDescriptionInfo);

                completionItems.Add(razorCompletionItem);
            }

            return(completionItems);
        }
Example #19
0
 public abstract ContainerElement CreateClassifiedDescription(AggregateBoundAttributeDescription completionDescription);
Example #20
0
        public override ContainerElement CreateClassifiedDescription(AggregateBoundAttributeDescription completionDescription)
        {
            if (completionDescription is null)
            {
                throw new ArgumentNullException(nameof(completionDescription));
            }

            var descriptionElements = new List <object>();

            foreach (var descriptionInfo in completionDescription.DescriptionInfos)
            {
                if (descriptionElements.Count > 0)
                {
                    descriptionElements.Add(SeparatorElement);
                }

                var returnTypeClassification = PredefinedClassificationNames.Type;
                if (TypeNameStringResolver.TryGetSimpleName(descriptionInfo.ReturnTypeName, out var returnTypeName))
                {
                    returnTypeClassification = PredefinedClassificationNames.Keyword;
                }
                else
                {
                    returnTypeName = descriptionInfo.ReturnTypeName;
                }

                var tagHelperTypeName       = descriptionInfo.TypeName;
                var tagHelperTypeNamePrefix = string.Empty;
                var tagHelperTypeNameProper = tagHelperTypeName;

                var lastDot = tagHelperTypeName.LastIndexOf('.');
                if (lastDot > 0)
                {
                    var afterLastDot = lastDot + 1;

                    // We're pulling apart the type name so the prefix looks like:
                    //
                    // Microsoft.AspnetCore.Components.
                    tagHelperTypeNamePrefix = tagHelperTypeName.Substring(0, afterLastDot);

                    // And the type name looks like BindBinds
                    tagHelperTypeNameProper = tagHelperTypeName.Substring(afterLastDot);
                }

                descriptionElements.Add(
                    new ContainerElement(
                        ContainerElementStyle.Wrapped,
                        PropertyGlyph,
                        new ClassifiedTextElement(
                            new ClassifiedTextRun(returnTypeClassification, returnTypeName),
                            SpaceLiteral,
                            new ClassifiedTextRun(PredefinedClassificationNames.Literal, tagHelperTypeNamePrefix),
                            new ClassifiedTextRun(PredefinedClassificationNames.Type, tagHelperTypeNameProper),
                            DotLiteral,
                            new ClassifiedTextRun(PredefinedClassificationNames.Identifier, descriptionInfo.PropertyName))));

                if (descriptionInfo.Documentation != null)
                {
                    descriptionElements.Add(
                        new ContainerElement(
                            ContainerElementStyle.Wrapped,
                            new ClassifiedTextElement(
                                new ClassifiedTextRun(PredefinedClassificationNames.NaturalLanguage, descriptionInfo.Documentation))));
                }
            }

            var descriptionContainer = new ContainerElement(ContainerElementStyle.Stacked, descriptionElements);

            return(descriptionContainer);
        }
 public abstract bool TryCreateTooltip(AggregateBoundAttributeDescription attributeDescriptionInfo, out MarkupContent tooltipContent);
 public abstract bool TryCreateTooltip(AggregateBoundAttributeDescription attributeDescriptionInfo, out VSClassifiedTextElement tooltipContent);
Example #23
0
        public void TryCreateTooltip_ContainerElement_Attribute_MultipleAssociatedAttributes_ReturnsTrue()
        {
            // Arrange
            var descriptionFactory = new DefaultVSLSPTagHelperTooltipFactory();
            var associatedAttributeDescriptions = new[]
            {
                new BoundAttributeDescriptionInfo(
                    returnTypeName: "System.String",
                    typeName: "Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName",
                    propertyName: "SomeProperty",
                    documentation: "<summary>Uses <see cref=\"T:System.Collections.List{System.String}\" />s</summary>"),
                new BoundAttributeDescriptionInfo(
                    propertyName: "AnotherProperty",
                    typeName: "Microsoft.AspNetCore.SomeTagHelpers.AnotherTypeName",
                    returnTypeName: "System.Boolean?",
                    documentation: "<summary>\nUses <see cref=\"T:System.Collections.List{System.String}\" />s\n</summary>"),
            };
            var attributeDescription = new AggregateBoundAttributeDescription(associatedAttributeDescriptions);

            // Act
            var result = descriptionFactory.TryCreateTooltip(attributeDescription, out VSContainerElement container);

            // Assert
            Assert.True(result);
            var containerElements = container.Elements.ToList();

            // Expected output:
            //     [Property Glyph] string Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName.SomeProperty
            //     Uses List<string>s
            //
            //     [Property Glyph] bool? Microsoft.AspNetCore.SomeTagHelpers.AnotherTypeName.AnotherProperty
            //     Uses List<string>s
            Assert.Equal(VSContainerElementStyle.Stacked, container.Style);
            Assert.Equal(4, containerElements.Count);

            // [TagHelper Glyph] string Microsoft.AspNetCore.SomeTagHelpers.SomeTypeName.SomeProperty
            var innerContainer        = ((VSContainerElement)containerElements[0]).Elements.ToList();
            var classifiedTextElement = (VSClassifiedTextElement)innerContainer[1];

            Assert.Equal(2, innerContainer.Count);
            Assert.Equal(PropertyGlyph, innerContainer[0]);
            Assert.Collection(classifiedTextElement.Runs,
                              run => AssertExpectedClassification(run, "string", VSPredefinedClassificationTypeNames.Keyword),
                              run => AssertExpectedClassification(run, " ", VSPredefinedClassificationTypeNames.WhiteSpace),
                              run => AssertExpectedClassification(run, "Microsoft", VSPredefinedClassificationTypeNames.Text),
                              run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "AspNetCore", VSPredefinedClassificationTypeNames.Text),
                              run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "SomeTagHelpers", VSPredefinedClassificationTypeNames.Text),
                              run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "SomeTypeName", VSPredefinedClassificationTypeNames.Type),
                              run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "SomeProperty", VSPredefinedClassificationTypeNames.Identifier));

            // Uses List<string>s
            innerContainer        = ((VSContainerElement)containerElements[1]).Elements.ToList();
            classifiedTextElement = (VSClassifiedTextElement)innerContainer[0];
            Assert.Single(innerContainer);
            Assert.Collection(classifiedTextElement.Runs,
                              run => AssertExpectedClassification(run, "Uses ", VSPredefinedClassificationTypeNames.Text),
                              run => AssertExpectedClassification(run, "List", VSPredefinedClassificationTypeNames.Type),
                              run => AssertExpectedClassification(run, "<", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "string", VSPredefinedClassificationTypeNames.Keyword),
                              run => AssertExpectedClassification(run, ">", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "s", VSPredefinedClassificationTypeNames.Text));

            // [TagHelper Glyph] bool? Microsoft.AspNetCore.SomeTagHelpers.AnotherTypeName.AnotherProperty
            innerContainer        = ((VSContainerElement)containerElements[2]).Elements.ToList();
            classifiedTextElement = (VSClassifiedTextElement)innerContainer[1];
            Assert.Equal(2, innerContainer.Count);
            Assert.Equal(PropertyGlyph, innerContainer[0]);
            Assert.Collection(classifiedTextElement.Runs,
                              run => AssertExpectedClassification(run, "bool", VSPredefinedClassificationTypeNames.Keyword),
                              run => AssertExpectedClassification(run, "?", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, " ", VSPredefinedClassificationTypeNames.WhiteSpace),
                              run => AssertExpectedClassification(run, "Microsoft", VSPredefinedClassificationTypeNames.Text),
                              run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "AspNetCore", VSPredefinedClassificationTypeNames.Text),
                              run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "SomeTagHelpers", VSPredefinedClassificationTypeNames.Text),
                              run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "AnotherTypeName", VSPredefinedClassificationTypeNames.Type),
                              run => AssertExpectedClassification(run, ".", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "AnotherProperty", VSPredefinedClassificationTypeNames.Identifier));

            // Uses List<string>s
            innerContainer        = ((VSContainerElement)containerElements[3]).Elements.ToList();
            classifiedTextElement = (VSClassifiedTextElement)innerContainer[0];
            Assert.Single(innerContainer);
            Assert.Collection(classifiedTextElement.Runs,
                              run => AssertExpectedClassification(run, "Uses ", VSPredefinedClassificationTypeNames.Text),
                              run => AssertExpectedClassification(run, "List", VSPredefinedClassificationTypeNames.Type),
                              run => AssertExpectedClassification(run, "<", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "string", VSPredefinedClassificationTypeNames.Keyword),
                              run => AssertExpectedClassification(run, ">", VSPredefinedClassificationTypeNames.Punctuation),
                              run => AssertExpectedClassification(run, "s", VSPredefinedClassificationTypeNames.Text));
        }
Example #24
0
        public override bool TryCreateTooltip(AggregateBoundAttributeDescription attributeDescriptionInfo, out MarkupContent tooltipContent)
        {
            if (attributeDescriptionInfo is null)
            {
                throw new ArgumentNullException(nameof(attributeDescriptionInfo));
            }

            var associatedAttributeInfos = attributeDescriptionInfo.DescriptionInfos;

            if (associatedAttributeInfos.Count == 0)
            {
                tooltipContent = null;
                return(false);
            }

            // This generates a markdown description that looks like the following:
            // **ReturnTypeName** SomeTypeName.**SomeProperty**
            //
            // The Summary documentation text with `CrefTypeValues` in code.
            //
            // Additional description infos result in a triple `---` to separate the markdown entries.

            var descriptionBuilder = new StringBuilder();

            for (var i = 0; i < associatedAttributeInfos.Count; i++)
            {
                var descriptionInfo = associatedAttributeInfos[i];

                if (descriptionBuilder.Length > 0)
                {
                    descriptionBuilder.AppendLine();
                    descriptionBuilder.AppendLine("---");
                }

                StartOrEndBold(descriptionBuilder);
                if (!TypeNameStringResolver.TryGetSimpleName(descriptionInfo.ReturnTypeName, out var returnTypeName))
                {
                    returnTypeName = descriptionInfo.ReturnTypeName;
                }
                var reducedReturnTypeName = ReduceTypeName(returnTypeName);
                descriptionBuilder.Append(reducedReturnTypeName);
                StartOrEndBold(descriptionBuilder);
                descriptionBuilder.Append(" ");
                var tagHelperTypeName        = descriptionInfo.TypeName;
                var reducedTagHelperTypeName = ReduceTypeName(tagHelperTypeName);
                descriptionBuilder.Append(reducedTagHelperTypeName);
                descriptionBuilder.Append(".");
                StartOrEndBold(descriptionBuilder);
                descriptionBuilder.Append(descriptionInfo.PropertyName);
                StartOrEndBold(descriptionBuilder);

                var documentation = descriptionInfo.Documentation;
                if (!TryExtractSummary(documentation, out var summaryContent))
                {
                    continue;
                }

                descriptionBuilder.AppendLine();
                descriptionBuilder.AppendLine();
                var finalSummaryContent = CleanSummaryContent(summaryContent);
                descriptionBuilder.Append(finalSummaryContent);
            }

            tooltipContent = new MarkupContent
            {
                Kind = GetMarkupKind()
            };

            tooltipContent.Value = descriptionBuilder.ToString();
            return(true);
        }
Example #25
0
        private IReadOnlyList <RazorCompletionItem> GetAttributeCompletions(
            SyntaxNode containingAttribute,
            string containingTagName,
            string?selectedAttributeName,
            IEnumerable <KeyValuePair <string, string> > attributes,
            TagHelperDocumentContext tagHelperDocumentContext,
            RazorCompletionOptions options)
        {
            var ancestors = containingAttribute.Parent.Ancestors();
            var nonDirectiveAttributeTagHelpers = tagHelperDocumentContext.TagHelpers.Where(tagHelper => !tagHelper.BoundAttributes.Any(attribute => attribute.IsDirectiveAttribute()));
            var filteredContext = TagHelperDocumentContext.Create(tagHelperDocumentContext.Prefix, nonDirectiveAttributeTagHelpers);

            var(ancestorTagName, ancestorIsTagHelper) = _tagHelperFactsService.GetNearestAncestorTagInfo(ancestors);
            var attributeCompletionContext = new AttributeCompletionContext(
                filteredContext,
                existingCompletions: Enumerable.Empty <string>(),
                containingTagName,
                selectedAttributeName,
                attributes,
                ancestorTagName,
                ancestorIsTagHelper,
                HtmlFactsService.IsHtmlTagName);

            var completionItems  = new List <RazorCompletionItem>();
            var completionResult = _tagHelperCompletionService.GetAttributeCompletions(attributeCompletionContext);

            foreach (var completion in completionResult.Completions)
            {
                var filterText = completion.Key;

                // This is a little bit of a hack because the information returned by _razorTagHelperCompletionService.GetAttributeCompletions
                // does not have enough information for us to determine if a completion is an indexer completion or not. Therefore we have to
                // jump through a few hoops below to:
                //   1. Determine if this specific completion is an indexer based completion
                //   2. Resolve an appropriate snippet if it is. This is more troublesome because we need to remove the ... suffix to accurately
                //      build a snippet that makes sense for the user to type.
                var indexerCompletion = filterText.EndsWith("...", StringComparison.Ordinal);
                if (indexerCompletion)
                {
                    filterText = filterText.Substring(0, filterText.Length - 3);
                }

                var attributeContext          = ResolveAttributeContext(completion.Value, indexerCompletion, options.SnippetsSupported);
                var attributeCommitCharacters = ResolveAttributeCommitCharacters(attributeContext);
                var isSnippet  = false;
                var insertText = filterText;
                if (TryResolveInsertText(insertText, attributeContext, out var snippetText))
                {
                    isSnippet  = true;
                    insertText = snippetText;
                }

                // We change the sort text depending on the tag name due to TagHelper/non-TagHelper concerns. For instance lets say you have a TagHelper that binds to `input`.
                // Chances are you're expecting to get every other `input` completion item in addition to the TagHelper completion items and the sort order should be the default
                // because HTML completion items are 100% as applicable as other items.
                //
                // Next assume that we have a TagHelper that binds `custom` (or even `Custom`); this is a special scenario where the user has effectively created a new HTML tag
                // meaning they're probably expecting to provide all of the attributes necessary for that tag to operate. Meaning, HTML attribute completions are less important.
                // To make sure we prioritize our attribute completions above all other types of completions we set the priority to high so they're showed in the completion list
                // above all other completion items.
                var sortText            = HtmlFactsService.IsHtmlTagName(containingTagName) ? CompletionSortTextHelper.DefaultSortPriority : CompletionSortTextHelper.HighSortPriority;
                var razorCompletionItem = new RazorCompletionItem(
                    displayText: completion.Key,
                    insertText: insertText,
                    sortText: sortText,
                    kind: RazorCompletionItemKind.TagHelperAttribute,
                    commitCharacters: attributeCommitCharacters,
                    isSnippet: isSnippet);

                var attributeDescriptions = completion.Value.Select(boundAttribute =>
                {
                    var descriptionInfo = BoundAttributeDescriptionInfo.From(boundAttribute, indexerCompletion);

                    return(descriptionInfo);
                });
                var attributeDescriptionInfo = new AggregateBoundAttributeDescription(attributeDescriptions.ToList());
                razorCompletionItem.SetAttributeCompletionDescription(attributeDescriptionInfo);

                completionItems.Add(razorCompletionItem);
            }

            return(completionItems);
        }