Skip to content

Commit

Permalink
Improve ETagRequired error range
Browse files Browse the repository at this point in the history
Fixes eclipse-lemminx#876

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
angelozerr committed Oct 9, 2020
1 parent 7fe05f1 commit 281c085
Show file tree
Hide file tree
Showing 12 changed files with 671 additions and 251 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.eclipse.lemminx.utils.XMLPositionUtility.selectCurrentTagOffset;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.xerces.xni.QName;
Expand All @@ -25,6 +26,7 @@
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.ETagRequiredCodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.ETagUnterminatedCodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.ElementUnterminatedCodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.EqRequiredInAttributeCodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.MarkupEntityMismatchCodeAction;
Expand Down Expand Up @@ -140,24 +142,6 @@ public static Range toLSPRange(XMLLocator location, XMLSyntaxErrorCode code, Obj
String attrName = getString(arguments[1]);
return XMLPositionUtility.selectAttributeNameFromGivenNameAt(attrName, offset, document);
}
case MarkupEntityMismatch:
case ElementUnterminated: {
String text = document.getText();
if (offset < text.length()) {
DOMNode element = document.findNodeAt(offset);
if (element.isElement() && !((DOMElement) element).isStartTagClosed()) {
// ex : <foo attr="" |
int endOffset = offset;
// remove spaces
while (Character.isWhitespace(text.charAt(endOffset))) {
endOffset--;
}
endOffset++;
return XMLPositionUtility.createRange(element.getStart() + 1, endOffset, document);
}
}
return XMLPositionUtility.selectRootStartTag(document);
}
case NoMorePseudoAttributes:
case EncodingDeclRequired:
case EqRequiredInXMLDecl:
Expand Down Expand Up @@ -204,19 +188,63 @@ public static Range toLSPRange(XMLLocator location, XMLSyntaxErrorCode code, Obj
*
* <a> <a> </a> </b
*/
String text = document.getText();
char ch = text.charAt(offset);
while(Character.isWhitespace(ch)) {
offset--;
ch = text.charAt(offset);
}
return XMLPositionUtility.selectEndTagName(offset, document);
int endOffset = removeLeftSpaces(offset, document.getText());
return XMLPositionUtility.selectEndTagName(endOffset, document);
}
case CustomETag:
return XMLPositionUtility.selectEndTagName(offset, document);
case ElementUnterminated: {
// Cases
// - <foo><bar <
// - <foo><bar /<
DOMNode node = document.findNodeAt(offset);
if (node != null && node.isElement()) {
DOMElement tagElement = (DOMElement) node;
int endOffset = offset;
if (tagElement.hasChildNodes()) {
for (DOMNode child : tagElement.getChildren()) {
if (child.isElement()) {
endOffset = child.getStart() - 1;
break;
}
}
}
return getRangeFromStartNodeToOffset(tagElement, endOffset, document);
}
// Should never occurs
return null;
}
case MarkupEntityMismatch: {
// Cases
// - <foo
// - <foo /
DOMNode root = document.getDocumentElement();
if (root == null) {
root = document.getChild(0);
}
return getRangeFromStartNodeToOffset(root, offset, document);
}
case ETagRequired: {
String tag = getString(arguments[0]);
return XMLPositionUtility.selectChildEndTag(tag, offset, document);
DOMNode node = document.findNodeAt(offset);
DOMElement startTagElement = null;
if (node != null && node.isElement()) {
DOMElement element = (DOMElement) node;
if (element.isOrphanEndTag()) {
// <foo></
startTagElement = element.getParentElement();
} else if (element.isInEndTag(offset)) {
// search the element inside this element
// <foo>
// <bar>
// </foo>
startTagElement = findChildTag(tag, element);

}
return getRangeFromStartNodeToOffset(startTagElement, offset, document);
}
// Should never occurs
return null;
}
case SemicolonRequiredInReference: {
EntityReferenceRange range = XMLPositionUtility.selectEntityReference(offset + 1, document, false);
Expand Down Expand Up @@ -280,8 +308,102 @@ public static Range toLSPRange(XMLLocator location, XMLSyntaxErrorCode code, Obj

}

/**
* Remove the offset of the first character from the left offset which is not a
* whitespace.
*
* @param initialOffset the initial offset.
*
* @param text the XML content.
* @return the offset of the first character from the left offset which is not a
* whitespace.
*/
private static int removeLeftSpaces(final int initialOffset, String text) {
int offset = initialOffset;
if (offset >= text.length()) {
return text.length();
}
char ch = text.charAt(offset);
while (Character.isWhitespace(ch)) {
offset--;
ch = text.charAt(offset);
}
boolean enclosed = false;
if (ch == '/') {
// Usecases :
// - <foo> <
// - <foo> </
char previous = text.charAt(offset - 1);
if (previous == '<') {
enclosed = false;
offset--;
} else {
enclosed = true;
}
} else if (ch != '<') {
enclosed = true;
}
if (enclosed) {
return offset + 1;
}
return offset;
}

/**
* Returns the proper range from the given node to the given offset.
*
* @param fromNode the from node.
* @param toOffset the to offset.
* @param document the DOM document.
*
* @return the proper range from the given node to the given offset.
*/
private static Range getRangeFromStartNodeToOffset(DOMNode fromNode, int toOffset, DOMDocument document) {
int endOffset = removeLeftSpaces(toOffset, document.getText());
int startOffset = fromNode.getStart();
if (fromNode.isElement()) {
// The from node is a DOM element, adjust end and start offset
DOMElement fromElement = (DOMElement) fromNode;
if (fromElement.hasTagName()) {
startOffset = fromElement.getStartTagOpenOffset() + 1;
endOffset = Math.max(endOffset,
fromElement.getStartTagOpenOffset() + fromElement.getTagName().length());
}
}
return XMLPositionUtility.createRange(startOffset, endOffset, document);
}

/**
* Returns the first child element of the given element which matches the given
* tag name and null otherwise.
*
* @param tagName the tag name.
* @param element the DOM element.
* @return the first child element of the given element which matches the given
* tag name and null otherwise.
*/
private static DOMElement findChildTag(String tagName, DOMElement element) {
List<DOMNode> children = element.getChildren();
for (int i = children.size() - 1; i >= 0; i--) {
DOMNode child = children.get(i);
if (child.isElement()) {
DOMElement childElement = ((DOMElement) child);
if (childElement.isSameTag(tagName)) {
return childElement;
} else {
DOMElement tagElement = findChildTag(tagName, childElement);
if (tagElement != null) {
return tagElement;
}
}
}
}
return null;
}

public static void registerCodeActionParticipants(Map<String, ICodeActionParticipant> codeActions,
SharedSettings sharedSettings) {
codeActions.put(ETagUnterminated.getCode(), new ETagUnterminatedCodeAction());
codeActions.put(ElementUnterminated.getCode(), new ElementUnterminatedCodeAction());
codeActions.put(EqRequiredInAttribute.getCode(), new EqRequiredInAttributeCodeAction());
codeActions.put(OpenQuoteExpected.getCode(), new OpenQuoteExpectedCodeAction());
Expand Down

This file was deleted.

Loading

0 comments on commit 281c085

Please sign in to comment.