Skip to content

Commit

Permalink
Quickfix to close open tag doesn't deal with attributes
Browse files Browse the repository at this point in the history
Fixes eclipse#646

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Jun 3, 2020
1 parent 84d2689 commit 9166feb
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 165 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.apache.xerces.xni.XMLLocator;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMDocumentType;
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.ElementUnterminatedCodeAction;
import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.EqRequiredInAttributeCodeAction;
Expand All @@ -44,7 +46,8 @@ public enum XMLSyntaxErrorCode implements IXMLErrorCode {

AttributeNotUnique, // https://wiki.xmldation.com/Support/Validator/AttributeNotUnique
AttributeNSNotUnique, // https://wiki.xmldation.com/Support/Validator/AttributeNSNotUnique
AttributePrefixUnbound, ContentIllegalInProlog, // https://wiki.xmldation.com/Support/Validator/ContentIllegalInProlog
AttributePrefixUnbound, //
ContentIllegalInProlog, // https://wiki.xmldation.com/Support/Validator/ContentIllegalInProlog
DashDashInComment, // https://wiki.xmldation.com/Support/Validator/DashDashInComment
ElementUnterminated, // https://wiki.xmldation.com/Support/Validator/ElementUnterminated
ElementPrefixUnbound, // https://wiki.xmldation.com/Support/Validator/ElementPrefixUnbound
Expand All @@ -53,11 +56,30 @@ public enum XMLSyntaxErrorCode implements IXMLErrorCode {
ETagRequired, // https://wiki.xmldation.com/Support/Validator/ETagRequired
ETagUnterminated, // https://wiki.xmldation.com/Support/Validator/ETagUnterminated
EqRequiredInAttribute, // https://wiki.xmldation.com/Support/Validator/EqRequiredInAttribute
the_element_type_lmsg("the-element-type-lmsg"), EqRequiredInXMLDecl, IllegalQName, InvalidCommentStart,
LessthanInAttValue, MarkupEntityMismatch, MarkupNotRecognizedInContent, NameRequiredInReference, OpenQuoteExpected,
PITargetRequired, PseudoAttrNameExpected, QuoteRequiredInXMLDecl, RootElementTypeMustMatchDoctypedecl,
SDDeclInvalid, SemicolonRequiredInReference, SpaceRequiredBeforeEncodingInXMLDecl, SpaceRequiredBeforeStandalone, SpaceRequiredInPI,
VersionInfoRequired, VersionNotSupported, XMLDeclUnterminated, CustomETag, PrematureEOF, DoctypeNotAllowed, NoMorePseudoAttributes;
EqRequiredInXMLDecl, //
IllegalQName, //
InvalidCommentStart, //
LessthanInAttValue, //
MarkupEntityMismatch, //
MarkupNotRecognizedInContent, //
NameRequiredInReference, //
OpenQuoteExpected, //
PITargetRequired, //
PseudoAttrNameExpected, //
QuoteRequiredInXMLDecl, //
RootElementTypeMustMatchDoctypedecl, //
SDDeclInvalid, //
SemicolonRequiredInReference, //
SpaceRequiredBeforeEncodingInXMLDecl, //
SpaceRequiredBeforeStandalone, //
SpaceRequiredInPI, //
VersionInfoRequired, //
VersionNotSupported, //
XMLDeclUnterminated, //
CustomETag, //
PrematureEOF, //
DoctypeNotAllowed, //
NoMorePseudoAttributes;

private final String code;

Expand Down Expand Up @@ -108,13 +130,30 @@ public static Range toLSPRange(XMLLocator location, XMLSyntaxErrorCode code, Obj
case SpaceRequiredBeforeEncodingInXMLDecl:
case VersionInfoRequired:
case ElementPrefixUnbound:
case ElementUnterminated:
case RootElementTypeMustMatchDoctypedecl:
return XMLPositionUtility.selectStartTagName(offset, document);
case EqRequiredInAttribute: {
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 @@ -201,16 +240,14 @@ public static Range toLSPRange(XMLLocator location, XMLSyntaxErrorCode code, Obj
case InvalidCommentStart:
case MarkupNotRecognizedInContent:
return XMLPositionUtility.createRange(offset, offset + 1, document);
case MarkupEntityMismatch:
return XMLPositionUtility.selectRootStartTag(document);
case NameRequiredInReference:
break;
case OpenQuoteExpected: {
return XMLPositionUtility.selectAttributeNameAt(offset - 1, document);
}
case DoctypeNotAllowed:
DOMDocumentType docType = document.getDoctype();
return XMLPositionUtility.createRange(docType);
return XMLPositionUtility.createRange(docType);
case PITargetRequired:
// Working
break;
Expand Down Expand Up @@ -238,6 +275,7 @@ public static void registerCodeActionParticipants(Map<String, ICodeActionPartici
codeActions.put(OpenQuoteExpected.getCode(), new OpenQuoteExpectedCodeAction());
codeActions.put(MarkupEntityMismatch.getCode(), new MarkupEntityMismatchCodeAction());
codeActions.put(ETagRequired.getCode(), new ETagRequiredCodeAction());
codeActions.put(RootElementTypeMustMatchDoctypedecl.getCode(), new RootElementTypeMustMatchDoctypedeclCodeAction());
codeActions.put(RootElementTypeMustMatchDoctypedecl.getCode(),
new RootElementTypeMustMatchDoctypedeclCodeAction());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,52 @@
package org.eclipse.lemminx.extensions.contentmodel.participants.codeactions;

import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.commons.CodeActionFactory;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.services.extensions.ICodeActionParticipant;
import org.eclipse.lemminx.services.extensions.IComponentProvider;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;

/**
* ETagRequiredCodeAction
*/
public class ETagRequiredCodeAction implements ICodeActionParticipant {

private static final Logger LOGGER = Logger.getLogger(ETagRequiredCodeAction.class.getName());

@Override
public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument document, List<CodeAction> codeActions,
SharedSettings sharedSettings, IComponentProvider componentProvider) {
MarkupEntityMismatchCodeAction.createEndTagInsertCodeAction(diagnostic, range, document, codeActions, componentProvider);
try {
int offset = document.offsetAt(diagnostic.getRange().getStart());
DOMNode node = document.findNodeAt(offset);
if (!node.isElement()) {
return;
}

DOMElement element = (DOMElement) node;
int startOffset = element.getStartTagOpenOffset();
if (startOffset == -1) {
return;
}
Position endPosition = document.positionAt(element.getStartTagCloseOffset() + 1);
String elementName = element.getTagName();
CodeAction action = CodeActionFactory.insert("Close with '</" + elementName + ">'", endPosition,
"</" + elementName + ">", document.getTextDocument(), diagnostic);
codeActions.add(action);
} catch (BadLocationException e) {
LOGGER.log(Level.WARNING, "Exception while resolving the code action for " + diagnostic.getCode() + ":", e);
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;

/**
Expand All @@ -36,27 +37,52 @@ public class ElementUnterminatedCodeAction implements ICodeActionParticipant {
public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument document, List<CodeAction> codeActions,
SharedSettings sharedSettings, IComponentProvider componentProvider) {
Range diagnosticRange = diagnostic.getRange();
// Close with '/>
CodeAction autoCloseAction = CodeActionFactory.insert("Close with '/>'", diagnosticRange.getEnd(), "/>",
document.getTextDocument(), diagnostic);
codeActions.add(autoCloseAction);

// Close with '>
CodeAction closeAction = CodeActionFactory.insert("Close with '>'", diagnosticRange.getEnd(), ">",
document.getTextDocument(), diagnostic);
codeActions.add(closeAction);

// Close with '$tag>
try {
int offset = document.offsetAt(range.getStart());
DOMNode node = document.findNodeAt(offset);
int startOffset = document.offsetAt(diagnosticRange.getStart());
DOMNode node = document.findNodeAt(startOffset);
if (node != null && node.isElement()) {
String tagName = ((DOMElement) node).getTagName();
if (tagName != null) {
String insertText = "></" + tagName + ">";
CodeAction closeEndTagAction = CodeActionFactory.insert("Close with '" + insertText + "'",
diagnosticRange.getEnd(), insertText, document.getTextDocument(), diagnostic);
codeActions.add(closeEndTagAction);
int diagnosticEndOffset = startOffset
+ (diagnosticRange.getEnd().getCharacter() - diagnosticRange.getStart().getCharacter());
DOMElement element = (DOMElement) node;
boolean startTagClosed = element.isStartTagClosed();
char c = document.getText().charAt(diagnosticEndOffset - 1);
if (c != '/') {
if (startTagClosed) {
// ex : <foo attr="" >
// // Close with '</$tag>
String tagName = element.getTagName();
int endOffset = element.hasChildNodes() ? element.getLastChild().getEnd()
: element.getStartTagCloseOffset() + 1;
if (tagName != null) {
String insertText = "</" + tagName + ">";
Position endPosition = document.positionAt(endOffset);
CodeAction closeEndTagAction = CodeActionFactory.insert("Close with '" + insertText + "'",
endPosition, insertText, document.getTextDocument(), diagnostic);
codeActions.add(closeEndTagAction);
}

} else {
// ex : <foo attr="
// Close with '/>
CodeAction autoCloseAction = CodeActionFactory.insert("Close with '/>'",
diagnosticRange.getEnd(), "/>", document.getTextDocument(), diagnostic);
codeActions.add(autoCloseAction);
// // Close with '></$tag>
String tagName = element.getTagName();
if (tagName != null) {
String insertText = "></" + tagName + ">";
CodeAction closeEndTagAction = CodeActionFactory.insert("Close with '" + insertText + "'",
diagnosticRange.getEnd(), insertText, document.getTextDocument(), diagnostic);
codeActions.add(closeEndTagAction);
}
}
}

if (!startTagClosed) {
// Close with '>
CodeAction closeAction = CodeActionFactory.insert("Close with '>'", diagnosticRange.getEnd(), ">",
document.getTextDocument(), diagnostic);
codeActions.add(closeAction);
}
}
} catch (BadLocationException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,77 +11,11 @@
*******************************************************************************/
package org.eclipse.lemminx.extensions.contentmodel.participants.codeactions;

import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.commons.CodeActionFactory;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.extensions.contentmodel.participants.XMLSyntaxErrorCode;
import org.eclipse.lemminx.services.extensions.ICodeActionParticipant;
import org.eclipse.lemminx.services.extensions.IComponentProvider;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;

/**
* MarkupEntityMismatchCodeAction is a code action that triggers when the end
* tag of the root element is missing. This will provide a codeaction that
* inserts that missing end tag.
*/
public class MarkupEntityMismatchCodeAction implements ICodeActionParticipant {
private static final Logger LOGGER = Logger.getLogger(MarkupEntityMismatchCodeAction.class.getName());

@Override
public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument document, List<CodeAction> codeActions,
SharedSettings sharedSettings, IComponentProvider componentProvider) {
createEndTagInsertCodeAction(diagnostic, range, document, codeActions, componentProvider);
}

public static void createEndTagInsertCodeAction(Diagnostic diagnostic, Range range, DOMDocument document,
List<CodeAction> codeActions, IComponentProvider componentProvider) {
try {
int offset = document.offsetAt(diagnostic.getRange().getStart());
DOMNode node = document.findNodeAt(offset);
if (!node.isElement()) {
return;
}

DOMElement element = (DOMElement) node;
int startOffset = element.getStartTagOpenOffset();
if (startOffset == -1) {
return;
}
Position startPosition = document.positionAt(startOffset);
Position endPosition;

XMLSyntaxErrorCode code = XMLSyntaxErrorCode.get(diagnostic.getCode().getLeft());
switch (code) {
case MarkupEntityMismatch:
endPosition = document.positionAt(document.getEnd());
if (endPosition.getLine() > startPosition.getLine()) {
endPosition.setCharacter(startPosition.getCharacter());
}
break;
case ETagRequired:
endPosition = document.positionAt(element.getStartTagCloseOffset() + 1);
break;
default:
return;
}

String elementName = element.getTagName();
CodeAction action = CodeActionFactory.insert("Close with '</" + elementName + ">'", endPosition,
"</" + elementName + ">", document.getTextDocument(), diagnostic);
codeActions.add(action);
} catch (BadLocationException e) {
LOGGER.log(Level.WARNING, "Exception while resolving the code action for " + diagnostic.getCode() + ":", e);
}
}
public class MarkupEntityMismatchCodeAction extends ElementUnterminatedCodeAction {

}
Loading

0 comments on commit 9166feb

Please sign in to comment.