Skip to content

Commit

Permalink
Use related information for unclosed elements
Browse files Browse the repository at this point in the history
Revert MarkupEntityMismatch and ETagRequired error ranges so they only
cover the start tag element name.

Keep existing CodeAction behaviour intact.

If the client supports DiagnosticRelatedInformation, add the expected
location of the close tag as related info to MarkupEntityMismatch and
ETagRequired errors.

Closes eclipse-lemminx#963
  • Loading branch information
datho7561 committed Mar 23, 2021
1 parent 81298da commit f796dfa
Show file tree
Hide file tree
Showing 15 changed files with 465 additions and 98 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.contentmodel.participants;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lsp4j.DiagnosticRelatedInformation;

/**
* Finds related info for any error code
*/
public class AggregateRelatedInfoFinder implements IRelatedInfoFinder {

private static IRelatedInfoFinder[] RELATED_INFO_FINDERS = {
new XMLSyntaxRelatedInfoFinder()
};

private static AggregateRelatedInfoFinder INSTANCE = null;

private AggregateRelatedInfoFinder() {}

public static AggregateRelatedInfoFinder getInstance() {
if (INSTANCE == null) {
INSTANCE = new AggregateRelatedInfoFinder();
}
return INSTANCE;
}

@Override
public List<DiagnosticRelatedInformation> findRelatedInformation(
int offset,
String errorKey,
DOMDocument document) {
List<DiagnosticRelatedInformation> relatedInfo = new ArrayList<>();
for (IRelatedInfoFinder relatedInfoFinder : RELATED_INFO_FINDERS) {
relatedInfo.addAll(relatedInfoFinder.findRelatedInformation(
offset,
errorKey,
document
));
}
return relatedInfo;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.contentmodel.participants;

import java.util.List;

import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lsp4j.DiagnosticRelatedInformation;

/**
* Provides an interface to find related info for a given error
*/
public interface IRelatedInfoFinder {

/**
* Returns a list of related information
*
* @param offset The LemMinX reported error start offset
* @param errorKey The error key
* @param document The document
* @return a list of related information
*/
List<DiagnosticRelatedInformation> findRelatedInformation(
int offset,
String errorKey,
DOMDocument document);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.contentmodel.participants;

import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;

/**
* Reusable utility functions for resolving syntax errors
*/
public class SyntaxErrorUtils {

/**
* Returns the offset at which the given unclosed start tag should be closed with an angle bracket
*
* @param document The xml document with the unclosed start tag
* @param element The element that has a start tag that's missing a closing angle bracket
* @return the offset at which the given unclosed start tag should be closed with an angle bracket
*/
public static int getUnclosedStartTagCloseLocation(DOMDocument document, DOMElement element) {
String documentText = document.getText();
int i = element.getStart() + 1;
for (; i < documentText.length() && documentText.charAt(i) != '/' && documentText.charAt(i) != '<'; i++) {
}
if (i < documentText.length() && documentText.charAt(i) == '/') {
return i + 1;
}
i--;
for (; i > 0 && Character.isWhitespace(documentText.charAt(i)); i--) {
}
return i + 1;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

/**
* XML error code.
*
*
* @see https://wiki.xmldation.com/Support/Validator
*
*/
Expand Down Expand Up @@ -120,7 +120,7 @@ public static XMLSyntaxErrorCode get(String name) {

/**
* Create the LSP range from the SAX error.
*
*
* @param location
* @param key
* @param arguments
Expand Down Expand Up @@ -181,11 +181,11 @@ public static Range toLSPRange(XMLLocator location, XMLSyntaxErrorCode code, Obj
case ETagUnterminated: {
/**
* Cases:
*
*
* <a> </b>
*
*
* <a> <b> </b> </c>
*
*
* <a> <a> </a> </b
*/
int endOffset = removeLeftSpaces(offset, document.getText());
Expand Down Expand Up @@ -218,11 +218,7 @@ public static Range toLSPRange(XMLLocator location, XMLSyntaxErrorCode code, Obj
// Cases
// - <foo
// - <foo /
DOMNode root = document.getDocumentElement();
if (root == null) {
root = document.getChild(0);
}
return getRangeFromStartNodeToOffset(root, offset, document);
return XMLPositionUtility.selectRootStartTag(document);
}
case ETagRequired: {
String tag = getString(arguments[0]);
Expand All @@ -241,7 +237,7 @@ public static Range toLSPRange(XMLLocator location, XMLSyntaxErrorCode code, Obj
startTagElement = findChildTag(tag, element);

}
return getRangeFromStartNodeToOffset(startTagElement, offset, document);
return XMLPositionUtility.selectStartTagName(startTagElement);
}
// Should never occurs
return null;
Expand Down Expand Up @@ -311,9 +307,9 @@ 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.
Expand Down Expand Up @@ -351,11 +347,11 @@ private static int removeLeftSpaces(final int initialOffset, String text) {

/**
* 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) {
Expand All @@ -376,7 +372,7 @@ private static Range getRangeFromStartNodeToOffset(DOMNode fromNode, int toOffse
/**
* 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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.extensions.contentmodel.participants;

import java.util.Collections;
import java.util.List;

import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.DiagnosticRelatedInformation;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Range;

/**
* Find related information for an XML syntax error
*/
public class XMLSyntaxRelatedInfoFinder implements IRelatedInfoFinder {

private static String CLOSING_TAG_EXPECTED_HERE = "Closing tag expected here";

@Override
public List<DiagnosticRelatedInformation> findRelatedInformation(int offset, String errorKey,
DOMDocument document) {

XMLSyntaxErrorCode syntaxCode = XMLSyntaxErrorCode.get(errorKey);

if (syntaxCode == null) {
return Collections.emptyList();
}

switch (syntaxCode) {
case ETagRequired:
case MarkupEntityMismatch: {
DOMNode node = document.findNodeAt(offset);
while (node != null && !node.isElement()) {
node = node.getParentNode();
}
if (node == null) {
return Collections.emptyList();
}

int closeTagOffset;
int numChildren = node.getChildren().size();
if (numChildren == 0) {
closeTagOffset = node.getEnd();
} else {
closeTagOffset = node.getChildren().get(numChildren - 1).getEnd();
}

Range range = XMLPositionUtility.createRange(closeTagOffset, closeTagOffset, document);
return Collections.singletonList(new DiagnosticRelatedInformation(
new Location(document.getDocumentURI(), range), CLOSING_TAG_EXPECTED_HERE));
}
default:
}
return Collections.emptyList();
}

}
Loading

0 comments on commit f796dfa

Please sign in to comment.