Skip to content

Commit

Permalink
Support xml.format.enforceQuoteStyle setting with experimental formatter
Browse files Browse the repository at this point in the history
Signed-off-by: Jessica He <[email protected]>
  • Loading branch information
JessicaJHee committed Jul 14, 2022
1 parent a099dc2 commit e8c960f
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lemminx.settings.EnforceQuoteStyle;
import org.eclipse.lemminx.utils.StringUtils;

/**
* DOM attribute formatter.
Expand Down Expand Up @@ -77,6 +79,24 @@ public void formatAttribute(DOMAttr attr, int prevOffset, boolean singleAttribut
removeLeftSpaces(delimiterOffset, attrValueStart, edits);
}
}

// replace current quote with preferred quote in case of attribute value
// ex: if preferred quote is single quote (')
// <a name="value"> </a>
// --> <a name='value'> </a>
String originalValue = attr.getOriginalValue();
if (getEnforceQuoteStyle() == EnforceQuoteStyle.preferred && originalValue != null) {
if (originalValue.charAt(0) != getQuotationAsChar()
&& StringUtils.isQuote(originalValue.charAt(0))) {
formatterDocument.replaceQuoteWithPreferred(attr.getNodeAttrValue().getStart(),
attr.getNodeAttrValue().getStart() + 1, getQuotationAsString(), edits);
}
if (originalValue.charAt(originalValue.length() - 1) != getQuotationAsChar()
&& StringUtils.isQuote(originalValue.charAt(originalValue.length() - 1))) {
formatterDocument.replaceQuoteWithPreferred(attr.getNodeAttrValue().getEnd() - 1,
attr.getNodeAttrValue().getEnd(), getQuotationAsString(), edits);
}
}
}

private void replaceLeftSpacesWithOneSpace(int from, int to, List<TextEdit> edits) {
Expand Down Expand Up @@ -108,4 +128,15 @@ private boolean hasLineBreak(int prevOffset, int start) {
return formatterDocument.hasLineBreak(prevOffset, start);
}

private char getQuotationAsChar() {
return formatterDocument.getSharedSettings().getPreferences().getQuotationAsChar();
}

private String getQuotationAsString() {
return formatterDocument.getSharedSettings().getPreferences().getQuotationAsString();
}

private EnforceQuoteStyle getEnforceQuoteStyle() {
return formatterDocument.getSharedSettings().getFormattingSettings().getEnforceQuoteStyle();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import org.eclipse.lemminx.dom.DTDDeclParameter;
import org.eclipse.lsp4j.TextEdit;
import org.w3c.dom.Node;
import org.eclipse.lemminx.settings.EnforceQuoteStyle;
import org.eclipse.lemminx.utils.StringUtils;

/**
* DOM docType formatter.
Expand Down Expand Up @@ -52,21 +54,38 @@ public void formatDocType(DOMDocumentType docType, XMLFormattingConstraints pare
constraints.setIndentLevel(constraints.getIndentLevel() + 1);
formatDTD(docType, constraints, start, end, edits);
}

}
}
DTDDeclParameter internalSubset = docType.getInternalSubsetNode();
if (internalSubset == null) {
if (docType.isClosed()) {
int endDocType = docType.getEnd() - 1;
removeLeftSpaces(endDocType, edits);
if (getEnforceQuoteStyle() == EnforceQuoteStyle.preferred) {
int quoteStart = getDocTypeIdStart(docType);
int quoteEnd = getDocTypeIdEnd(docType);

if (quoteStart != -1 && quoteEnd != -1) {
// replace current quote with preferred quote in the case:
// <!DOCTYPE note SYSTEM "note.dtd">
formatterDocument.replaceQuoteWithPreferred(quoteStart,
quoteStart + 1, getQuotationAsString(), edits);
formatterDocument.replaceQuoteWithPreferred(quoteEnd - 1,
quoteEnd, getQuotationAsString(), edits);
}
}
} else {
int endDocType = internalSubset.getEnd() - 1;
String lineDelimiter = formatterDocument.getLineDelimiter();
replaceLeftSpacesWith(endDocType, lineDelimiter, edits);
}
}
DTDDeclParameter internalSubset = docType.getInternalSubsetNode();
if (internalSubset == null) {
if (docType.isClosed()) {
// Remove space between content and end bracket in case of no internal subset
// Example: <!DOCTYPE note SYSTEM "note.dtd"|>
int endDocType = docType.getEnd() - 1;
removeLeftSpaces(endDocType, edits);
}
} else {
// Add new line at end of internal subset
// <!DOCTYPE person [...
// <!ENTITY AUTHOR \"John Doe\">|]>
int endDocType = internalSubset.getEnd() - 1;
String lineDelimiter = formatterDocument.getLineDelimiter();
replaceLeftSpacesWith(endDocType, lineDelimiter, edits);
}
}

private void formatDTD(DOMDocumentType docType, XMLFormattingConstraints parentConstraints, int start, int end,
Expand All @@ -75,20 +94,22 @@ private void formatDTD(DOMDocumentType docType, XMLFormattingConstraints parentC
for (DOMNode child : docType.getChildren()) {
switch (child.getNodeType()) {

case DOMNode.DTD_ELEMENT_DECL_NODE:
case DOMNode.DTD_ATT_LIST_NODE:
case Node.ENTITY_NODE:
case DOMNode.DTD_NOTATION_DECL:
DTDDeclNode nodeDecl = (DTDDeclNode) child;
formatDTDNodeDecl(nodeDecl, parentConstraints, addLineSeparator, edits);
addLineSeparator = true;
break;

default:
// unknown, so just leave alone for now but make sure to update
// available line width
int width = updateLineWidthWithLastLine(child, parentConstraints.getAvailableLineWidth());
parentConstraints.setAvailableLineWidth(width);
case DOMNode.DTD_ELEMENT_DECL_NODE:
case DOMNode.DTD_ATT_LIST_NODE:
case Node.ENTITY_NODE:
case DOMNode.DTD_NOTATION_DECL:
// Format DTD node declaration, for example:
// <!ENTITY AUTHOR "John Doe">
DTDDeclNode nodeDecl = (DTDDeclNode) child;
formatDTDNodeDecl(nodeDecl, parentConstraints, addLineSeparator, edits);
addLineSeparator = true;
break;

default:
// unknown, so just leave alone for now but make sure to update
// available line width
int width = updateLineWidthWithLastLine(child, parentConstraints.getAvailableLineWidth());
parentConstraints.setAvailableLineWidth(width);
}
}
}
Expand All @@ -114,7 +135,13 @@ private void formatDTDNodeDecl(DTDDeclNode nodeDecl, XMLFormattingConstraints pa
List<DTDAttlistDecl> internalDecls = attlist.getInternalChildren();
if (internalDecls == null) {
for (DTDDeclParameter parameter : attlist.getParameters()) {
// Normalize space at the start of parameter to a single space for ATTLIST, for
// example:
// <!ATTLIST |E |WIDTH |CDATA |"0">
replaceLeftSpacesWithOneSpace(parameter.getStart(), edits);
// replace current quote with preferred quote in the case:
// <!ATTLIST E WIDTH CDATA "0">
replaceQuoteWithPreferred(nodeDecl, parameter, edits);
}
} else {
boolean multipleInternalAttlistDecls = false;
Expand Down Expand Up @@ -152,7 +179,13 @@ private void formatDTDNodeDecl(DTDDeclNode nodeDecl, XMLFormattingConstraints pa
List<DTDDeclParameter> parameters = nodeDecl.getParameters();
if (!parameters.isEmpty()) {
for (DTDDeclParameter parameter : parameters) {
// Normalize space at the start of parameter to a single space for non-ATTLIST,
// for example:
// <!ENTITY |AUTHOR |"John Doe">
replaceLeftSpacesWithOneSpace(parameter.getStart(), edits);
// replace current quote with preferred quote in the case:
// <!ENTITY AUTHOR "John Doe">
replaceQuoteWithPreferred(nodeDecl, parameter, edits);
}
}
}
Expand All @@ -175,4 +208,44 @@ private void removeLeftSpaces(int to, List<TextEdit> edits) {
formatterDocument.removeLeftSpaces(to, edits);
}

private String getQuotationAsString() {
return formatterDocument.getSharedSettings().getPreferences().getQuotationAsString();
}

private EnforceQuoteStyle getEnforceQuoteStyle() {
return formatterDocument.getSharedSettings().getFormattingSettings().getEnforceQuoteStyle();
}

private static int getDocTypeIdStart(DOMDocumentType docType) {
if (docType.getPublicIdNode() != null) {
return docType.getPublicIdNode().getStart();
} else if (docType.getSystemIdNode() != null) {
return docType.getSystemIdNode().getStart();
} else
return -1;
}

private static int getDocTypeIdEnd(DOMDocumentType docType) {
if (docType.getPublicIdNode() != null) {
return docType.getPublicIdNode().getEnd();
} else if (docType.getSystemIdNode() != null) {
return docType.getSystemIdNode().getEnd();
} else
return -1;
}

private void replaceQuoteWithPreferred(DTDDeclNode nodeDecl, DTDDeclParameter parameter, List<TextEdit> edits) {
int paramStart = parameter.getStart();
int paramEnd = parameter.getEnd();
if (StringUtils.isQuote(nodeDecl.getOwnerDocument().getText().charAt(paramStart)) &&
StringUtils.isQuote(nodeDecl.getOwnerDocument().getText().charAt(paramEnd - 1))) {
if (getEnforceQuoteStyle() == EnforceQuoteStyle.preferred) {
formatterDocument.replaceQuoteWithPreferred(paramStart,
paramStart + 1, getQuotationAsString(), edits);
formatterDocument.replaceQuoteWithPreferred(paramEnd - 1,
paramEnd, getQuotationAsString(), edits);

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

import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.commons.TextDocument;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
Expand Down Expand Up @@ -92,6 +93,11 @@ public static TextEdit createTextEditIfNeeded(int from, int to, String expectedC
matchExpectedContent = to - from == expectedContent.length();
}

if (from == to && !expectedContent.isEmpty() && StringUtils.isQuote(expectedContent.toCharArray()[0])) {
from--;
matchExpectedContent = false;
}

if (!matchExpectedContent) {
try {
Position endPos = textDocument.positionAt(to);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@ void replaceLeftSpacesWith(int leftLimit, int to, String replacement, List<TextE
createTextEditIfNeeded(from, to, replacement, edits);
}

void replaceQuoteWithPreferred(int from, int to, String replacement, List<TextEdit> edits){
createTextEditIfNeeded(from, to, replacement, edits);
}

private int getLeftWhitespacesOffset(int leftLimit, int to) {
String text = textDocument.getText();
int from = leftLimit != -1 ? leftLimit : to - 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1619,7 +1619,7 @@ public static void assertFormat(XMLLanguageService languageService, String unfor
String formatted = applyEdits(document, edits);
assertEquals(expected, formatted);

if (expectedEdits != null) {
if (expectedEdits != null && expectedEdits.length != 0) {
Assertions.assertArrayEquals(expectedEdits, edits.toArray(new TextEdit[0]));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,13 @@ public void textEdit2() {
assertNotNull(edit);
assertEquals(te(0, 2, 0, 4, " "), edit);
}

@Test
public void textEditQuote() {
TextDocument document = new TextDocument("<a name=\'value \'> </a>", "test.xml");
TextEdit edit = TextEditUtils.createTextEditIfNeeded(8, 9, "\"", document);
assertNotNull(edit);
assertEquals(te(0, 8, 0, 9, "\""), edit);
}

}
Loading

0 comments on commit e8c960f

Please sign in to comment.