public void InEmptyElementName(string testFileName, int line, int column, string expectedElementName)
        {
            Position testPosition = new Position(line, column);

            string            testXml   = LoadTestFile("TestFiles", testFileName + ".xml");
            TextPositions     positions = new TextPositions(testXml);
            XmlDocumentSyntax document  = Parser.ParseText(testXml);

            XmlLocator  locator = new XmlLocator(document, positions);
            XmlLocation result  = locator.Inspect(testPosition);

            Assert.NotNull(result);
            Assert.Equal(XSNodeKind.Element, result.Node.Kind);
            Assert.True(result.IsElement(), "IsElement");

            XSElement element = (XSElement)result.Node;

            Assert.Equal(expectedElementName, element.Name);

            Assert.True(result.IsEmptyElement(), "IsEmptyElement");
            Assert.True(result.IsName(), "IsName");

            Assert.False(result.IsElementContent(), "IsElementContent");

            // TODO: Verify Parent, PreviousSibling, and NextSibling.
        }
        public void CanCompleteAttribute(string testFileName, int line, int column, string expectedElementName, PaddingType expectedPadding)
        {
            Position testPosition = new Position(line, column);

            string            testXml   = LoadTestFile("TestFiles", testFileName + ".xml");
            TextPositions     positions = new TextPositions(testXml);
            XmlDocumentSyntax document  = Parser.ParseText(testXml);

            XmlLocator  locator  = new XmlLocator(document, positions);
            XmlLocation location = locator.Inspect(testPosition);

            Assert.NotNull(location);

            XSPath elementPath = XSPath.Parse(expectedElementName);

            XSElement   element;
            XSAttribute replaceAttribute;
            PaddingType needsPadding;

            Assert.True(
                location.CanCompleteAttribute(out element, out replaceAttribute, out needsPadding, onElementWithPath: elementPath),
                "CanCompleteAttribute"
                );
            Assert.NotNull(element);
            Assert.Null(replaceAttribute);
            Assert.Equal(expectedPadding, needsPadding);
        }
        /// <summary>
        ///     Provide completions for the specified location.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="triggerCharacters">
        ///     The character(s), if any, that triggered completion.
        /// </param>
        /// <param name="cancellationToken">
        ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided.
        /// </returns>
        public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, string triggerCharacters, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (projectDocument == null)
            {
                throw new ArgumentNullException(nameof(projectDocument));
            }

            bool isIncomplete = false;
            List <CompletionItem> completions = new List <CompletionItem>();

            Log.Verbose("Evaluate completions for {XmlLocation:l}", location);

            using (await projectDocument.Lock.ReaderLockAsync())
            {
                if (location.CanCompleteAttributeValue(out XSAttribute attribute, WellKnownElementPaths.Item, "Include", "Version") && SupportedElementNames.Contains(attribute.Element.Name))
                {
                    Log.Verbose("Offering completions for value of attribute {AttributeName} of {ElementName} element @ {Position:l}",
                                attribute.Name,
                                attribute.Element.Name,
                                location.Position
                                );

                    List <CompletionItem> packageCompletions = await HandlePackageReferenceAttributeCompletion(projectDocument, attribute, cancellationToken);

                    if (packageCompletions != null)
                    {
                        isIncomplete |= packageCompletions.Count > 10; // Default page size.
                        completions.AddRange(packageCompletions);
                    }
                }
Exemple #4
0
            private Location GetIncludeElementLocation(XElement includeElement, ref string currentXmlFilePath, ref CSharpSyntaxNode originatingSyntax)
            {
                Location location = includeElement.Annotation <Location>();

                if (location != null)
                {
                    return(location);
                }

                // If we are not in an XML file, then we must be in a source file.  Since we're traversing the XML tree in the same
                // order as the DocumentationCommentWalker, we can access the elements of includeElementNodes in order.
                if (currentXmlFilePath == null)
                {
                    Debug.Assert(_nextSourceIncludeElementIndex < _sourceIncludeElementNodes.Length);
                    Debug.Assert(originatingSyntax == null);
                    originatingSyntax = _sourceIncludeElementNodes[_nextSourceIncludeElementIndex];
                    location          = originatingSyntax.Location;
                    _nextSourceIncludeElementIndex++;

                    // #line shall not affect the base path:
                    currentXmlFilePath = location.GetLineSpan().Path;
                }
                else
                {
                    location = XmlLocation.Create(includeElement, currentXmlFilePath);
                }

                Debug.Assert(location != null);
                includeElement.AddAnnotation(location);
                return(location);
            }
Exemple #5
0
 public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
 {
     var xel = config as XElement;
     if (xel == null)
         throw new TestLibsException($"Couldn't cast following config to XElement:\n{config}");
     return xel;
 }
        /// <summary>
        ///     Provide completions for the specified location.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="cancellationToken">
        ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided.
        /// </returns>
        public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (projectDocument == null)
            {
                throw new ArgumentNullException(nameof(projectDocument));
            }

            List <CompletionItem> completions = new List <CompletionItem>();

            Log.Verbose("Evaluate completions for {XmlLocation:l}", location);

            using (await projectDocument.Lock.ReaderLockAsync())
            {
                if (!projectDocument.EnableExpressions)
                {
                    return(null);
                }

                ExpressionNode expression;
                Range          expressionRange;
                if (!location.IsExpression(out expression, out expressionRange))
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (not on an expression or a location where an expression can be added).", location);

                    return(null);
                }

                if (expression.Kind != ExpressionKind.Evaluate)
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (this provider only supports MSBuild Evaluation expressions, not {ExpressionKind} expressions).", location, expression.Kind);

                    return(null);
                }

                Log.Verbose("Offering completions to replace Evaluate expression @ {ReplaceRange:l}",
                            expressionRange
                            );

                completions.AddRange(
                    GetCompletionItems(projectDocument, expressionRange)
                    );
            }

            Log.Verbose("Offering {CompletionCount} completion(s) for {XmlLocation:l}", completions.Count, location);

            if (completions.Count == 0)
            {
                return(null);
            }

            return(new CompletionList(completions,
                                      isIncomplete: false // Consider this list to be exhaustive
                                      ));
        }
Exemple #7
0
        public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
        {
            var xel = config as XElement;

            if (xel == null)
            {
                throw new TestLibsException($"Couldn't cast following config to XElement:\n{config}");
            }
            return(xel);
        }
Exemple #8
0
        public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
        {
            var configEl = config as XElement;
            if (configEl == null)
                throw new TestLibsException($"Following configuration element:\n{config}\n\tcouldn't be cast to type: {type}. XElement expected");
            
            var genArgs = type.GetGenericArguments();

            if (genArgs.Length > 2)
                throw new TestLibsException($"Unsupported type: {type.ToString()}. Couldn't parse following config to that type:\n{configEl}");

            var childObjType = genArgs.Last();
            var childObjs = XmlParser.Parse(childObjType, configEl, childLocation, isAssignableTypeAllowed, context);

            var propertyObj = Activator.CreateInstance(type);

            if (type.GetGenericTypeDefinition() == typeof(List<>))
            {
                var add = type.GetMethod("Add", new[] { childObjType });

                foreach (var child in childObjs)
                {
                    add.Invoke(propertyObj, new object[] { child });
                }
            }
            else if (type.GetGenericTypeDefinition() == typeof(LinkedList<>))
            {
                var add = type.GetMethod("AddLast", new[] { childObjType });

                foreach (var child in childObjs)
                {
                    add.Invoke(propertyObj, new object[] { child });
                }
            }
            else if (type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
            {
                var childXmlType = ReflectionManager.GetXmlType(childObjType);
                if (childXmlType == null)
                    throw new TestLibsException($"Couldn't parse type: {type} to dictionary, because child type: {childObjType} isn't XmlType");

                var add = type.GetMethod("Add", new[] { childXmlType.KeyProperty.PropertyType, childObjType });

                foreach (var child in childObjs)
                {
                    var key = childXmlType.KeyProperty.GetValue(child);
                    add.Invoke(propertyObj, new object[] { key, child });
                }
            }
            else
            {
                throw new TestLibsException($"Unsupported generic type: {type}. Couldn't parse following config to that type:\n{configEl}");
            }

            return propertyObj;
        }
Exemple #9
0
        public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
        {
            var value = config.Value();

            var m = type.GetMethod("TryParse", new Type[] { typeof(string), type.MakeByRefType() });
            object[] args = { value, null };
            var r = (bool)m.Invoke(null, args);

            if (!r)
                throw new TypeCastException(value, type, config);

            return args[1];
        }
        /// <summary>
        ///     Provide completions for the specified location.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="triggerCharacters">
        ///     The character(s), if any, that triggered completion.
        /// </param>
        /// <param name="cancellationToken">
        ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided.
        /// </returns>
        public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, string triggerCharacters, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (projectDocument == null)
            {
                throw new ArgumentNullException(nameof(projectDocument));
            }

            List <CompletionItem> completions = new List <CompletionItem>();

            using (await projectDocument.Lock.ReaderLockAsync())
            {
                XSAttribute conditionAttribute;
                if (!location.IsAttributeValue(out conditionAttribute) || conditionAttribute.Name != "Condition")
                {
                    return(null);
                }

                if (conditionAttribute.Element.ParentElement?.Name != "PropertyGroup")
                {
                    return(null);
                }

                LspModels.Range replaceRange = conditionAttribute.ValueRange.ToLsp();

                completions.Add(new CompletionItem
                {
                    Label         = "If not already defined",
                    Detail        = "Condition",
                    Documentation = "Only use this property if the property does not already have a value.",
                    TextEdit      = new TextEdit
                    {
                        NewText = $"'$({conditionAttribute.Element.Name})' == ''",
                        Range   = replaceRange
                    }
                });
            }

            if (completions.Count == 0)
            {
                return(null);
            }

            return(new CompletionList(completions, isIncomplete: false));
        }
Exemple #11
0
        public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
        {
            var value = config.Value();

            var m = type.GetMethod("TryParse", new Type[] { typeof(string), type.MakeByRefType() });

            object[] args = { value, null };
            var      r    = (bool)m.Invoke(null, args);

            if (!r)
            {
                throw new TypeCastException(value, type, config);
            }

            return(args[1]);
        }
        /// <summary>
        ///     Provide completions for the specified location.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="cancellationToken">
        ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided.
        /// </returns>
        public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (projectDocument == null)
            {
                throw new ArgumentNullException(nameof(projectDocument));
            }

            Log.Verbose("Evaluate completions for {XmlLocation:l}", location);

            List <CompletionItem> completions = new List <CompletionItem>();

            using (await projectDocument.Lock.ReaderLockAsync())
            {
                if (location.IsElement(out XSElement element) && !element.HasParentPath(WellKnownElementPaths.ItemGroup))
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (not a direct child of an ItemGroup element).", location);

                    return(null);
                }

                HashSet <string> existingMetadata = new HashSet <string>();

                completions.AddRange(
                    GetAttributeCompletions(location, projectDocument, existingMetadata)
                    );

                completions.AddRange(
                    GetElementCompletions(location, projectDocument, existingMetadata)
                    );
            }

            if (completions.Count == 0)
            {
                return(null); // No completions offered
            }
            Log.Verbose("Offering {CompletionCount} completions for {XmlLocation:l}.", location);

            return(new CompletionList(completions,
                                      isIncomplete: false // List is exhaustive
                                      ));
        }
Exemple #13
0
        public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
        {
            var configEl = config as XElement;
            if (configEl == null)
                throw new TestLibsException($"Following configuration element:\n{config}\n\tcouldn't be cast to type: {type}. XElement expected");

            var childObjType = type.GetElementType();
            var childObjs = XmlParser.Parse(childObjType, configEl, childLocation, isAssignableTypeAllowed, context);

            var memObj = Activator.CreateInstance(type, new object[] { childObjs.Count }) as object[];

            for (int i = 0; i < childObjs.Count; i++)
            {
                memObj[i] = childObjs[i];
            }

            return memObj;
        }
        public void CanCompleteElement(string testFileName, int line, int column)
        {
            Position testPosition = new Position(line, column);

            string            testXml   = LoadTestFile("TestFiles", testFileName + ".xml");
            TextPositions     positions = new TextPositions(testXml);
            XmlDocumentSyntax document  = Parser.ParseText(testXml);

            XmlLocator  locator  = new XmlLocator(document, positions);
            XmlLocation location = locator.Inspect(testPosition);

            Assert.NotNull(location);

            XSElement replacingElement;

            Assert.True(location.CanCompleteElement(out replacingElement), "CanCompleteElement");
            Assert.NotNull(replacingElement);
        }
Exemple #15
0
        public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
        {
            var value = config.Value();

            try
            {
                var enumVal = Enum.Parse(type, value);
                if (!Enum.IsDefined(type, enumVal))
                    throw new TypeCastException(value, type, config);
                return enumVal;
            }
            catch (TypeCastException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw new TypeCastException(value, type, config, ex);
            }
        }
Exemple #16
0
        public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
        {
            var configEl = config as XElement;

            if (configEl == null)
            {
                throw new TestLibsException($"Following configuration element:\n{config}\n\tcouldn't be cast to type: {type}. XElement expected");
            }

            var childObjType = type.GetElementType();
            var childObjs    = XmlParser.Parse(childObjType, configEl, childLocation, isAssignableTypeAllowed, context);

            var memObj = Activator.CreateInstance(type, new object[] { childObjs.Count }) as object[];

            for (int i = 0; i < childObjs.Count; i++)
            {
                memObj[i] = childObjs[i];
            }

            return(memObj);
        }
        public void InAttributeValue(string testFileName, int line, int column, string expectedAttributeName)
        {
            Position testPosition = new Position(line, column);

            string            testXml   = LoadTestFile("TestFiles", testFileName + ".xml");
            TextPositions     positions = new TextPositions(testXml);
            XmlDocumentSyntax document  = Parser.ParseText(testXml);

            XmlLocator  locator = new XmlLocator(document, positions);
            XmlLocation result  = locator.Inspect(testPosition);

            Assert.NotNull(result);

            XSAttribute attribute;

            Assert.True(result.IsAttribute(out attribute), "IsAttribute");
            Assert.True(result.IsAttributeValue(), "IsAttributeValue");

            Assert.Equal(expectedAttributeName, attribute.Name);

            // TODO: Verify Parent, PreviousSibling, and NextSibling.
        }
Exemple #18
0
        public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
        {
            var value = config.Value();

            try
            {
                var enumVal = Enum.Parse(type, value);
                if (!Enum.IsDefined(type, enumVal))
                {
                    throw new TypeCastException(value, type, config);
                }
                return(enumVal);
            }
            catch (TypeCastException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw new TypeCastException(value, type, config, ex);
            }
        }
        public void IsExpression_Success(string testFileName, int line, int column, ExpressionKind expectedExpressionKind)
        {
            Position testPosition = new Position(line, column);

            string            testXml   = LoadTestFile("TestFiles", testFileName + ".xml");
            TextPositions     positions = new TextPositions(testXml);
            XmlDocumentSyntax document  = Parser.ParseText(testXml);

            XmlLocator  locator  = new XmlLocator(document, positions);
            XmlLocation location = locator.Inspect(testPosition);

            Assert.NotNull(location);

            ExpressionNode actualExpression;
            Range          actualExpressionRange;

            Assert.True(
                location.IsExpression(out actualExpression, out actualExpressionRange),
                "IsExpression"
                );
            Assert.NotNull(actualExpression);

            Assert.Equal(expectedExpressionKind, actualExpression.Kind);
        }
        public void CanCompleteElementInParentWithRelativePath(string testFileName, int line, int column, string expectedParent)
        {
            Position testPosition = new Position(line, column);

            string            testXml   = LoadTestFile("TestFiles", testFileName + ".xml");
            TextPositions     positions = new TextPositions(testXml);
            XmlDocumentSyntax document  = Parser.ParseText(testXml);

            XmlLocator  locator  = new XmlLocator(document, positions);
            XmlLocation location = locator.Inspect(testPosition);

            Assert.NotNull(location);

            XSPath expectedParentPath = XSPath.Parse(expectedParent);

            XSElement replaceElement;

            Assert.True(
                location.CanCompleteElement(out replaceElement, parentPath: expectedParentPath),
                "CanCompleteElement"
                );
            Assert.NotNull(replaceElement);
            Assert.Equal(expectedParent, replaceElement.ParentElement?.Name);
        }
Exemple #21
0
            private XNode[] RewriteIncludeElement(XElement includeElement, string currentXmlFilePath, CSharpSyntaxNode originatingSyntax, out string commentMessage)
            {
                Location location = GetIncludeElementLocation(includeElement, ref currentXmlFilePath, ref originatingSyntax);

                Debug.Assert(originatingSyntax != null);

                bool diagnose = originatingSyntax.SyntaxTree.ReportDocumentationCommentDiagnostics();

                if (!EnterIncludeElement(location))
                {
                    // NOTE: these must exist since we're already processed this node elsewhere in the call stack.
                    XAttribute fileAttr      = includeElement.Attribute(XName.Get(DocumentationCommentXmlNames.FileAttributeName));
                    XAttribute pathAttr      = includeElement.Attribute(XName.Get(DocumentationCommentXmlNames.PathAttributeName));
                    string     filePathValue = fileAttr.Value;
                    string     xpathValue    = pathAttr.Value;

                    if (diagnose)
                    {
                        _diagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, new LocalizableErrorArgument(MessageID.IDS_OperationCausedStackOverflow));
                    }

                    commentMessage = ErrorFacts.GetMessage(MessageID.IDS_XMLNOINCLUDE, CultureInfo.CurrentUICulture);

                    // Don't inspect the children - we're already in a cycle.
                    return(new XNode[] { new XComment(commentMessage), includeElement.Copy(copyAttributeAnnotations: false) });
                }

                DiagnosticBag includeDiagnostics = DiagnosticBag.GetInstance();

                try
                {
                    XAttribute fileAttr = includeElement.Attribute(XName.Get(DocumentationCommentXmlNames.FileAttributeName));
                    XAttribute pathAttr = includeElement.Attribute(XName.Get(DocumentationCommentXmlNames.PathAttributeName));

                    bool hasFileAttribute = fileAttr != null;
                    bool hasPathAttribute = pathAttr != null;
                    if (!hasFileAttribute || !hasPathAttribute)
                    {
                        var subMessage = hasFileAttribute ? MessageID.IDS_XMLMISSINGINCLUDEPATH.Localize() : MessageID.IDS_XMLMISSINGINCLUDEFILE.Localize();
                        includeDiagnostics.Add(ErrorCode.WRN_InvalidInclude, location, subMessage);
                        commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLBADINCLUDE);
                        return(null);
                    }

                    string xpathValue    = pathAttr.Value;
                    string filePathValue = fileAttr.Value;

                    var resolver = _compilation.Options.XmlReferenceResolver;
                    if (resolver == null)
                    {
                        includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, new CodeAnalysisResourcesLocalizableErrorArgument(nameof(CodeAnalysisResources.XmlReferencesNotSupported)));
                        commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLFAILEDINCLUDE);
                        return(null);
                    }

                    string resolvedFilePath = resolver.ResolveReference(filePathValue, currentXmlFilePath);

                    if (resolvedFilePath == null)
                    {
                        // NOTE: same behavior as IOException.
                        includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, new CodeAnalysisResourcesLocalizableErrorArgument(nameof(CodeAnalysisResources.FileNotFound)));
                        commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLFAILEDINCLUDE);
                        return(null);
                    }

                    if (_includedFileCache == null)
                    {
                        _includedFileCache = new DocumentationCommentIncludeCache(resolver);
                    }

                    try
                    {
                        XDocument doc;

                        try
                        {
                            doc = _includedFileCache.GetOrMakeDocument(resolvedFilePath);
                        }
                        catch (IOException e)
                        {
                            // NOTE: same behavior as resolvedFilePath == null.
                            includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, e.Message);
                            commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLFAILEDINCLUDE);
                            return(null);
                        }

                        Debug.Assert(doc != null);

                        string     errorMessage;
                        bool       invalidXPath;
                        XElement[] loadedElements = XmlUtilities.TrySelectElements(doc, xpathValue, out errorMessage, out invalidXPath);
                        if (loadedElements == null)
                        {
                            includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, errorMessage);

                            commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLFAILEDINCLUDE);
                            if (invalidXPath)
                            {
                                // leave the include node as is
                                return(null);
                            }

                            if (location.IsInSource)
                            {
                                // As in Dev11, return only the comment - drop the include element.
                                return(new XNode[] { new XComment(commentMessage) });
                            }
                            else
                            {
                                commentMessage = null;
                                return(Array.Empty <XNode>());
                            }
                        }

                        if (loadedElements != null && loadedElements.Length > 0)
                        {
                            // change the current XML file path for nodes contained in the document:
                            XNode[] result = RewriteMany(loadedElements, resolvedFilePath, originatingSyntax);

                            // The elements could be rewritten away if they are includes that refer to invalid
                            // (but existing and accessible) XML files.  If this occurs, behave as if we
                            // had failed to find any XPath results (as in Dev11).
                            if (result.Length > 0)
                            {
                                // NOTE: in this case, we do NOT visit the children of the include element -
                                // they are dropped.
                                commentMessage = null;
                                return(result);
                            }
                        }

                        commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLNOINCLUDE);
                        return(null);
                    }
                    catch (XmlException e)
                    {
                        // NOTE: invalid XML is handled differently from other errors - we don't include the include element
                        // in the results and the location is in the included (vs includING) file.

                        Location errorLocation = XmlLocation.Create(e, resolvedFilePath);
                        includeDiagnostics.Add(ErrorCode.WRN_XMLParseIncludeError, errorLocation, GetDescription(e)); //NOTE: location is in included file.

                        if (location.IsInSource)
                        {
                            commentMessage = string.Format(ErrorFacts.GetMessage(MessageID.IDS_XMLIGNORED2, CultureInfo.CurrentUICulture), resolvedFilePath);

                            // As in Dev11, return only the comment - drop the include element.
                            return(new XNode[] { new XComment(commentMessage) });
                        }
                        else
                        {
                            commentMessage = null;
                            return(Array.Empty <XNode>());
                        }
                    }
                }
                finally
                {
                    if (diagnose)
                    {
                        _diagnostics.AddRange(includeDiagnostics);
                    }

                    includeDiagnostics.Free();

                    LeaveIncludeElement(location);
                }
            }
Exemple #22
0
 /// <summary>
 ///     Provide completions for the specified location.
 /// </summary>
 /// <param name="location">
 ///     The <see cref="XmlLocation"/> where completions are requested.
 /// </param>
 /// <param name="projectDocument">
 ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
 /// </param>
 /// <param name="triggerCharacters">
 ///     The character(s), if any, that triggered completion.
 /// </param>
 /// <param name="cancellationToken">
 ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
 /// </param>
 /// <returns>
 ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>, or <c>null</c> if no completions are provided.
 /// </returns>
 public abstract Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, string triggerCharacters, CancellationToken cancellationToken = default(CancellationToken));
Exemple #23
0
        public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
        {
            var configElement = config as XElement;

            if (configElement == null)
            {
                throw new TestLibsException($"Couldn't parse type: {type} from following config (element node expected):\n{config}");
            }

            var xmlType = _xmlType.Value;

            string uniqueName    = null;
            object createdObject = null;

            if (context != null)
            {
                createdObject = new XmlBaseType();

                var xmlNode = _uniqProperty.Value.Location.Value.GetElement(configElement);
                if (xmlNode != null)
                {
                    uniqueName = XmlParser.Parse(_uniqProperty.Value.PropertyType, xmlNode) as string;
                }

                if (uniqueName != null)
                {
                    if (context.Contains(type, uniqueName))
                    {
                        return(context.ResolveValue(type, uniqueName));
                    }
                }
            }

            if (isAssignableTypeAllowed)
            {
                var possibleTypeName   = configElement.Name.ToString();
                var assignableXmlTypes = ReflectionManager.GetAssignableTypes(type);

                foreach (var assignableXmlType in assignableXmlTypes)
                {
                    if (assignableXmlType.Location.Check(XmlLocationType.Element, possibleTypeName))
                    {
                        xmlType = assignableXmlType;
                    }
                }

                if (xmlType == _xmlType.Value)
                {
                    throw new TestLibsException($"Couldn't find any type that could be parsed from element with name: {possibleTypeName}\nPossible element names: {assignableXmlTypes.Select(xt => $"Type: {xt.XType}, description: {xt.Description}\n{xt.Location}").ToList().ToStringWithList("=")}");
                }
            }
            else
            {
                xmlType = ReflectionManager.GetXmlType(type);
            }

            var parsedPropertiesDict = new Dictionary <XmlProperty, bool>();

            xmlType.XmlProperties.ForEach(p => parsedPropertiesDict.Add(p, false));

            createdObject = Activator.CreateInstance(xmlType.XType);
            if (uniqueName != null)
            {
                _uniqProperty.Value.SetValue(createdObject, uniqueName);
                parsedPropertiesDict[_uniqProperty.Value] = true;
            }


            var propertyToParse = parsedPropertiesDict.Keys.FirstOrDefault(k => !parsedPropertiesDict[k]);

            while (propertyToParse != null)
            {
                ParseProperty(propertyToParse, configElement, createdObject, parsedPropertiesDict, context);
                propertyToParse = parsedPropertiesDict.Keys.FirstOrDefault(k => !parsedPropertiesDict[k]);
            }

            return(createdObject);
        }
        /// <summary>
        ///     Provide completions for the specified location.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="triggerCharacters">
        ///     The character(s), if any, that triggered completion.
        /// </param>
        /// <param name="cancellationToken">
        ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided.
        /// </returns>
        public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, string triggerCharacters, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (projectDocument == null)
            {
                throw new ArgumentNullException(nameof(projectDocument));
            }

            if (!projectDocument.Workspace.Configuration.Language.CompletionsFromProject.Contains(CompletionSource.Task))
            {
                Log.Verbose("Not offering task element completions for {XmlLocation:l} (task completions not enabled in extension settings).", location);

                return(null);
            }

            if (!projectDocument.HasMSBuildProject)
            {
                Log.Verbose("Not offering task element completions for {XmlLocation:l} (underlying MSBuild project is not loaded).", location);

                return(null);
            }

            List <CompletionItem> completions = new List <CompletionItem>();

            Log.Verbose("Evaluate completions for {XmlLocation:l}", location);

            using (await projectDocument.Lock.ReaderLockAsync())
            {
                XSElement replaceElement;
                if (!location.CanCompleteElement(out replaceElement, parentPath: WellKnownElementPaths.Target))
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (does not represent the direct child of a 'Target' element).", location);

                    return(null);
                }

                Range targetRange = replaceElement?.Range ?? location.Position.ToEmptyRange();

                // Replace any characters that were typed to trigger the completion.
                HandleTriggerCharacters(triggerCharacters, projectDocument, ref targetRange);

                if (replaceElement != null)
                {
                    Log.Verbose("Offering completions to replace element {ElementName} @ {ReplaceRange:l}",
                                replaceElement.Name,
                                targetRange
                                );
                }
                else
                {
                    Log.Verbose("Offering completions to create task element @ {ReplaceRange:l}",
                                targetRange
                                );
                }

                Dictionary <string, MSBuildTaskMetadata> projectTasks = await GetProjectTasks(projectDocument);

                completions.AddRange(
                    GetCompletionItems(projectDocument, projectTasks, targetRange)
                    );
            }

            Log.Verbose("Offering {CompletionCount} completion(s) for {XmlLocation:l}", completions.Count, location);

            if (completions.Count == 0)
            {
                return(null);
            }

            return(new CompletionList(completions,
                                      isIncomplete: false // Consider this list to be exhaustive
                                      ));
        }
Exemple #25
0
        /// <summary>
        ///     Provide completions for the specified location.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="cancellationToken">
        ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided.
        /// </returns>
        public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (projectDocument == null)
            {
                throw new ArgumentNullException(nameof(projectDocument));
            }

            List <CompletionItem> completions = new List <CompletionItem>();

            Log.Verbose("Evaluate completions for {XmlLocation:l}", location);

            using (await projectDocument.Lock.ReaderLockAsync())
            {
                XSElement replaceElement;
                if (!location.CanCompleteElement(out replaceElement, parentPath: WellKnownElementPaths.PropertyGroup))
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (not a direct child of a 'PropertyGroup' element).", location);

                    return(null);
                }

                Range replaceRange;
                if (replaceElement != null)
                {
                    replaceRange = replaceElement.Range;

                    Log.Verbose("Offering completions to replace element {ElementName} @ {ReplaceRange:l}",
                                replaceElement.Name,
                                replaceRange
                                );
                }
                else
                {
                    replaceRange = location.Position.ToEmptyRange();

                    Log.Verbose("Offering completions to create element @ {ReplaceRange:l}",
                                replaceRange
                                );
                }

                completions.AddRange(
                    GetCompletionItems(projectDocument, replaceRange)
                    );
            }

            Log.Verbose("Offering {CompletionCount} completion(s) for {XmlLocation:l}", completions.Count, location);

            if (completions.Count == 0)
            {
                return(null);
            }

            return(new CompletionList(completions,
                                      isIncomplete: false // Consider this list to be exhaustive
                                      ));
        }
        /// <summary>
        ///     Provide completions for the specified location.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="cancellationToken">
        ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided.
        /// </returns>
        public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (projectDocument == null)
            {
                throw new ArgumentNullException(nameof(projectDocument));
            }

            List <CompletionItem> completions = new List <CompletionItem>();

            using (await projectDocument.Lock.ReaderLockAsync())
            {
                XSElement   element;
                XSAttribute replaceAttribute;
                PaddingType needsPadding;
                if (!location.CanCompleteAttribute(out element, out replaceAttribute, out needsPadding))
                {
                    return(null);
                }

                // Must be a valid item element.
                if (!element.IsValid || !element.HasParentPath(WellKnownElementPaths.ItemGroup))
                {
                    return(null);
                }

                Range replaceRange = replaceAttribute?.Range ?? location.Position.ToEmptyRange();

                completions.AddRange(
                    WellKnownItemAttributes.Except(
                        element.AttributeNames
                        )
                    .Select(attributeName => new CompletionItem
                {
                    Label         = attributeName,
                    Detail        = "Attribute",
                    Documentation =
                        MSBuildSchemaHelp.ForItemMetadata(itemType: element.Name, metadataName: attributeName)
                        ??
                        MSBuildSchemaHelp.ForAttribute(element.Name, attributeName),
                    Kind     = CompletionItemKind.Field,
                    SortText = GetItemSortText(attributeName),
                    TextEdit = new TextEdit
                    {
                        NewText = $"{attributeName}=\"$1\"$0".WithPadding(needsPadding),
                        Range   = replaceRange.ToLsp()
                    },
                    InsertTextFormat = InsertTextFormat.Snippet
                })
                    );
            }

            if (completions.Count == 0)
            {
                return(null);
            }

            return(new CompletionList(completions, isIncomplete: false));
        }
Exemple #27
0
 public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
 {
     return(config.Value());
 }
        /// <summary>
        ///     Get completions for item attributes.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="existingMetadata">
        ///     Metadata already declared on the item.
        /// </param>
        /// <returns>
        ///     A sequence of <see cref="CompletionItem"/>s.
        /// </returns>
        IEnumerable <CompletionItem> GetAttributeCompletions(XmlLocation location, ProjectDocument projectDocument, HashSet <string> existingMetadata)
        {
            Log.Verbose("Evaluate attribute completions for {XmlLocation:l}", location);

            XSElement   itemElement;
            XSAttribute replaceAttribute;
            PaddingType needsPadding;

            if (!location.CanCompleteAttribute(out itemElement, out replaceAttribute, out needsPadding))
            {
                Log.Verbose("Not offering any attribute completions for {XmlLocation:l} (not a location where we can offer attribute completion.", location);

                yield break;
            }

            // Must be an item element.
            if (!itemElement.HasParentPath(WellKnownElementPaths.ItemGroup))
            {
                Log.Verbose("Not offering any attribute completions for {XmlLocation:l} (element is not a direct child of a 'PropertyGroup' element).", location);

                yield break;
            }

            string itemType = itemElement.Name;

            if (String.IsNullOrWhiteSpace(itemType))
            {
                Log.Verbose("Not offering any attribute completions for {XmlLocation:l} (element represents a new, unnamed, item group).",
                            location,
                            itemType
                            );

                yield break;
            }

            if (MSBuildSchemaHelp.ForItemType(itemType) == null)
            {
                Log.Verbose("Not offering any attribute completions for {XmlLocation:l} ({ItemType} is not a well-known item type).",
                            location,
                            itemType
                            );

                yield break;
            }

            Log.Verbose("Will offer attribute completions for {XmlLocation:l} (padding: {NeedsPadding})", location, needsPadding);

            // Don't offer completions for existing metadata.
            existingMetadata.UnionWith(
                GetExistingMetadataNames(itemElement)
                );

            Range replaceRange = replaceAttribute?.Range ?? location.Position.ToEmptyRange();

            foreach (string metadataName in MSBuildSchemaHelp.WellKnownItemMetadataNames(itemType))
            {
                if (existingMetadata.Contains(metadataName))
                {
                    continue;
                }

                if (MSBuildHelper.IsWellKnownItemMetadata(metadataName))
                {
                    continue;
                }

                yield return(new CompletionItem
                {
                    Label = metadataName,
                    Kind = CompletionItemKind.Field,
                    Detail = "Item Metadata",
                    Documentation = MSBuildSchemaHelp.ForItemMetadata(itemType, metadataName),
                    SortText = GetItemSortText(metadataName),
                    TextEdit = new TextEdit
                    {
                        NewText = $"{metadataName}=\"$0\"".WithPadding(needsPadding),
                        Range = replaceRange.ToLsp()
                    },
                    InsertTextFormat = InsertTextFormat.Snippet
                });
            }
        }
Exemple #29
0
        public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
        {
            var configElement = config as XElement;
            if (configElement == null)
                throw new TestLibsException($"Couldn't parse type: {type} from following config (element node expected):\n{config}");

            var xmlType = _xmlType.Value;

            string uniqueName = null;
            object createdObject = null;

            if (context != null)
            {
                createdObject = new XmlBaseType();

                var xmlNode = _uniqProperty.Value.Location.Value.GetElement(configElement);
                uniqueName = XmlParser.Parse(_uniqProperty.Value.PropertyType, xmlNode) as string;

                if (uniqueName != null)
                {
                    if (context.Contains(type, uniqueName))
                        return context.ResolveValue(type, uniqueName);
                }
            }

            if (isAssignableTypeAllowed)
            {
                var possibleTypeName = configElement.Name.ToString();
                var assignableXmlTypes = ReflectionManager.GetAssignableTypes(type);

                foreach (var assignableXmlType in assignableXmlTypes)
                {
                    if (assignableXmlType.Location.Check(XmlLocationType.Element, possibleTypeName))
                    {
                        xmlType = assignableXmlType;
                    }
                }

                if (xmlType == _xmlType.Value)
                    throw new TestLibsException($"Couldn't find any type that could be parsed from element with name: {possibleTypeName}\nPossible element names: {assignableXmlTypes.Select(xt => $"Type: {xt.XType}, description: {xt.Description}\n{xt.Location}").ToList().ToStringWithList("=")}");
            }
            else
            {
                xmlType = ReflectionManager.GetXmlType(type);
            }

            var parsedPropertiesDict = new Dictionary<XmlProperty, bool>();
            xmlType.XmlProperties.ForEach(p => parsedPropertiesDict.Add(p, false));

            createdObject = Activator.CreateInstance(xmlType.XType);
            if (uniqueName != null)
            {
                _uniqProperty.Value.SetValue(createdObject, uniqueName);
                parsedPropertiesDict[_uniqProperty.Value] = true;
            }


            var propertyToParse = parsedPropertiesDict.Keys.FirstOrDefault(k => !parsedPropertiesDict[k]);
            while (propertyToParse != null)
            {
                ParseProperty(propertyToParse, configElement, createdObject, parsedPropertiesDict, context);
                propertyToParse = parsedPropertiesDict.Keys.FirstOrDefault(k => !parsedPropertiesDict[k]);
            }

            return createdObject;
        }
Exemple #30
0
        public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
        {
            var configEl = config as XElement;

            if (configEl == null)
            {
                throw new TestLibsException($"Following configuration element:\n{config}\n\tcouldn't be cast to type: {type}. XElement expected");
            }

            var genArgs = type.GetGenericArguments();

            if (genArgs.Length > 2)
            {
                throw new TestLibsException($"Unsupported type: {type.ToString()}. Couldn't parse following config to that type:\n{configEl}");
            }

            var childObjType = genArgs.Last();
            var childObjs    = XmlParser.Parse(childObjType, configEl, childLocation, isAssignableTypeAllowed, context);

            var propertyObj = Activator.CreateInstance(type);

            if (type.GetGenericTypeDefinition() == typeof(List <>))
            {
                var add = type.GetMethod("Add", new[] { childObjType });

                foreach (var child in childObjs)
                {
                    add.Invoke(propertyObj, new object[] { child });
                }
            }
            else if (type.GetGenericTypeDefinition() == typeof(LinkedList <>))
            {
                var add = type.GetMethod("AddLast", new[] { childObjType });

                foreach (var child in childObjs)
                {
                    add.Invoke(propertyObj, new object[] { child });
                }
            }
            else if (type.GetGenericTypeDefinition() == typeof(Dictionary <,>))
            {
                var childXmlType = ReflectionManager.GetXmlType(childObjType);
                if (childXmlType == null)
                {
                    throw new TestLibsException($"Couldn't parse type: {type} to dictionary, because child type: {childObjType} isn't XmlType");
                }

                var add = type.GetMethod("Add", new[] { childXmlType.KeyProperty.PropertyType, childObjType });

                foreach (var child in childObjs)
                {
                    var key = childXmlType.KeyProperty.GetValue(child);
                    add.Invoke(propertyObj, new object[] { key, child });
                }
            }
            else
            {
                throw new TestLibsException($"Unsupported generic type: {type}. Couldn't parse following config to that type:\n{configEl}");
            }

            return(propertyObj);
        }
        /// <summary>
        ///     Provide completions for the specified location.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="cancellationToken">
        ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided.
        /// </returns>
        public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (projectDocument == null)
            {
                throw new ArgumentNullException(nameof(projectDocument));
            }

            Log.Verbose("Evaluate completions for {XmlLocation:l}", location);

            List <CompletionItem> completions = new List <CompletionItem>();

            using (await projectDocument.Lock.ReaderLockAsync())
            {
                XSAttribute attribute;
                if (!location.CanCompleteAttributeValue(out attribute, onElementWithPath: WellKnownElementPaths.Target) || !SupportedAttributeNames.Contains(attribute.Name))
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (not the value of a supported attribute on a 'Target' element).", location);

                    return(null);
                }

                Range            targetRange        = attribute.ValueRange;
                HashSet <string> excludeTargetNames = new HashSet <string>();

                // Handle potentially composite (i.e. "Value1;Value2;Value3") values, where it's legal to have them.
                if (attribute.Name != "Name" && attribute.Value.IndexOf(';') != -1)
                {
                    int startPosition    = projectDocument.XmlPositions.GetAbsolutePosition(attribute.ValueRange.Start);
                    int relativePosition = location.AbsolutePosition - startPosition;

                    SimpleList     list           = MSBuildExpression.ParseSimpleList(attribute.Value);
                    SimpleListItem itemAtPosition = list.FindItemAt(relativePosition);
                    if (itemAtPosition == null)
                    {
                        return(null);
                    }

                    Log.Verbose("Completions will replace item {ItemValue} spanning [{ItemStartPosition}..{ItemEndPosition}) in MSBuild simple list expression {ListExpression}.",
                                itemAtPosition.Value,
                                itemAtPosition.AbsoluteStart,
                                itemAtPosition.AbsoluteEnd,
                                attribute.Value
                                );

                    targetRange = projectDocument.XmlPositions.GetRange(
                        absoluteStartPosition: startPosition + itemAtPosition.AbsoluteStart,
                        absoluteEndPosition: startPosition + itemAtPosition.AbsoluteEnd
                        );
                }

                completions.AddRange(
                    GetCompletionItems(projectDocument, targetRange, excludeTargetNames)
                    );
            }

            if (completions.Count == 0)
            {
                return(null); // No completions provided.
            }
            Log.Verbose("Offering {CompletionCount} completion(s) for {XmlLocation:l}", completions.Count, location);

            return(new CompletionList(completions,
                                      isIncomplete: false // Consider this list to be exhaustive
                                      ));
        }
        /// <summary>
        ///     Provide completions for the specified location.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="triggerCharacters">
        ///     The character(s), if any, that triggered completion.
        /// </param>
        /// <param name="cancellationToken">
        ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided.
        /// </returns>
        public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, string triggerCharacters, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (projectDocument == null)
            {
                throw new ArgumentNullException(nameof(projectDocument));
            }

            if (!projectDocument.Workspace.Configuration.Language.CompletionsFromProject.Contains(CompletionSource.Task))
            {
                Log.Verbose("Not offering task attribute completions for {XmlLocation:l} (task completions not enabled in extension settings).", location);

                return(null);
            }

            if (!projectDocument.HasMSBuildProject)
            {
                Log.Verbose("Not offering task attribute completions for {XmlLocation:l} (underlying MSBuild project is not loaded).", location);

                return(null);
            }

            List <CompletionItem> completions = new List <CompletionItem>();

            Log.Verbose("Evaluate completions for {XmlLocation:l}", location);

            using (await projectDocument.Lock.ReaderLockAsync())
            {
                XSElement   taskElement;
                XSAttribute replaceAttribute;
                PaddingType needsPadding;
                if (!location.CanCompleteAttribute(out taskElement, out replaceAttribute, out needsPadding))
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (not a location an attribute can be created or replaced by completion).", location);

                    return(null);
                }

                if (taskElement.ParentElement?.Name != "Target")
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (attribute is not on an element that's a direct child of a 'Target' element).", location);

                    return(null);
                }

                Dictionary <string, MSBuildTaskMetadata> projectTasks = await GetProjectTasks(projectDocument);

                MSBuildTaskMetadata taskMetadata;
                if (!projectTasks.TryGetValue(taskElement.Name, out taskMetadata))
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (no metadata available for task {TaskName}).", location, taskElement.Name);

                    return(null);
                }

                Range replaceRange = replaceAttribute?.Range ?? location.Position.ToEmptyRange();
                if (replaceAttribute != null)
                {
                    Log.Verbose("Offering completions to replace attribute {AttributeName} @ {ReplaceRange:l}",
                                replaceAttribute.Name,
                                replaceRange
                                );
                }
                else
                {
                    Log.Verbose("Offering completions to create attribute @ {ReplaceRange:l}",
                                replaceRange
                                );
                }

                HashSet <string> existingAttributeNames = new HashSet <string>(
                    taskElement.AttributeNames
                    );
                if (replaceAttribute != null)
                {
                    existingAttributeNames.Remove(replaceAttribute.Name);
                }

                completions.AddRange(
                    GetCompletionItems(projectDocument, taskMetadata, existingAttributeNames, replaceRange, needsPadding)
                    );
            }

            Log.Verbose("Offering {CompletionCount} completion(s) for {XmlLocation:l}", completions.Count, location);

            if (completions.Count == 0)
            {
                return(null);
            }

            return(new CompletionList(completions,
                                      isIncomplete: false // Consider this list to be exhaustive
                                      ));
        }
        /// <summary>
        ///     Get completions for item elements.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="existingMetadata">
        ///     Metadata already declared on the item.
        /// </param>
        /// <returns>
        ///     A sequence of <see cref="CompletionItem"/>s.
        /// </returns>
        IEnumerable <CompletionItem> GetElementCompletions(XmlLocation location, ProjectDocument projectDocument, HashSet <string> existingMetadata)
        {
            Log.Verbose("Evaluate element completions for {XmlLocation:l}", location);

            XSElement replaceElement;

            if (!location.CanCompleteElement(out replaceElement, parentPath: WellKnownElementPaths.Item))
            {
                Log.Verbose("Not offering any element completions for {XmlLocation:l} (not a location where an item metadata element can be created or replaced by completion).", location);

                yield break;
            }

            Range  replaceRange;
            string itemType;

            if (replaceElement != null)
            {
                replaceRange = replaceElement.Range;
                itemType     = replaceElement.ParentElement?.Name;
            }
            else
            {
                replaceRange = location.Position.ToEmptyRange();
                itemType     = location.Node.Path.Parent.Name;
            }

            // These items are handled by PackageReferenceCompletion.
            if (itemType == "PackageReference" || itemType == "DotNetCliToolReference")
            {
                Log.Verbose("Not offering any element completions for {XmlLocation:l} ({ItemType} items are handled by another provider).",
                            location,
                            itemType
                            );

                yield break;
            }

            if (MSBuildSchemaHelp.ForItemType(itemType) == null)
            {
                Log.Verbose("Not offering any element completions for {XmlLocation:l} ({ItemType} is not a well-known item type).",
                            location,
                            itemType
                            );

                yield break;
            }

            if (replaceElement != null)
            {
                // Don't offer completions for existing metadata.
                existingMetadata.UnionWith(
                    GetExistingMetadataNames(replaceElement)
                    );

                Log.Verbose("Will offer completions to replace item metadata element spanning {Range:l}", replaceRange);
            }
            else
            {
                Log.Verbose("Will offer completions to create item metadata element at {Position:l}", location.Position);
            }

            foreach (string metadataName in MSBuildSchemaHelp.WellKnownItemMetadataNames(itemType))
            {
                if (existingMetadata.Contains(metadataName))
                {
                    continue;
                }

                if (MSBuildHelper.IsWellKnownItemMetadata(metadataName))
                {
                    continue;
                }

                string completionLabel = $"<{metadataName}>";

                yield return(new CompletionItem
                {
                    Label = completionLabel,
                    Kind = CompletionItemKind.Field,
                    Detail = $"Item Metadata ({itemType})",
                    Documentation = MSBuildSchemaHelp.ForItemMetadata(itemType, metadataName),
                    SortText = GetItemSortText(completionLabel),
                    TextEdit = new TextEdit
                    {
                        NewText = $"<{metadataName}>$0</{metadataName}>",
                        Range = replaceRange.ToLsp()
                    },
                    InsertTextFormat = InsertTextFormat.Snippet
                });
            }
        }
        /// <summary>
        ///     Provide completions for the specified location.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="triggerCharacters">
        ///     The character(s), if any, that triggered completion.
        /// </param>
        /// <param name="cancellationToken">
        ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided.
        /// </returns>
        public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, string triggerCharacters, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (projectDocument == null)
            {
                throw new ArgumentNullException(nameof(projectDocument));
            }

            List <CompletionItem> completions = new List <CompletionItem>();

            Log.Verbose("Evaluate completions for {XmlLocation:l} (trigger characters = {TriggerCharacters})", location, triggerCharacters);

            using (await projectDocument.Lock.ReaderLockAsync())
            {
                XSElement replaceElement;
                if (!location.CanCompleteElement(out replaceElement, parentPath: WellKnownElementPaths.Project))
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (not a direct child of the 'Project' element).", location);

                    return(null);
                }

                Range targetRange;

                if (replaceElement != null)
                {
                    targetRange = replaceElement.Range;

                    Log.Verbose("Offering completions to replace element {ElementName} @ {ReplaceRange:l}",
                                replaceElement.Name,
                                targetRange
                                );
                }
                else
                {
                    targetRange = location.Position.ToEmptyRange();

                    Log.Verbose("Offering completions to insert element @ {InsertPosition:l}",
                                location.Position
                                );
                }

                // Replace any characters that were typed to trigger the completion.
                HandleTriggerCharacters(triggerCharacters, projectDocument, ref targetRange);

                completions.AddRange(
                    GetCompletionItems(targetRange)
                    );
            }

            Log.Verbose("Offering {CompletionCount} completion(s) for {XmlLocation:l}", completions.Count, location);

            if (completions.Count == 0)
            {
                return(null);
            }

            return(new CompletionList(completions,
                                      isIncomplete: false // Consider this list to be exhaustive
                                      ));
        }
        /// <summary>
        ///     Provide completions for the specified location.
        /// </summary>
        /// <param name="location">
        ///     The <see cref="XmlLocation"/> where completions are requested.
        /// </param>
        /// <param name="triggerCharacters">
        ///     The character(s), if any, that triggered completion.
        /// </param>
        /// <param name="projectDocument">
        ///     The <see cref="ProjectDocument"/> that contains the <paramref name="location"/>.
        /// </param>
        /// <param name="cancellationToken">
        ///     A <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     A <see cref="Task{TResult}"/> that resolves either a <see cref="CompletionList"/>s, or <c>null</c> if no completions are provided.
        /// </returns>
        public override async Task <CompletionList> ProvideCompletions(XmlLocation location, ProjectDocument projectDocument, string triggerCharacters, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (location == null)
            {
                throw new ArgumentNullException(nameof(location));
            }

            if (projectDocument == null)
            {
                throw new ArgumentNullException(nameof(projectDocument));
            }

            List <CompletionItem> completions = new List <CompletionItem>();

            Log.Verbose("Evaluate completions for {XmlLocation:l}", location);

            using (await projectDocument.Lock.ReaderLockAsync())
            {
                if (!projectDocument.EnableExpressions)
                {
                    return(null);
                }

                ExpressionNode expression;
                Range          expressionRange;
                if (!location.IsExpression(out expression, out expressionRange))
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (not on an expression or a location where an expression can be added).", location);

                    return(null);
                }

                // AF: This code is getting complicated; refactor, please.

                if (expression is Symbol)
                {
                    expression = expression.Parent; // The containing expression.
                }
                // These are handled by ItemGroupExpressionCompletion.
                if (expression is ItemGroup)
                {
                    Log.Verbose("Not offering any completions for {XmlLocation:l} (location represents an item group expression, not an item metadata expression).", location);

                    return(null);
                }

                bool   offerItemTypes = true; // By default, we offer item types.
                string targetItemType = null;
                if (expression is ItemMetadata metadataExpression && metadataExpression.HasItemType)
                {
                    targetItemType = metadataExpression.ItemType;
                    offerItemTypes = false; // We don't offer item types if one is already specified in the metadata expression.
                }

                bool offerUnqualifiedCompletions = false;
                if (location.IsAttribute(out XSAttribute attribute) && attribute.Element.ParentElement?.Name == "ItemGroup")
                {
                    // An attribute on an item element.
                    targetItemType = targetItemType ?? attribute.Element.Name;
                    offerUnqualifiedCompletions = true;
                }
Exemple #36
0
 public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context)
 {
     return config.Value();
 }