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); } }
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); }
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 )); }
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); }
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; }
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)); }
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 )); }
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); }
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 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. }
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); }
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); } }
/// <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));
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 )); }
/// <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)); }
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 }); } }
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; }
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; }
public object Parse(Type type, XObject config, bool isAssignableTypeAllowed, XmlLocation childLocation, IContext context) { return config.Value(); }