From 7e6f38313ed2450619879b3debd40f2171c1c6d4 Mon Sep 17 00:00:00 2001 From: azerr Date: Thu, 24 Sep 2020 17:36:40 +0200 Subject: [PATCH] DOM document should store Invalid end tag ` --- .../org/eclipse/lemminx/dom/DOMElement.java | 30 +++- .../java/org/eclipse/lemminx/dom/DOMNode.java | 2 +- .../org/eclipse/lemminx/dom/DOMParser.java | 141 ++++++++++------- .../ContentModelCompletionParticipant.java | 20 +-- .../codeactions/CloseStartTagCodeAction.java | 2 +- .../EqRequiredInAttributeCodeAction.java | 4 +- .../NoGrammarConstraintsCodeAction.java | 7 +- .../services/AbstractPositionRequest.java | 2 +- .../lemminx/services/XMLCompletions.java | 6 +- .../lemminx/services/XMLHighlighting.java | 4 +- .../eclipse/lemminx/services/XMLHover.java | 2 +- .../eclipse/lemminx/services/XMLRename.java | 149 ++++++++++-------- .../ProcessingInstructionSnippetContext.java | 2 +- .../snippets/SnippetContextUtils.java | 7 +- .../XMLDeclarationSnippetContext.java | 2 +- .../org/eclipse/lemminx/utils/XMLBuilder.java | 4 +- .../lemminx/utils/XMLPositionUtility.java | 4 +- .../eclipse/lemminx/dom/DOMParserTest.java | 109 +++++++++++++ .../lemminx/services/XMLCompletionTest.java | 9 +- .../lemminx/services/XMLFormatterTest.java | 35 +++- .../services/XMLSymbolInformationsTest.java | 30 ++++ 21 files changed, 398 insertions(+), 173 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java index fb2396e32..a768f8a52 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java @@ -77,6 +77,17 @@ public String getTagName() { return tag; } + /** + * Returns true if the DOM element have a tag name and false otherwise (ex : '<' + * or ') and false otherwise. * - * @param tagName the end tag name. * @return true if the given element is an orphan end tag (which has no start * tag, eg: ) and false otherwise. */ - public boolean isOrphanEndTag(String tagName) { - return isSameTag(tagName) && hasEndTag() && !hasStartTag(); + public boolean isOrphanEndTag() { + return hasEndTag() && !hasStartTag(); + } + + /** + * Returns true if the given element is an orphan end tag (which has no start + * tag, eg: ) of the given tag name and false otherwise. + * + * @param tagName the end tag name. + * @return true if the given element is an orphan end tag (which has no start + * tag, eg: ) of the given tag name and false otherwise. + */ + public boolean isOrphanEndTagOf(String tagName) { + return isSameTag(tagName) && isOrphanEndTag(); } @Override @@ -423,7 +445,7 @@ public DOMElement getOrphanEndElement(int offset, String tagName) { for (DOMNode child : children) { if (child.isElement()) { DOMElement childElement = (DOMElement) child; - if (childElement.isOrphanEndTag(tagName)) { + if (childElement.isOrphanEndTagOf(tagName)) { return childElement; } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMNode.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMNode.java index 871c1a1f2..9f4ebca0d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMNode.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMNode.java @@ -746,7 +746,7 @@ public DOMElement getOrphanEndElement(int offset, String tagName) { } // emp| DOMElement nextElement = (DOMElement) next; - if (nextElement.isOrphanEndTag(tagName)) { + if (nextElement.isOrphanEndTagOf(tagName)) { return nextElement; } return null; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMParser.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMParser.java index 47c84fe08..73f47a05f 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMParser.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMParser.java @@ -52,7 +52,8 @@ public DOMDocument parse(String text, String uri, URIResolverExtensionManager re return parse(new TextDocument(text, uri), resolverExtensionManager); } - public DOMDocument parse(String text, String uri, URIResolverExtensionManager resolverExtensionManager, boolean ignoreWhitespaceContent) { + public DOMDocument parse(String text, String uri, URIResolverExtensionManager resolverExtensionManager, + boolean ignoreWhitespaceContent) { return parse(new TextDocument(text, uri), resolverExtensionManager, ignoreWhitespaceContent); } @@ -60,25 +61,27 @@ public DOMDocument parse(TextDocument document, URIResolverExtensionManager reso return parse(document, resolverExtensionManager, true); } - public DOMDocument parse(TextDocument document, URIResolverExtensionManager resolverExtensionManager, boolean ignoreWhitespaceContent) { + public DOMDocument parse(TextDocument document, URIResolverExtensionManager resolverExtensionManager, + boolean ignoreWhitespaceContent) { return parse(document, resolverExtensionManager, ignoreWhitespaceContent, null); } - public DOMDocument parse(TextDocument document, URIResolverExtensionManager resolverExtensionManager, boolean ignoreWhitespaceContent, CancelChecker monitor) { + public DOMDocument parse(TextDocument document, URIResolverExtensionManager resolverExtensionManager, + boolean ignoreWhitespaceContent, CancelChecker monitor) { boolean isDTD = DOMUtils.isDTD(document.getUri()); boolean inDTDInternalSubset = false; String text = document.getText(); Scanner scanner = XMLScanner.createScanner(text, 0, isDTD); DOMDocument xmlDocument = new DOMDocument(document, resolverExtensionManager); xmlDocument.setCancelChecker(monitor); - + DOMNode curr = isDTD ? new DOMDocumentType(0, text.length()) : xmlDocument; if (isDTD) { xmlDocument.addChild(curr); // This DOMDocumentType object is hidden, and just represents the DTD file // nothing should affect it's closed status - curr.closed = true; + curr.closed = true; } DOMNode lastClosed = curr; DOMAttr attr = null; @@ -86,29 +89,39 @@ public DOMDocument parse(TextDocument document, URIResolverExtensionManager reso String pendingAttribute = null; DOMNode tempWhitespaceContent = null; boolean isInitialDeclaration = true; // A declaration can have multiple internal declarations + boolean previousTokenWasEndTagOpen = false; TokenType token = scanner.scan(); while (token != TokenType.EOS) { if (monitor != null) { monitor.checkCanceled(); } - if(tempWhitespaceContent != null && token != TokenType.EndTagOpen) { + if (tempWhitespaceContent != null && token != TokenType.EndTagOpen) { tempWhitespaceContent = null; } + if (previousTokenWasEndTagOpen) { + previousTokenWasEndTagOpen = false; + if (token != TokenType.EndTag) { + // The excepted token is not an EndTag, create a fake end tag element + DOMElement element = xmlDocument.createElement(endTagOpenOffset, endTagOpenOffset + 2); + element.endTagOpenOffset = endTagOpenOffset; + curr.addChild(element); + } + } switch (token) { case StartTagOpen: { - if(!curr.isClosed() && curr.parent != null) { - //The next node's parent (curr) is not closed at this point - //so the node's parent (curr) will have its end position updated - //to a newer end position. + if (!curr.isClosed() && curr.parent != null) { + // The next node's parent (curr) is not closed at this point + // so the node's parent (curr) will have its end position updated + // to a newer end position. curr.end = scanner.getTokenOffset(); } - if((curr.isClosed()) || curr.isDoctype()) { - //The next node being considered is a child of 'curr' - //and if 'curr' is already closed then 'curr' was not updated properly. - //Or if we get a Doctype node then we know it was not closed and 'curr' - //wasn't updated properly. + if ((curr.isClosed()) || curr.isDoctype()) { + // The next node being considered is a child of 'curr' + // and if 'curr' is already closed then 'curr' was not updated properly. + // Or if we get a Doctype node then we know it was not closed and 'curr' + // wasn't updated properly. curr = curr.parent; - inDTDInternalSubset = false; //In case it was previously in the internal subset + inDTDInternalSubset = false; // In case it was previously in the internal subset } DOMElement child = xmlDocument.createElement(scanner.getTokenOffset(), scanner.getTokenEnd()); child.startTagOpenOffset = scanner.getTokenOffset(); @@ -124,15 +137,14 @@ public DOMDocument parse(TextDocument document, URIResolverExtensionManager reso break; } - case StartTagClose: if (curr.isElement()) { DOMElement element = (DOMElement) curr; curr.end = scanner.getTokenEnd(); // might be later set to end tag position element.startTagCloseOffset = scanner.getTokenOffset(); - //never enters isEmptyElement() is always false - if (element.getTagName() != null && isEmptyElement(element.getTagName()) && curr.parent != null) { + // never enters isEmptyElement() is always false + if (element.hasTagName() && isEmptyElement(element.getTagName()) && curr.parent != null) { curr.closed = true; curr = curr.parent; } @@ -149,12 +161,13 @@ public DOMDocument parse(TextDocument document, URIResolverExtensionManager reso break; case EndTagOpen: - if(tempWhitespaceContent != null) { + if (tempWhitespaceContent != null) { curr.addChild(tempWhitespaceContent); tempWhitespaceContent = null; } endTagOpenOffset = scanner.getTokenOffset(); curr.end = scanner.getTokenOffset(); + previousTokenWasEndTagOpen = true; break; case EndTag: @@ -163,8 +176,8 @@ public DOMDocument parse(TextDocument document, URIResolverExtensionManager reso DOMNode current = curr; /** - eg: will set a,b,c end position to the start of | - */ + * eg: will set a,b,c end position to the start of | + */ while (!(curr.isElement() && ((DOMElement) curr).isSameTag(closeTag)) && curr.parent != null) { curr.end = endTagOpenOffset; curr = curr.parent; @@ -202,14 +215,14 @@ public DOMDocument parse(TextDocument document, URIResolverExtensionManager reso if (curr.parent != null) { curr.end = scanner.getTokenEnd(); lastClosed = curr; - if(lastClosed.isElement()) { + if (lastClosed.isElement()) { ((DOMElement) curr).endTagCloseOffset = scanner.getTokenOffset(); } - if(curr.isDoctype()) { + if (curr.isDoctype()) { curr.closed = true; } curr = curr.parent; - + } break; @@ -223,8 +236,8 @@ public DOMDocument parse(TextDocument document, URIResolverExtensionManager reso } case DelimiterAssign: { - if(attr != null) { - //Sets the value to the '=' position in case there is no AttributeValue + if (attr != null) { + // Sets the value to the '=' position in case there is no AttributeValue attr.setValue(null, scanner.getTokenOffset(), scanner.getTokenEnd()); attr.setDelimiter(true); } @@ -302,14 +315,13 @@ public DOMDocument parse(TextDocument document, URIResolverExtensionManager reso } case StartCommentTag: { - //Incase the tag before the comment tag (curr) was not properly closed - //curr should be set to the root node. - if(xmlDocument.isDTD() || inDTDInternalSubset) { - while(!curr.isDoctype()) { + // Incase the tag before the comment tag (curr) was not properly closed + // curr should be set to the root node. + if (xmlDocument.isDTD() || inDTDInternalSubset) { + while (!curr.isDoctype()) { curr = curr.parent; } - } - else if((curr.isClosed())) { + } else if ((curr.isClosed())) { curr = curr.parent; } DOMComment comment = xmlDocument.createComment(scanner.getTokenOffset(), text.length()); @@ -343,13 +355,13 @@ else if((curr.isClosed())) { case Content: { // FIXME: don't use getTokenText (substring) to know if the content is only - // spaces or line feed (scanner should know that). + // spaces or line feed (scanner should know that). boolean currIsDeclNode = curr instanceof DTDDeclNode; if (currIsDeclNode) { curr.end = scanner.getTokenOffset() - 1; - while(!curr.isDoctype()) { + while (!curr.isDoctype()) { curr = curr.getParentNode(); - } + } } int start = scanner.getTokenOffset(); int end = scanner.getTokenEnd(); @@ -357,23 +369,21 @@ else if((curr.isClosed())) { textNode.closed = true; String content = scanner.getTokenText(); - if(StringUtils.isWhitespace(content)) { - if(ignoreWhitespaceContent) { - if(curr.hasChildNodes()) { + if (StringUtils.isWhitespace(content)) { + if (ignoreWhitespaceContent) { + if (curr.hasChildNodes()) { break; } - + tempWhitespaceContent = textNode; break; - - } - else if(!currIsDeclNode) { + + } else if (!currIsDeclNode) { textNode.setWhitespace(true); - } - else { + } else { break; } - + } curr.addChild(textNode); @@ -439,12 +449,12 @@ else if(!currIsDeclNode) { } case DTDStartElement: { - //If previous 'curr' was an unclosed DTD Declaration + // If previous 'curr' was an unclosed DTD Declaration while (!curr.isDoctype()) { curr.end = scanner.getTokenOffset(); curr = curr.getParentNode(); } - + DTDElementDecl child = new DTDElementDecl(scanner.getTokenOffset(), text.length()); curr.addChild(child); curr = child; @@ -487,7 +497,7 @@ else if(!currIsDeclNode) { curr = curr.getParentNode(); } DTDAttlistDecl child = new DTDAttlistDecl(scanner.getTokenOffset(), text.length()); - + isInitialDeclaration = true; curr.addChild(child); curr = child; @@ -502,7 +512,7 @@ else if(!currIsDeclNode) { case DTDAttlistAttributeName: { DTDAttlistDecl attribute = (DTDAttlistDecl) curr; - if(isInitialDeclaration == false) { + if (isInitialDeclaration == false) { // All additional declarations are created as new DTDAttlistDecl's DTDAttlistDecl child = new DTDAttlistDecl(attribute.getStart(), attribute.getEnd()); attribute.addAdditionalAttDecl(child); @@ -525,15 +535,14 @@ else if(!currIsDeclNode) { DTDAttlistDecl attribute = (DTDAttlistDecl) curr; attribute.setAttributeValue(scanner.getTokenOffset(), scanner.getTokenEnd()); - if(attribute.parent.isDTDAttListDecl()) { // Is not the root/main ATTLIST node + if (attribute.parent.isDTDAttListDecl()) { // Is not the root/main ATTLIST node curr = attribute.parent; - } - else { + } else { isInitialDeclaration = false; } break; } - + case DTDStartEntity: { while (!curr.isDoctype()) { // If previous DTD Decl was unclosed curr.end = scanner.getTokenOffset(); @@ -551,13 +560,13 @@ else if(!currIsDeclNode) { break; } - case DTDEntityName : { + case DTDEntityName: { DTDEntityDecl entity = (DTDEntityDecl) curr; entity.setName(scanner.getTokenOffset(), scanner.getTokenEnd()); break; } - case DTDEntityValue : { + case DTDEntityValue: { DTDEntityDecl entity = (DTDEntityDecl) curr; entity.setValue(scanner.getTokenOffset(), scanner.getTokenEnd()); break; @@ -625,8 +634,9 @@ else if(!currIsDeclNode) { } case DTDEndTag: { - if ((curr.isDTDElementDecl() || curr.isDTDAttListDecl() || curr.isDTDEntityDecl() || curr.isDTDNotationDecl()) ) { - while(curr.parent != null && !curr.parent.isDoctype()) { + if ((curr.isDTDElementDecl() || curr.isDTDAttListDecl() || curr.isDTDEntityDecl() + || curr.isDTDNotationDecl())) { + while (curr.parent != null && !curr.parent.isDoctype()) { curr = curr.parent; } curr.end = scanner.getTokenEnd(); @@ -635,7 +645,7 @@ else if(!currIsDeclNode) { } break; } - + case DTDEndDoctypeTag: { ((DOMDocumentType) curr).end = scanner.getTokenEnd(); curr.closed = true; @@ -645,7 +655,7 @@ else if(!currIsDeclNode) { case DTDUnrecognizedParameters: { DTDDeclNode node = (DTDDeclNode) curr; - node.setUnrecognized(scanner.getTokenOffset(), ((XMLScanner)scanner).getLastNonWhitespaceOffset()); + node.setUnrecognized(scanner.getTokenOffset(), ((XMLScanner) scanner).getLastNonWhitespaceOffset()); break; } @@ -653,7 +663,16 @@ else if(!currIsDeclNode) { } token = scanner.scan(); } - while (curr.parent != null ) { + if (previousTokenWasEndTagOpen) { + previousTokenWasEndTagOpen = false; + if (token != TokenType.EndTag) { + // The excepted token is not an EndTag, create a fake end tag element + DOMElement element = xmlDocument.createElement(endTagOpenOffset, endTagOpenOffset + 2); + element.endTagOpenOffset = endTagOpenOffset; + curr.addChild(element); + } + } + while (curr.parent != null) { curr.end = text.length(); curr = curr.parent; } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java index 91e93e387..a1c14ed50 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java @@ -211,15 +211,17 @@ private static void addTagName(NodeList list, Set tags, ICompletionReque Node node = list.item(i); if (Node.ELEMENT_NODE == node.getNodeType()) { DOMElement elt = (DOMElement) node; - String tagName = elt.getTagName(); - if (!StringUtils.isEmpty(tagName) && !tags.contains(tagName)) { - CompletionItem item = new CompletionItem(tagName); - item.setKind(CompletionItemKind.Property); - item.setFilterText(request.getFilterForStartTagName(tagName)); - String xml = elt.getOwnerDocument().getText().substring(elt.getStart(), elt.getEnd()); - item.setTextEdit(new TextEdit(request.getReplaceRange(), xml)); - response.addCompletionItem(item); - tags.add(item.getLabel()); + if (elt.hasTagName()) { + String tagName = elt.getTagName(); + if (!tags.contains(tagName)) { + CompletionItem item = new CompletionItem(tagName); + item.setKind(CompletionItemKind.Property); + item.setFilterText(request.getFilterForStartTagName(tagName)); + String xml = elt.getOwnerDocument().getText().substring(elt.getStart(), elt.getEnd()); + item.setTextEdit(new TextEdit(request.getReplaceRange(), xml)); + response.addCompletionItem(item); + tags.add(item.getLabel()); + } } addTagName(elt.getChildNodes(), tags, request, response); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/CloseStartTagCodeAction.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/CloseStartTagCodeAction.java index d223f4887..be478bf38 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/CloseStartTagCodeAction.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/CloseStartTagCodeAction.java @@ -47,7 +47,7 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen if (!element.hasStartTag()) { // DOMElement parent = element.getParentElement(); - if (parent != null && parent.getTagName() != null) { + if (parent != null && parent.hasTagName()) { // // Replace with 'b' closing tag String tagName = parent.getTagName(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/EqRequiredInAttributeCodeAction.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/EqRequiredInAttributeCodeAction.java index 4bd89c77c..23d94e132 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/EqRequiredInAttributeCodeAction.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/EqRequiredInAttributeCodeAction.java @@ -42,8 +42,8 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen int offset = document.offsetAt(range.getStart()); DOMNode node = document.findNodeAt(offset); if (node != null && node.isElement()) { - String tagName = ((DOMElement) node).getTagName(); - if (tagName != null) { + DOMElement element = (DOMElement) node; + if (element.hasTagName()) { String insertText = "=\"\""; CodeAction insertEqualsAndQuotesAction = CodeActionFactory.insert("Insert '" + insertText + "'", diagnosticRange.getEnd(), insertText, document.getTextDocument(), diagnostic); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/NoGrammarConstraintsCodeAction.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/NoGrammarConstraintsCodeAction.java index 4fd418364..ce5ced722 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/NoGrammarConstraintsCodeAction.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/NoGrammarConstraintsCodeAction.java @@ -30,7 +30,6 @@ import org.eclipse.lemminx.services.extensions.ICodeActionParticipant; import org.eclipse.lemminx.services.extensions.IComponentProvider; import org.eclipse.lemminx.settings.SharedSettings; -import org.eclipse.lemminx.utils.StringUtils; import org.eclipse.lemminx.utils.XMLBuilder; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.Diagnostic; @@ -51,7 +50,7 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen SharedSettings sharedSettings, IComponentProvider componentProvider) { try { DOMElement documentElement = document.getDocumentElement(); - if (documentElement == null || StringUtils.isEmpty(documentElement.getTagName())) { + if (documentElement == null || !documentElement.hasTagName()) { return; } @@ -103,8 +102,8 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen docType.endDoctype(); docType.linefeed(); CodeAction docTypeAction = createGrammarFileAndBindIt( - "Generate '" + dtdFileName + "' and bind with DOCTYPE", dtdURI, dtdTemplate, - docType.toString(), beforeTagOffset, document, diagnostic); + "Generate '" + dtdFileName + "' and bind with DOCTYPE", dtdURI, dtdTemplate, docType.toString(), + beforeTagOffset, document, diagnostic); codeActions.add(docTypeAction); // xml-model diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/AbstractPositionRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/AbstractPositionRequest.java index adff2fe96..61966afd5 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/AbstractPositionRequest.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/AbstractPositionRequest.java @@ -96,7 +96,7 @@ public int getOffset() { @Override public String getCurrentTag() { - if (node != null && node.isElement() && ((DOMElement) node).getTagName() != null) { + if (node != null && node.isElement() && ((DOMElement) node).hasTagName()) { return ((DOMElement) node).getTagName(); } return null; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java index ae593e3c8..7a7ea19bb 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java @@ -489,7 +489,7 @@ public AutoCloseTagResponse doTagComplete(DOMDocument xmlDocument, Position posi return null; } DOMElement element = ((DOMElement) node); - if (node != null && node.isElement() && !element.isSelfClosed() && element.getTagName() != null + if (node != null && node.isElement() && !element.isSelfClosed() && element.hasTagName() && !isEmptyElement(((DOMElement) node).getTagName()) && node.getStart() < offset && (!element.hasEndTag() || (element.getTagName().equals(node.getParentNode().getNodeName()) && !isBalanced(node)))) { @@ -498,7 +498,7 @@ public AutoCloseTagResponse doTagComplete(DOMDocument xmlDocument, Position posi } } else if (cBefore == '<' && c == '/') { // Case: | + // ex : | return XMLPositionUtility.selectText((DOMText) node); } // should never occur diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLHighlighting.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLHighlighting.java index 618763514..96348c843 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLHighlighting.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLHighlighting.java @@ -70,7 +70,7 @@ public List findDocumentHighlights(DOMDocument xmlDocument, P private static void fillWithDefaultHighlights(DOMNode node, Position position, int offset, List highlights, CancelChecker cancelChecker) { - if (!node.isElement() || ((DOMElement) node).getTagName() == null) { + if (!node.isElement() || !((DOMElement) node).hasTagName()) { return; } @@ -122,7 +122,7 @@ private void fillWithCustomHighlights(DOMNode node, Position position, int offse List highlights, CancelChecker cancelChecker) { // Consume highlighting participant for (IHighlightingParticipant highlightingParticipant : extensionsRegistry.getHighlightingParticipants()) { - highlightingParticipant.findDocumentHighlights(node, position, offset,highlights, cancelChecker); + highlightingParticipant.findDocumentHighlights(node, position, offset, highlights, cancelChecker); } } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLHover.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLHover.java index fe0a0845a..6946bcbf2 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLHover.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLHover.java @@ -66,7 +66,7 @@ public Hover doHover(DOMDocument xmlDocument, Position position, SharedSettings if (node == null) { return null; } - if (node.isElement() && ((DOMElement) node).getTagName() != null) { + if (node.isElement() && ((DOMElement) node).hasTagName()) { // Element is hovered DOMElement element = (DOMElement) node; if (element.hasEndTag() && offset >= element.getEndTagOpenOffset()) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLRename.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLRename.java index 826ab8bb0..3ac8af904 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLRename.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLRename.java @@ -42,8 +42,7 @@ /** * Handle all rename requests * - * Author: - * Nikolas Komonen - nkomonen@redhat.com + * Author: Nikolas Komonen - nkomonen@redhat.com */ public class XMLRename { @@ -68,27 +67,27 @@ public WorkspaceEdit doRename(DOMDocument xmlDocument, Position position, String DOMNode node = renameRequest.getNode(); - if (node == null || - (!node.isAttribute() && !node.isElement()) || - (node.isElement() && ((DOMElement) node).getTagName() == null)) { - + if (node == null || (!node.isAttribute() && !node.isElement()) + || (node.isElement() && !((DOMElement) node).hasTagName())) { + return createWorkspaceEdit(xmlDocument.getDocumentURI(), Collections.emptyList()); } List textEdits = new ArrayList<>(); - + for (IRenameParticipant participant : extensionsRegistry.getRenameParticipants()) { participant.doRename(renameRequest, textEdits); } - for (TextEdit textEdit: getRenameTextEdits(xmlDocument, node, position, newText)) { + for (TextEdit textEdit : getRenameTextEdits(xmlDocument, node, position, newText)) { textEdits.add(textEdit); } return createWorkspaceEdit(xmlDocument.getDocumentURI(), textEdits); } - private List getRenameTextEdits(DOMDocument xmlDocument, DOMNode node, Position position, String newText) { + private List getRenameTextEdits(DOMDocument xmlDocument, DOMNode node, Position position, + String newText) { DOMElement element = getAssociatedElement(node); if (node == null) { @@ -111,8 +110,7 @@ private List getRenameTextEdits(DOMDocument xmlDocument, DOMNode node, } /** - * Returns DOMElement associated with - * node + * Returns DOMElement associated with node * * @param node node representing an element or attribute * @return associated DOMElement @@ -125,11 +123,12 @@ private DOMElement getAssociatedElement(DOMNode node) { if (node.isAttribute()) { return ((DOMAttr) node).getOwnerElement(); } - + return (DOMElement) node; } - private List getCDATARenameTextEdits(DOMDocument xmlDocument, DOMElement element, Position position, String newText) { + private List getCDATARenameTextEdits(DOMDocument xmlDocument, DOMElement element, Position position, + String newText) { Position startPos = null; Position endPos = null; Range tempRange = null; @@ -143,7 +142,7 @@ private List getCDATARenameTextEdits(DOMDocument xmlDocument, DOMEleme LOGGER.log(Level.SEVERE, "In XMLRename the Node at provided Offset is a BadLocation", e); return Collections.emptyList(); } - + if (covers(tempRange, position)) { startPos.setCharacter(startPos.getCharacter() + 1); // {Cursor} <{Cursor}![CDATA[ endPos.setCharacter(endPos.getCharacter() - 1); // ]]>{Cursor} -> ]]{Cursor}> @@ -159,27 +158,32 @@ private List getCDATARenameTextEdits(DOMDocument xmlDocument, DOMEleme private boolean isRenameTagName(DOMDocument document, DOMElement element, Position position) { Range startTagRange = getTagNameRange(TokenType.StartTag, element.getStart(), document); - Range endTagRange = element.hasEndTag() ? getTagNameRange(TokenType.EndTag, element.getEndTagOpenOffset(), document) + Range endTagRange = element.hasEndTag() + ? getTagNameRange(TokenType.EndTag, element.getEndTagOpenOffset(), document) : null; - + return doesTagCoverPosition(startTagRange, endTagRange, position); } - private List getTagNameRenameTextEdits(DOMDocument xmlDocument, DOMElement element, Position position, String newText) { - + private List getTagNameRenameTextEdits(DOMDocument xmlDocument, DOMElement element, Position position, + String newText) { + Range startTagRange = getTagNameRange(TokenType.StartTag, element.getStart(), xmlDocument); - Range endTagRange = element.hasEndTag() ? getTagNameRange(TokenType.EndTag, element.getEndTagOpenOffset(), xmlDocument) - : null; - - //Check if xsd namespace rename + Range endTagRange = element.hasEndTag() + ? getTagNameRange(TokenType.EndTag, element.getEndTagOpenOffset(), xmlDocument) + : null; + + // Check if xsd namespace rename String fullNodeName = element.getNodeName(); int indexOfColon = fullNodeName.indexOf(":"); - if(indexOfColon > 0) { + if (indexOfColon > 0) { Position startTagStartPosition = startTagRange.getStart(); - Position startTagPrefixPosition = new Position(startTagStartPosition.getLine(), startTagStartPosition.getCharacter() + indexOfColon); + Position startTagPrefixPosition = new Position(startTagStartPosition.getLine(), + startTagStartPosition.getCharacter() + indexOfColon); Position endTagStartPosition = endTagRange.getStart(); - Position endTagPrefixPosition = new Position(endTagStartPosition.getLine(), endTagStartPosition.getCharacter() + indexOfColon); + Position endTagPrefixPosition = new Position(endTagStartPosition.getLine(), + endTagStartPosition.getCharacter() + indexOfColon); Range startTagPrefixRange = new Range(startTagStartPosition, startTagPrefixPosition); Range endTagPrefixRange = new Range(endTagStartPosition, endTagPrefixPosition); @@ -187,14 +191,16 @@ private List getTagNameRenameTextEdits(DOMDocument xmlDocument, DOMEle if (doesTagCoverPosition(startTagPrefixRange, endTagPrefixRange, position)) {// Element prefix rename String prefix = element.getPrefix(); return renameElementNamespace(xmlDocument, element, prefix.length(), newText); - } else { //suffix rename without wiping namespace + } else { // suffix rename without wiping namespace String suffixName = element.getLocalName(); int suffixLength = suffixName.length(); Position startTagEndPosition = startTagRange.getEnd(); - Position suffixStartPositionStart = new Position(startTagEndPosition.getLine(), startTagEndPosition.getCharacter() - suffixLength); + Position suffixStartPositionStart = new Position(startTagEndPosition.getLine(), + startTagEndPosition.getCharacter() - suffixLength); Position endTagEndPosition = endTagRange.getEnd(); - Position suffixEndPositionStart = new Position(endTagEndPosition.getLine(), endTagEndPosition.getCharacter() - suffixLength); + Position suffixEndPositionStart = new Position(endTagEndPosition.getLine(), + endTagEndPosition.getCharacter() - suffixLength); Range suffixRangeStart = new Range(suffixStartPositionStart, startTagEndPosition); Range suffixRangeEnd = new Range(suffixEndPositionStart, endTagEndPosition); @@ -202,21 +208,22 @@ private List getTagNameRenameTextEdits(DOMDocument xmlDocument, DOMEle return getRenameList(suffixRangeStart, suffixRangeEnd, newText); } } - //Regular tag name rename + // Regular tag name rename return getRenameList(startTagRange, endTagRange, newText); } - private List getXmlnsAttrRenameTextEdits(DOMDocument xmlDocument, DOMElement element, Position position, String newText) { + private List getXmlnsAttrRenameTextEdits(DOMDocument xmlDocument, DOMElement element, Position position, + String newText) { List attributes = element.getAttributeNodes(); - if(attributes == null) { + if (attributes == null) { return Collections.emptyList(); } for (DOMAttr attr : attributes) { DOMNode nameNode = attr.getNodeAttrName(); - if(!attr.isXmlns()) { + if (!attr.isXmlns()) { continue; } @@ -228,8 +235,8 @@ private List getXmlnsAttrRenameTextEdits(DOMDocument xmlDocument, DOME } catch (BadLocationException e) { continue; } - - if(covers(new Range(start, end), position)) { // Rename over the suffix of 'xmlns:XXX' + + if (covers(new Range(start, end), position)) { // Rename over the suffix of 'xmlns:XXX' String namespaceName = attr.getLocalName(); return renameAllNamespaceOccurrences(xmlDocument, namespaceName, newText, attr); } @@ -245,6 +252,7 @@ private WorkspaceEdit createWorkspaceEdit(String documentURI, List tex /** * Creates a list of start and end tag rename's. + * * @param startTagRange * @param endTagRange * @param newText @@ -262,42 +270,45 @@ private static List getRenameList(Range startTagRange, Range endTagRan } /** - * Renames all occurences of the namespace in a document, that match - * the given old namespace. + * Renames all occurences of the namespace in a document, that match the given + * old namespace. + * * @param document * @param oldNamespace * @param newNamespace * @param rootAttr * @return */ - private static List renameAllNamespaceOccurrences(DOMDocument document, String oldNamespace, String newNamespace, @Nullable DOMAttr rootAttr) { + private static List renameAllNamespaceOccurrences(DOMDocument document, String oldNamespace, + String newNamespace, @Nullable DOMAttr rootAttr) { DOMElement rootElement = document.getDocumentElement(); - + List edits = new ArrayList(); // Renames the xmlns:NAME_SPACE attribute - if(rootAttr != null) { + if (rootAttr != null) { Position start; try { start = document.positionAt(rootAttr.getStart() + "xmlns:".length()); } catch (BadLocationException e) { start = null; } - - if(start != null) { + + if (start != null) { Position end = new Position(start.getLine(), start.getCharacter() + oldNamespace.length()); edits.add(new TextEdit(new Range(start, end), newNamespace)); } } - //Renames all elements with oldNamespace + // Renames all elements with oldNamespace List children = Arrays.asList(rootElement); return renameElementsNamespace(document, edits, children, oldNamespace, newNamespace); } /** - * Will traverse through the given elements and their children, - * updating all namespaces that match the given old namespace. + * Will traverse through the given elements and their children, updating all + * namespaces that match the given old namespace. + * * @param document * @param edits * @param elements @@ -305,38 +316,40 @@ private static List renameAllNamespaceOccurrences(DOMDocument document * @param newNamespace * @return */ - private static List renameElementsNamespace(DOMDocument document, List edits, List elements, String oldNamespace, String newNamespace) { + private static List renameElementsNamespace(DOMDocument document, List edits, + List elements, String oldNamespace, String newNamespace) { int oldNamespaceLength = oldNamespace.length(); for (DOMNode node : elements) { - if(node.isElement()) { + if (node.isElement()) { DOMElement element = (DOMElement) node; - if(oldNamespace.equals(element.getPrefix())) { + if (oldNamespace.equals(element.getPrefix())) { edits.addAll(renameElementNamespace(document, element, oldNamespaceLength, newNamespace)); } - if(element.hasAttributes()) { + if (element.hasAttributes()) { edits.addAll(renameElementAttributeValueNamespace(document, element, oldNamespace, newNamespace)); } - - if(element.hasChildNodes()) { + + if (element.hasChildNodes()) { renameElementsNamespace(document, edits, element.getChildren(), oldNamespace, newNamespace); } } } - + return edits; } /** - * Will rename the namespace of a given element + * Will rename the namespace of a given element */ - private static List renameElementNamespace(DOMDocument document, DOMElement element, int oldNamespaceLength, String newNamespace) { + private static List renameElementNamespace(DOMDocument document, DOMElement element, + int oldNamespaceLength, String newNamespace) { List edits = new ArrayList(); Range[] ranges = createNamespaceRange(document, element, oldNamespaceLength); - if(ranges == null) { + if (ranges == null) { return edits; } for (Range r : ranges) { - if(r != null) { + if (r != null) { edits.add(new TextEdit(r, newNamespace)); } } @@ -344,26 +357,29 @@ private static List renameElementNamespace(DOMDocument document, DOMEl } /** - * Will rename the namespace of an element's attribute values with the matching namespace. + * Will rename the namespace of an element's attribute values with the matching + * namespace. + * * @param document * @param element * @param oldNamespace * @param newNamespace * @return */ - private static List renameElementAttributeValueNamespace(DOMDocument document, DOMElement element, String oldNamespace, String newNamespace) { - + private static List renameElementAttributeValueNamespace(DOMDocument document, DOMElement element, + String oldNamespace, String newNamespace) { + List attributes = element.getAttributeNodes(); List edits = new ArrayList(); - if(attributes != null) { + if (attributes != null) { for (DOMAttr attr : attributes) { DOMNode attrValue = attr.getNodeAttrValue(); - if(attrValue != null) { + if (attrValue != null) { String attrValueText = attr.getValue(); - if(attrValueText != null && attrValueText.startsWith(oldNamespace + ":")) { + if (attrValueText != null && attrValueText.startsWith(oldNamespace + ":")) { int startOffset = attrValue.getStart() + 1; - Position start,end; + Position start, end; try { start = document.positionAt(startOffset); end = new Position(start.getLine(), start.getCharacter() + oldNamespace.length()); @@ -380,6 +396,7 @@ private static List renameElementAttributeValueNamespace(DOMDocument d /** * Returns the ranges of the namespace of a start and end tag of an element. + * * @param document * @param element * @param namespaceLength @@ -391,14 +408,14 @@ private static Range[] createNamespaceRange(DOMDocument document, DOMElement ele Position start; Position end; try { - if(element.hasStartTag()) { - int startName = element.getStart() + 1; //skip '<' + if (element.hasStartTag()) { + int startName = element.getStart() + 1; // skip '<' start = document.positionAt(startName); end = new Position(start.getLine(), start.getCharacter() + namespaceLength); ranges[0] = new Range(start, end); } - if(element.hasEndTag()) { - int startName = element.getEndTagOpenOffset() + 2; //skip ' model) { return false; } - if (documentElement != null && documentElement.getTagName() != null) { + if (documentElement != null && documentElement.hasTagName()) { return offset <= documentElement.getStart(); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/SnippetContextUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/SnippetContextUtils.java index 71b6b5bfe..7d4683cbf 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/SnippetContextUtils.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/snippets/SnippetContextUtils.java @@ -45,7 +45,12 @@ public static boolean canAcceptExpression(ICompletionRequest request) { } if (node.isElement()) { DOMElement element = (DOMElement) node; - if (element.getTagName() == null) { + if (element.isOrphanEndTag()) { + // + return false; + } + if (!element.hasTagName()) { // <| // model) { // No xml processing instruction, check if completion was triggered before the // document element DOMElement documentElement = document.getDocumentElement(); - if (documentElement != null && documentElement.getTagName() != null) { + if (documentElement != null && documentElement.hasTagName()) { return offset <= documentElement.getStart(); } return true; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLBuilder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLBuilder.java index cfed27deb..b74f93e52 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLBuilder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLBuilder.java @@ -90,7 +90,9 @@ public XMLBuilder endElement(String prefix, String name, boolean isEndTagClosed) append(prefix); append(":"); } - append(name); + if (name != null) { + append(name); + } if (isEndTagClosed) { append(">"); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java index 55608f7ba..d8c40f7ee 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/XMLPositionUtility.java @@ -268,7 +268,7 @@ private static int adjustOffsetForAttribute(int offset, DOMDocument document) { public static Range selectChildEndTag(String childTag, int offset, DOMDocument document) { DOMNode parent = document.findNodeAt(offset); - if (parent == null || !parent.isElement() || ((DOMElement) parent).getTagName() == null) { + if (parent == null || !parent.isElement() || !((DOMElement) parent).hasTagName()) { return null; } @@ -421,7 +421,7 @@ private static Range selectStartTagName(DOMNode element, boolean localNameOnly) private static int getStartTagLength(DOMNode node) { if (node.isElement()) { DOMElement element = (DOMElement) node; - return element.getTagName() != null ? element.getTagName().length() : 0; + return element.hasTagName() ? element.getTagName().length() : 0; } else if (node.isProcessingInstruction() || node.isProlog()) { DOMProcessingInstruction element = (DOMProcessingInstruction) node; return element.getTarget() != null ? element.getTarget().length() : 0; diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/dom/DOMParserTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/dom/DOMParserTest.java index bc82bd771..3cc7c666c 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/dom/DOMParserTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/dom/DOMParserTest.java @@ -459,6 +459,115 @@ public void elementOffsets() { assertFalse(a.isInStartTag(3)); // | } + @Test + public void startTag() { + // '<' + DOMDocument document = DOMParser.getInstance().parse("<", "", null); + DOMElement a = document.getDocumentElement(); + assertNotNull(a); + assertFalse(a.hasTagName()); + assertTrue(a.hasStartTag()); + assertFalse(a.hasEndTag()); + + // '' + document = DOMParser.getInstance().parse("", "", null); + a = document.getDocumentElement(); + assertNotNull(a); + assertTrue(a.hasTagName()); + assertEquals("a", a.getTagName()); + assertTrue(a.hasStartTag()); + assertTrue(a.isStartTagClosed()); + assertFalse(a.hasEndTag()); + + // '' + document = DOMParser.getInstance().parse("", "", null); + a = document.getDocumentElement(); + assertNotNull(a); + assertTrue(a.hasTagName()); + assertEquals("a", a.getTagName()); + assertTrue(a.hasStartTag()); + assertTrue(a.isStartTagClosed()); + assertTrue(a.hasEndTag()); + assertTrue(a.isEndTagClosed()); + } + + @Test + public void endTag() { + // '' + document = DOMParser.getInstance().parse("", "", null); + DOMElement root = document.getDocumentElement(); + assertNotNull(root); + assertTrue(root.hasChildNodes()); + + a = (DOMElement) root.getChild(0); + assertNotNull(a); + assertTrue(a.hasChildNodes()); + + child = a.getChild(0); + assertNotNull(child); + assertTrue(child.isElement()); + invalidEndTag = (DOMElement) child; + assertFalse(invalidEndTag.hasTagName()); + assertFalse(invalidEndTag.hasStartTag()); + assertTrue(invalidEndTag.hasEndTag()); + assertTrue(invalidEndTag.isOrphanEndTag()); + + } + @Test public void testDoctype1() { String xml = " \n" diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLCompletionTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLCompletionTest.java index fd06c96ac..2514751e3 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLCompletionTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLCompletionTest.java @@ -80,14 +80,7 @@ public void successfulEndTagCompletion() throws BadLocationException { @Test public void successfulEndTagCompletionWithIndent() throws BadLocationException { - testCompletionFor(" \r\n" + // - "|", 3 + 2 /* CDATA and Comments */, // - c("End with ''", " ", r(1, 0, 1, 0), ""), // - c("#region", "", r(1, 0, 1, 0), ""), // - c("#endregion", "", r(1, 0, 1, 0), "")); - testCompletionFor(" \r\n" + // - "<|", 1 + 2 /* CDATA and Comments */, // - c("End with ''", " ", r(1, 0, 1, 1), "")); + testCompletionFor("'", "/a>", r(0, 4, 0, 5), "/a>")); testCompletionFor(" \r\n" + // diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLFormatterTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLFormatterTest.java index c1e67558a..05ca63234 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLFormatterTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLFormatterTest.java @@ -33,7 +33,7 @@ public class XMLFormatterTest { public void closeStartTagMissing() throws BadLocationException { // Don't close tag with bad XML String content = "\r\n" + // + " \r\n" + // + ""; + String expected = content; + assertFormat(content, expected); + } + @Test public void endTagMissing() throws BadLocationException { String content = "\r\n" + // diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLSymbolInformationsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLSymbolInformationsTest.java index 352aef5f6..06c2ce0e6 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLSymbolInformationsTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLSymbolInformationsTest.java @@ -210,6 +210,36 @@ public void singleEndTag() throws BadLocationException { } + @Test + public void invalidEndTag() { + String xmlText = " expectedSymbolInfos = new ArrayList(); + currentLocation = createLocation(testURI, 0, 2, xmlDocument); + currentSymbolInfo = createSymbolInformation("?", SymbolKind.Field, currentLocation, ""); + expectedSymbolInfos.add(currentSymbolInfo); + + assertSymbols(expectedSymbolInfos, actualSymbolInfos); + } + + @Test + public void invalidEndTagAfterRoot() { + String xmlText = " expectedSymbolInfos = new ArrayList(); + currentLocation = createLocation(testURI, 0, 5, xmlDocument); + currentSymbolInfo = createSymbolInformation("a", SymbolKind.Field, currentLocation, ""); + expectedSymbolInfos.add(currentSymbolInfo); + + currentLocation = createLocation(testURI, 3, 5, xmlDocument); + currentSymbolInfo = createSymbolInformation("?", SymbolKind.Field, currentLocation, "a"); + expectedSymbolInfos.add(currentSymbolInfo); + + assertSymbols(expectedSymbolInfos, actualSymbolInfos); + } + @Test public void insideEndTag() throws BadLocationException { // assertRename("", "newText", edits("newText", r(0, 1, 5),