/// <summary> /// Does the location represent a place where an element can be created or replaced by a completion? /// </summary> /// <param name="location"> /// The XML location. /// </param> /// <param name="replaceElement"> /// The element (if any) that will be replaced by the completion. /// </param> /// <param name="parentPath"> /// If specified, the location's node's parent must have the specified <see cref="XSPath"/>. /// </param> /// <returns> /// <c>true</c>, if the location represents an element that can be replaced by completion; otherwise, <c>false</c>. /// </returns> /// <remarks> /// We can replace "<>" and "<<Element />". /// </remarks> public static bool CanCompleteElement(this XmlLocation location, out XSElement replaceElement, XSPath parentPath = null) { if (location == null) { throw new ArgumentNullException(nameof(location)); } replaceElement = null; // Simplest case - we're on whitespace so we can simply insert an element without replacing anything. if (location.IsWhitespace(out XSWhitespace whitespace) && (parentPath == null || whitespace.HasParentPath(parentPath))) { return(true); } XSElement element; if (!location.IsElement(out element)) { return(false); } if (location.IsElementBetweenAttributes()) { return(false); } // Check if we need to perform a substition of the element to be replaced (the common case is simply replacing an existing element or partial element). if (element.IsValid) { // Do we have an invalid parent (e.g. "<<Foo />", which yields invalid element named "" with child element named "Foo")? bool isParentValid = element.ParentElement?.IsValid ?? true; if (!isParentValid) { // We can't handle the case where the parent isn't on the same line as the child (since that's not the case outlined above). if (element.ParentElement.Start.LineNumber != location.Node.Start.LineNumber) { return(false); } // But we *can* handle this case by targeting the "parent" element (since that's the element we're actually after anyway). if (location.Node.Start.ColumnNumber - element.ParentElement.Start.ColumnNumber == 1) { element = element.ParentElement; } } } if (parentPath != null && !element.HasParentPath(parentPath)) { return(false); } replaceElement = element; return(true); }
/// <summary> /// Create new <see cref="XSElementText"/>. /// </summary> /// <param name="textNode"> /// The <see cref="XmlTextSyntax"/> represented by the <see cref="XSElementText"/>. /// </param> /// <param name="range"> /// The <see cref="Range"/>, within the source text, spanned by the text. /// </param> /// <param name="element"> /// The element whose content includes the text. /// </param> public XSElementText(XmlTextSyntax textNode, Range range, XSElement element) : base(textNode, range) { if (element == null) { throw new ArgumentNullException(nameof(element)); } Element = element; _path = Element.Path + Name; }
/// <summary> /// Create a new <see cref="XSElement"/>. /// </summary> /// <param name="element"> /// The <see cref="XmlElementSyntaxBase"/> represented by the <see cref="XSElement"/>. /// </param> /// <param name="range"> /// The range, within the source text, spanned by the element. /// </param> /// <param name="nameRange"> /// The range, within the source text, spanned by the element's name. /// </param> /// <param name="attributesRange"> /// The range, within the source text, spanned by the element's attributes. /// </param> /// <param name="parent"> /// The <see cref="XSElement"/>'s parent element (if any). /// </param> protected XSElement(XmlElementSyntaxBase element, Range range, Range nameRange, Range attributesRange, XSElement parent) : base(element, range) { NameRange = nameRange; AttributesRange = attributesRange; ParentElement = parent; XSPath parentPath = parent?.Path ?? XSPath.Root; _path = parentPath + Name; }
/// <summary> /// Push an <see cref="XSElement"/> onto the stack. /// </summary> /// <param name="element"> /// The <see cref="XSElement"/> being processed. /// </param> /// <returns> /// The <see cref="XSElement"/>. /// </returns> XSElement PushElement(XSElement element) { if (element == null) { throw new ArgumentNullException(nameof(element)); } _elementStack.Push(element); DiscoveredNodes.Add(element); return(element); }
/// <summary> /// Create a new <see cref="XSAttribute"/>. /// </summary> /// <param name="attribute"> /// The <see cref="XmlAttributeSyntax"/> represented by the <see cref="XSAttribute"/>. /// </param> /// <param name="element"> /// The element that contains the attribute. /// </param> /// <param name="attributeRange"> /// The <see cref="Range"/>, within the source text, spanned by the attribute. /// </param> /// <param name="nameRange"> /// The <see cref="Range"/>, within the source text, spanned by the attribute's name. /// </param> /// <param name="valueRange"> /// The <see cref="Range"/>, within the source text, spanned by the attribute's value. /// </param> public XSAttribute(XmlAttributeSyntax attribute, XSElement element, Range attributeRange, Range nameRange, Range valueRange) : base(attribute, attributeRange) { if (element == null) { throw new ArgumentNullException(nameof(element)); } NameRange = nameRange; ValueRange = valueRange; Element = element; _path = Element.Path + Name; }
/// <summary> /// Create new <see cref="XSWhitespace"/>. /// </summary> /// <param name="range"> /// The <see cref="Range"/>, within the source text, spanned by the whitespace. /// </param> /// <param name="parent"> /// The <see cref="XSElement"/> that contains the whitespace. /// </param> public XSWhitespace(Range range, XSElement parent) : base(range) { if (parent == null) { throw new ArgumentNullException(nameof(parent)); } ParentElement = parent; XSPath parentPath = parent?.Path ?? XSPath.Root; _path = parentPath + Name; }
/// <summary> /// Does the location represent an element's attributes range (but not a specific attribute)? /// </summary> /// <param name="location"> /// The XML location. /// </param> /// <param name="element"> /// Receives the <see cref="XSElement"/>. /// </param> /// <returns> /// <c>true</c>, if the location represents an element; otherwise, <c>false</c>. /// </returns> public static bool IsElementBetweenAttributes(this XmlLocation location, out XSElement element) { if (location == null) { throw new ArgumentNullException(nameof(location)); } if (location.IsElementBetweenAttributes()) { element = (XSElement)location.Node; return(true); } else { element = null; return(false); } }
/// <summary> /// Create a new <see cref="XSElement"/>. /// </summary> /// <param name="element"> /// The <see cref="XmlElementSyntaxBase"/> represented by the <see cref="XSElement"/>. /// </param> /// <param name="range"> /// The range, within the source text, spanned by the element. /// </param> /// <param name="nameRange"> /// The range, within the source text, spanned by the element's name. /// </param> /// <param name="attributesRange"> /// The range, within the source text, spanned by the element's attributes. /// </param> /// <param name="parent"> /// The <see cref="XSElement"/>'s parent element (if any). /// </param> protected XSElement(XmlElementSyntaxBase element, Range range, Range nameRange, Range attributesRange, XSElement parent) : base(element, range) { if (nameRange == null) { throw new ArgumentNullException(nameof(nameRange)); } if (nameRange == null) { throw new ArgumentNullException(nameof(nameRange)); } NameRange = nameRange; AttributesRange = attributesRange; ParentElement = parent; XSPath parentPath = parent?.Path ?? XSPath.Root; _path = parentPath + Name; }
/// <summary> /// Create a new <see cref="MSBuildProperty"/>. /// </summary> /// <param name="property"> /// The underlying MSBuild <see cref="ProjectProperty"/>. /// </param> /// <param name="declaringXml"> /// The <see cref="ProjectPropertyElement"/> that results in the underlying MSBuild <see cref="ProjectProperty"/>'s current value. /// </param> /// <param name="propertyElement"> /// An <see cref="XSElement"/> representing the property's XML element. /// </param> public MSBuildProperty(ProjectProperty property, ProjectPropertyElement declaringXml, XSElement propertyElement) : base(property, propertyElement) { if (declaringXml == null) { throw new ArgumentNullException(nameof(declaringXml)); } DeclaringXml = declaringXml; }
/// <summary> /// Create a new <see cref="MSBuildItemGroup"/>. /// </summary> /// <param name="items"> /// The underlying MSBuild <see cref="ProjectItem"/>s. /// </param> /// <param name="originatingElement"> /// The MSBuild <see cref="ProjectItemElement"/> from where the items originate. /// </param> /// <param name="itemElement"> /// An <see cref="XSElement"/> representing the item's XML element. /// </param> public MSBuildItemGroup(IReadOnlyList <ProjectItem> items, ProjectItemElement originatingElement, XSElement itemElement) : base(items, itemElement) { if (Items.Count == 0) { throw new ArgumentException("Must specify at least one ProjectItem.", nameof(items)); } if (originatingElement == null) { throw new ArgumentNullException(nameof(originatingElement)); } Name = itemElement.Name; OriginatingElement = originatingElement; }
/// <summary> /// Create a new <see cref="MSBuildTarget"/>. /// </summary> /// <param name="target"> /// The underlying MSBuild <see cref="ProjectTargetElement"/>. /// </param> /// <param name="element"> /// An <see cref="XSElement"/> representing the target's XML element. /// </param> public MSBuildTarget(ProjectTargetElement target, XSElement element) : base(target, element) { }
/// <summary> /// Create a new <see cref="MSBuildProperty"/>. /// </summary> /// <param name="propertyElement"> /// An <see cref="ProjectPropertyElement"/> representing the MSBuild property. /// </param> /// <param name="declaringElement"> /// An <see cref="XSElement"/> representing the property's declaring XML element. /// </param> public MSBuildUnusedProperty(ProjectPropertyElement propertyElement, XSElement declaringElement) : base(propertyElement, declaringElement) { }
/// <summary> /// Create a new <see cref="XSInvalidAttribute"/>. /// </summary> /// <param name="attribute"> /// The <see cref="XmlAttributeSyntax"/> represented by the <see cref="XSInvalidAttribute"/>. /// </param> /// <param name="element"> /// The element that contains the attribute. /// </param> /// <param name="range"> /// The <see cref="Range"/>, within the source text, spanned by the attribute. /// </param> /// <param name="nameRange"> /// The <see cref="Range"/>, within the source text, spanned by the attribute's name. /// </param> /// <param name="valueRange"> /// The <see cref="Range"/>, within the source text, spanned by the attribute's value. /// </param> public XSInvalidAttribute(XmlAttributeSyntax attribute, XSElement element, Range range, Range nameRange, Range valueRange) : base(attribute, element, range, nameRange, valueRange) { }
/// <summary> /// The range, within the source text, spanned by the node. /// </summary> /// <param name="element"> /// The <see cref="XmlElementSyntax"/> represented by the <see cref="XSElementWithContent"/>. /// </param> /// <param name="range"> /// The <see cref="Range"/>, within the source text, spanned by the element and its content. /// </param> /// <param name="nameRange"> /// The range, within the source text, spanned by the element's name. /// </param> /// <param name="openingTagRange"> /// The <see cref="Range"/>, within the source text, spanned by the element's opening tag. /// </param> /// <param name="attributesRange"> /// The range, within the source text, spanned by the element's attributes. /// </param> /// <param name="contentRange"> /// The <see cref="Range"/>, within the source text, spanned by the element's content. /// </param> /// <param name="closingTagRange"> /// The <see cref="Range"/>, within the source text, spanned by the element's closing tag. /// </param> /// <param name="parent"> /// The <see cref="XSElementWithContent"/>'s parent element (if any). /// </param> public XSElementWithContent(XmlElementSyntax element, Range range, Range nameRange, Range openingTagRange, Range attributesRange, Range contentRange, Range closingTagRange, XSElement parent) : base(element, range, nameRange, attributesRange, parent) { OpeningTagRange = openingTagRange; ContentRange = contentRange; ClosingTagRange = closingTagRange; }
/// <summary> /// Create a new <see cref="XSEmptyElement"/>. /// </summary> /// <param name="emptyElement"> /// The <see cref="XmlEmptyElementSyntax"/> represented by the <see cref="XSEmptyElement"/>. /// </param> /// <param name="range"> /// The <see cref="Range"/>, within the source text, spanned by the node. /// </param> /// <param name="nameRange"> /// The range, within the source text, spanned by the element's name. /// </param> /// <param name="attributesRange"> /// The range, within the source text, spanned by the element's attributes. /// </param> /// <param name="parent"> /// The <see cref="XSEmptyElement"/>'s parent element (if any). /// </param> public XSEmptyElement(XmlEmptyElementSyntax emptyElement, Range range, Range nameRange, Range attributesRange, XSElement parent) : base(emptyElement, range, nameRange, attributesRange, parent) { }
/// <summary> /// Create a new <see cref="MSBuildImport"/>. /// </summary> /// <param name="imports"> /// The underlying MSBuild <see cref="ResolvedImport"/>. /// </param> /// <param name="importElement"> /// An <see cref="XSElement"/> representing the import's XML element. /// </param> public MSBuildImport(IReadOnlyList <ResolvedImport> imports, XSElement importElement) : base(imports, importElement) { }
/// <summary> /// Create a new <see cref="MSBuildUnresolvedImport"/>. /// </summary> /// <param name="import"> /// The underlying MSBuild <see cref="ProjectImportElement"/>. /// </param> /// <param name="importElement"> /// An <see cref="XSElement"/> representing the import's XML element. /// </param> public MSBuildUnresolvedImport(ProjectImportElement import, XSElement importElement) : base(import, importElement) { }
/// <summary> /// The range, within the source text, spanned by the node. /// </summary> /// <param name="element"> /// The <see cref="XmlElementSyntax"/> represented by the <see cref="XSElementWithContent"/>. /// </param> /// <param name="range"> /// The <see cref="Range"/>, within the source text, spanned by the element and its content. /// </param> /// <param name="nameRange"> /// The range, within the source text, spanned by the element's name. /// </param> /// <param name="openingTagRange"> /// The <see cref="Range"/>, within the source text, spanned by the element's opening tag. /// </param> /// <param name="attributesRange"> /// The range, within the source text, spanned by the element's attributes. /// </param> /// <param name="contentRange"> /// The <see cref="Range"/>, within the source text, spanned by the element's content. /// </param> /// <param name="closingTagRange"> /// The <see cref="Range"/>, within the source text, spanned by the element's closing tag. /// </param> /// <param name="parent"> /// The <see cref="XSElementWithContent"/>'s parent element (if any). /// </param> public XSElementWithContent(XmlElementSyntax element, Range range, Range nameRange, Range openingTagRange, Range attributesRange, Range contentRange, Range closingTagRange, XSElement parent) : base(element, range, nameRange, attributesRange, parent) { if (openingTagRange == null) { throw new ArgumentNullException(nameof(openingTagRange)); } if (contentRange == null) { throw new ArgumentNullException(nameof(contentRange)); } if (closingTagRange == null) { throw new ArgumentNullException(nameof(closingTagRange)); } OpeningTagRange = openingTagRange; ContentRange = contentRange; ClosingTagRange = closingTagRange; }
/// <summary> /// Does the location represent a place where an attribute can be created or replaced by a completion? /// </summary> /// <param name="location"> /// The XML location. /// </param> /// <param name="element"> /// The element whose attribute will be completed. /// </param> /// <param name="replaceAttribute"> /// The attribute (if any) that will be replaced by the completion. /// </param> /// <param name="needsPadding"> /// An <see cref="PaddingType"/> value indicating what sort of padding (if any) is required before / after the attribute. /// </param> /// <param name="onElementWithPath"> /// If specified, the location's element must have the specified path. /// </param> /// <returns> /// <c>true</c>, if the location represents an element that can be replaced by completion; otherwise, <c>false</c>. /// </returns> public static bool CanCompleteAttribute(this XmlLocation location, out XSElement element, out XSAttribute replaceAttribute, out PaddingType needsPadding, XSPath onElementWithPath = null) { if (location == null) { throw new ArgumentNullException(nameof(location)); } replaceAttribute = null; needsPadding = PaddingType.None; XSAttribute attribute; if (location.IsAttribute(out attribute) && !location.IsValue()) { element = attribute.Element; if (location.Position == attribute.Start) { // Since we're on an existing attribute, we'll add a new attribute after it. attribute = null; needsPadding = PaddingType.Trailing; } } else if (location.IsElementBetweenAttributes(out element)) { if (element.Attributes.Count > 0) { // Check if we're directly before an attribute. foreach (XSAttribute currentAttribute in element.Attributes) { if (location.Position == currentAttribute.End) { needsPadding = PaddingType.Leading; } else { continue; } break; } } else if (location.Position == element.NameRange.End) // We're directly after the name. { needsPadding = PaddingType.Leading; } } else if (location.IsElement(out element)) { // Check if we're directly after the name. if (location.Position != element.NameRange.End) { return(false); } needsPadding = PaddingType.Leading; } else { return(false); } if (onElementWithPath != null && !element.Path.Matches(onElementWithPath)) { return(false); } replaceAttribute = attribute; return(true); }
/// <summary> /// Create a new <see cref="XSInvalidElement"/>. /// </summary> /// <param name="element"> /// The <see cref="XmlElementSyntaxBase"/> represented by the <see cref="XSInvalidElement"/>. /// </param> /// <param name="range"> /// The <see cref="Range"/>, within the source text, spanned by the element. /// </param> /// <param name="nameRange"> /// The range, within the source text, spanned by the element's name. /// </param> /// <param name="attributesRange"> /// The range, within the source text, spanned by the element's attributes. /// </param> /// <param name="parent"> /// The <see cref="XSInvalidElement"/>'s parent element (if any). /// </param> /// <param name="hasContent"> /// Does the <see cref="XSInvalidElement"/> have any content (besides attributes)? /// </param> public XSInvalidElement(XmlElementSyntaxBase element, Range range, Range nameRange, Range attributesRange, XSElement parent, bool hasContent) : base(element, range, nameRange, attributesRange, parent) { if (parent == null) { System.Diagnostics.Debugger.Break(); } HasContent = hasContent; }