From a555ce130b3a5adc5e3116c1f12a38be0f6e4314 Mon Sep 17 00:00:00 2001 From: azerr Date: Thu, 20 May 2021 15:57:34 +0200 Subject: [PATCH] Provide basic experimental formatter which supports invalid XML Fixes #1195 Signed-off-by: azerr --- .../eclipse/lemminx/XMLLanguageServer.java | 6 +- .../lemminx/XMLTextDocumentService.java | 16 +- .../eclipse/lemminx/commons/TextDocument.java | 6 + .../java/org/eclipse/lemminx/dom/DOMAttr.java | 7 + .../org/eclipse/lemminx/dom/DOMElement.java | 15 +- .../java/org/eclipse/lemminx/dom/DOMText.java | 4 +- .../contentmodel/ContentModelPlugin.java | 10 +- .../model/CMElementDeclaration.java | 18 + .../ContentModelFormatterParticipant.java | 70 ++ .../contentmodel/CMDTDElementDeclaration.java | 10 + .../contentmodel/CMXSDElementDeclaration.java | 37 +- .../xsi/settings/XSISchemaLocationSplit.java | 6 +- .../lemminx/services/XMLFormatter.java | 785 +------------- .../lemminx/services/XMLLanguageService.java | 10 +- .../format/IFormatterParticipant.java | 42 + .../format/FormatElementCategory.java | 57 ++ .../services/format/TextEditUtils.java | 199 ++++ .../services/format/XMLFormatterDocument.java | 804 +++++++++++++++ .../format/XMLFormatterDocumentNew.java | 873 ++++++++++++++++ .../format/XMLFormattingConstraints.java | 63 ++ .../settings/LSPFormattingOptions.java | 105 ++ .../settings/XMLFormattingOptions.java | 325 +++--- .../lemminx/settings/XMLPreferences.java | 9 +- .../eclipse/lemminx/utils/JSONUtility.java | 9 +- .../java/org/eclipse/lemminx/XMLAssert.java | 27 +- .../IncrementalParsingTest.java | 198 ++-- .../xsi/XSIFormatterExperimentalTest.java | 185 ++++ .../services/format/TextEditUtilsTest.java | 53 + .../{ => format}/XMLFormatterTest.java | 32 +- .../XMLFormatterEmptyElementsTest.java | 231 +++++ .../XMLFormatterExperimentalIndentTest.java | 153 +++ .../XMLFormatterExperimentalTest.java | 959 ++++++++++++++++++ .../experimental/XMLFormatterForDTDTest.java | 676 ++++++++++++ .../XMLFormatterInsertFinalNewLineTest.java | 126 +++ .../XMLFormatterMaxLineWithTest.java | 135 +++ .../XMLFormatterMixedContentWithTest.java | 62 ++ ...matterPreserveAttributeLineBreaksTest.java | 316 ++++++ .../XMLFormatterPreserveEmptyContentTest.java | 297 ++++++ .../XMLFormatterPreserveNewLinesTest.java | 207 ++++ .../XMLFormatterPreserveSpacesTest.java | 126 +++ .../XMLFormatterQuoteStyleTest.java | 358 +++++++ ...matterSetSpaceBeforeEmptyCloseTagTest.java | 145 +++ .../XMLFormatterSplitAttributesTest.java | 237 +++++ .../XMLFormatterWithRangeTest.java | 359 +++++++ .../lemminx/settings/SettingsTest.java | 36 +- 45 files changed, 7308 insertions(+), 1096 deletions(-) create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelFormatterParticipant.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/FormatElementCategory.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/TextEditUtils.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocument.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocumentNew.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormattingConstraints.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/LSPFormattingOptions.java rename org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/{services => commons}/IncrementalParsingTest.java (59%) create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsi/XSIFormatterExperimentalTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/TextEditUtilsTest.java rename org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/{ => format}/XMLFormatterTest.java (95%) create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterEmptyElementsTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterExperimentalIndentTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterExperimentalTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterForDTDTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterInsertFinalNewLineTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterMaxLineWithTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterMixedContentWithTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveAttributeLineBreaksTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveEmptyContentTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveNewLinesTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveSpacesTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterQuoteStyleTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterSetSpaceBeforeEmptyCloseTagTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterSplitAttributesTest.java create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterWithRangeTest.java diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java index abcaebd351..93b3ea14f1 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLLanguageServer.java @@ -275,10 +275,6 @@ public XMLLanguageService getXMLLanguageService() { return xmlLanguageService; } - public SharedSettings getSettings() { - return xmlTextDocumentService.getSharedSettings(); - } - public ScheduledFuture schedule(Runnable command, int delay, TimeUnit unit) { return delayer.schedule(command, delay, unit); } @@ -292,7 +288,7 @@ public long getParentProcessId() { public CompletableFuture closeTag(TextDocumentPositionParams params) { return xmlTextDocumentService.computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> { return getXMLLanguageService().doAutoClose(xmlDocument, params.getPosition(), - getSettings().getCompletionSettings(), cancelChecker); + getSharedSettings().getCompletionSettings(), cancelChecker); }); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java index a29d38b761..203717134f 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java @@ -312,25 +312,17 @@ public CompletableFuture>> docume @Override public CompletableFuture> formatting(DocumentFormattingParams params) { - return computeAsync((cancelChecker) -> { - TextDocument document = getDocument(params.getTextDocument()); - if (document == null) { - return null; - } + return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> { CompositeSettings settings = new CompositeSettings(getSharedSettings(), params.getOptions()); - return getXMLLanguageService().format(document, null, settings); + return getXMLLanguageService().format(xmlDocument, null, settings); }); } @Override public CompletableFuture> rangeFormatting(DocumentRangeFormattingParams params) { - return computeAsync((cancelChecker) -> { - TextDocument document = getDocument(params.getTextDocument()); - if (document == null) { - return null; - } + return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> { CompositeSettings settings = new CompositeSettings(getSharedSettings(), params.getOptions()); - return getXMLLanguageService().format(document, params.getRange(), settings); + return getXMLLanguageService().format(xmlDocument, params.getRange(), settings); }); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TextDocument.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TextDocument.java index b80247bb46..ac997863e0 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TextDocument.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/TextDocument.java @@ -78,6 +78,12 @@ public String lineText(int lineNumber) throws BadLocationException { return text.substring(line.offset, line.offset + line.length); } + public int lineOffsetAt(int position) throws BadLocationException { + ILineTracker lineTracker = getLineTracker(); + Line line = lineTracker.getLineInformationOfOffset(position); + return line.offset; + } + public String lineDelimiter(int lineNumber) throws BadLocationException { ILineTracker lineTracker = getLineTracker(); String lineDelimiter = lineTracker.getLineDelimiter(lineNumber); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMAttr.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMAttr.java index 0c20b4c629..a9c69b5652 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMAttr.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMAttr.java @@ -403,11 +403,14 @@ public int getStart() { @Override public int getEnd() { if (nodeAttrValue != null) { + // return nodeAttrValue.getEnd(); } if (hasDelimiter()) { + // return delimiter + 1; } + // return nodeAttrName.getEnd(); } @@ -452,4 +455,8 @@ public boolean equals(Object obj) { return true; } + public int getDelimiterOffset() { + return delimiter; + } + } 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 c39228ba44..d2b7f9250c 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 @@ -412,9 +412,11 @@ public boolean isOrphanEndTagOf(String tagName) { } /** - * Returns the offset at which the given unclosed start tag should be closed with an angle bracket + * Returns the offset at which the given unclosed start tag should be closed + * with an angle bracket * - * @returns the offset at which the given unclosed start tag should be closed with an angle bracket + * @returns the offset at which the given unclosed start tag should be closed + * with an angle bracket */ public int getUnclosedStartTagCloseOffset() { String documentText = getOwnerDocument().getText(); @@ -554,7 +556,7 @@ public boolean isEmpty() { for (DOMNode child : getChildren()) { if (child.isText()) { DOMText text = (DOMText) child; - if (!text.isWhitespace()) { + if (!text.isElementContentWhitespace()) { return false; } } else { @@ -564,4 +566,11 @@ public boolean isEmpty() { return true; } + public int getOffsetAfterStartTag() { + if (hasTagName()) { + return getStartTagOpenOffset() + 1; + } + return getStartTagOpenOffset() + getTagName().length() + 1; + } + } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMText.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMText.java index e4f66a2351..ba408bc891 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMText.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMText.java @@ -12,6 +12,7 @@ */ package org.eclipse.lemminx.dom; +import org.eclipse.lemminx.utils.StringUtils; import org.w3c.dom.DOMException; /** @@ -61,7 +62,8 @@ public String getWholeText() { */ @Override public boolean isElementContentWhitespace() { - throw new UnsupportedOperationException(); + String text = getOwnerDocument().getOwnerDocument().getText(); + return StringUtils.isWhitespace(text, getStart(), getEnd()); } /* diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/ContentModelPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/ContentModelPlugin.java index 42a048b041..0fbd561a07 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/ContentModelPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/ContentModelPlugin.java @@ -25,6 +25,7 @@ import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelCodeLensParticipant; import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelCompletionParticipant; import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelDocumentLinkParticipant; +import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelFormatterParticipant; import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelHoverParticipant; import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelSymbolsProviderParticipant; import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelTypeDefinitionParticipant; @@ -85,6 +86,8 @@ public class ContentModelPlugin implements IXMLExtension { private DocumentTelemetryParticipant documentTelemetryParticipant; + private ContentModelFormatterParticipant formatterParticipant; + public ContentModelPlugin() { completionParticipant = new ContentModelCompletionParticipant(); hoverParticipant = new ContentModelHoverParticipant(); @@ -207,7 +210,9 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) { documentTelemetryParticipant = new DocumentTelemetryParticipant(registry.getTelemetryManager(), contentModelManager); registry.registerDocumentLifecycleParticipant(documentTelemetryParticipant); - + formatterParticipant = new ContentModelFormatterParticipant(contentModelManager); + registry.registerFormatterParticipant(formatterParticipant); + // Register custom commands to re-validate XML files IXMLCommandService commandService = registry.getCommandService(); if (commandService != null) { @@ -236,7 +241,8 @@ public void stop(XMLExtensionsRegistry registry) { registry.unregisterSymbolsProviderParticipant(symbolsProviderParticipant); registry.unregisterCodeLensParticipant(codeLensParticipant); registry.unregisterDocumentLifecycleParticipant(documentTelemetryParticipant); - + registry.unregisterFormatterParticipant(formatterParticipant); + // Un-register custom commands to re-validate XML files IXMLCommandService commandService = registry.getCommandService(); if (commandService != null) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/model/CMElementDeclaration.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/model/CMElementDeclaration.java index 91df3937ea..6878e1a91e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/model/CMElementDeclaration.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/model/CMElementDeclaration.java @@ -142,4 +142,22 @@ default String getName(String prefix) { */ String getDocumentURI(); + /** + * Returns true if the element is a string type (ex : xs:string) and false + * otherwise. + * + * @return true if the element is a string type (ex : xs:string) and false + * otherwise. + */ + boolean isStringType(); + + /** + * Returns true if the element can contains text and element both and false + * otherwise. + * + * @return true if the element can contains text and element both and false + * otherwise. + */ + boolean isMixedContent(); + } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelFormatterParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelFormatterParticipant.java new file mode 100644 index 0000000000..d9853e3f6c --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelFormatterParticipant.java @@ -0,0 +1,70 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.extensions.contentmodel.participants; + +import java.util.Collection; + +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.extensions.contentmodel.model.CMDocument; +import org.eclipse.lemminx.extensions.contentmodel.model.CMElementDeclaration; +import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; +import org.eclipse.lemminx.services.extensions.format.IFormatterParticipant; +import org.eclipse.lemminx.services.format.FormatElementCategory; +import org.eclipse.lemminx.services.format.XMLFormattingConstraints; +import org.eclipse.lemminx.settings.SharedSettings; + +/** + * Formatter participant which uses XSD/DTD grammar information to know the + * {@link FormatElementCategory} of a given element. + * + *

+ * + * This participant is enabled when 'xml.format.grammarAwareFormatting' setting + * is set to true. + * + *

+ * + * @author Angelo ZERR + * + */ +public class ContentModelFormatterParticipant implements IFormatterParticipant { + + private final ContentModelManager contentModelManager; + + public ContentModelFormatterParticipant(ContentModelManager contentModelManager) { + this.contentModelManager = contentModelManager; + } + + @Override + public FormatElementCategory getFormatElementCategory(DOMElement element, + XMLFormattingConstraints parentConstraints, SharedSettings sharedSettings) { + boolean enabled = sharedSettings.getFormattingSettings().isGrammarAwareFormatting(); + if (!enabled) { + return null; + } + + Collection cmDocuments = contentModelManager.findCMDocument(element); + for (CMDocument cmDocument : cmDocuments) { + CMElementDeclaration cmElement = cmDocument.findCMElement(element); + if (cmElement != null) { + if (cmElement.isStringType()) { + return FormatElementCategory.PreserveSpace; + } + if (cmElement.isMixedContent()) { + return FormatElementCategory.MixedContent; + } + } + } + return null; + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/contentmodel/CMDTDElementDeclaration.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/contentmodel/CMDTDElementDeclaration.java index efcd1fc4d6..15e2ce8ec7 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/contentmodel/CMDTDElementDeclaration.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/contentmodel/CMDTDElementDeclaration.java @@ -146,4 +146,14 @@ public int getIndex() { public String getDocumentURI() { return document.getURI(); } + + @Override + public boolean isStringType() { + return false; + } + + @Override + public boolean isMixedContent() { + return super.type == XMLElementDecl.TYPE_MIXED; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDElementDeclaration.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDElementDeclaration.java index 5230b82beb..7ac460011e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDElementDeclaration.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDElementDeclaration.java @@ -202,11 +202,11 @@ public Collection getPossibleElements(DOMElement parentEle /** * Returns the possible elements declaration if the given declaration is an * xs:any and null otherwise. - * + * * @param declaration the element, wildcard declaration. * @return the possible elements declaration if the given declaration is an * xs:any and null otherwise. - * + * */ private Collection getXSAnyElements(Object declaration) { short processContents = getXSAnyProcessContents(declaration); @@ -230,7 +230,7 @@ private Collection getXSAnyElements(Object declaration) { /** * Returns the value of the xs:any/@processContents if the given element is a * xs:any and {@link #PC_UNKWOWN} otherwise. - * + * * @param declaration the element, wildcard declaration. * @return the value of the xs:any/@processContents if the given element is a * xs:any and {@link #PC_UNKWOWN} otherwise. @@ -248,7 +248,7 @@ private static short getXSAnyProcessContents(Object declaration) { /** * Returns list of element (QName) of child elements of the given parent element * upon the given offset - * + * * @param parentElement the parent element * @param offset the offset where child element must be belong to * @return list of element (QName) of child elements of the given parent element @@ -355,7 +355,7 @@ public String getDocumentation(ISharedSettingsRequest request) { /** * Returns list of xs:annotation from the element declaration or type * declaration. - * + * * @return list of xs:annotation from the element declaration or type * declaration. */ @@ -490,4 +490,31 @@ public String getDocumentURI() { SchemaGrammar schemaGrammar = document.getOwnerSchemaGrammar(elementDeclaration); return CMXSDDocument.getSchemaURI(schemaGrammar); } + + @Override + public boolean isStringType() { + XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition(); + if (typeDefinition != null) { + XSSimpleTypeDefinition simpleDefinition = null; + if (typeDefinition.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) { + simpleDefinition = (XSSimpleTypeDefinition) typeDefinition; + } else if (typeDefinition.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) { + simpleDefinition = ((XSComplexTypeDefinition) typeDefinition).getSimpleType(); + } + if (simpleDefinition != null) { + return "string".equals(simpleDefinition.getName()); + } + } + return false; + } + + @Override + public boolean isMixedContent() { + XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition(); + if (typeDefinition != null && typeDefinition.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) { + XSComplexTypeDefinition complexTypeDefinition = (XSComplexTypeDefinition) typeDefinition; + return complexTypeDefinition.getContentType() == XSComplexTypeDefinition.CONTENTTYPE_MIXED; + } + return false; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/settings/XSISchemaLocationSplit.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/settings/XSISchemaLocationSplit.java index 5761d7ac83..8f5c019de3 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/settings/XSISchemaLocationSplit.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/settings/XSISchemaLocationSplit.java @@ -24,14 +24,12 @@ public enum XSISchemaLocationSplit { onElement, onPair, none; - private static final String XSI_SCHEMA_LOCATION_SPLIT = "xsiSchemaLocationSplit"; - public static XSISchemaLocationSplit getSplit(XMLFormattingOptions formattingSettings) { - return getSplit(formattingSettings.getString(XSI_SCHEMA_LOCATION_SPLIT)); + return getSplit(formattingSettings.getXsiSchemaLocationSplit()); } public static void setSplit(XSISchemaLocationSplit split, XMLFormattingOptions formattingSettings) { - formattingSettings.putString(XSI_SCHEMA_LOCATION_SPLIT, split.name()); + formattingSettings.setXsiSchemaLocationSplit(split.name()); } public static XSISchemaLocationSplit getSplit(String value) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLFormatter.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLFormatter.java index 6e0c55540b..963fca77ce 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLFormatter.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLFormatter.java @@ -12,7 +12,6 @@ */ package org.eclipse.lemminx.services; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.logging.Level; @@ -20,25 +19,12 @@ import org.eclipse.lemminx.commons.BadLocationException; import org.eclipse.lemminx.commons.TextDocument; -import org.eclipse.lemminx.dom.DOMAttr; -import org.eclipse.lemminx.dom.DOMCDATASection; -import org.eclipse.lemminx.dom.DOMComment; 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.dom.DOMParser; -import org.eclipse.lemminx.dom.DOMProcessingInstruction; -import org.eclipse.lemminx.dom.DOMText; -import org.eclipse.lemminx.dom.DTDAttlistDecl; -import org.eclipse.lemminx.dom.DTDDeclNode; -import org.eclipse.lemminx.dom.DTDDeclParameter; import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; import org.eclipse.lemminx.services.extensions.format.IFormatterParticipant; +import org.eclipse.lemminx.services.format.XMLFormatterDocument; +import org.eclipse.lemminx.services.format.XMLFormatterDocumentNew; import org.eclipse.lemminx.settings.SharedSettings; -import org.eclipse.lemminx.settings.XMLFormattingOptions.EmptyElements; -import org.eclipse.lemminx.utils.XMLBuilder; -import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextEdit; @@ -49,762 +35,6 @@ class XMLFormatter { private static final Logger LOGGER = Logger.getLogger(XMLFormatter.class.getName()); - private static class XMLFormatterDocument { - private final TextDocument textDocument; - private final Range range; - private final SharedSettings sharedSettings; - private final Collection formatterParticipants; - private final EmptyElements emptyElements; - - private int startOffset; - private int endOffset; - private DOMDocument fullDomDocument; - private DOMDocument rangeDomDocument; - private XMLBuilder xmlBuilder; - private int indentLevel; - private boolean linefeedOnNextWrite; - private boolean withinDTDContent; - - /** - * XML formatter document. - */ - public XMLFormatterDocument(TextDocument textDocument, Range range, SharedSettings sharedSettings, - Collection formatterParticipants) { - this.textDocument = textDocument; - this.range = range; - this.sharedSettings = sharedSettings; - this.formatterParticipants = formatterParticipants; - this.emptyElements = sharedSettings.getFormattingSettings().getEmptyElements(); - this.linefeedOnNextWrite = false; - } - - /** - * Returns a List containing a single TextEdit, containing the newly formatted - * changes of this.textDocument - * - * @return List containing a single TextEdit - * @throws BadLocationException - */ - public List format() throws BadLocationException { - this.fullDomDocument = DOMParser.getInstance().parse(textDocument.getText(), textDocument.getUri(), null, - false); - - if (isRangeFormatting()) { - setupRangeFormatting(range); - } else { - setupFullFormatting(range); - } - - this.indentLevel = getStartingIndentLevel(); - format(this.rangeDomDocument); - - List textEdits = getFormatTextEdit(); - return textEdits; - } - - private boolean isRangeFormatting() { - return this.range != null; - } - - private void setupRangeFormatting(Range range) throws BadLocationException { - int startOffset = this.textDocument.offsetAt(range.getStart()); - int endOffset = this.textDocument.offsetAt(range.getEnd()); - - Position startPosition = this.textDocument.positionAt(startOffset); - Position endPosition = this.textDocument.positionAt(endOffset); - enlargePositionToGutters(startPosition, endPosition); - - this.startOffset = this.textDocument.offsetAt(startPosition); - this.endOffset = this.textDocument.offsetAt(endPosition); - - String fullText = this.textDocument.getText(); - String rangeText = fullText.substring(this.startOffset, this.endOffset); - - withinDTDContent = this.fullDomDocument.isWithinInternalDTD(startOffset); - String uri = this.textDocument.getUri(); - if (withinDTDContent) { - uri += ".dtd"; - } - this.rangeDomDocument = DOMParser.getInstance().parse(rangeText, uri, null, false); - - if (containsTextWithinStartTag()) { - adjustOffsetToStartTag(); - rangeText = fullText.substring(this.startOffset, this.endOffset); - this.rangeDomDocument = DOMParser.getInstance().parse(rangeText, uri, null, false); - } - - this.xmlBuilder = new XMLBuilder(this.sharedSettings, "", - textDocument.lineDelimiter(startPosition.getLine()), formatterParticipants); - } - - private boolean containsTextWithinStartTag() { - - if (this.rangeDomDocument.getChildren().size() < 1) { - return false; - } - - DOMNode firstChild = this.rangeDomDocument.getChild(0); - if (!firstChild.isText()) { - return false; - } - - int tagContentOffset = firstChild.getStart(); - int fullDocOffset = getFullOffsetFromRangeOffset(tagContentOffset); - DOMNode fullNode = this.fullDomDocument.findNodeAt(fullDocOffset); - - if (!fullNode.isElement()) { - return false; - } - return ((DOMElement) fullNode).isInStartTag(fullDocOffset); - } - - private void adjustOffsetToStartTag() throws BadLocationException { - int tagContentOffset = this.rangeDomDocument.getChild(0).getStart(); - int fullDocOffset = getFullOffsetFromRangeOffset(tagContentOffset); - DOMNode fullNode = this.fullDomDocument.findNodeAt(fullDocOffset); - Position nodePosition = this.textDocument.positionAt(fullNode.getStart()); - nodePosition.setCharacter(0); - this.startOffset = this.textDocument.offsetAt(nodePosition); - } - - private void setupFullFormatting(Range range) throws BadLocationException { - this.startOffset = 0; - this.endOffset = textDocument.getText().length(); - this.rangeDomDocument = this.fullDomDocument; - - Position startPosition = textDocument.positionAt(startOffset); - this.xmlBuilder = new XMLBuilder(this.sharedSettings, "", - textDocument.lineDelimiter(startPosition.getLine()), formatterParticipants); - } - - private void enlargePositionToGutters(Position start, Position end) throws BadLocationException { - start.setCharacter(0); - - if (end.getCharacter() == 0 && end.getLine() > 0) { - end.setLine(end.getLine() - 1); - } - - end.setCharacter(this.textDocument.lineText(end.getLine()).length()); - } - - private int getStartingIndentLevel() throws BadLocationException { - if (withinDTDContent) { - return 1; - } - DOMNode startNode = this.fullDomDocument.findNodeAt(this.startOffset); - if (startNode.isOwnerDocument()) { - return 0; - } - - DOMNode startNodeParent = startNode.getParentNode(); - - if (startNodeParent.isOwnerDocument()) { - return 0; - } - - // the starting indent level is the parent's indent level + 1 - int startNodeIndentLevel = getNodeIndentLevel(startNodeParent) + 1; - return startNodeIndentLevel; - } - - private int getNodeIndentLevel(DOMNode node) throws BadLocationException { - - Position nodePosition = this.textDocument.positionAt(node.getStart()); - String textBeforeNode = this.textDocument.lineText(nodePosition.getLine()).substring(0, - nodePosition.getCharacter() + 1); - - int spaceOrTab = getSpaceOrTabStartOfString(textBeforeNode); - - if (this.sharedSettings.getFormattingSettings().isInsertSpaces()) { - return (spaceOrTab / this.sharedSettings.getFormattingSettings().getTabSize()); - } - return spaceOrTab; - } - - private int getSpaceOrTabStartOfString(String string) { - int i = 0; - int spaceOrTab = 0; - while (i < string.length() && (string.charAt(i) == ' ' || string.charAt(i) == '\t')) { - spaceOrTab++; - i++; - } - return spaceOrTab; - } - - private DOMElement getFullDocElemFromRangeElem(DOMElement elemFromRangeDoc) { - int fullOffset = -1; - - if (elemFromRangeDoc.hasStartTag()) { - fullOffset = getFullOffsetFromRangeOffset(elemFromRangeDoc.getStartTagOpenOffset()) + 1; - // +1 because offset must be here: <|root - // for DOMNode.findNodeAt() to find the correct element - } else if (elemFromRangeDoc.hasEndTag()) { - fullOffset = getFullOffsetFromRangeOffset(elemFromRangeDoc.getEndTagOpenOffset()) + 1; - // +1 because offset must be here: <|/root - // for DOMNode.findNodeAt() to find the correct element - } else { - return null; - } - - DOMElement elemFromFullDoc = (DOMElement) this.fullDomDocument.findNodeAt(fullOffset); - return elemFromFullDoc; - } - - private int getFullOffsetFromRangeOffset(int rangeOffset) { - return rangeOffset + this.startOffset; - } - - private boolean startTagExistsInRangeDocument(DOMNode node) { - if (!node.isElement()) { - return false; - } - - return ((DOMElement) node).hasStartTag(); - } - - private boolean startTagExistsInFullDocument(DOMNode node) { - if (!node.isElement()) { - return false; - } - - DOMElement elemFromFullDoc = getFullDocElemFromRangeElem((DOMElement) node); - - if (elemFromFullDoc == null) { - return false; - } - - return elemFromFullDoc.hasStartTag(); - } - - private void format(DOMNode node) throws BadLocationException { - - if (linefeedOnNextWrite && (!node.isText() || !((DOMText) node).isWhitespace())) { - this.xmlBuilder.linefeed(); - linefeedOnNextWrite = false; - } - - if (node.getNodeType() != DOMNode.DOCUMENT_NODE) { - boolean doLineFeed = !node.getOwnerDocument().isDTD() - && !(node.isComment() && ((DOMComment) node).isCommentSameLineEndTag()) - && (!node.isText() || (!((DOMText) node).isWhitespace() && ((DOMText) node).hasSiblings())); - - if (this.indentLevel > 0 && doLineFeed) { - // add new line + indent - if (!node.isChildOfOwnerDocument() || node.getPreviousNonTextSibling() != null) { - this.xmlBuilder.linefeed(); - } - - if (!startTagExistsInRangeDocument(node) && startTagExistsInFullDocument(node)) { - DOMNode startNode = getFullDocElemFromRangeElem((DOMElement) node); - int currentIndentLevel = getNodeIndentLevel(startNode); - this.xmlBuilder.indent(currentIndentLevel); - this.indentLevel = currentIndentLevel; - } else { - this.xmlBuilder.indent(this.indentLevel); - } - } - if (node.isElement()) { - // Format Element - formatElement((DOMElement) node); - } else if (node.isCDATA()) { - // Format CDATA - formatCDATA((DOMCDATASection) node); - } else if (node.isComment()) { - // Format comment - formatComment((DOMComment) node); - } else if (node.isProcessingInstruction()) { - // Format processing instruction - formatProcessingInstruction(node); - } else if (node.isProlog()) { - // Format prolog - formatProlog(node); - } else if (node.isText()) { - // Format Text - formatText((DOMText) node); - } else if (node.isDoctype()) { - // Format document type - formatDocumentType((DOMDocumentType) node); - } - } else if (node.hasChildNodes()) { - // Other nodes kind like root - for (DOMNode child : node.getChildren()) { - format(child); - } - } - } - - /** - * Format the given DOM prolog - * - * @param node the DOM prolog to format. - */ - private void formatProlog(DOMNode node) { - addPrologToXMLBuilder(node, this.xmlBuilder); - linefeedOnNextWrite = true; - } - - /** - * Format the given DOM text node. - * - * @param textNode the DOM text node to format. - */ - private void formatText(DOMText textNode) { - String content = textNode.getData(); - if (textNode.equals(this.fullDomDocument.getLastChild())) { - xmlBuilder.addContent(content); - } else { - xmlBuilder.addContent(content, textNode.isWhitespace(), textNode.hasSiblings(), - textNode.getDelimiter()); - } - } - - /** - * Format the given DOM document type. - * - * @param documentType the DOM document type to format. - */ - private void formatDocumentType(DOMDocumentType documentType) { - boolean isDTD = documentType.getOwnerDocument().isDTD(); - if (!isDTD) { - this.xmlBuilder.startDoctype(); - List params = documentType.getParameters(); - - for (DTDDeclParameter param : params) { - if (!documentType.isInternalSubset(param)) { - xmlBuilder.addParameter(param.getParameter()); - } else { - xmlBuilder.startDoctypeInternalSubset(); - xmlBuilder.linefeed(); - // level + 1 since the 'level' value is the doctype tag's level - formatDTD(documentType, this.indentLevel + 1, this.endOffset, this.xmlBuilder); - xmlBuilder.linefeed(); - xmlBuilder.endDoctypeInternalSubset(); - } - } - if (documentType.isClosed()) { - xmlBuilder.endDoctype(); - } - linefeedOnNextWrite = true; - - } else { - formatDTD(documentType, this.indentLevel, this.endOffset, this.xmlBuilder); - } - } - - /** - * Format the given DOM ProcessingIntsruction. - * - * @param element the DOM ProcessingIntsruction to format. - * - */ - private void formatProcessingInstruction(DOMNode node) { - addPIToXMLBuilder(node, this.xmlBuilder); - if (this.indentLevel == 0) { - this.xmlBuilder.linefeed(); - } - } - - /** - * Format the given DOM Comment - * - * @param element the DOM Comment to format. - * - */ - private void formatComment(DOMComment comment) { - this.xmlBuilder.startComment(comment); - this.xmlBuilder.addContentComment(comment.getData()); - if (comment.isClosed()) { - // Generate --> only if comment is closed. - this.xmlBuilder.endComment(); - } - if (this.indentLevel == 0) { - linefeedOnNextWrite = true; - } - } - - /** - * Format the given DOM CDATA - * - * @param element the DOM CDATA to format. - * - */ - private void formatCDATA(DOMCDATASection cdata) { - this.xmlBuilder.startCDATA(); - this.xmlBuilder.addContentCDATA(cdata.getData()); - if (cdata.isClosed()) { - // Generate ]> only if CDATA is closed. - this.xmlBuilder.endCDATA(); - } - } - - /** - * Format the given DOM element - * - * @param element the DOM element to format. - * - * @throws BadLocationException - */ - private void formatElement(DOMElement element) throws BadLocationException { - String tag = element.getTagName(); - if (element.hasEndTag() && !element.hasStartTag()) { - // bad element without start tag (ex: <\root>) - xmlBuilder.endElement(tag, element.isEndTagClosed()); - } else { - // generate start element - xmlBuilder.startElement(tag, false); - if (element.hasAttributes()) { - formatAttributes(element); - } - - EmptyElements emptyElements = getEmptyElements(element); - switch (emptyElements) { - case expand: - // expand empty element: -> - xmlBuilder.closeStartElement(); - // end tag element is done, only if the element is closed - // the format, doesn't fix the close tag - this.xmlBuilder.endElement(tag, true); - break; - case collapse: - // collapse empty element: -> - formatElementStartTagSelfCloseBracket(element); - break; - default: - if (element.isStartTagClosed()) { - formatElementStartTagCloseBracket(element); - } - boolean hasElements = false; - if (element.hasChildNodes()) { - // element has body - - this.indentLevel++; - for (DOMNode child : element.getChildren()) { - hasElements = hasElements || !child.isText(); - format(child); - } - this.indentLevel--; - } - if (element.hasEndTag()) { - if (hasElements) { - this.xmlBuilder.linefeed(); - this.xmlBuilder.indent(this.indentLevel); - } - // end tag element is done, only if the element is closed - // the format, doesn't fix the close tag - if (element.hasEndTag() && element.getEndTagOpenOffset() <= this.endOffset) { - this.xmlBuilder.endElement(tag, element.isEndTagClosed()); - } else { - formatElementStartTagSelfCloseBracket(element); - } - } else if (element.isSelfClosed()) { - formatElementStartTagSelfCloseBracket(element); - } - } - } - } - - /** - * Formats the start tag's closing bracket (>) according to - * {@code XMLFormattingOptions#isPreserveAttrLineBreaks()} - * - * {@code XMLFormattingOptions#isPreserveAttrLineBreaks()}: If true, must add a - * newline + indent before the closing bracket if the last attribute of the - * element and the closing bracket are in different lines. - * - * @param element - * @throws BadLocationException - */ - private void formatElementStartTagCloseBracket(DOMElement element) throws BadLocationException { - if (this.sharedSettings.getFormattingSettings().isPreserveAttrLineBreaks() && element.hasAttributes() - && !isSameLine(getLastAttribute(element).getEnd(), element.getStartTagCloseOffset())) { - xmlBuilder.linefeed(); - this.xmlBuilder.indent(this.indentLevel); - } - xmlBuilder.closeStartElement(); - } - - /** - * Formats the self-closing tag (/>) according to - * {@code XMLFormattingOptions#isPreserveAttrLineBreaks()} - * - * {@code XMLFormattingOptions#isPreserveAttrLineBreaks()}: If true, must add a - * newline + indent before the self-closing tag if the last attribute of the - * element and the closing bracket are in different lines. - * - * @param element - * @throws BadLocationException - */ - private void formatElementStartTagSelfCloseBracket(DOMElement element) throws BadLocationException { - if (this.sharedSettings.getFormattingSettings().isPreserveAttrLineBreaks() && element.hasAttributes()) { - int elementEndOffset = element.getEnd(); - if (element.isStartTagClosed()) { - elementEndOffset = element.getStartTagCloseOffset(); - } - if (!isSameLine(getLastAttribute(element).getEnd(), elementEndOffset)) { - this.xmlBuilder.linefeed(); - this.xmlBuilder.indent(this.indentLevel); - } - } - - this.xmlBuilder.selfCloseElement(); - } - - private void formatAttributes(DOMElement element) throws BadLocationException { - List attributes = element.getAttributeNodes(); - boolean isSingleAttribute = hasSingleAttributeInFullDoc(element); - int prevOffset = element.getStart(); - for (DOMAttr attr : attributes) { - formatAttribute(attr, isSingleAttribute, prevOffset); - prevOffset = attr.getEnd(); - } - if ((this.sharedSettings.getFormattingSettings().getClosingBracketNewLine() && this.sharedSettings.getFormattingSettings().isSplitAttributes()) && !isSingleAttribute) { - xmlBuilder.linefeed(); - // Indent by tag + splitAttributesIndentSize to match with attribute indent level - int totalIndent = this.indentLevel + this.sharedSettings.getFormattingSettings().getSplitAttributesIndentSize(); - xmlBuilder.indent(totalIndent); - } - } - - private void formatAttribute(DOMAttr attr, boolean isSingleAttribute, int prevOffset) - throws BadLocationException { - if (this.sharedSettings.getFormattingSettings().isPreserveAttrLineBreaks() - && !isSameLine(prevOffset, attr.getStart())) { - xmlBuilder.linefeed(); - xmlBuilder.indent(this.indentLevel + 1); - xmlBuilder.addSingleAttribute(attr, false, false); - } else if (isSingleAttribute) { - xmlBuilder.addSingleAttribute(attr); - } else { - xmlBuilder.addAttribute(attr, this.indentLevel); - } - } - - /** - * Returns true if first offset and second offset belong in the same line of the - * document - * - * If current formatting is range formatting, the provided offsets must be - * ranged offsets (offsets relative to the formatting range) - * - * @param first the first offset - * @param second the second offset - * @return true if first offset and second offset belong in the same line of the - * document - * @throws BadLocationException - */ - private boolean isSameLine(int first, int second) throws BadLocationException { - if (isRangeFormatting()) { - // adjust range offsets so that they are relative to the full document - first = getFullOffsetFromRangeOffset(first); - second = getFullOffsetFromRangeOffset(second); - } - return getLineNumber(first) == getLineNumber(second); - } - - private int getLineNumber(int offset) throws BadLocationException { - return this.textDocument.positionAt(offset).getLine(); - } - - private DOMAttr getLastAttribute(DOMElement element) { - if (!element.hasAttributes()) { - return null; - } - List attributes = element.getAttributeNodes(); - return attributes.get(attributes.size() - 1); - } - - /** - * Returns true if the provided element has one attribute in the fullDomDocument - * (not the rangeDomDocument) - * - * @param element - * @return true if the provided element has one attribute in the fullDomDocument - * (not the rangeDomDocument) - */ - private boolean hasSingleAttributeInFullDoc(DOMElement element) { - DOMElement fullElement = getFullDocElemFromRangeElem(element); - return fullElement.getAttributeNodes().size() == 1; - } - - /** - * Return the option to use to generate empty elements. - * - * @param element the DOM element - * @return the option to use to generate empty elements. - */ - private EmptyElements getEmptyElements(DOMElement element) { - if (this.emptyElements != EmptyElements.ignore) { - if (element.isClosed() && element.isEmpty()) { - // Element is empty and closed - switch (this.emptyElements) { - case expand: - case collapse: { - if (this.sharedSettings.getFormattingSettings().isPreserveEmptyContent()) { - // preserve content - if (element.hasChildNodes()) { - // The element is empty and contains somes spaces which must be preserved - return EmptyElements.ignore; - } - } - return this.emptyElements; - } - default: - return this.emptyElements; - } - } - } - return EmptyElements.ignore; - } - - private static boolean formatDTD(DOMDocumentType doctype, int level, int end, XMLBuilder xmlBuilder) { - DOMNode previous = null; - for (DOMNode node : doctype.getChildren()) { - if (previous != null) { - xmlBuilder.linefeed(); - } - - xmlBuilder.indent(level); - - if (node.isText()) { - xmlBuilder.addContent(((DOMText) node).getData().trim()); - } else if (node.isComment()) { - DOMComment comment = (DOMComment) node; - xmlBuilder.startComment(comment); - xmlBuilder.addContentComment(comment.getData()); - xmlBuilder.endComment(); - } else if (node.isProcessingInstruction()) { - addPIToXMLBuilder(node, xmlBuilder); - } else if (node.isProlog()) { - addPrologToXMLBuilder(node, xmlBuilder); - } else { - boolean setEndBracketOnNewLine = false; - DTDDeclNode decl = (DTDDeclNode) node; - xmlBuilder.addDeclTagStart(decl); - - if (decl.isDTDAttListDecl()) { - DTDAttlistDecl attlist = (DTDAttlistDecl) decl; - List internalDecls = attlist.getInternalChildren(); - - if (internalDecls == null) { - for (DTDDeclParameter param : decl.getParameters()) { - xmlBuilder.addParameter(param.getParameter()); - } - } else { - boolean multipleInternalAttlistDecls = false; - List params = attlist.getParameters(); - DTDDeclParameter param; - for (int i = 0; i < params.size(); i++) { - param = params.get(i); - if (attlist.getNameParameter().equals(param)) { - xmlBuilder.addParameter(param.getParameter()); - if (attlist.getParameters().size() > 1) { // has parameters after elementName - xmlBuilder.linefeed(); - xmlBuilder.indent(level + 1); - setEndBracketOnNewLine = true; - multipleInternalAttlistDecls = true; - } - } else { - if (multipleInternalAttlistDecls && i == 1) { - xmlBuilder.addUnindentedParameter(param.getParameter()); - } else { - xmlBuilder.addParameter(param.getParameter()); - } - } - } - - for (DTDAttlistDecl attlistDecl : internalDecls) { - xmlBuilder.linefeed(); - xmlBuilder.indent(level + 1); - params = attlistDecl.getParameters(); - for (int i = 0; i < params.size(); i++) { - param = params.get(i); - - if (i == 0) { - xmlBuilder.addUnindentedParameter(param.getParameter()); - } else { - xmlBuilder.addParameter(param.getParameter()); - } - } - } - } - } else { - for (DTDDeclParameter param : decl.getParameters()) { - xmlBuilder.addParameter(param.getParameter()); - } - } - if (setEndBracketOnNewLine) { - xmlBuilder.linefeed(); - xmlBuilder.indent(level); - } - if (decl.isClosed()) { - xmlBuilder.closeStartElement(); - } - } - previous = node; - } - return true; - } - - private List getFormatTextEdit() throws BadLocationException { - Position startPosition = this.textDocument.positionAt(this.startOffset); - Position endPosition = this.textDocument.positionAt(this.endOffset); - Range r = new Range(startPosition, endPosition); - List edits = new ArrayList<>(); - - // check if format range reaches the end of the document - if (this.endOffset == this.textDocument.getText().length()) { - - if (this.sharedSettings.getFormattingSettings().isTrimFinalNewlines()) { - this.xmlBuilder.trimFinalNewlines(); - } - - if (this.sharedSettings.getFormattingSettings().isInsertFinalNewline() - && !this.xmlBuilder.isLastLineEmptyOrWhitespace()) { - this.xmlBuilder.linefeed(); - } - } - - edits.add(new TextEdit(r, this.xmlBuilder.toString())); - return edits; - } - - private static void addPIToXMLBuilder(DOMNode node, XMLBuilder xml) { - DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) node; - xml.startPrologOrPI(processingInstruction.getTarget()); - - String content = processingInstruction.getData(); - if (content.length() > 0) { - xml.addContentPI(content); - } else { - xml.addContent(" "); - } - - xml.endPrologOrPI(); - } - - private static void addPrologToXMLBuilder(DOMNode node, XMLBuilder xml) { - DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) node; - xml.startPrologOrPI(processingInstruction.getTarget()); - if (node.hasAttributes()) { - addPrologAttributes(node, xml); - } - xml.endPrologOrPI(); - } - - /** - * Will add all attributes, to the given builder, on a single line - */ - private static void addPrologAttributes(DOMNode node, XMLBuilder xmlBuilder) { - List attrs = node.getAttributeNodes(); - if (attrs == null) { - return; - } - for (DOMAttr attr : attrs) { - xmlBuilder.addPrologAttribute(attr); - } - } - } - private final XMLExtensionsRegistry extensionsRegistry; public XMLFormatter(XMLExtensionsRegistry extensionsRegistry) { @@ -820,10 +50,15 @@ public XMLFormatter(XMLExtensionsRegistry extensionsRegistry) { * @param sharedSettings settings containing formatting preferences * @return List containing a TextEdit with formatting changes */ - public List format(TextDocument textDocument, Range range, SharedSettings sharedSettings) { + public List format(DOMDocument xmlDocument, Range range, SharedSettings sharedSettings) { try { - XMLFormatterDocument formatterDocument = new XMLFormatterDocument(textDocument, range, sharedSettings, - getFormatterParticipants()); + if (sharedSettings.getFormattingSettings().isExperimental()) { + XMLFormatterDocumentNew formatterDocument = new XMLFormatterDocumentNew(xmlDocument, range, + sharedSettings, getFormatterParticipants()); + return formatterDocument.format(); + } + XMLFormatterDocument formatterDocument = new XMLFormatterDocument(xmlDocument.getTextDocument(), range, + sharedSettings, getFormatterParticipants()); return formatterDocument.format(); } catch (BadLocationException e) { LOGGER.log(Level.SEVERE, "Formatting failed due to BadLocation", e); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java index 1654ad5836..6f407cb60d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java @@ -22,6 +22,7 @@ import org.eclipse.lemminx.commons.TextDocument; import org.eclipse.lemminx.customservice.AutoCloseTagResponse; import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMParser; import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings; import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; import org.eclipse.lemminx.services.extensions.diagnostics.DiagnosticsResult; @@ -104,14 +105,15 @@ public XMLLanguageService() { this.linkedEditing = new XMLLinkedEditing(); } - @Override + @Override public String formatFull(String text, String uri, SharedSettings sharedSettings, CancelChecker cancelChecker) { - List edits = this.format(new TextDocument(text, uri), null, sharedSettings); + DOMDocument xmlDocument = DOMParser.getInstance().parse(new TextDocument(text, uri), null); + List edits = this.format(xmlDocument, null, sharedSettings); return edits.isEmpty() ? null : edits.get(0).getNewText(); } - public List format(TextDocument document, Range range, SharedSettings sharedSettings) { - return formatter.format(document, range, sharedSettings); + public List format(DOMDocument xmlDocument, Range range, SharedSettings sharedSettings) { + return formatter.format(xmlDocument, range, sharedSettings); } public List findDocumentHighlights(DOMDocument xmlDocument, Position position) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/format/IFormatterParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/format/IFormatterParticipant.java index 82c6c90e31..19566327fe 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/format/IFormatterParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/format/IFormatterParticipant.java @@ -12,8 +12,15 @@ *******************************************************************************/ package org.eclipse.lemminx.services.extensions.format; +import java.util.List; + import org.eclipse.lemminx.dom.DOMAttr; +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.services.format.FormatElementCategory; +import org.eclipse.lemminx.services.format.XMLFormattingConstraints; +import org.eclipse.lemminx.settings.SharedSettings; import org.eclipse.lemminx.utils.XMLBuilder; +import org.eclipse.lsp4j.TextEdit; /** * XML formatter participant. @@ -41,4 +48,39 @@ default boolean formatAttributeValue(String name, String valueWithoutQuote, Char XMLBuilder xml) { return false; } + + /** + * Format the given attribute value. + * + *

+ * The formatter must take care of to generate attribute value with quote. + *

+ * + * @param name the attribute name. + * @param valueWithoutQuote the attribute value without quote. + * @param quote the quote and null otherwise. + * @param attr the DOM attribute and null otherwise. + * @param xml the XML builder. + * @return true if the given attribute can be formatted and false otherwise. + */ + default void formatAttributeValue(String name, String valueWithoutQuote, Character quote, DOMAttr attr, + List edits) { + + } + + /** + * Returns the format element category for the given DOM element and null + * otherwise. + * + * @param element the DOM element. + * @param parentConstraints the parent constraints. + * @param sharedSettings the shared settings. + * + * @return the format element category for the given DOM element and null + * otherwise. + */ + default FormatElementCategory getFormatElementCategory(DOMElement element, + XMLFormattingConstraints parentConstraints, SharedSettings sharedSettings) { + return null; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/FormatElementCategory.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/FormatElementCategory.java new file mode 100644 index 0000000000..b487fa0c23 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/FormatElementCategory.java @@ -0,0 +1,57 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format; + +/** + * Format element catagory. + * + * @author Angelo ZERR + * + * @see https://www.oxygenxml.com/doc/versions/24.0/ug-editorEclipse/topics/format-and-indent-xml.html + */ +public enum FormatElementCategory { + + /** + * In the ignore space category, all whitespace is considered insignificant. + * This generally applies to content that consists only of elements nested + * inside other elements, with no text content. + */ + IgnoreSpace, + + /** + * In the normalize space category, a single whitespace character between + * character strings is considered significant and all other spaces are + * considered insignificant. Therefore, all consecutive whitespaces will be + * replaced with a single space. This generally applies to elements that contain + * text content only. + */ + NormalizeSpace, + + /** + * In the mixed content category, a single whitespace between text characters is + * considered significant and all other spaces are considered insignificant. + */ + MixedContent, + + /** + * In the preserve space category, all whitespace in the element is regarded as + * significant. No changes are made to the spaces in elements in this category. + * However, child elements may be in another category, and may be treated + * differently. + * + * Attribute values are always in the preserve space category. The spaces + * between attributes in an element tag are always in the default space + * category. + * + */ + PreserveSpace +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/TextEditUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/TextEditUtils.java new file mode 100644 index 0000000000..96542e6b1a --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/TextEditUtils.java @@ -0,0 +1,199 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.BiFunction; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.commons.TextDocument; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextEdit; + +/** + * Utilities for {@link TextEdit}. + * + * @author Angelo ZERR + * + */ +public class TextEditUtils { + + private static final Logger LOGGER = Logger.getLogger(TextEditUtils.class.getName()); + + /** + * Returns the {@link TextEdit} to insert the given expected content from the + * given range (from, to) of the given text document and null otherwise. + * + * @param from the range from. + * @param to the range to. + * @param expectedContent the expected content. + * @param textDocument the text document. + * + * @return the {@link TextEdit} to insert the given expected content from the + * given range (from, to) of the given text document and null otherwise. + */ + public static TextEdit createTextEditIfNeeded(int from, int to, String expectedContent, TextDocument textDocument) { + String text = textDocument.getText(); + + // Check if content from the range [from, to] is the same than expected content + if (isMatchExpectedContent(from, to, expectedContent, text)) { + // The expected content exists, no need to create a TextEdit + return null; + } + + if (from == to) { + // Insert the expected content. + try { + Position endPos = textDocument.positionAt(to); + Position startPos = endPos; + Range range = new Range(startPos, endPos); + return new TextEdit(range, expectedContent); + } catch (BadLocationException e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + } + + int i = expectedContent.length() - 1; + boolean matchExpectedContent = true; + while (from > 0) { + char c = text.charAt(from); + if (Character.isWhitespace(c)) { + if (matchExpectedContent) { + if (i < 0) { + matchExpectedContent = false; + } else { + if (expectedContent.charAt(i) != c) { + matchExpectedContent = false; + } + i--; + } + } + } else { + break; + } + from--; + } + from++; + if (matchExpectedContent) { + matchExpectedContent = to - from == expectedContent.length(); + } + + if (!matchExpectedContent) { + try { + Position endPos = textDocument.positionAt(to); + Position startPos = to == from ? endPos : textDocument.positionAt(from); + Range range = new Range(startPos, endPos); + return new TextEdit(range, expectedContent); + } catch (BadLocationException e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + } + return null; + } + + /** + * Returns true if the given content from the range [from, to] of the given text + * is the same than expected content and false otherwise. + * + * @param from the from range. + * @param to the to range. + * @param expectedContent the expected content. + * @param text the text document. + * + * @return true if the given content from the range [from, to] of the given text + * is the same than expected content and false otherwise. + */ + private static boolean isMatchExpectedContent(int from, int to, String expectedContent, String text) { + if (expectedContent.length() == to - from) { + int j = 0; + for (int i = from; i < to; i++) { + char c = text.charAt(i); + if (expectedContent.charAt(j) != c) { + return false; + } + j++; + } + } else { + return false; + } + return true; + } + + public static String applyEdits(TextDocument document, List edits) throws BadLocationException { + String text = document.getText(); + List sortedEdits = mergeSort(edits /* .map(getWellformedEdit) */, (a, b) -> { + int diff = a.getRange().getStart().getLine() - b.getRange().getStart().getLine(); + if (diff == 0) { + return a.getRange().getStart().getCharacter() - b.getRange().getStart().getCharacter(); + } + return diff; + }); + int lastModifiedOffset = 0; + List spans = new ArrayList<>(); + for (TextEdit e : sortedEdits) { + int startOffset = document.offsetAt(e.getRange().getStart()); + if (startOffset < lastModifiedOffset) { + throw new Error("Overlapping edit"); + } else if (startOffset > lastModifiedOffset) { + spans.add(text.substring(lastModifiedOffset, startOffset)); + } + if (e.getNewText() != null) { + spans.add(e.getNewText()); + } + lastModifiedOffset = document.offsetAt(e.getRange().getEnd()); + } + spans.add(text.substring(lastModifiedOffset)); + return spans.stream() // + .collect(Collectors.joining()); + } + + private static List mergeSort(List data, Comparator comparator) { + if (data.size() <= 1) { + // sorted + return data; + } + int p = (data.size() / 2) | 0; + List left = data.subList(0, p); + List right = data.subList(p, data.size()); + + mergeSort(left, comparator); + mergeSort(right, comparator); + + int leftIdx = 0; + int rightIdx = 0; + int i = 0; + while (leftIdx < left.size() && rightIdx < right.size()) { + int ret = comparator.compare(left.get(leftIdx), right.get(rightIdx)); + if (ret <= 0) { + // smaller_equal -> take left to preserve order + data.set(i++, left.get(leftIdx++)); + } else { + // greater -> take right + data.set(i++, right.get(rightIdx++)); + } + } + while (leftIdx < left.size()) { + data.set(i++, left.get(leftIdx++)); + } + while (rightIdx < right.size()) { + data.set(i++, right.get(rightIdx++)); + } + return data; + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocument.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocument.java new file mode 100644 index 0000000000..320068f45a --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocument.java @@ -0,0 +1,804 @@ +/** + * Copyright (c) 2022 Red Hat, Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + */ +package org.eclipse.lemminx.services.format; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.commons.TextDocument; +import org.eclipse.lemminx.dom.DOMAttr; +import org.eclipse.lemminx.dom.DOMCDATASection; +import org.eclipse.lemminx.dom.DOMComment; +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.dom.DOMParser; +import org.eclipse.lemminx.dom.DOMProcessingInstruction; +import org.eclipse.lemminx.dom.DOMText; +import org.eclipse.lemminx.dom.DTDAttlistDecl; +import org.eclipse.lemminx.dom.DTDDeclNode; +import org.eclipse.lemminx.dom.DTDDeclParameter; +import org.eclipse.lemminx.services.extensions.format.IFormatterParticipant; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lemminx.settings.XMLFormattingOptions.EmptyElements; +import org.eclipse.lemminx.utils.XMLBuilder; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextEdit; + +/** + * Default XML formatter which generates one text edit by rewriting the DOM node + * which must be formatted. + * + * @author Angelo ZERR + * + */ +public class XMLFormatterDocument { + private final TextDocument textDocument; + private final Range range; + private final SharedSettings sharedSettings; + private final Collection formatterParticipants; + private final EmptyElements emptyElements; + + private int startOffset; + private int endOffset; + private DOMDocument fullDomDocument; + private DOMDocument rangeDomDocument; + private XMLBuilder xmlBuilder; + private int indentLevel; + private boolean linefeedOnNextWrite; + private boolean withinDTDContent; + + /** + * XML formatter document. + */ + public XMLFormatterDocument(TextDocument textDocument, Range range, SharedSettings sharedSettings, + Collection formatterParticipants) { + this.textDocument = textDocument; + this.range = range; + this.sharedSettings = sharedSettings; + this.formatterParticipants = formatterParticipants; + this.emptyElements = sharedSettings.getFormattingSettings().getEmptyElements(); + this.linefeedOnNextWrite = false; + } + + /** + * Returns a List containing a single TextEdit, containing the newly formatted + * changes of this.textDocument + * + * @return List containing a single TextEdit + * @throws BadLocationException + */ + public List format() throws BadLocationException { + this.fullDomDocument = DOMParser.getInstance().parse(textDocument.getText(), textDocument.getUri(), null, + false); + + if (isRangeFormatting()) { + setupRangeFormatting(range); + } else { + setupFullFormatting(range); + } + + this.indentLevel = getStartingIndentLevel(); + format(this.rangeDomDocument); + + List textEdits = getFormatTextEdit(); + return textEdits; + } + + private boolean isRangeFormatting() { + return this.range != null; + } + + private void setupRangeFormatting(Range range) throws BadLocationException { + int startOffset = this.textDocument.offsetAt(range.getStart()); + int endOffset = this.textDocument.offsetAt(range.getEnd()); + + Position startPosition = this.textDocument.positionAt(startOffset); + Position endPosition = this.textDocument.positionAt(endOffset); + enlargePositionToGutters(startPosition, endPosition); + + this.startOffset = this.textDocument.offsetAt(startPosition); + this.endOffset = this.textDocument.offsetAt(endPosition); + + String fullText = this.textDocument.getText(); + String rangeText = fullText.substring(this.startOffset, this.endOffset); + + withinDTDContent = this.fullDomDocument.isWithinInternalDTD(startOffset); + String uri = this.textDocument.getUri(); + if (withinDTDContent) { + uri += ".dtd"; + } + this.rangeDomDocument = DOMParser.getInstance().parse(rangeText, uri, null, false); + + if (containsTextWithinStartTag()) { + adjustOffsetToStartTag(); + rangeText = fullText.substring(this.startOffset, this.endOffset); + this.rangeDomDocument = DOMParser.getInstance().parse(rangeText, uri, null, false); + } + + this.xmlBuilder = new XMLBuilder(this.sharedSettings, "", textDocument.lineDelimiter(startPosition.getLine()), + formatterParticipants); + } + + private boolean containsTextWithinStartTag() { + + if (this.rangeDomDocument.getChildren().size() < 1) { + return false; + } + + DOMNode firstChild = this.rangeDomDocument.getChild(0); + if (!firstChild.isText()) { + return false; + } + + int tagContentOffset = firstChild.getStart(); + int fullDocOffset = getFullOffsetFromRangeOffset(tagContentOffset); + DOMNode fullNode = this.fullDomDocument.findNodeAt(fullDocOffset); + + if (!fullNode.isElement()) { + return false; + } + return ((DOMElement) fullNode).isInStartTag(fullDocOffset); + } + + private void adjustOffsetToStartTag() throws BadLocationException { + int tagContentOffset = this.rangeDomDocument.getChild(0).getStart(); + int fullDocOffset = getFullOffsetFromRangeOffset(tagContentOffset); + DOMNode fullNode = this.fullDomDocument.findNodeAt(fullDocOffset); + Position nodePosition = this.textDocument.positionAt(fullNode.getStart()); + nodePosition.setCharacter(0); + this.startOffset = this.textDocument.offsetAt(nodePosition); + } + + private void setupFullFormatting(Range range) throws BadLocationException { + this.startOffset = 0; + this.endOffset = textDocument.getText().length(); + this.rangeDomDocument = this.fullDomDocument; + + Position startPosition = textDocument.positionAt(startOffset); + this.xmlBuilder = new XMLBuilder(this.sharedSettings, "", textDocument.lineDelimiter(startPosition.getLine()), + formatterParticipants); + } + + private void enlargePositionToGutters(Position start, Position end) throws BadLocationException { + start.setCharacter(0); + + if (end.getCharacter() == 0 && end.getLine() > 0) { + end.setLine(end.getLine() - 1); + } + + end.setCharacter(this.textDocument.lineText(end.getLine()).length()); + } + + private int getStartingIndentLevel() throws BadLocationException { + if (withinDTDContent) { + return 1; + } + DOMNode startNode = this.fullDomDocument.findNodeAt(this.startOffset); + if (startNode.isOwnerDocument()) { + return 0; + } + + DOMNode startNodeParent = startNode.getParentNode(); + + if (startNodeParent.isOwnerDocument()) { + return 0; + } + + // the starting indent level is the parent's indent level + 1 + int startNodeIndentLevel = getNodeIndentLevel(startNodeParent) + 1; + return startNodeIndentLevel; + } + + private int getNodeIndentLevel(DOMNode node) throws BadLocationException { + + Position nodePosition = this.textDocument.positionAt(node.getStart()); + String textBeforeNode = this.textDocument.lineText(nodePosition.getLine()).substring(0, + nodePosition.getCharacter() + 1); + + int spaceOrTab = getSpaceOrTabStartOfString(textBeforeNode); + + if (this.sharedSettings.getFormattingSettings().isInsertSpaces()) { + return (spaceOrTab / this.sharedSettings.getFormattingSettings().getTabSize()); + } + return spaceOrTab; + } + + private int getSpaceOrTabStartOfString(String string) { + int i = 0; + int spaceOrTab = 0; + while (i < string.length() && (string.charAt(i) == ' ' || string.charAt(i) == '\t')) { + spaceOrTab++; + i++; + } + return spaceOrTab; + } + + private DOMElement getFullDocElemFromRangeElem(DOMElement elemFromRangeDoc) { + int fullOffset = -1; + + if (elemFromRangeDoc.hasStartTag()) { + fullOffset = getFullOffsetFromRangeOffset(elemFromRangeDoc.getStartTagOpenOffset()) + 1; + // +1 because offset must be here: <|root + // for DOMNode.findNodeAt() to find the correct element + } else if (elemFromRangeDoc.hasEndTag()) { + fullOffset = getFullOffsetFromRangeOffset(elemFromRangeDoc.getEndTagOpenOffset()) + 1; + // +1 because offset must be here: <|/root + // for DOMNode.findNodeAt() to find the correct element + } else { + return null; + } + + DOMElement elemFromFullDoc = (DOMElement) this.fullDomDocument.findNodeAt(fullOffset); + return elemFromFullDoc; + } + + private int getFullOffsetFromRangeOffset(int rangeOffset) { + return rangeOffset + this.startOffset; + } + + private boolean startTagExistsInRangeDocument(DOMNode node) { + if (!node.isElement()) { + return false; + } + + return ((DOMElement) node).hasStartTag(); + } + + private boolean startTagExistsInFullDocument(DOMNode node) { + if (!node.isElement()) { + return false; + } + + DOMElement elemFromFullDoc = getFullDocElemFromRangeElem((DOMElement) node); + + if (elemFromFullDoc == null) { + return false; + } + + return elemFromFullDoc.hasStartTag(); + } + + private void format(DOMNode node) throws BadLocationException { + + if (linefeedOnNextWrite && (!node.isText() || !((DOMText) node).isWhitespace())) { + this.xmlBuilder.linefeed(); + linefeedOnNextWrite = false; + } + + if (node.getNodeType() != DOMNode.DOCUMENT_NODE) { + boolean doLineFeed = !node.getOwnerDocument().isDTD() + && !(node.isComment() && ((DOMComment) node).isCommentSameLineEndTag()) + && (!node.isText() || (!((DOMText) node).isWhitespace() && ((DOMText) node).hasSiblings())); + + if (this.indentLevel > 0 && doLineFeed) { + // add new line + indent + if (!node.isChildOfOwnerDocument() || node.getPreviousNonTextSibling() != null) { + this.xmlBuilder.linefeed(); + } + + if (!startTagExistsInRangeDocument(node) && startTagExistsInFullDocument(node)) { + DOMNode startNode = getFullDocElemFromRangeElem((DOMElement) node); + int currentIndentLevel = getNodeIndentLevel(startNode); + this.xmlBuilder.indent(currentIndentLevel); + this.indentLevel = currentIndentLevel; + } else { + this.xmlBuilder.indent(this.indentLevel); + } + } + if (node.isElement()) { + // Format Element + formatElement((DOMElement) node); + } else if (node.isCDATA()) { + // Format CDATA + formatCDATA((DOMCDATASection) node); + } else if (node.isComment()) { + // Format comment + formatComment((DOMComment) node); + } else if (node.isProcessingInstruction()) { + // Format processing instruction + formatProcessingInstruction(node); + } else if (node.isProlog()) { + // Format prolog + formatProlog(node); + } else if (node.isText()) { + // Format Text + formatText((DOMText) node); + } else if (node.isDoctype()) { + // Format document type + formatDocumentType((DOMDocumentType) node); + } + } else if (node.hasChildNodes()) { + // Other nodes kind like root + for (DOMNode child : node.getChildren()) { + format(child); + } + } + } + + /** + * Format the given DOM prolog + * + * @param node the DOM prolog to format. + */ + private void formatProlog(DOMNode node) { + addPrologToXMLBuilder(node, this.xmlBuilder); + linefeedOnNextWrite = true; + } + + /** + * Format the given DOM text node. + * + * @param textNode the DOM text node to format. + */ + private void formatText(DOMText textNode) { + String content = textNode.getData(); + if (textNode.equals(this.fullDomDocument.getLastChild())) { + xmlBuilder.addContent(content); + } else { + xmlBuilder.addContent(content, textNode.isWhitespace(), textNode.hasSiblings(), textNode.getDelimiter()); + } + } + + /** + * Format the given DOM document type. + * + * @param documentType the DOM document type to format. + */ + private void formatDocumentType(DOMDocumentType documentType) { + boolean isDTD = documentType.getOwnerDocument().isDTD(); + if (!isDTD) { + this.xmlBuilder.startDoctype(); + List params = documentType.getParameters(); + + for (DTDDeclParameter param : params) { + if (!documentType.isInternalSubset(param)) { + xmlBuilder.addParameter(param.getParameter()); + } else { + xmlBuilder.startDoctypeInternalSubset(); + xmlBuilder.linefeed(); + // level + 1 since the 'level' value is the doctype tag's level + formatDTD(documentType, this.indentLevel + 1, this.endOffset, this.xmlBuilder); + xmlBuilder.linefeed(); + xmlBuilder.endDoctypeInternalSubset(); + } + } + if (documentType.isClosed()) { + xmlBuilder.endDoctype(); + } + linefeedOnNextWrite = true; + + } else { + formatDTD(documentType, this.indentLevel, this.endOffset, this.xmlBuilder); + } + } + + /** + * Format the given DOM ProcessingIntsruction. + * + * @param element the DOM ProcessingIntsruction to format. + * + */ + private void formatProcessingInstruction(DOMNode node) { + addPIToXMLBuilder(node, this.xmlBuilder); + if (this.indentLevel == 0) { + this.xmlBuilder.linefeed(); + } + } + + /** + * Format the given DOM Comment + * + * @param element the DOM Comment to format. + * + */ + private void formatComment(DOMComment comment) { + this.xmlBuilder.startComment(comment); + this.xmlBuilder.addContentComment(comment.getData()); + if (comment.isClosed()) { + // Generate --> only if comment is closed. + this.xmlBuilder.endComment(); + } + if (this.indentLevel == 0) { + linefeedOnNextWrite = true; + } + } + + /** + * Format the given DOM CDATA + * + * @param element the DOM CDATA to format. + * + */ + private void formatCDATA(DOMCDATASection cdata) { + this.xmlBuilder.startCDATA(); + this.xmlBuilder.addContentCDATA(cdata.getData()); + if (cdata.isClosed()) { + // Generate ]> only if CDATA is closed. + this.xmlBuilder.endCDATA(); + } + } + + /** + * Format the given DOM element + * + * @param element the DOM element to format. + * + * @throws BadLocationException + */ + private void formatElement(DOMElement element) throws BadLocationException { + String tag = element.getTagName(); + if (element.hasEndTag() && !element.hasStartTag()) { + // bad element without start tag (ex: <\root>) + xmlBuilder.endElement(tag, element.isEndTagClosed()); + } else { + // generate start element + xmlBuilder.startElement(tag, false); + if (element.hasAttributes()) { + formatAttributes(element); + } + + EmptyElements emptyElements = getEmptyElements(element); + switch (emptyElements) { + case expand: + // expand empty element: -> + xmlBuilder.closeStartElement(); + // end tag element is done, only if the element is closed + // the format, doesn't fix the close tag + this.xmlBuilder.endElement(tag, true); + break; + case collapse: + // collapse empty element: -> + formatElementStartTagSelfCloseBracket(element); + break; + default: + if (element.isStartTagClosed()) { + formatElementStartTagCloseBracket(element); + } + boolean hasElements = false; + if (element.hasChildNodes()) { + // element has body + + this.indentLevel++; + for (DOMNode child : element.getChildren()) { + hasElements = hasElements || !child.isText(); + format(child); + } + this.indentLevel--; + } + if (element.hasEndTag()) { + if (hasElements) { + this.xmlBuilder.linefeed(); + this.xmlBuilder.indent(this.indentLevel); + } + // end tag element is done, only if the element is closed + // the format, doesn't fix the close tag + if (element.hasEndTag() && element.getEndTagOpenOffset() <= this.endOffset) { + this.xmlBuilder.endElement(tag, element.isEndTagClosed()); + } else { + formatElementStartTagSelfCloseBracket(element); + } + } else if (element.isSelfClosed()) { + formatElementStartTagSelfCloseBracket(element); + } + } + } + } + + /** + * Formats the start tag's closing bracket (>) according to + * {@code XMLFormattingOptions#isPreserveAttrLineBreaks()} + * + * {@code XMLFormattingOptions#isPreserveAttrLineBreaks()}: If true, must add a + * newline + indent before the closing bracket if the last attribute of the + * element and the closing bracket are in different lines. + * + * @param element + * @throws BadLocationException + */ + private void formatElementStartTagCloseBracket(DOMElement element) throws BadLocationException { + if (this.sharedSettings.getFormattingSettings().isPreserveAttributeLineBreaks() && element.hasAttributes() + && !isSameLine(getLastAttribute(element).getEnd(), element.getStartTagCloseOffset())) { + xmlBuilder.linefeed(); + this.xmlBuilder.indent(this.indentLevel); + } + xmlBuilder.closeStartElement(); + } + + /** + * Formats the self-closing tag (/>) according to + * {@code XMLFormattingOptions#isPreserveAttrLineBreaks()} + * + * {@code XMLFormattingOptions#isPreserveAttrLineBreaks()}: If true, must add a + * newline + indent before the self-closing tag if the last attribute of the + * element and the closing bracket are in different lines. + * + * @param element + * @throws BadLocationException + */ + private void formatElementStartTagSelfCloseBracket(DOMElement element) throws BadLocationException { + if (this.sharedSettings.getFormattingSettings().isPreserveAttributeLineBreaks() && element.hasAttributes()) { + int elementEndOffset = element.getEnd(); + if (element.isStartTagClosed()) { + elementEndOffset = element.getStartTagCloseOffset(); + } + if (!isSameLine(getLastAttribute(element).getEnd(), elementEndOffset)) { + this.xmlBuilder.linefeed(); + this.xmlBuilder.indent(this.indentLevel); + } + } + + this.xmlBuilder.selfCloseElement(); + } + + private void formatAttributes(DOMElement element) throws BadLocationException { + List attributes = element.getAttributeNodes(); + boolean isSingleAttribute = hasSingleAttributeInFullDoc(element); + int prevOffset = element.getStart(); + for (DOMAttr attr : attributes) { + formatAttribute(attr, isSingleAttribute, prevOffset); + prevOffset = attr.getEnd(); + } + if ((this.sharedSettings.getFormattingSettings().getClosingBracketNewLine() + && this.sharedSettings.getFormattingSettings().isSplitAttributes()) && !isSingleAttribute) { + xmlBuilder.linefeed(); + // Indent by tag + splitAttributesIndentSize to match with attribute indent + // level + int totalIndent = this.indentLevel + + this.sharedSettings.getFormattingSettings().getSplitAttributesIndentSize(); + xmlBuilder.indent(totalIndent); + } + } + + private void formatAttribute(DOMAttr attr, boolean isSingleAttribute, int prevOffset) throws BadLocationException { + if (this.sharedSettings.getFormattingSettings().isPreserveAttributeLineBreaks() + && !isSameLine(prevOffset, attr.getStart())) { + xmlBuilder.linefeed(); + xmlBuilder.indent(this.indentLevel + 1); + xmlBuilder.addSingleAttribute(attr, false, false); + } else if (isSingleAttribute) { + xmlBuilder.addSingleAttribute(attr); + } else { + xmlBuilder.addAttribute(attr, this.indentLevel); + } + } + + /** + * Returns true if first offset and second offset belong in the same line of the + * document + * + * If current formatting is range formatting, the provided offsets must be + * ranged offsets (offsets relative to the formatting range) + * + * @param first the first offset + * @param second the second offset + * @return true if first offset and second offset belong in the same line of the + * document + * @throws BadLocationException + */ + private boolean isSameLine(int first, int second) throws BadLocationException { + if (isRangeFormatting()) { + // adjust range offsets so that they are relative to the full document + first = getFullOffsetFromRangeOffset(first); + second = getFullOffsetFromRangeOffset(second); + } + return getLineNumber(first) == getLineNumber(second); + } + + private int getLineNumber(int offset) throws BadLocationException { + return this.textDocument.positionAt(offset).getLine(); + } + + private DOMAttr getLastAttribute(DOMElement element) { + if (!element.hasAttributes()) { + return null; + } + List attributes = element.getAttributeNodes(); + return attributes.get(attributes.size() - 1); + } + + /** + * Returns true if the provided element has one attribute in the fullDomDocument + * (not the rangeDomDocument) + * + * @param element + * @return true if the provided element has one attribute in the fullDomDocument + * (not the rangeDomDocument) + */ + private boolean hasSingleAttributeInFullDoc(DOMElement element) { + DOMElement fullElement = getFullDocElemFromRangeElem(element); + return fullElement.getAttributeNodes().size() == 1; + } + + /** + * Return the option to use to generate empty elements. + * + * @param element the DOM element + * @return the option to use to generate empty elements. + */ + private EmptyElements getEmptyElements(DOMElement element) { + if (this.emptyElements != EmptyElements.ignore) { + if (element.isClosed() && element.isEmpty()) { + // Element is empty and closed + switch (this.emptyElements) { + case expand: + case collapse: { + if (this.sharedSettings.getFormattingSettings().isPreserveEmptyContent()) { + // preserve content + if (element.hasChildNodes()) { + // The element is empty and contains somes spaces which must be preserved + return EmptyElements.ignore; + } + } + return this.emptyElements; + } + default: + return this.emptyElements; + } + } + } + return EmptyElements.ignore; + } + + private static boolean formatDTD(DOMDocumentType doctype, int level, int end, XMLBuilder xmlBuilder) { + DOMNode previous = null; + for (DOMNode node : doctype.getChildren()) { + if (previous != null) { + xmlBuilder.linefeed(); + } + + xmlBuilder.indent(level); + + if (node.isText()) { + xmlBuilder.addContent(((DOMText) node).getData().trim()); + } else if (node.isComment()) { + DOMComment comment = (DOMComment) node; + xmlBuilder.startComment(comment); + xmlBuilder.addContentComment(comment.getData()); + xmlBuilder.endComment(); + } else if (node.isProcessingInstruction()) { + addPIToXMLBuilder(node, xmlBuilder); + } else if (node.isProlog()) { + addPrologToXMLBuilder(node, xmlBuilder); + } else { + boolean setEndBracketOnNewLine = false; + DTDDeclNode decl = (DTDDeclNode) node; + xmlBuilder.addDeclTagStart(decl); + + if (decl.isDTDAttListDecl()) { + DTDAttlistDecl attlist = (DTDAttlistDecl) decl; + List internalDecls = attlist.getInternalChildren(); + + if (internalDecls == null) { + for (DTDDeclParameter param : decl.getParameters()) { + xmlBuilder.addParameter(param.getParameter()); + } + } else { + boolean multipleInternalAttlistDecls = false; + List params = attlist.getParameters(); + DTDDeclParameter param; + for (int i = 0; i < params.size(); i++) { + param = params.get(i); + if (attlist.getNameParameter().equals(param)) { + xmlBuilder.addParameter(param.getParameter()); + if (attlist.getParameters().size() > 1) { // has parameters after elementName + xmlBuilder.linefeed(); + xmlBuilder.indent(level + 1); + setEndBracketOnNewLine = true; + multipleInternalAttlistDecls = true; + } + } else { + if (multipleInternalAttlistDecls && i == 1) { + xmlBuilder.addUnindentedParameter(param.getParameter()); + } else { + xmlBuilder.addParameter(param.getParameter()); + } + } + } + + for (DTDAttlistDecl attlistDecl : internalDecls) { + xmlBuilder.linefeed(); + xmlBuilder.indent(level + 1); + params = attlistDecl.getParameters(); + for (int i = 0; i < params.size(); i++) { + param = params.get(i); + + if (i == 0) { + xmlBuilder.addUnindentedParameter(param.getParameter()); + } else { + xmlBuilder.addParameter(param.getParameter()); + } + } + } + } + } else { + for (DTDDeclParameter param : decl.getParameters()) { + xmlBuilder.addParameter(param.getParameter()); + } + } + if (setEndBracketOnNewLine) { + xmlBuilder.linefeed(); + xmlBuilder.indent(level); + } + if (decl.isClosed()) { + xmlBuilder.closeStartElement(); + } + } + previous = node; + } + return true; + } + + private List getFormatTextEdit() throws BadLocationException { + Position startPosition = this.textDocument.positionAt(this.startOffset); + Position endPosition = this.textDocument.positionAt(this.endOffset); + Range r = new Range(startPosition, endPosition); + List edits = new ArrayList<>(); + + // check if format range reaches the end of the document + if (this.endOffset == this.textDocument.getText().length()) { + + if (this.sharedSettings.getFormattingSettings().isTrimFinalNewlines()) { + this.xmlBuilder.trimFinalNewlines(); + } + + if (this.sharedSettings.getFormattingSettings().isInsertFinalNewline() + && !this.xmlBuilder.isLastLineEmptyOrWhitespace()) { + this.xmlBuilder.linefeed(); + } + } + + edits.add(new TextEdit(r, this.xmlBuilder.toString())); + return edits; + } + + private static void addPIToXMLBuilder(DOMNode node, XMLBuilder xml) { + DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) node; + xml.startPrologOrPI(processingInstruction.getTarget()); + + String content = processingInstruction.getData(); + if (content.length() > 0) { + xml.addContentPI(content); + } else { + xml.addContent(" "); + } + + xml.endPrologOrPI(); + } + + private static void addPrologToXMLBuilder(DOMNode node, XMLBuilder xml) { + DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) node; + xml.startPrologOrPI(processingInstruction.getTarget()); + if (node.hasAttributes()) { + addPrologAttributes(node, xml); + } + xml.endPrologOrPI(); + } + + /** + * Will add all attributes, to the given builder, on a single line + */ + private static void addPrologAttributes(DOMNode node, XMLBuilder xmlBuilder) { + List attrs = node.getAttributeNodes(); + if (attrs == null) { + return; + } + for (DOMAttr attr : attrs) { + xmlBuilder.addPrologAttribute(attr); + } + } +} \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocumentNew.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocumentNew.java new file mode 100644 index 0000000000..27437d954c --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormatterDocumentNew.java @@ -0,0 +1,873 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format; + +import java.util.ArrayList; +import java.util.Collection; +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.TextDocument; +import org.eclipse.lemminx.dom.DOMAttr; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.dom.DOMNode; +import org.eclipse.lemminx.dom.DOMProcessingInstruction; +import org.eclipse.lemminx.dom.DOMText; +import org.eclipse.lemminx.services.extensions.format.IFormatterParticipant; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lemminx.settings.XMLFormattingOptions.EmptyElements; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextEdit; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.w3c.dom.Node; +import org.w3c.dom.Text; + +/** + * Experimental XML formatter which generates several text edit to remove, add, + * update spaces / indent. + * + * @author Angelo ZERR + * + */ +public class XMLFormatterDocumentNew { + + private static final Logger LOGGER = Logger.getLogger(XMLFormatterDocumentNew.class.getName()); + + private static final String XML_SPACE_ATTR = "xml:space"; + + private static final String XML_SPACE_ATTR_DEFAULT = "default"; + + private static final String XML_SPACE_ATTR_PRESERVE = "preserve"; + + private final DOMDocument xmlDocument; + private final TextDocument textDocument; + private final String lineDelimiter; + private final SharedSettings sharedSettings; + private final Collection formatterParticipants; + private final EmptyElements emptyElements; + + private int startOffset = -1; + private int endOffset = -1; + + private CancelChecker monitor; + + /** + * XML formatter document. + */ + public XMLFormatterDocumentNew(DOMDocument xmlDocument, Range range, SharedSettings sharedSettings, + Collection formatterParticipants) { + this.xmlDocument = xmlDocument; + this.textDocument = xmlDocument.getTextDocument(); + this.lineDelimiter = computeLineDelimiter(textDocument); + if (range != null) { + try { + startOffset = textDocument.offsetAt(range.getStart()); + endOffset = textDocument.offsetAt(range.getEnd()); + } catch (BadLocationException e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + } + this.sharedSettings = sharedSettings; + this.formatterParticipants = formatterParticipants; + this.emptyElements = sharedSettings.getFormattingSettings().getEmptyElements(); + } + + private static String computeLineDelimiter(TextDocument textDocument) { + try { + return textDocument.lineDelimiter(0); + } catch (BadLocationException e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + return System.lineSeparator(); + } + + /** + * Returns a List containing a single TextEdit, containing the newly formatted + * changes of this.textDocument + * + * @return List containing a single TextEdit + * @throws BadLocationException + */ + public List format() throws BadLocationException { + return format(xmlDocument, startOffset, endOffset); + } + + public List format(DOMDocument document, int start, int end) { + List edits = new ArrayList<>(); + + // get initial document region + DOMNode currentDOMNode = getDOMNodeToFormat(document, start, end); + + if (currentDOMNode != null) { + int startOffset = currentDOMNode.getStart(); + + XMLFormattingConstraints parentConstraints = getNodeConstraints(currentDOMNode); + + // initialize available line width + int lineWidth = getMaxLineWidth(); + + try { + int lineOffset = textDocument.lineOffsetAt(startOffset); + lineWidth = lineWidth - (startOffset - lineOffset); + } catch (BadLocationException e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + parentConstraints.setAvailableLineWidth(lineWidth); + + // format all siblings (and their children) as long they + // overlap with start/end offset + if (currentDOMNode.isElement()) { + parentConstraints.setFormatElementCategory(getFormatElementCategory((DOMElement) currentDOMNode, null)); + } else { + parentConstraints.setFormatElementCategory(FormatElementCategory.IgnoreSpace); + } + formatSiblings(edits, currentDOMNode, parentConstraints, start, end); + } + + boolean insertFinalNewline = isInsertFinalNewline(); + if (isTrimFinalNewlines()) { + trimFinalNewlines(insertFinalNewline, edits); + } + if (insertFinalNewline) { + String xml = textDocument.getText(); + int endDocument = xml.length() - 1; + if (endDocument >= 0) { + char c = xml.charAt(endDocument); + if (c != '\n') { + try { + Position pos = textDocument.positionAt(endDocument); + pos.setCharacter(pos.getCharacter() + 1); + Range range = new Range(pos, pos); + edits.add(new TextEdit(range, lineDelimiter)); + } catch (BadLocationException e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + } + } + } + return edits; + } + + /** + * Returns the DOM node to format according to the given range and the DOM + * document otherwise. + * + * @param document the DOM document. + * @param start the start range offset and -1 otherwise. + * @param end the end range offset and -1 otherwise. + * + * @return the DOM node to format according to the given range and the DOM + * document otherwise. + */ + private static DOMNode getDOMNodeToFormat(DOMDocument document, int start, int end) { + if (start != -1 && end != -1) { + DOMNode startNode = document.findNodeAt(start); + DOMNode endNode = document.findNodeBefore(end); + + if (endNode.getStart() == start) { + // ex : + //
+ // || + //
+ return endNode; + } + + if (isCoverNode(startNode, endNode)) { + return startNode; + } else if (isCoverNode(endNode, startNode)) { + return endNode; + } else { + DOMNode startParent = startNode.getParentNode(); + DOMNode endParent = endNode.getParentNode(); + while (startParent != null && endParent != null) { + if (isCoverNode(startParent, endParent)) { + return startParent; + } else if (isCoverNode(endParent, startParent)) { + return endParent; + } + startParent = startParent.getParentNode(); + endParent = endParent.getParentNode(); + } + } + } + return document; + } + + private static boolean isCoverNode(DOMNode startNode, DOMNode endNode) { + return (startNode.getStart() < endNode.getStart() && startNode.getEnd() > endNode.getEnd()) + || startNode == endNode; + } + + /** + * Returns the DOM node constraints of the given DOM node. + * + * @param node the DOM node. + * + * @return the DOM node constraints of the given DOM node. + */ + private XMLFormattingConstraints getNodeConstraints(DOMNode node) { + XMLFormattingConstraints result = new XMLFormattingConstraints(); + // Compute the indent level according to the parent node. + int indentLevel = 0; + while (node != null) { + node = node.getParentElement(); + if (node != null) { + indentLevel++; + } + } + result.setIndentLevel(indentLevel); + return result; + } + + private void formatSiblings(List edits, DOMNode domNode, XMLFormattingConstraints parentConstraints, + int start, int end) { + DOMNode currentDOMNode = domNode; + while (currentDOMNode != null) { + if (monitor != null) { + monitor.checkCanceled(); + } + format(currentDOMNode, parentConstraints, start, end, edits); + currentDOMNode = currentDOMNode.getNextSibling(); + } + } + + private void format(DOMNode child, XMLFormattingConstraints parentConstraints, int start, int end, + List edits) { + + switch (child.getNodeType()) { + + case DOMNode.PROCESSING_INSTRUCTION_NODE: + DOMProcessingInstruction processingInstruction = (DOMProcessingInstruction) child; + formatProcessingInstruction(processingInstruction, parentConstraints, edits); + break; + + case Node.DOCUMENT_NODE: + DOMDocument document = (DOMDocument) child; + formatChildren(document, parentConstraints, start, end, edits); + break; + + case Node.ELEMENT_NODE: + DOMElement element = (DOMElement) child; + formatElement(element, parentConstraints, start, end, edits); + break; + + case Node.TEXT_NODE: + DOMText textNode = (DOMText) child; + formatText(textNode, parentConstraints, edits); + 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); + } + } + + private void formatProcessingInstruction(DOMProcessingInstruction processingInstruction, + XMLFormattingConstraints parentConstraints, List edits) { + int prevOffset = processingInstruction.getStartContent(); + // 1. format attributes : attributes must be in a same line separate with only + // one space + if (processingInstruction.hasAttributes()) { + // --- + // --> + List attributes = processingInstruction.getAttributeNodes(); + boolean singleAttribute = attributes.size() == 1; + for (DOMAttr attr : attributes) { + formatAttribute(attr, prevOffset, singleAttribute, false, parentConstraints, edits); + prevOffset = attr.getEnd(); + } + } + // 2. format end of processing instruction : remove extra space between the last + // attribute value and the end of processing instruction + // --- + // --> + if (processingInstruction.isClosed()) { + // it ends with ?> + int endPIOffset = processingInstruction.getEnd() - 2; + if (prevOffset != endPIOffset) { + replaceLeftSpacesWith(prevOffset, endPIOffset, "", edits); + } + } + } + + private void formatChildren(DOMNode currentDOMNode, XMLFormattingConstraints parentConstraints, int start, int end, + List edits) { + for (DOMNode child : currentDOMNode.getChildren()) { + format(child, parentConstraints, start, end, edits); + } + } + + private void formatElement(DOMElement element, XMLFormattingConstraints parentConstraints, int start, int end, + List edits) { + EmptyElements emptyElements = getEmptyElements(element); + + // Format start tag element with proper indentation + int indentLevel = parentConstraints.getIndentLevel(); + int nb = formatStartTagElement(element, parentConstraints, emptyElements, edits); + + if (emptyElements == EmptyElements.ignore) { + // Format children of the element + XMLFormattingConstraints constraints = new XMLFormattingConstraints(); + constraints.copyConstraints(parentConstraints); + if ((element.isClosed())) { + constraints.setIndentLevel(indentLevel + 1); + } + constraints.setFormatElementCategory(getFormatElementCategory(element, parentConstraints)); + constraints.setAvailableLineWidth(getMaxLineWidth() - nb); + + formatChildren(element, constraints, start, end, edits); + + // Format end tag element with proper indentation + if (element.hasEndTag()) { + formatEndTagElement(element, parentConstraints, constraints, edits); + } + } + } + + private int formatStartTagElement(DOMElement element, XMLFormattingConstraints parentConstraints, + EmptyElements emptyElements, List edits) { + int width = 0; + int indentLevel = parentConstraints.getIndentLevel(); + FormatElementCategory formatElementCategory = parentConstraints.getFormatElementCategory(); + + switch (formatElementCategory) { + case PreserveSpace: + // Preserve existing spaces + break; + case MixedContent: + break; + case IgnoreSpace: + // remove spaces and indent + int startTagOffset = element.getStartTagOpenOffset(); + int nbSpaces = indentIfNotAlreadyIndented(indentLevel, startTagOffset, true, edits); + width = nbSpaces + element.getStartTagCloseOffset() - startTagOffset; + case NormalizeSpace: + break; + } + + if (formatElementCategory != FormatElementCategory.PreserveSpace) { + formatAttributes(element, parentConstraints, edits); + + boolean formatted = false; + switch (emptyElements) { + case expand: { + if (element.isSelfClosed()) { + // expand empty element: -> + StringBuilder tag = new StringBuilder(); + tag.append(">"); + tag.append("'); + createTextEditIfNeeded(element.getEnd() - 3, element.getEnd(), tag.toString(), edits); + formatted = true; + } + break; + } + case collapse: { + // collapse empty element: -> + if (!element.isSelfClosed()) { + StringBuilder tag = new StringBuilder(); + if (isSpaceBeforeEmptyCloseTag()) { + tag.append(" "); + } + tag.append("/>"); + createTextEditIfNeeded(element.getStartTagCloseOffset() - 1, element.getEnd(), tag.toString(), + edits); + formatted = true; + } + break; + } + default: + } + + if (!formatted) { + if (element.isSelfClosed()) { + // --> + int offset = element.getEnd() - 2; + if (isSpaceBeforeEmptyCloseTag()) { + replaceLeftSpacesWithOneSpace(offset, edits); + } else { + removeLeftSpaces(offset, edits); + } + } else if (element.isStartTagClosed()) { + formatElementStartTagCloseBracket(element, edits); + } + } + } + return width; + } + + /** + * Formats the start tag's closing bracket (>) according to + * {@code XMLFormattingOptions#isPreserveAttrLineBreaks()} + * + * {@code XMLFormattingOptions#isPreserveAttrLineBreaks()}: If true, must add a + * newline + indent before the closing bracket if the last attribute of the + * element and the closing bracket are in different lines. + * + * @param element + * @throws BadLocationException + */ + private void formatElementStartTagCloseBracket(DOMElement element, List edits) { + int offset = element.getStartTagCloseOffset(); + String replace = ""; + if (isPreserveAttributeLineBreaks() && element.hasAttributes() + && hasLineBreak(getLastAttribute(element).getEnd(), element.getStartTagCloseOffset())) { + replace = lineDelimiter; + } + replaceLeftSpacesWith(offset, replace, edits); + } + + private DOMAttr getLastAttribute(DOMElement element) { + if (!element.hasAttributes()) { + return null; + } + List attributes = element.getAttributeNodes(); + return attributes.get(attributes.size() - 1); + } + + private int formatAttributes(DOMElement element, XMLFormattingConstraints parentConstraints, List edits) { + if (element.hasAttributes()) { + List attributes = element.getAttributeNodes(); + int prevOffset = element.getOffsetAfterStartTag(); + boolean singleAttribute = attributes.size() == 1; + for (DOMAttr attr : attributes) { + formatAttribute(attr, prevOffset, singleAttribute, true, parentConstraints, edits); + prevOffset = attr.getEnd(); + } + } + return 0; + } + + private void formatAttribute(DOMAttr attr, int prevOffset, boolean singleAttribute, boolean useSettings, + XMLFormattingConstraints parentConstraints, List edits) { + // 1) format before attrbute name : indent left of the attribute name + // ex : + // attr0='name'[space]attr1='name' + + // Adjust the startAttr to avoid ignoring invalid content + // ex : + // must be adjusted with to keep the invalid content "" + int from = prevOffset; + int to = attr.getStart(); + replaceLeftSpacesWithOneSpace(from, to, edits); + } + + // 2) format delimiter : remove whitespaces between '=' + // ex : edits) { + replaceLeftSpacesWith(to, "", edits); + } + + private void removeLeftSpaces(int from, int to, List edits) { + replaceLeftSpacesWith(from, to, "", edits); + } + + private void replaceLeftSpacesWithOneSpace(int to, List edits) { + replaceLeftSpacesWith(to, " ", edits); + } + + private void replaceLeftSpacesWithOneSpace(int from, int to, List edits) { + replaceLeftSpacesWith(from, to, " ", edits); + } + + private void replaceLeftSpacesWith(int from, int to, String replacement, List edits) { + String text = textDocument.getText(); + for (int i = to - 1; i > from; i--) { + char c = text.charAt(i); + if (!Character.isWhitespace(c)) { + from = i; + break; + } + } + createTextEditIfNeeded(from, to, replacement, edits); + } + + private void replaceLeftSpacesWith(int to, String replacement, List edits) { + String text = textDocument.getText(); + int from = to - 1; + for (int i = to - 1; i >= 0; i--) { + char c = text.charAt(i); + if (!Character.isWhitespace(c)) { + from = i; + break; + } + } + createTextEditIfNeeded(from, to, replacement, edits); + } + + private boolean hasLineBreak(int startAttr, int start) { + String text = textDocument.getText(); + for (int i = startAttr; i < start; i++) { + char c = text.charAt(i); + if (isLineSeparator(c)) { + return true; + } + } + return false; + } + + private int formatEndTagElement(DOMElement element, XMLFormattingConstraints parentConstraints, + XMLFormattingConstraints constraints, List edits) { + // 1) remove / add some spaces on the left of the end tag element + // before formatting : + // [space][space] + // after formatting : + // + int indentLevel = parentConstraints.getIndentLevel(); + FormatElementCategory formatElementCategory = constraints.getFormatElementCategory(); + switch (formatElementCategory) { + case PreserveSpace: + // Preserve existing spaces + break; + case MixedContent: + break; + case IgnoreSpace: + // remove spaces and indent + int endTagOffset = element.getEndTagOpenOffset(); + indentIfNotAlreadyIndented(indentLevel, endTagOffset, true, edits); + break; + case NormalizeSpace: + break; + } + // 2) remove some spaces between the end tag and and close bracket + // before formatting : + // after formatting : + if (element.isEndTagClosed()) { + int endTagOffset = element.getEndTagCloseOffset(); + removeLeftSpaces(endTagOffset, edits); + } + return 0; + } + + private int updateLineWidthWithLastLine(DOMNode child, int availableLineWidth) { + String text = textDocument.getText(); + int lineWidth = availableLineWidth; + int end = child.getEnd(); + // Check if next char after the end of the DOM node is a new line feed. + if (end < text.length()) { + char c = text.charAt(end); + if (isLineSeparator(c)) { + // ex: \r\n + return getMaxLineWidth(); + } + } + for (int i = end - 1; i > child.getStart(); i--) { + char c = text.charAt(i); + if (isLineSeparator(c)) { + return lineWidth; + } else { + lineWidth--; + } + } + return lineWidth; + } + + private static boolean isLineSeparator(char c) { + return c == '\r' || c == '\n'; + } + + private void formatText(DOMText textNode, XMLFormattingConstraints parentConstraints, List edits) { + FormatElementCategory formatElementCategory = parentConstraints.getFormatElementCategory(); + String text = textDocument.getText(); + int availableLineWidth = parentConstraints.getAvailableLineWidth(); + + int spaceStart = -1; + int spaceEnd = -1; + + for (int i = textNode.getStart(); i < textNode.getEnd(); i++) { + char c = text.charAt(i); + if (Character.isWhitespace(c)) { + // Whitespaces... + if (spaceStart == -1) { + spaceStart = i; + } else { + spaceEnd = i; + } + } else { + // Text content... + int contentStart = i; + while (i < textNode.getEnd() + 1 && !Character.isWhitespace(text.charAt(i + 1))) { + i++; + } + + int contentEnd = i; + availableLineWidth -= (contentEnd - contentStart); + + if (formatElementCategory != FormatElementCategory.PreserveSpace + && formatElementCategory != FormatElementCategory.IgnoreSpace) { + if (availableLineWidth <= 0) { + if (spaceStart != -1) { + insertLineBreak(spaceStart, contentStart, edits); + availableLineWidth = getMaxLineWidth(); + } + } else { + replaceSpacesWithOneSpace(spaceStart, spaceEnd, edits); + } + } + + spaceStart = -1; + spaceEnd = -1; + } + } + if (formatElementCategory != FormatElementCategory.PreserveSpace + && formatElementCategory != FormatElementCategory.IgnoreSpace) { + replaceSpacesWithOneSpace(spaceStart, spaceEnd, edits); + } + } + + private void insertLineBreak(int start, int end, List edits) { + createTextEditIfNeeded(start, end, lineDelimiter, edits); + } + + private void replaceSpacesWithOneSpace(int spaceStart, int spaceEnd, List edits) { + if (spaceStart >= 0) { + spaceEnd = spaceEnd == -1 ? spaceStart + 1 : spaceEnd + 1; + // Replace several spaces with one space + // a[space][space][space]b + // --> a[space]b + replaceLeftSpacesWithOneSpace(spaceStart, spaceEnd, edits); + } + } + + /** + * Returns the format element category of the given DOM element. + * + * @param element the DOM element. + * @param parentConstraints the parent constraints. + * + * @return the format element category of the given DOM element. + */ + private FormatElementCategory getFormatElementCategory(DOMElement element, + XMLFormattingConstraints parentConstraints) { + if (!element.isClosed()) { + return parentConstraints.getFormatElementCategory(); + } + + // Get the category from the settings + FormatElementCategory fromSettings = sharedSettings.getFormattingSettings().getFormatElementCategory(element); + if (fromSettings != null) { + return fromSettings; + } + + // Get the category from the participants (ex : from the XSD/DTD grammar + // information) + for (IFormatterParticipant participant : formatterParticipants) { + FormatElementCategory fromParticipant = participant.getFormatElementCategory(element, parentConstraints, + sharedSettings); + if (fromParticipant != null) { + return fromParticipant; + } + } + + if (XML_SPACE_ATTR_PRESERVE.equals(element.getAttribute(XML_SPACE_ATTR))) { + return FormatElementCategory.PreserveSpace; + } + + if (parentConstraints != null) { + if (parentConstraints.getFormatElementCategory() == FormatElementCategory.PreserveSpace) { + if (!XML_SPACE_ATTR_DEFAULT.equals(element.getAttribute(XML_SPACE_ATTR))) { + return FormatElementCategory.PreserveSpace; + } + } + } + + boolean hasElement = false; + boolean hasText = false; + boolean onlySpaces = true; + for (DOMNode child : element.getChildren()) { + if (child.isElement()) { + hasElement = true; + } else if (child.isText()) { + onlySpaces = ((Text) child).isElementContentWhitespace(); + if (!onlySpaces) { + hasText = true; + } + } + if (hasElement && hasText) { + return FormatElementCategory.MixedContent; + } + } + if (hasElement && onlySpaces) { + return FormatElementCategory.IgnoreSpace; + } + return FormatElementCategory.NormalizeSpace; + } + + private int indentIfNotAlreadyIndented(int indentLevel, int offset, boolean addLineSeparator, + List edits) { + int start = offset - 1; + if (start > 0) { + String expectedSpaces = getIndentSpaces(indentLevel, addLineSeparator); + createTextEditIfNeeded(start, offset, expectedSpaces, edits); + return expectedSpaces.length(); + } + return 0; + } + + private void createTextEditIfNeeded(int from, int to, String expectedContent, List edits) { + TextEdit edit = TextEditUtils.createTextEditIfNeeded(from, to, expectedContent, textDocument); + if (edit != null) { + edits.add(edit); + } + } + + private String getIndentSpaces(int level, boolean addLineSeparator) { + StringBuilder spaces = new StringBuilder(); + if (addLineSeparator) { + spaces.append(lineDelimiter); + } + + for (int i = 0; i < level; i++) { + if (isInsertSpaces()) { + for (int j = 0; j < getTabSize(); j++) { + spaces.append(" "); + } + } else { + spaces.append("\t"); + } + } + return spaces.toString(); + } + + private void trimFinalNewlines(boolean insertFinalNewline, List edits) { + String xml = textDocument.getText(); + int end = xml.length() - 1; + int i = end; + while (i >= 0 && isLineSeparator(xml.charAt(i))) { + i--; + } + if (end > i) { + if (insertFinalNewline) { + // re-adjust offset to keep insert final new line + i++; + if (xml.charAt(end - 1) == '\r') { + i++; + } + } + if (end > i) { + try { + Position endPos = textDocument.positionAt(end + 1); + Position startPos = textDocument.positionAt(i + 1); + Range range = new Range(startPos, endPos); + edits.add(new TextEdit(range, "")); + } catch (BadLocationException e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + } + } + } + + /** + * Return the option to use to generate empty elements. + * + * @param element the DOM element + * @return the option to use to generate empty elements. + */ + private EmptyElements getEmptyElements(DOMElement element) { + if (this.emptyElements != EmptyElements.ignore) { + if (element.isClosed() && element.isEmpty()) { + // Element is empty and closed + switch (this.emptyElements) { + case expand: + case collapse: { + if (this.sharedSettings.getFormattingSettings().isPreserveEmptyContent()) { + // preserve content + if (element.hasChildNodes()) { + // The element is empty and contains somes spaces which must be preserved + return EmptyElements.ignore; + } + } + return this.emptyElements; + } + default: + return this.emptyElements; + } + } + } + return EmptyElements.ignore; + } + + private int getMaxLineWidth() { + return sharedSettings.getFormattingSettings().getMaxLineWidth(); + } + + private int getTabSize() { + return sharedSettings.getFormattingSettings().getTabSize(); + } + + private boolean isInsertSpaces() { + return sharedSettings.getFormattingSettings().isInsertSpaces(); + } + + private boolean isSpaceBeforeEmptyCloseTag() { + return sharedSettings.getFormattingSettings().isSpaceBeforeEmptyCloseTag(); + } + + private boolean isPreserveAttributeLineBreaks() { + return sharedSettings.getFormattingSettings().isPreserveAttributeLineBreaks(); + } + + private boolean isSplitAttributes() { + return sharedSettings.getFormattingSettings().isSplitAttributes(); + } + + private int getSplitAttributesIndentSize() { + return sharedSettings.getFormattingSettings().getSplitAttributesIndentSize(); + } + + private boolean isTrimFinalNewlines() { + return sharedSettings.getFormattingSettings().isTrimFinalNewlines(); + } + + private boolean isInsertFinalNewline() { + return sharedSettings.getFormattingSettings().isInsertFinalNewline(); + } +} \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormattingConstraints.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormattingConstraints.java new file mode 100644 index 0000000000..17ad8f33b7 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/format/XMLFormattingConstraints.java @@ -0,0 +1,63 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format; + +/** + * XML formatting constraints. + * + * @author Angelo ZERR + * + */ +public class XMLFormattingConstraints { + + private FormatElementCategory formatElementCategory; + + private int availableLineWidth = 0; + private int indentLevel = 0; + + /** + * Initializes the values in this formatting constraint with values from + * constraints + * + * @param constraints cannot be null + */ + public void copyConstraints(XMLFormattingConstraints constraints) { + setFormatElementCategory(constraints.getFormatElementCategory()); + setAvailableLineWidth(constraints.getAvailableLineWidth()); + setIndentLevel(constraints.getIndentLevel()); + } + + public FormatElementCategory getFormatElementCategory() { + return formatElementCategory; + } + + public void setFormatElementCategory(FormatElementCategory formatElementCategory) { + this.formatElementCategory = formatElementCategory; + } + + public int getAvailableLineWidth() { + return availableLineWidth; + } + + public void setAvailableLineWidth(int availableLineWidth) { + this.availableLineWidth = availableLineWidth; + } + + public int getIndentLevel() { + return indentLevel; + } + + public void setIndentLevel(int indentLevel) { + this.indentLevel = indentLevel; + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/LSPFormattingOptions.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/LSPFormattingOptions.java new file mode 100644 index 0000000000..6f24fce828 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/LSPFormattingOptions.java @@ -0,0 +1,105 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.settings; + +import java.util.LinkedHashMap; + +import org.eclipse.lsp4j.FormattingOptions; + +/** + * Value-object describing what options formatting should use. + * + *

+ * This class redefines the LSP4J {@link FormattingOptions} without extending + * {@link LinkedHashMap} (public class FormattingOptions extends + * LinkedHashMap> {). + *

+ * + *

+ * FormattingOptions can support only String, Number, Boolean, but not Array of + * String. It is the reason why LemMinX redefines LSPFormattingOptions. + *

+ */ +public class LSPFormattingOptions { + + /** + * Size of a tab in spaces. + */ + private int tabSize; + + /** + * Prefer spaces over tabs. + */ + private boolean insertSpaces; + + /** + * Trim trailing whitespace on a line. + * + * @since 3.15.0 + */ + private boolean trimTrailingWhitespace; + + /** + * Insert a newline character at the end of the file if one does not exist. + * + * @since 3.15.0 + */ + private boolean insertFinalNewline; + + /** + * Trim all newlines after the final newline at the end of the file. + * + * @since 3.15.0 + */ + private boolean trimFinalNewlines; + + public int getTabSize() { + return tabSize; + } + + public void setTabSize(int tabSize) { + this.tabSize = tabSize; + } + + public boolean isInsertSpaces() { + return insertSpaces; + } + + public void setInsertSpaces(boolean insertSpaces) { + this.insertSpaces = insertSpaces; + } + + public boolean isTrimTrailingWhitespace() { + return trimTrailingWhitespace; + } + + public void setTrimTrailingWhitespace(boolean trimTrailingWhitespace) { + this.trimTrailingWhitespace = trimTrailingWhitespace; + } + + public boolean isInsertFinalNewline() { + return insertFinalNewline; + } + + public void setInsertFinalNewline(boolean insertFinalNewline) { + this.insertFinalNewline = insertFinalNewline; + } + + public boolean isTrimFinalNewlines() { + return trimFinalNewlines; + } + + public void setTrimFinalNewlines(boolean trimFinalNewlines) { + this.trimFinalNewlines = trimFinalNewlines; + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/XMLFormattingOptions.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/XMLFormattingOptions.java index fec3bde27f..2ad4d47549 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/XMLFormattingOptions.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/XMLFormattingOptions.java @@ -12,6 +12,10 @@ */ package org.eclipse.lemminx.settings; +import java.util.List; + +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.services.format.FormatElementCategory; import org.eclipse.lsp4j.FormattingOptions; /** @@ -20,7 +24,7 @@ * * All defaults should be set here to eventually be overridden if needed. */ -public class XMLFormattingOptions extends FormattingOptions { +public class XMLFormattingOptions extends org.eclipse.lemminx.settings.LSPFormattingOptions { public static final String DEFAULT_QUOTATION = "\""; public static final int DEFAULT_PRESERVER_NEW_LINES = 2; @@ -31,22 +35,23 @@ public class XMLFormattingOptions extends FormattingOptions { public static final int DEFAULT_SPLIT_ATTRIBUTES_INDENT_SIZE = 2; public static final boolean DEFAULT_CLOSING_BRACKET_NEW_LINE = false; - // All possible keys - private static final String SPLIT_ATTRIBUTES = "splitAttributes"; - private static final String JOIN_CDATA_LINES = "joinCDATALines"; - private static final String FORMAT_COMMENTS = "formatComments"; - private static final String JOIN_COMMENT_LINES = "joinCommentLines"; - private static final String ENABLED = "enabled"; - private static final String SPACE_BEFORE_EMPTY_CLOSE_TAG = "spaceBeforeEmptyCloseTag"; - private static final String JOIN_CONTENT_LINES = "joinContentLines"; - private static final String PRESERVED_NEWLINES = "preservedNewlines"; - private static final String TRIM_FINAL_NEWLINES = "trimFinalNewlines"; - private static final String TRIM_TRAILING_WHITESPACE = "trimTrailingWhitespace"; - private static final String ENFORCE_QUOTE_STYLE = "enforceQuoteStyle"; - private static final String PRESERVE_ATTR_LINE_BREAKS = "preserveAttributeLineBreaks"; - private static final String PRESERVE_EMPTY_CONTENT = "preserveEmptyContent"; - private static final String SPLIT_ATTRIBUTES_INDENT_SIZE = "splitAttributesIndentSize"; - private static final String CLOSING_BRACKET_NEW_LINE = "closingBracketNewLine"; + private boolean experimental; + private int maxLineWidth; + + private boolean splitAttributes; + private boolean joinCDATALines; + private boolean formatComments; + private boolean joinCommentLines; + private boolean enabled; + private boolean spaceBeforeEmptyCloseTag; + private boolean joinContentLines; + private int preservedNewlines; + private String enforceQuoteStyle; + + private boolean preserveAttributeLineBreaks; + private boolean preserveEmptyContent; + private int splitAttributesIndentSize; + private boolean closingBracketNewLine; /** * Options for formatting empty elements. @@ -88,8 +93,7 @@ public class XMLFormattingOptions extends FormattingOptions { * * * - *
  • {@link #ignore} : keeps the original XML content for empty elements. - *
  • + *
  • {@link #ignore} : keeps the original XML content for empty elements.
  • * * */ @@ -97,7 +101,12 @@ public static enum EmptyElements { expand, collapse, ignore; } - private static final String EMPTY_ELEMENTS = "emptyElements"; + private String emptyElements; + private List preserveSpace; + + private boolean grammarAwareFormatting; + + private String xsiSchemaLocationSplit; public XMLFormattingOptions() { this(false); @@ -119,18 +128,23 @@ public XMLFormattingOptions(boolean initializeDefaults) { private void initializeDefaultSettings() { super.setTabSize(DEFAULT_TAB_SIZE); super.setInsertSpaces(true); + super.setTrimFinalNewlines(true); this.setSplitAttributes(false); this.setJoinCDATALines(false); this.setFormatComments(true); this.setJoinCommentLines(false); this.setJoinContentLines(false); this.setEnabled(true); + this.setExperimental(false); + this.setMaxLineWidth(80); this.setSpaceBeforeEmptyCloseTag(true); this.setPreserveEmptyContent(false); this.setPreservedNewlines(DEFAULT_PRESERVER_NEW_LINES); this.setEmptyElement(EmptyElements.ignore); this.setSplitAttributesIndentSize(DEFAULT_SPLIT_ATTRIBUTES_INDENT_SIZE); this.setClosingBracketNewLine(DEFAULT_CLOSING_BRACKET_NEW_LINE); + this.setPreserveAttributeLineBreaks(DEFAULT_PRESERVE_ATTR_LINE_BREAKS); + this.setGrammarAwareFormatting(true); } public XMLFormattingOptions(int tabSize, boolean insertSpaces, boolean initializeDefaultSettings) { @@ -157,128 +171,120 @@ public XMLFormattingOptions(FormattingOptions options) { } public boolean isSplitAttributes() { - final Boolean value = this.getBoolean(XMLFormattingOptions.SPLIT_ATTRIBUTES); - if ((value != null)) { - return (value).booleanValue(); - } else { - return false; - } + return splitAttributes; } public void setSplitAttributes(final boolean splitAttributes) { - this.putBoolean(XMLFormattingOptions.SPLIT_ATTRIBUTES, Boolean.valueOf(splitAttributes)); + this.splitAttributes = splitAttributes; } public boolean isJoinCDATALines() { - final Boolean value = this.getBoolean(XMLFormattingOptions.JOIN_CDATA_LINES); - if ((value != null)) { - return (value).booleanValue(); - } else { - return false; - } + return joinCDATALines; } public void setJoinCDATALines(final boolean joinCDATALines) { - this.putBoolean(XMLFormattingOptions.JOIN_CDATA_LINES, Boolean.valueOf(joinCDATALines)); + this.joinCDATALines = joinCDATALines; } public boolean isFormatComments() { - final Boolean value = this.getBoolean(XMLFormattingOptions.FORMAT_COMMENTS); - if ((value != null)) { - return (value).booleanValue(); - } else { - return false; - } + return formatComments; } public void setFormatComments(final boolean formatComments) { - this.putBoolean(XMLFormattingOptions.FORMAT_COMMENTS, Boolean.valueOf(formatComments)); + this.formatComments = formatComments; } public boolean isJoinCommentLines() { - final Boolean value = this.getBoolean(XMLFormattingOptions.JOIN_COMMENT_LINES); - if ((value != null)) { - return (value).booleanValue(); - } else { - return false; - } + return joinCommentLines; } public void setJoinCommentLines(final boolean joinCommentLines) { - this.putBoolean(XMLFormattingOptions.JOIN_COMMENT_LINES, Boolean.valueOf(joinCommentLines)); + this.joinCommentLines = joinCommentLines; } public boolean isJoinContentLines() { - final Boolean value = this.getBoolean(XMLFormattingOptions.JOIN_CONTENT_LINES); - if ((value != null)) { - return (value).booleanValue(); - } else { - return false; - } + return joinContentLines; } public void setJoinContentLines(final boolean joinContentLines) { - this.putBoolean(XMLFormattingOptions.JOIN_CONTENT_LINES, Boolean.valueOf(joinContentLines)); + this.joinContentLines = joinContentLines; + } + + /** + * Returns true if the experimental formatter must be used and false otherwise. + * + * @return true if the experimental formatter must be used and false otherwise. + */ + public boolean isExperimental() { + return experimental; + } + + /** + * Set true if the experimental formatter must be used and false otherwise. + * + * @param experimental true if the experimental formatter must be used and false + * otherwise. + */ + public void setExperimental(final boolean experimental) { + this.experimental = experimental; + } + + /** + * Sets the value of max line width. + * + * @param maxLineWidth the new value for max line width. + */ + public void setMaxLineWidth(int maxLineWidth) { + this.maxLineWidth = maxLineWidth; + } + + /** + * Returns the value of max line width or zero if it was set to a negative value + * + * @return the value of max line width or zero if it was set to a negative value + */ + public int getMaxLineWidth() { + return maxLineWidth < 0 ? 0 : maxLineWidth; } public boolean isEnabled() { - final Boolean value = this.getBoolean(XMLFormattingOptions.ENABLED); - if ((value != null)) { - return (value).booleanValue(); - } else { - return false; - } + return enabled; } public void setEnabled(final boolean enabled) { - this.putBoolean(XMLFormattingOptions.ENABLED, Boolean.valueOf(enabled)); + this.enabled = enabled; } public void setSpaceBeforeEmptyCloseTag(final boolean spaceBeforeEmptyCloseTag) { - this.putBoolean(XMLFormattingOptions.SPACE_BEFORE_EMPTY_CLOSE_TAG, Boolean.valueOf(spaceBeforeEmptyCloseTag)); + this.spaceBeforeEmptyCloseTag = spaceBeforeEmptyCloseTag; } public boolean isSpaceBeforeEmptyCloseTag() { - final Boolean value = this.getBoolean(XMLFormattingOptions.SPACE_BEFORE_EMPTY_CLOSE_TAG); - if ((value != null)) { - return (value).booleanValue(); - } else { - return true; - } + return spaceBeforeEmptyCloseTag; } public void setPreserveEmptyContent(final boolean preserveEmptyContent) { - this.putBoolean(XMLFormattingOptions.PRESERVE_EMPTY_CONTENT, Boolean.valueOf(preserveEmptyContent)); + this.preserveEmptyContent = preserveEmptyContent; } public boolean isPreserveEmptyContent() { - final Boolean value = this.getBoolean(XMLFormattingOptions.PRESERVE_EMPTY_CONTENT); - if ((value != null)) { - return (value).booleanValue(); - } else { - return true; - } + return preserveEmptyContent; } public void setPreservedNewlines(final int preservedNewlines) { - this.putNumber(XMLFormattingOptions.PRESERVED_NEWLINES, preservedNewlines); + this.preservedNewlines = preservedNewlines; } public int getPreservedNewlines() { - final Number value = this.getNumber(XMLFormattingOptions.PRESERVED_NEWLINES); - if ((value != null)) { - return value.intValue(); - } else { - return 2; - } + return preservedNewlines; } public void setEmptyElement(EmptyElements emptyElement) { - this.putString(XMLFormattingOptions.EMPTY_ELEMENTS, emptyElement.name()); + this.emptyElements = emptyElement.name(); } public EmptyElements getEmptyElements() { - String value = this.getString(XMLFormattingOptions.EMPTY_ELEMENTS); + String value = emptyElements; if ((value != null)) { try { return EmptyElements.valueOf(value); @@ -288,32 +294,12 @@ public EmptyElements getEmptyElements() { return EmptyElements.ignore; } - /** - * Returns the value of trimFinalNewlines. - * - * If the trimFinalNewlines does not exist, defaults to true. - */ - @Override - public boolean isTrimFinalNewlines() { - final Boolean value = this.getBoolean(TRIM_FINAL_NEWLINES); - return (value == null) ? true: value; - } - - public void setTrimTrailingWhitespace(boolean newValue) { - this.putBoolean(TRIM_TRAILING_WHITESPACE, newValue); - } - - public boolean isTrimTrailingWhitespace() { - final Boolean value = this.getBoolean(TRIM_TRAILING_WHITESPACE); - return (value == null) ? DEFAULT_TRIM_TRAILING_SPACES: value; - } - public void setEnforceQuoteStyle(EnforceQuoteStyle enforce) { - this.putString(XMLFormattingOptions.ENFORCE_QUOTE_STYLE, enforce.name()); + this.enforceQuoteStyle = enforce.name(); } public EnforceQuoteStyle getEnforceQuoteStyle() { - String value = this.getString(XMLFormattingOptions.ENFORCE_QUOTE_STYLE); + String value = this.enforceQuoteStyle; EnforceQuoteStyle enforceStyle = null; try { @@ -328,8 +314,8 @@ public EnforceQuoteStyle getEnforceQuoteStyle() { /** * Sets the value of preserveAttrLineBreaks */ - public void setPreserveAttrLineBreaks(final boolean preserveAttrLineBreaks) { - this.putBoolean(XMLFormattingOptions.PRESERVE_ATTR_LINE_BREAKS, Boolean.valueOf(preserveAttrLineBreaks)); + public void setPreserveAttributeLineBreaks(final boolean preserveAttributeLineBreaks) { + this.preserveAttributeLineBreaks = preserveAttributeLineBreaks; } /** @@ -337,18 +323,12 @@ public void setPreserveAttrLineBreaks(final boolean preserveAttrLineBreaks) { * * @return the value of preserveAttrLineBreaks */ - public boolean isPreserveAttrLineBreaks() { + public boolean isPreserveAttributeLineBreaks() { if (this.isSplitAttributes()) { // splitAttributes overrides preserveAttrLineBreaks return false; } - - final Boolean value = this.getBoolean(XMLFormattingOptions.PRESERVE_ATTR_LINE_BREAKS); - if ((value != null)) { - return (value).booleanValue(); - } else { - return XMLFormattingOptions.DEFAULT_PRESERVE_ATTR_LINE_BREAKS; - } + return preserveAttributeLineBreaks; } /** @@ -357,41 +337,31 @@ public boolean isPreserveAttrLineBreaks() { * @param splitAttributesIndentSize the new value for splitAttributesIndentSize */ public void setSplitAttributesIndentSize(int splitAttributesIndentSize) { - this.putNumber(SPLIT_ATTRIBUTES_INDENT_SIZE, Integer.valueOf(splitAttributesIndentSize)); + this.splitAttributesIndentSize = splitAttributesIndentSize; } /** - * Returns the value of splitAttributesIndentSize or zero if it was set to a negative value + * Returns the value of splitAttributesIndentSize or zero if it was set to a + * negative value * - * @return the value of splitAttributesIndentSize or zero if it was set to a negative value + * @return the value of splitAttributesIndentSize or zero if it was set to a + * negative value */ public int getSplitAttributesIndentSize() { - int splitAttributesIndentSize = getNumber(SPLIT_ATTRIBUTES_INDENT_SIZE).intValue(); + int splitAttributesIndentSize = this.splitAttributesIndentSize; return splitAttributesIndentSize < 0 ? 0 : splitAttributesIndentSize; } - public XMLFormattingOptions merge(FormattingOptions formattingOptions) { - formattingOptions.entrySet().stream().forEach(entry -> { - this.put(entry.getKey(), entry.getValue()); - }); - return this; - } - - public static XMLFormattingOptions create(FormattingOptions options, FormattingOptions sharedFormattingOptions) { - return new XMLFormattingOptions(options).merge(sharedFormattingOptions); - } - /** * Returns the value of closingBracketNewLine or false if it was set to null * - * A setting for enabling the XML formatter to move the closing bracket of a tag with at least 2 attributes - * to a new line. + * A setting for enabling the XML formatter to move the closing bracket of a tag + * with at least 2 attributes to a new line. * * @return the value of closingBracketNewLine or false if it was set to null */ public boolean getClosingBracketNewLine() { - final Boolean value = this.getBoolean(CLOSING_BRACKET_NEW_LINE); - return (value == null) ? DEFAULT_CLOSING_BRACKET_NEW_LINE: value; + return closingBracketNewLine; } /** @@ -400,7 +370,88 @@ public boolean getClosingBracketNewLine() { * @param closingBracketNewLine the new value for closingBracketNewLine */ public void setClosingBracketNewLine(final boolean closingBracketNewLine) { - this.putBoolean(XMLFormattingOptions.CLOSING_BRACKET_NEW_LINE, Boolean.valueOf(closingBracketNewLine)); + this.closingBracketNewLine = closingBracketNewLine; } + /** + * Sets the element name list which must preserve space. + * + * @param preserveSpace the element name list which must preserve space. + */ + public void setPreserveSpace(List preserveSpace) { + this.preserveSpace = preserveSpace; + } + + /** + * Returns the element name list which must preserve space. + * + * @return the element name list which must preserve space. + */ + public List getPreserveSpace() { + return preserveSpace; + } + + public boolean isGrammarAwareFormatting() { + return grammarAwareFormatting; + } + + public void setGrammarAwareFormatting(boolean grammarAwareFormatting) { + this.grammarAwareFormatting = grammarAwareFormatting; + } + + public String getXsiSchemaLocationSplit() { + return xsiSchemaLocationSplit; + } + + public void setXsiSchemaLocationSplit(String xsiSchemaLocationSplit) { + this.xsiSchemaLocationSplit = xsiSchemaLocationSplit; + } + + public XMLFormattingOptions merge(XMLFormattingOptions formattingOptions) { + setTabSize(formattingOptions.getTabSize()); + setInsertFinalNewline(formattingOptions.isInsertFinalNewline()); + setInsertSpaces(formattingOptions.isInsertSpaces()); + setTrimFinalNewlines(formattingOptions.isTrimFinalNewlines()); + setTrimTrailingWhitespace(formattingOptions.isTrimTrailingWhitespace()); + setExperimental(formattingOptions.isExperimental()); + setMaxLineWidth(formattingOptions.getMaxLineWidth()); + setSplitAttributes(formattingOptions.isSplitAttributes()); + setJoinCDATALines(formattingOptions.isJoinCDATALines()); + setFormatComments(formattingOptions.isFormatComments()); + setJoinCommentLines(formattingOptions.isJoinCommentLines()); + setEnabled(formattingOptions.isEnabled()); + setSpaceBeforeEmptyCloseTag(formattingOptions.isSpaceBeforeEmptyCloseTag()); + setJoinContentLines(formattingOptions.isJoinContentLines()); + setPreservedNewlines(formattingOptions.getPreservedNewlines()); + setEnforceQuoteStyle(formattingOptions.getEnforceQuoteStyle()); + setPreserveAttributeLineBreaks(formattingOptions.isPreserveAttributeLineBreaks()); + setPreserveEmptyContent(formattingOptions.isPreserveEmptyContent()); + setSplitAttributesIndentSize(formattingOptions.getSplitAttributesIndentSize()); + setClosingBracketNewLine(formattingOptions.getClosingBracketNewLine()); + setEmptyElement(formattingOptions.getEmptyElements()); + setPreserveSpace(formattingOptions.getPreserveSpace()); + setGrammarAwareFormatting(formattingOptions.isGrammarAwareFormatting()); + setXsiSchemaLocationSplit(formattingOptions.getXsiSchemaLocationSplit()); + return this; + } + + public XMLFormattingOptions merge(FormattingOptions formattingOptions) { + setTabSize(formattingOptions.getTabSize()); + setInsertFinalNewline(formattingOptions.isInsertFinalNewline()); + setInsertSpaces(formattingOptions.isInsertSpaces()); + setTrimFinalNewlines(formattingOptions.isTrimFinalNewlines()); + setTrimTrailingWhitespace(formattingOptions.isTrimTrailingWhitespace()); + return this; + } + + public FormatElementCategory getFormatElementCategory(DOMElement element) { + if (preserveSpace != null) { + for (String elementName : preserveSpace) { + if (elementName.equals(element.getTagName())) { + return FormatElementCategory.PreserveSpace; + } + } + } + return null; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/XMLPreferences.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/XMLPreferences.java index 0a52e29e5d..ca15b0acf2 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/XMLPreferences.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/XMLPreferences.java @@ -11,20 +11,24 @@ *******************************************************************************/ package org.eclipse.lemminx.settings; +import java.util.List; + /** * XML Preferences * */ public class XMLPreferences { - + public static final QuoteStyle DEFAULT_QUOTE_STYLE = QuoteStyle.doubleQuotes; - + public static final SchemaDocumentationType DEFAULT_SCHEMA_DOCUMENTATION_TYPE = SchemaDocumentationType.all; private QuoteStyle quoteStyle; private SchemaDocumentationType showSchemaDocumentationType; + private List preserveSpace; + public XMLPreferences() { this.quoteStyle = DEFAULT_QUOTE_STYLE; this.showSchemaDocumentationType = DEFAULT_SCHEMA_DOCUMENTATION_TYPE; @@ -97,4 +101,5 @@ public void merge(XMLPreferences newPreferences) { this.setQuoteStyle(newPreferences.getQuoteStyle()); this.setShowSchemaDocumentationType(newPreferences.getShowSchemaDocumentationType()); } + } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/JSONUtility.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/JSONUtility.java index 08a245bd8a..36341100be 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/JSONUtility.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/JSONUtility.java @@ -13,19 +13,20 @@ *******************************************************************************/ package org.eclipse.lemminx.utils; +import org.eclipse.lemminx.settings.FaultTolerantTypeAdapterFactory; +import org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; -import org.eclipse.lemminx.settings.FaultTolerantTypeAdapterFactory; -import org.eclipse.lsp4j.jsonrpc.json.adapters.EitherTypeAdapter; - /** * JSONUtility */ public class JSONUtility { - private JSONUtility(){} + private JSONUtility() { + } public static T toModel(Object object, Class clazz) { if (object == null) { diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java index aaacd15f4d..4a792bc1b4 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java @@ -12,6 +12,7 @@ */ package org.eclipse.lemminx; +import static org.eclipse.lemminx.services.format.TextEditUtils.applyEdits; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertIterableEquals; @@ -1395,6 +1396,13 @@ public static void assertFormat(String unformatted, String expected, SharedSetti public static void assertFormat(XMLLanguageService languageService, String unformatted, String expected, SharedSettings sharedSettings, String uri, Boolean considerRangeFormat) throws BadLocationException { + assertFormat(languageService, unformatted, expected, sharedSettings, uri, considerRangeFormat, + (TextEdit[]) null); + } + + public static void assertFormat(XMLLanguageService languageService, String unformatted, String expected, + SharedSettings sharedSettings, String uri, Boolean considerRangeFormat, TextEdit... expectedEdits) + throws BadLocationException { Range range = null; int rangeStart = considerRangeFormat ? unformatted.indexOf('|') : -1; int rangeEnd = considerRangeFormat ? unformatted.lastIndexOf('|') : -1; @@ -1409,23 +1417,20 @@ public static void assertFormat(XMLLanguageService languageService, String unfor } TextDocument document = new TextDocument(unformatted, uri); + document.setIncremental(true); + DOMDocument xmlDocument = DOMParser.getInstance().parse(document, null); + if (languageService == null) { languageService = new XMLLanguageService(); } - List edits = languageService.format(document, range, sharedSettings); + List edits = languageService.format(xmlDocument, range, sharedSettings); - String formatted = edits.stream().map(edit -> edit.getNewText()).collect(Collectors.joining("")); - - Range textEditRange = edits.get(0).getRange(); - int textEditStartOffset = document.offsetAt(textEditRange.getStart()); - int textEditEndOffset = document.offsetAt(textEditRange.getEnd()) + 1; + String formatted = applyEdits(document, edits); + assertEquals(expected, formatted); - if (textEditStartOffset != -1 && textEditEndOffset != -1) { - formatted = unformatted.substring(0, textEditStartOffset) + formatted - + unformatted.substring(textEditEndOffset - 1, unformatted.length()); + if (expectedEdits != null) { + Assertions.assertArrayEquals(expectedEdits, edits.toArray(new TextEdit[0])); } - - assertEquals(expected, formatted); } // ------------------- Rename assert diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/IncrementalParsingTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/commons/IncrementalParsingTest.java similarity index 59% rename from org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/IncrementalParsingTest.java rename to org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/commons/IncrementalParsingTest.java index 07d9435282..341e3b49d8 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/IncrementalParsingTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/commons/IncrementalParsingTest.java @@ -9,51 +9,49 @@ * Contributors: * Red Hat Inc. - initial API and implementation *******************************************************************************/ -package org.eclipse.lemminx.services; +package org.eclipse.lemminx.commons; + +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; -import org.eclipse.lemminx.commons.TextDocument; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextDocumentContentChangeEvent; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; /** * IncrementalParsingTest */ public class IncrementalParsingTest { - String textTemplate = - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + - "\r\n" + + + String textTemplate = "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // "\r\n"; @Test public void testBasicChange() { - String text = - "<>\r\n" + /// <-- inserting 'a' in tag name - " \r\n" + - " \r\n" + - "\r\n"; - - String expectedText = - "\r\n" + /// <-- inserted 'a' in tag name - " \r\n" + - " \r\n" + - "\r\n"; + String text = "<>\r\n" + // /// <-- inserting 'a' in tag name + " \r\n" + // + " \r\n" + // + "\r\n"; + + String expectedText = "\r\n" + // /// <-- inserted 'a' in tag name + " \r\n" + // + " \r\n" + // + "\r\n"; TextDocument document = new TextDocument(text, "uri"); document.setIncremental(true); - Range range1 = new Range(new Position(0, 1), new Position(0,1)); + Range range1 = new Range(new Position(0, 1), new Position(0, 1)); TextDocumentContentChangeEvent change1 = new TextDocumentContentChangeEvent(range1, 0, "a"); - + ArrayList changes = new ArrayList<>(); changes.add(change1); @@ -65,24 +63,22 @@ public void testBasicChange() { @Test public void testBasicChangeWord() { - String text = - "<>\r\n" + /// <-- inserting 'a' in tag name - " \r\n" + - " \r\n" + - "\r\n"; - - String expectedText = - "\r\n" + /// <-- inserted 'a' in tag name - " \r\n" + - " \r\n" + - "\r\n"; + String text = "<>\r\n" + // /// <-- inserting 'a' in tag name + " \r\n" + // + " \r\n" + // + "\r\n"; + + String expectedText = "\r\n" + // /// <-- inserted 'a' in tag name + " \r\n" + // + " \r\n" + // + "\r\n"; TextDocument document = new TextDocument(text, "uri"); document.setIncremental(true); - Range range1 = new Range(new Position(0, 1), new Position(0,1)); + Range range1 = new Range(new Position(0, 1), new Position(0, 1)); TextDocumentContentChangeEvent change1 = new TextDocumentContentChangeEvent(range1, 0, "aaa"); - + ArrayList changes = new ArrayList<>(); changes.add(change1); @@ -93,24 +89,22 @@ public void testBasicChangeWord() { @Test public void testChangeReplaceRange() { - String text = - "\r\n" + /// <-- inserting 'a' in tag name - " \r\n" + - " \r\n" + - "\r\n"; - - String expectedText = - "\r\n" + /// <-- inserted 'a' in tag name - " \r\n" + - " \r\n" + - "\r\n"; + String text = "\r\n" + // /// <-- inserting 'a' in tag name + " \r\n" + // + " \r\n" + // + "\r\n"; + + String expectedText = "\r\n" + // /// <-- inserted 'a' in tag name + " \r\n" + // + " \r\n" + // + "\r\n"; TextDocument document = new TextDocument(text, "uri"); document.setIncremental(true); - Range range1 = new Range(new Position(0, 1), new Position(0,4)); + Range range1 = new Range(new Position(0, 1), new Position(0, 4)); TextDocumentContentChangeEvent change1 = new TextDocumentContentChangeEvent(range1, 3, "aaa"); - + ArrayList changes = new ArrayList<>(); changes.add(change1); @@ -121,27 +115,25 @@ public void testChangeReplaceRange() { @Test public void testBasicChangeMultipleChanges() { - String text = - "<>\r\n" + // <-- inserting 'a' in tag name - " \r\n" + - " \r\n" + // <-- inserting 'b' in tag name - "\r\n"; - - String expectedText = - "\r\n" + // <-- inserted 'a' in tag name - " \r\n" + - " \r\n" + // <-- inserted 'b' in tag name - "\r\n"; + String text = "<>\r\n" + // // <-- inserting 'a' in tag name + " \r\n" + // + " \r\n" + // // <-- inserting 'b' in tag name + "\r\n"; + + String expectedText = "\r\n" + // // <-- inserted 'a' in tag name + " \r\n" + // + " \r\n" + // // <-- inserted 'b' in tag name + "\r\n"; TextDocument document = new TextDocument(text, "uri"); document.setIncremental(true); - Range range1 = new Range(new Position(0, 1), new Position(0,1)); + Range range1 = new Range(new Position(0, 1), new Position(0, 1)); TextDocumentContentChangeEvent change1 = new TextDocumentContentChangeEvent(range1, 0, "a"); - Range range2 = new Range(new Position(2, 4), new Position(2,4)); + Range range2 = new Range(new Position(2, 4), new Position(2, 4)); TextDocumentContentChangeEvent change2 = new TextDocumentContentChangeEvent(range2, 0, "b"); - + ArrayList changes = new ArrayList<>(); // The order they are added in is backwards with the largest offset being first changes.add(change2); @@ -155,27 +147,25 @@ public void testBasicChangeMultipleChanges() { @Test public void testBasicChangeMultipleChangesReplaceRange() { - String text = - "\r\n" + // <-- inserting 'a' in tag name - " \r\n" + - " \r\n" + // <-- inserting 'b' in tag name - "\r\n"; - - String expectedText = - "\r\n" + // <-- inserted 'a' in tag name - " \r\n" + - " \r\n" + // <-- inserted 'b' in tag name - "\r\n"; + String text = "\r\n" + // // <-- inserting 'a' in tag name + " \r\n" + // + " \r\n" + // // <-- inserting 'b' in tag name + "\r\n"; + + String expectedText = "\r\n" + // // <-- inserted 'a' in tag name + " \r\n" + // + " \r\n" + // // <-- inserted 'b' in tag name + "\r\n"; TextDocument document = new TextDocument(text, "uri"); document.setIncremental(true); - Range range1 = new Range(new Position(0, 1), new Position(0,4)); + Range range1 = new Range(new Position(0, 1), new Position(0, 4)); TextDocumentContentChangeEvent change1 = new TextDocumentContentChangeEvent(range1, 3, "a"); - Range range2 = new Range(new Position(2, 4), new Position(2,7)); + Range range2 = new Range(new Position(2, 4), new Position(2, 7)); TextDocumentContentChangeEvent change2 = new TextDocumentContentChangeEvent(range2, 3, "b"); - + ArrayList changes = new ArrayList<>(); // The order they are added in is backwards with the largest offset being first changes.add(change2); @@ -189,24 +179,22 @@ public void testBasicChangeMultipleChangesReplaceRange() { @Test public void testBasicDeletionChange() { - String text = - "\r\n" + /// <-- deleting 'a' in tag name - " \r\n" + - " \r\n" + - "\r\n"; - - String expectedText = - "\r\n" + /// <-- deleted 'a' in tag name - " \r\n" + - " \r\n" + - "\r\n"; + String text = "\r\n" + // /// <-- deleting 'a' in tag name + " \r\n" + // + " \r\n" + // + "\r\n"; + + String expectedText = "\r\n" + // /// <-- deleted 'a' in tag name + " \r\n" + // + " \r\n" + // + "\r\n"; TextDocument document = new TextDocument(text, "uri"); document.setIncremental(true); - Range range1 = new Range(new Position(0, 2), new Position(0,3)); + Range range1 = new Range(new Position(0, 2), new Position(0, 3)); TextDocumentContentChangeEvent change1 = new TextDocumentContentChangeEvent(range1, 1, ""); - + ArrayList changes = new ArrayList<>(); changes.add(change1); @@ -218,29 +206,27 @@ public void testBasicDeletionChange() { @Test public void testMultipleDeletionChanges() { - String text = - "\r\n" + /// <-- deleting 'a' in tag name - " \r\n" + - " \r\n" + - "\r\n"; - - String expectedText = - "\r\n" + /// <-- deleted 'a' in tag name - " \r\n" + - " \r\n" + - "\r\n"; + String text = "\r\n" + // /// <-- deleting 'a' in tag name + " \r\n" + // + " \r\n" + // + "\r\n"; + + String expectedText = "\r\n" + // /// <-- deleted 'a' in tag name + " \r\n" + // + " \r\n" + // + "\r\n"; TextDocument document = new TextDocument(text, "uri"); document.setIncremental(true); - Range range1 = new Range(new Position(0, 2), new Position(0,3)); + Range range1 = new Range(new Position(0, 2), new Position(0, 3)); TextDocumentContentChangeEvent change1 = new TextDocumentContentChangeEvent(range1, 1, ""); - Range range2 = new Range(new Position(2, 5), new Position(2,6)); + Range range2 = new Range(new Position(2, 5), new Position(2, 6)); TextDocumentContentChangeEvent change2 = new TextDocumentContentChangeEvent(range2, 1, ""); - + ArrayList changes = new ArrayList<>(); - changes.add(change2); + changes.add(change2); changes.add(change1); document.update(changes); diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsi/XSIFormatterExperimentalTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsi/XSIFormatterExperimentalTest.java new file mode 100644 index 0000000000..4b8e4f2382 --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/xsi/XSIFormatterExperimentalTest.java @@ -0,0 +1,185 @@ +/******************************************************************************* +* Copyright (c) 2022 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* are made available under the terms of the Eclipse Public License v2.0 +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.extensions.xsi; + +import static org.eclipse.lemminx.XMLAssert.assertFormat; + +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.extensions.xsi.settings.XSISchemaLocationSplit; +import org.eclipse.lemminx.settings.SharedSettings; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * XSI xsi:schemaLocation formatter tests + * + */ +public class XSIFormatterExperimentalTest { + + @Test + public void xsiSchemaLocationSplitNone() throws BadLocationException { + // Default + SharedSettings settings = createSettings(); + String content = "\r\n" + // + " "; + String expected = content; + assertFormat(content, expected, settings); + + // None + settings = createSettings(); + XSISchemaLocationSplit.setSplit(XSISchemaLocationSplit.none, settings.getFormattingSettings()); + assertFormat(content, expected, settings); + } + + @Test + public void xsiSchemaLocationSplitOnElement() throws BadLocationException { + SharedSettings settings = createSettings(); + XSISchemaLocationSplit.setSplit(XSISchemaLocationSplit.onElement, settings.getFormattingSettings()); + String content = "\r\n" + // + " "; + String expected = "\r\n" + // + "\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void xsiSchemaLocationSplitOnPair() throws BadLocationException { + SharedSettings settings = createSettings(); + XSISchemaLocationSplit.setSplit(XSISchemaLocationSplit.onPair, settings.getFormattingSettings()); + String content = "\r\n" + // + "\r\n" + + // + "\r\n" + // + ""; + String expected = "\r\n" + // + "\r\n" + + // + "\r\n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void xsiSchemaLocationEmpty() throws BadLocationException { + SharedSettings settings = createSettings(); + XSISchemaLocationSplit.setSplit(XSISchemaLocationSplit.onElement, settings.getFormattingSettings()); + String content = "\r\n" + // + ""; + assertFormat(content, content, settings); + + content = "\r\n" + // + ""; + assertFormat(content, content, settings); + + content = "\r\n" + // + ""; + assertFormat(content, content, settings); + } + + @Disabled + @Test + public void xsiSchemaLocationSplitOnElementWithTabs() throws BadLocationException { + SharedSettings settings = createSettings(); + settings.getFormattingSettings().setTabSize(4); + settings.getFormattingSettings().setInsertSpaces(false); + XSISchemaLocationSplit.setSplit(XSISchemaLocationSplit.onElement, settings.getFormattingSettings()); + String content = "\r\n" + // + "\r\n" + + // + "\r\n" + // + ""; + String expected = "\r\n" + // + "\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected, settings); + } + + private static SharedSettings createSettings() { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setInsertSpaces(true); + settings.getFormattingSettings().setTabSize(2); + settings.getFormattingSettings().setSplitAttributes(true); + settings.getFormattingSettings().setPreserveEmptyContent(true); + // Force to "experimental" formatter + settings.getFormattingSettings().setExperimental(true); + return settings; + } +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/TextEditUtilsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/TextEditUtilsTest.java new file mode 100644 index 0000000000..3d5930ab37 --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/TextEditUtilsTest.java @@ -0,0 +1,53 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format; + +import static org.eclipse.lemminx.XMLAssert.te; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.eclipse.lemminx.commons.TextDocument; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Test; + +/** + * Test for {@link TextEditUtils}. + * + * @author Angelo ZERR + * + */ +public class TextEditUtilsTest { + + @Test + public void textEdit() { + TextDocument document = new TextDocument("", "test.xml"); + TextEdit edit = TextEditUtils.createTextEditIfNeeded(1, 2, " ", document); + assertNotNull(edit); + assertEquals(te(0, 2, 0, 2, " "), edit); + } + + @Test + public void noTextEdit() { + TextDocument document = new TextDocument("", "test.xml"); + TextEdit edit = TextEditUtils.createTextEditIfNeeded(1, 3, " ", document); + assertNull(edit); + } + + @Test + public void textEdit2() { + TextDocument document = new TextDocument("", "test.xml"); + TextEdit edit = TextEditUtils.createTextEditIfNeeded(1, 4, " ", document); + assertNotNull(edit); + assertEquals(te(0, 2, 0, 4, " "), edit); + } +} 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/format/XMLFormatterTest.java similarity index 95% rename from org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/XMLFormatterTest.java rename to org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/XMLFormatterTest.java index c8a3b97483..df84f594d0 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/format/XMLFormatterTest.java @@ -10,7 +10,7 @@ * Contributors: * Angelo Zerr - initial API and implementation */ -package org.eclipse.lemminx.services; +package org.eclipse.lemminx.services.format; import static java.lang.System.lineSeparator; import static org.eclipse.lemminx.XMLAssert.assertFormat; @@ -2616,7 +2616,7 @@ public void dontEnforceDoubleQuoteStyle() throws BadLocationException { @Test public void preserveAttributeLineBreaksFormatProlog() throws BadLocationException { SharedSettings settings = new SharedSettings(); - settings.getFormattingSettings().setPreserveAttrLineBreaks(true); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); String content = "\n" + // @@ -2636,7 +2636,7 @@ public void preserveAttributeLineBreaksFormatProlog() throws BadLocationExceptio @Test public void preserveAttributeLineBreaks() throws BadLocationException { SharedSettings settings = new SharedSettings(); - settings.getFormattingSettings().setPreserveAttrLineBreaks(true); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); String content = "\n" + // ""; @@ -2723,7 +2723,7 @@ public void preserveAttributeLineBreaks5() throws BadLocationException { @Test public void preserveAttributeLineBreaksMissingValue() throws BadLocationException { SharedSettings settings = new SharedSettings(); - settings.getFormattingSettings().setPreserveAttrLineBreaks(true); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); String content = ""; @@ -2736,7 +2736,7 @@ public void preserveAttributeLineBreaksMissingValue() throws BadLocationExceptio @Test public void preserveAttributeLineBreaksCollapseEmptyElement() throws BadLocationException { SharedSettings settings = new SharedSettings(); - settings.getFormattingSettings().setPreserveAttrLineBreaks(true); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); String content = "\n" + // @@ -2754,7 +2754,7 @@ public void preserveAttributeLineBreaksCollapseEmptyElement() throws BadLocation @Test public void preserveAttributeLineBreaksCollapseEmptyElement2() throws BadLocationException { SharedSettings settings = new SharedSettings(); - settings.getFormattingSettings().setPreserveAttrLineBreaks(true); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); String content = "\n" + // @@ -2776,7 +2776,7 @@ public void preserveAttributeLineBreaksCollapseEmptyElement2() throws BadLocatio @Test public void preserveAttributeLineBreaksCollapseEmptyElement3() throws BadLocationException { SharedSettings settings = new SharedSettings(); - settings.getFormattingSettings().setPreserveAttrLineBreaks(true); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); String content = "\n" + // @@ -2788,7 +2788,7 @@ public void preserveAttributeLineBreaksCollapseEmptyElement3() throws BadLocatio @Test public void preserveAttributeLineBreaksRangeFormatting() throws BadLocationException { SharedSettings settings = new SharedSettings(); - settings.getFormattingSettings().setPreserveAttrLineBreaks(true); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); String content = ""; + String expected = ""; + assertFormat(content, expected, settings, // + te(0, 20, 0, 23, ">
    ")); + + content = ""; + assertFormat(content, expected, settings, // + te(0, 8, 1, 2, " "), // + te(1, 13, 2, 4, ">")); + + assertFormat(expected, expected, settings); + } + + @Test + public void collapseEmptyElements() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); + + String content = ""; + String expected = ""; + assertFormat(content, expected, settings, // + te(0, 20, 0, 31, " />")); + + content = "" + System.lineSeparator() + // + ""; + assertFormat(content, expected, settings, // + te(0, 8, 0, 11, " "), // + te(0, 22, 2, 10, " />")); + + content = " "; + assertFormat(content, expected, settings, // + te(0, 20, 0, 34, " />")); + + assertFormat(expected, expected, settings); + + content = " X "; + expected = " X "; + assertFormat(content, expected, settings); + + content = " "; + expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + assertFormat(content, expected, settings, // + te(0, 21, 0, 22, lineSeparator() + " "), // + te(0, 24, 0, 24, " "), // + te(0, 26, 0, 27, lineSeparator())); + assertFormat(expected, expected, settings); + + } + + @Test + public void ignoreEmptyElements() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setEmptyElement(EmptyElements.ignore); + + String content = ""; + assertFormat(content, content, settings); + + content = ""; + String expected = ""; + assertFormat(content, expected, settings, // + te(0, 20, 0, 23, " ")); + assertFormat(expected, expected, settings); + } + + @Disabled + @Test + public void expandEmptyElementsAndPreserveEmptyContent() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setEmptyElement(EmptyElements.expand); + settings.getFormattingSettings().setPreserveEmptyContent(true); + + String content = "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected, settings); + + content = "\r\n" + // + " \r\n" + // + ""; + expected = "\r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void collapseEmptyElementsAndPreserveEmptyContent() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); + settings.getFormattingSettings().setPreserveEmptyContent(true); + + String content = "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected, settings); + + content = "\r\n" + // + " \r\n" + // + ""; + expected = "\r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void collapseEmptyElementsInRange() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); + + // Range doesn't cover the b element, collapse cannot be done + String content = "\r\n" + // + "<|b>\r\n" + // + " | \r\n" + // + "\r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected, settings); + + // Range covers the b element, collapse is done + content = "\r\n" + // + "<|b>\r\n" + // + " \r\n" + // + "\r\n" + // + ""; + expected = "\r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected, settings); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } + +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterExperimentalIndentTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterExperimentalIndentTest.java new file mode 100644 index 0000000000..8bf8d121ae --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterExperimentalIndentTest.java @@ -0,0 +1,153 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import static java.lang.System.lineSeparator; +import static org.eclipse.lemminx.XMLAssert.te; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with indentation. + * + */ +public class XMLFormatterExperimentalIndentTest { + + @Test + public void oneElementsInSameLine() throws BadLocationException { + String content = ""; + String expected = content; + assertFormat(content, expected); + + content = "\r\n" + // + ""; + expected = content; + assertFormat(content, expected); + } + + @Test + public void oneElementsInDifferentLine() throws BadLocationException { + String content = "\r\n" + // + ""; + String expected = " "; + assertFormat(content, expected, // + te(0, 3, 1, 0, " ")); + assertFormat(expected, expected); + } + + @Test + public void oneElementsInDifferentLineWithSpace() throws BadLocationException { + String content = "\r\n" + // + " "; + String expected = " "; + assertFormat(content, expected, // + te(0, 3, 1, 2, " ")); + assertFormat(expected, expected); + } + + @Test + public void twoElementsInSameLine() throws BadLocationException { + String content = ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + assertFormat(content, expected, // + te(0, 3, 0, 3, lineSeparator() + " "), // + te(0, 10, 0, 10, lineSeparator())); + assertFormat(expected, expected); + } + + @Test + public void textSpaces() throws BadLocationException { + String content = "b c"; + String expected = "b c"; + assertFormat(content, expected, // + te(0, 4, 0, 6, " ")); + assertFormat(expected, expected); + } + + @Test + public void mixedContent() throws BadLocationException { + String content = "B"; + String expected = "" + lineSeparator() + // + " B" + lineSeparator() + // indent with 2 spaces + ""; + assertFormat(content, expected, // + te(0, 3, 0, 3, lineSeparator() + " "), // indent with 2 spaces + te(0, 11, 0, 11, lineSeparator())); + assertFormat(expected, expected); + } + + @Test + public void mixedContentWithTabs4Spaces() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setInsertSpaces(true); + settings.getFormattingSettings().setTabSize(4); + + String content = "B"; + String expected = "" + lineSeparator() + // + " B" + lineSeparator() + // indent with 4 spaces + ""; + assertFormat(content, expected, settings, // + te(0, 3, 0, 3, lineSeparator() + " "), // indent with 4 spaces + te(0, 11, 0, 11, lineSeparator())); + assertFormat(expected, expected, settings); + } + + @Test + public void mixedContentWithTabs() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setInsertSpaces(false); + + String content = "B"; + String expected = "" + lineSeparator() + // + " B" + lineSeparator() + // indent with one tab + ""; + assertFormat(content, expected, settings, // + te(0, 3, 0, 3, lineSeparator() + " "), // indent with one tab + te(0, 11, 0, 11, lineSeparator())); + assertFormat(expected, expected, settings); + } + + @Test + public void mixedContent2() throws BadLocationException { + String content = "AB"; + String expected = content; + assertFormat(content, expected); + } + + private static void assertFormat(String unformatted, String actual, TextEdit... expectedEdits) + throws BadLocationException { + assertFormat(unformatted, actual, new SharedSettings(), expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterExperimentalTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterExperimentalTest.java new file mode 100644 index 0000000000..4317b90be2 --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterExperimentalTest.java @@ -0,0 +1,959 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import static java.lang.System.lineSeparator; +import static org.eclipse.lemminx.XMLAssert.te; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests + * + */ +public class XMLFormatterExperimentalTest { + + @Test + public void emptyXML() throws BadLocationException { + String content = ""; + String expected = content; + assertFormat(expected, expected); + } + + @Test + public void bracket() throws BadLocationException { + String content = "<"; + String expected = content; + assertFormat(expected, expected); + } + + // ---------- Tests for tag elements formatting + + @Test + 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" + // + " \r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected, // + te(1, 7, 2, 4, "\r\n ")); + assertFormat(expected, expected); + } + + @Test + public void testUnclosedEndTagBracketTrailingElement() throws BadLocationException { + String content = "\r\n" + // + " content \r\n" + // + ""; + String expected = "\r\n" + // + " content \r\n" + // + ""; + assertFormat(content, expected, // + te(0, 6, 1, 9, "\r\n "), // + te(1, 24, 2, 6, "\r\n ")); + assertFormat(expected, expected); + } + + @Test + public void endTagWithSpace() throws BadLocationException { + String content = ""; + String expected = ""; + assertFormat(content, expected, // + te(0, 6, 0, 14, "")); + assertFormat(expected, expected); + } + + @Test + public void endTagWithLineBreak() throws BadLocationException { + String content = ""; + String expected = ""; + assertFormat(content, expected, // + te(0, 6, 1, 6, "")); + assertFormat(expected, expected); + } + + // ---------- Tests for attributes formatting + + @Test + public void attrWithEqualsSpace() throws BadLocationException { + String content = "
    \n" + // + "
    \n" + // + "
    "; + String expected = "
    \n" + // + "
    \n" + // + "
    "; + assertFormat(content, expected, // + te(0, 4, 0, 6, " "), // + te(0, 11, 0, 12, ""), // + te(0, 13, 0, 14, ""), // + te(0, 20, 1, 0, "\n "), // + te(1, 3, 1, 3, " "), // + te(1, 5, 2, 1, "\n")); + assertFormat(expected, expected); + } + + @Test + public void attrValueWithLineBreakSpace() throws BadLocationException { + String content = "
    \n" + // + "
    \n" + // + "
    "; + String expected = "
    \n" + // + "
    \n" + // + "
    "; + assertFormat(content, expected, // + te(0, 4, 0, 6, " "), // + te(0, 11, 0, 12, ""), // + te(0, 13, 1, 0, ""), // + te(1, 6, 2, 0, "\n "), // + te(2, 3, 2, 3, " "), // + te(2, 5, 3, 1, "\n")); + assertFormat(expected, expected); + } + + @Test + public void testInvalidAttr() throws BadLocationException { + String content = ""; + String expected = ""; + assertFormat(content, expected, // + te(0, 8, 0, 8, " "), // + te(0, 10, 0, 10, " ")); + assertFormat(expected, expected); + } + + @Test + public void testAttributeNameValueTwoLines() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected, // + te(1, 4, 2, 3, " "), // + te(2, 4, 2, 17, ""), // + te(2, 18, 2, 27, "")); + assertFormat(expected, expected); + } + + @Test + public void testAttributeNameValueMultipleLines() throws BadLocationException { + String content = "\r\n" + // + " |\r\n" + // + " \r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected, // + te(1, 4, 2, 2, " "), // + te(2, 3, 3, 2, ""), // + te(3, 3, 4, 2, ""), // + te(4, 6, 6, 2, "")); + assertFormat(expected, expected); + } + + @Test + public void testAttributeNameValueMultipleLinesWithChild() throws BadLocationException { + String content = "\r\n" + // + " |\r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected, // + te(1, 4, 2, 3, " "), // + te(2, 4, 2, 14, ""), // + te(2, 15, 4, 3, ""), // + te(4, 8, 4, 8, "\r\n ")); + assertFormat(expected, expected); + } + + @Test + public void testAttributeNameValueMultipleLinesWithChildrenSiblings() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " |\r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected, // + te(1, 4, 2, 2, " "), // + te(2, 3, 3, 2, ""), // + te(3, 3, 4, 2, ""), // + te(4, 6, 6, 2, ""), // + te(6, 3, 7, 8, "\r\n "), // + te(7, 11, 8, 10, "\r\n "), // + te(10, 6, 11, 8, "\r\n ")); + assertFormat(expected, expected); + } + + // ---------- Tests for processing instruction formatting + + @Test + public void testProlog() throws BadLocationException { + String content = "\r\n"; + String expected = ""; + assertFormat(content, expected, // + te(0, 14, 0, 17, ""), // + te(0, 22, 0, 29, " "), // + te(0, 45, 0, 47, ""), // + te(0, 49, 1, 0, "")); + assertFormat(expected, expected); + } + + @Test + public void testProlog2() throws BadLocationException { + String content = "bb"; + String expected = "" + lineSeparator() + // + "bb"; + assertFormat(content, expected, // + te(0, 14, 0, 17, ""), // + te(0, 22, 0, 29, " "), // + te(0, 45, 0, 47, ""), // + te(0, 49, 0, 49, lineSeparator())); + assertFormat(expected, expected); + } + + @Test + public void testProlog3() throws BadLocationException { + String content = "c"; + String expected = "" + lineSeparator() + // + "" + lineSeparator() + // + " c" + lineSeparator() + // + ""; + assertFormat(content, expected, // + te(0, 14, 0, 17, ""), // + te(0, 22, 0, 29, " "), // + te(0, 45, 0, 47, ""), // + te(0, 49, 0, 49, lineSeparator()), // + te(0, 52, 0, 52, lineSeparator() + " "), // + te(0, 60, 0, 60, lineSeparator())); + assertFormat(expected, expected); + } + + @Test + public void testProlog4WithUnknownVariable() throws BadLocationException { + String content = "c"; + String expected = "" + lineSeparator() + // + "" + lineSeparator() + // + " c" + lineSeparator() + // + ""; + assertFormat(content, expected, // + te(0, 14, 0, 17, ""), // + te(0, 22, 0, 29, " "), // + te(0, 45, 0, 47, " "), // + te(0, 69, 0, 70, ""), // + te(0, 72, 0, 72, lineSeparator()), // + te(0, 75, 0, 75, lineSeparator() + " "), // + te(0, 83, 0, 83, lineSeparator())); + assertFormat(expected, expected); + } + + @Disabled + @Test + public void testPI() throws BadLocationException { + String content = ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testPINoContent() throws BadLocationException { + String content = ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testDefinedPIWithVariables() throws BadLocationException { + String content = ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testDefinedPIWithJustAttributeNames() throws BadLocationException { + String content = ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testPIWithVariables() throws BadLocationException { + String content = ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + assertFormat(content, expected); + } + + // ---------- Tests for comment formatting + + @Test + public void testComment() throws BadLocationException { + String content = "Val"; + String expected = "" + lineSeparator() + // + "Val"; + assertFormat(content, expected, // + te(0, 20, 0, 20, lineSeparator())); + assertFormat(expected, expected); + } + + @Disabled + @Test + public void testComment2() throws BadLocationException { + String content = "Val"; + String expected = "" + lineSeparator() + // + "" + lineSeparator() + // + "Val"; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testCommentNested() throws BadLocationException { + String content = ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testCommentNested2() throws BadLocationException { + String content = ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + " " + lineSeparator() + // + " " + lineSeparator() + // + " " + lineSeparator() + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testCommentMultiLineContent() throws BadLocationException { + String content = ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + assertFormat(content, expected); + } + + @Test + public void testCommentNotClosed() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testJoinCommentLines() throws BadLocationException { + String content = ""; + String expected = ""; + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setJoinCommentLines(true); + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUnclosedEndTagTrailingComment() throws BadLocationException { + String content = "" + lineSeparator() + // + " content " + lineSeparator() + // + " "; + String expected = "" + lineSeparator() + // + " content " + lineSeparator() + // + ""; + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setJoinCommentLines(true); + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testJoinCommentLinesNested() throws BadLocationException { + String content = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setJoinCommentLines(true); + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testCommentFormatSameLine() throws BadLocationException { + String content = "" + lineSeparator() + // + " Content" + lineSeparator() + // + " "; + String expected = "" + lineSeparator() + // + " Content" + lineSeparator() + // + " "; + + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setJoinCommentLines(true); + assertFormat(content, expected, settings); + } + + // ---------- Tests for CDATA formatting + + @Test + public void testCDATANotClosed() throws BadLocationException { + String content = "\r\n" + // + " "; + String expected = content; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testCDATAWithRange() throws BadLocationException { + String content = "\r\n" + // + " |\r\n" + // + " \r\n" + // + " ]]>\r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + " \r\n" + // + " ]]>\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testJoinCDATALines() throws BadLocationException { + String content = "" + lineSeparator() + // + " "; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setJoinCDATALines(true); + assertFormat(content, expected, settings); + } + + // ---------- Tests for Text formatting + + @Disabled + @Test + public void testElementContentNotNormalized() throws BadLocationException { + String content = "\r" + // + " Content\r" + // + " Content2\r" + // + " Content3\r" + // + " Content4\r" + // + " Content5\r" + // + ""; + String expected = "\r" + // + " Content\r" + // + " Content2\r" + // + " Content3\r" + // + " Content4\r" + // + " Content5\r" + // + ""; + + assertFormat(content, expected); + } + + @Disabled + @Test + public void testContentFormatting2() throws BadLocationException { + String content = "\r" + // + " Content\r" + // + " \r" + // + " Content2\r" + // + " Content3\r" + // + " \r" + // + ""; + String expected = "\r" + // + " Content\r" + // + " \r" + // + " Content2\r" + // + " Content3\r" + // + " \r" + // + ""; + + assertFormat(content, expected); + } + + @Disabled + @Test + public void testContentFormattingDontMoveEndTag() throws BadLocationException { + String content = "\r" + // + " Content\r" + // + " \r" + // + " Content2\r" + // + " Content3 \r" + // + ""; + String expected = "\r" + // + " Content\r" + // + " \r" + // + " Content2\r" + // + " Content3 \r" + // + ""; + + assertFormat(content, expected); + } + + @Test + public void testContentFormatting3() throws BadLocationException { + String content = " content "; + String expected = " content "; + + assertFormat(content, expected); + } + + @Disabled + @Test + public void testContentFormatting6() throws BadLocationException { + String content = "\r" + // + "\r" + // + " Content\r" + // + ""; + String expected = "\r" + // + "\r" + // + " Content\r" + // + ""; + assertFormat(content, expected); + + content = "\r\n" + // + "\r\n" + // + " Content\r\n" + // + ""; + expected = "\r\n" + // + "\r\n" + // + " Content\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testTrimTrailingWhitespaceText() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimTrailingWhitespace(true); + String content = " \n" + // + "text \n" + // + " text text text \n" + // + " text\n" + // + " "; + String expected = "\n" + // + "text\n" + // + " text text text\n" + // + " text\n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testTrimTrailingWhitespaceNewlines() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimTrailingWhitespace(true); + String content = " \n" + // + " \n" + // + " "; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testTrimTrailingWhitespaceTextAndNewlines() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimTrailingWhitespace(true); + String content = " \n" + // + " \n" + // + "text \n" + // + " text text text \n" + // + " \n" + // + " text\n" + // + " \n" + // + " "; + String expected = "\n" + // + "\n" + // + "text\n" + // + " text text text\n" + // + "\n" + // + " text\n" + // + "\n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testDontInsertFinalNewLineWithRange() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setInsertFinalNewline(true); + String content = "
    \r\n" + // + " |\r\n" + // + "
    "; + String expected = "
    \r\n" + // + " \r\n" + // + "
    "; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testInsertFinalNewLineWithRange2() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setInsertFinalNewline(true); + String content = "
    \r\n" + // + " |\r\n" + // + "
    |"; + String expected = "
    \r\n" + // + " \r\n" + // + "
    \r\n"; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testInsertFinalNewLineWithRange3() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setInsertFinalNewline(true); + String content = "
    \r\n" + // + " |\r\n" + // + "\r\n" + "|" + "\r\n" + // + "

    \r\n" + // + "
    "; + String expected = "
    \r\n" + // + " \r\n" + // + "\r\n" + // + "

    " + "\r\n" + // + "
    "; + assertFormat(content, expected, settings); + } + + @Test + public void testDontTrimFinalNewLines() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimFinalNewlines(false); + String content = "\r\n\r\n\r\n"; + String expected = "\r\n\r\n\r\n"; + + assertFormat(content, expected, settings, // + te(0, 2, 0, 4, "")); + assertFormat(expected, expected, settings); + } + + @Disabled + @Test + public void testDontTrimFinalNewLines2() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimFinalNewlines(false); + String content = "\r\n" + // + " \r\n\r\n"; + String expected = "\r\n" + // + " \r\n\r\n"; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testDontTrimFinalNewLines3() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimFinalNewlines(false); + String content = "\r\n" + // + " text \r\n" + // + " more text \r\n" + // + " \r\n"; + String expected = "\r\n" + // + " text \r\n" + // + " more text \r\n" + // + " \r\n"; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testFormatRemoveFinalNewlinesWithoutTrimTrailing() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimFinalNewlines(true); + settings.getFormattingSettings().setTrimTrailingWhitespace(false); + settings.getFormattingSettings().setSpaceBeforeEmptyCloseTag(false); + String content = " \r\n\r\n\r\n"; + String expected = " "; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testClosingBracketNewLine() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + settings.getFormattingSettings().setSplitAttributesIndentSize(0); + settings.getFormattingSettings().setClosingBracketNewLine(true); + String content = ""; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testClosingBracketNewLineWithDefaultIndentSize() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + settings.getFormattingSettings().setClosingBracketNewLine(true); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + String content = ""; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testClosingBracketNewLineWithoutSplitAttributes() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(false); + settings.getFormattingSettings().setClosingBracketNewLine(true); + String content = ""; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testClosingBracketNewLineWithSingleAttribute() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + settings.getFormattingSettings().setSplitAttributesIndentSize(0); + settings.getFormattingSettings().setClosingBracketNewLine(true); + String content = ""; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testClosingBracketNewLineWithPreserveEmptyContent() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + settings.getFormattingSettings().setSplitAttributesIndentSize(0); + settings.getFormattingSettings().setPreserveEmptyContent(true); + settings.getFormattingSettings().setClosingBracketNewLine(true); + String content = "" + lineSeparator() + "" + lineSeparator() + ""; + String expected = "" + lineSeparator() + " " + lineSeparator() + ""; + assertFormat(content, expected, settings); + } + + private static void assertFormat(String unformatted, String actual, TextEdit... expectedEdits) + throws BadLocationException { + assertFormat(unformatted, actual, new SharedSettings(), expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } + +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterForDTDTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterForDTDTest.java new file mode 100644 index 0000000000..358949c23a --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterForDTDTest.java @@ -0,0 +1,676 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import static org.eclipse.lemminx.XMLAssert.te; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with DTD. + * + */ +public class XMLFormatterForDTDTest { + + @Disabled + @Test + public void testDoctypeNoInternalSubset() throws BadLocationException { + String content = "\r\n" + // + "\r\n" + // + " Fred\r\n" + // + "\r\n" + // + " Jani\r\n" + // + "\r\n" + // + " Reminder\r\n" + // + " \r\n" + // + " Don't forget me this weekend\r\n" + // + ""; + String expected = "\r\n" + // + "\r\n" + // + " Fred\r\n" + // + "\r\n" + // + " Jani\r\n" + // + "\r\n" + // + " Reminder\r\n" + // + "\r\n" + // + " Don't forget me this weekend\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testDoctypeNoInternalSubsetNoNewlines() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreservedNewlines(0); + + String content = "\r\n" + // + "\r\n" + // + " Fred\r\n" + // + "\r\n" + // + " Jani\r\n" + // + "\r\n" + // + " Reminder\r\n" + // + " \r\n" + // + " Don't forget me this weekend\r\n" + // + ""; + String expected = "\r\n" + // + "\r\n" + // + " Fred\r\n" + // + " Jani\r\n" + // + " Reminder\r\n" + // + " Don't forget me this weekend\r\n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testDoctypeInternalSubset() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // + " Fred\r\n" + // + "\r\n" + // + "\r\n" + // + " Jani\r\n" + // + " \r\n" + // + " Reminder\r\n" + // + " Don't forget me this weekend\r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // + " Fred\r\n" + // + "\r\n" + // + "\r\n" + // + " Jani\r\n" + // + "\r\n" + // + " Reminder\r\n" + // + " Don't forget me this weekend\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testDoctypeInternalSubsetNoNewlines() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreservedNewlines(0); + + String content = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // + " Fred\r\n" + // + "\r\n" + // + "\r\n" + // + " Jani\r\n" + // + " \r\n" + // + " Reminder\r\n" + // + " Don't forget me this weekend\r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // + " Fred\r\n" + // + " Jani\r\n" + // + " Reminder\r\n" + // + " Don't forget me this weekend\r\n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testDoctypeInternalDeclSpacesBetweenParameters() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // + " Fred\r\n" + // + " Jani\r\n" + // + " Reminder\r\n" + // + " Don't forget me this weekend\r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // + " Fred\r\n" + // + " Jani\r\n" + // + " Reminder\r\n" + // + " Don't forget me this weekend\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testDoctypeInternalWithAttlist() throws BadLocationException { + String content = "\r\n" + // + "\r\n" + // + " \r\n" + // + "\r\n" + // + " \r\n" + // + "\r\n" + // + "]>\r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + " Fred\r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + " Fred\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testDoctypeInternalAllDecls() throws BadLocationException { + String content = "\r\n" + // + "\r\n" + // + " \r\n" + // + "\r\n" + // + " \r\n" + // + "\r\n" + // + " \r\n" + // + "]>\r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]>"; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testDoctypeInternalWithComments() throws BadLocationException { + String content = "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]>\r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]>"; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testDoctypeInternalWithText() throws BadLocationException { + String content = "\r\n" + // + "\r\n" + // + "\r\n" + // + " garbageazg df\r\n" + // + " gdf\r\n" + // + "garbageazgdfg\r\n" + // + " df\r\n" + // + " gd\r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]>"; + String expected = "\r\n" + // + " garbageazg df\r\n" + // + " gdf\r\n" + // + "garbageazgdfg\r\n" + // + " df\r\n" + // + " gd\r\n" + // + " \r\n" + // + "]>"; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testDTDMultiParameterAttlist() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + + String content = "\r\n"; + String expected = ""; + assertFormat(content, expected, settings, "test.dtd"); + } + + @Disabled + @Test + public void testDTDIndentation() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + + String content = " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " "; + String expected = "\r\n" + // + "\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected, settings, "test.dtd"); + } + + @Disabled + @Test + public void testDTDNotEndBrackets() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + + String content = "\r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + "\r\n" + // + " asdasd\r\n" + // + " asd\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + ""; + String expected = "\r\n" + // + "\r\n" + // + "asdasd\r\n" + // + " asd\r\n" + // + "\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected, settings, "test.dtd"); + } + + @Disabled + @Test + public void testAllDoctypeParameters() throws BadLocationException { + String content = "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]\r\n" + // + "\r\n" + // + "\r\n" + // + ">\r\n" + // + "\r\n" + // + " sdsd\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + " er\r\n" + // + " dd\r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // + " sdsd\r\n" + // + "\r\n" + // + " \r\n" + // + "\r\n" + // + " er\r\n" + // + " dd\r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testDTDElementContentWithAsterisk() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + + String content = ""; + String expected = ""; + assertFormat(content, expected, settings, "test.dtd", false); + } + + @Disabled + @Test + public void testDoctypeSingleLineFormat() throws BadLocationException { + String content = "]>\r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + "]>"; + assertFormat(content, expected); + } + + @Test + public void testDoctypeInvalidParameter() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + "]>"; + String expected = "\r\n" + // + " \r\n" + // + "]>"; + assertFormat(content, expected); + } + + @Test + public void testDoctypeInvalidParameterUnclosed() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + "]\r\n" + // + "\r\n" + // + "\r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + "]\r\n" + // + ""; + assertFormat(content, expected, // + te(2, 1, 5, 0, "\r\n")); + assertFormat(expected, expected); + } + + @Disabled + @Test + public void testUnclosedSystemId() throws BadLocationException { + String content = "\r\n" + // + "\r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // + ""; + String expected = "\r\n" + // + "\r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testUnclosedPublicId() throws BadLocationException { + String content = "\r\n" + // + "\r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // + ""; + String expected = "\r\n" + // + "\r\n" + // + " \r\n" + // + "]>\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testCommentAfterMissingClosingBracket() throws BadLocationException { + String content = "\r\n" + // + "]>\r\n" + // + "\r\n" + // + ""; + String expected = "\r\n" + // + "]>\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testHTMLDTD() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + + String content = "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + " ...\r\n" + // + " \r\n" + // + " \r\n" + // + " ...\r\n" + // + " \r\n" + // + " \r\n" + // + "-->\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "%HTML4.dtd;"; + String expected = "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "...\r\n" + // + "\r\n" + // + "\r\n" + // + "...\r\n" + // + "\r\n" + // + "\r\n" + // + "-->\r\n" + // + "\r\n" + // + "\r\n" + // + "%HTML4.dtd;"; + assertFormat(content, expected, settings, "test.dtd"); + } + + @Disabled + @Test + public void testXMLInDTDFile() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + + String content = "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected, settings, "test.dtd"); + } + + @Disabled + @Test + public void testBadDTDFile() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + + String content = "\r\n" + // + "\r\n" + // + "]]>\r\n" + // + "\r\n" + // + ""; + String expected = "\r\n" + // + "]]>\r\n" + // + ""; + assertFormat(content, expected, settings, "test.dtd"); + } + + @Disabled + @Test + public void testIncompleteAttlistInternalDecl() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + + String content = "\r\n" + // + "\r\n" + // + ""; + String expected = "\r\n" + // + ""; + assertFormat(content, expected, settings, "test.dtd"); + } + + private static void assertFormat(String unformatted, String actual, TextEdit... expectedEdits) + throws BadLocationException { + assertFormat(unformatted, actual, new SharedSettings(), expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } + +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterInsertFinalNewLineTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterInsertFinalNewLineTest.java new file mode 100644 index 0000000000..6721ad9384 --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterInsertFinalNewLineTest.java @@ -0,0 +1,126 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import static java.lang.System.lineSeparator; +import static org.eclipse.lemminx.XMLAssert.te; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with insert final new lines. + * + */ +public class XMLFormatterInsertFinalNewLineTest { + + @Test + public void testTrimFinalNewlinesDefault() throws BadLocationException { + String content = "\r\n"; + String expected = ""; + assertFormat(content, expected, // + te(0, 2, 0, 4, ""), // + te(0, 9, 1, 0, "")); + assertFormat(expected, expected); + + } + + @Test + public void testDontInsertFinalNewLine1() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimFinalNewlines(false); + settings.getFormattingSettings().setInsertFinalNewline(true); + String content = ""; + assertFormat(content, content, settings); + } + + @Test + public void testDontInsertFinalNewLine2() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimFinalNewlines(false); + settings.getFormattingSettings().setInsertFinalNewline(true); + String content = "\r\n"; + String expected = "\r\n"; + assertFormat(content, expected, settings, te(0, 2, 0, 4, "")); + assertFormat(expected, expected, settings); + } + + @Disabled + @Test + public void testDontInsertFinalNewLine3() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimFinalNewlines(false); + settings.getFormattingSettings().setInsertFinalNewline(true); + String content = "\r\n" + " "; + String expected = "\r\n" + " "; + assertFormat(content, expected, settings); + } + + @Test + public void testInsertFinalNewLine1() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimFinalNewlines(false); + settings.getFormattingSettings().setInsertFinalNewline(true); + String content = ""; + String expected = "" + lineSeparator(); + assertFormat(content, expected, settings, // + te(0, 7, 0, 7, lineSeparator())); + } + + @Test + public void testInsertFinalNewLine2() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimFinalNewlines(true); + settings.getFormattingSettings().setInsertFinalNewline(true); + String content = "\r\n\r\n"; + String expected = "\r\n"; + assertFormat(content, expected, settings, // + te(1, 0, 2, 0, "")); + } + + @Test + public void testInsertFinalNewLine3() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setTrimFinalNewlines(true); + settings.getFormattingSettings().setInsertFinalNewline(true); + String content = "\n\n"; + String expected = "\n"; + assertFormat(content, expected, settings, // + te(1, 0, 2, 0, "")); + } + + private static void assertFormat(String unformatted, String actual, TextEdit... expectedEdits) + throws BadLocationException { + assertFormat(unformatted, actual, new SharedSettings(), expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterMaxLineWithTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterMaxLineWithTest.java new file mode 100644 index 0000000000..cc7c9966a4 --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterMaxLineWithTest.java @@ -0,0 +1,135 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import static org.eclipse.lemminx.XMLAssert.te; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with max line width. + * + */ +public class XMLFormatterMaxLineWithTest { + + @Test + public void splitText() throws BadLocationException { + String content = "abcde fghi"; + String expected = "abcde" + // + System.lineSeparator() + // + "fghi"; + assertFormat(content, expected, 6, // + te(0, 8, 0, 9, System.lineSeparator())); + assertFormat(expected, expected, 6); + } + + @Test + public void splitMixedText() throws BadLocationException { + String content = " efgh"; + String expected = "" + // + System.lineSeparator() + // + "efgh"; + assertFormat(content, expected, 5, // + te(0, 8, 0, 9, System.lineSeparator())); + assertFormat(expected, expected, 5); + } + + @Test + public void noSplit() throws BadLocationException { + String content = "abcde fghi"; + String expected = content; + assertFormat(content, expected, 20); + } + + @Test + public void longText() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + " vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\r\n" + + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv \r\n" + + // + ""; + assertFormat(content, expected, 20, // + te(0, 5, 1, 1, "\r\n "), // + te(1, 7, 2, 2, "\r\n"), // + te(2, 102, 3, 1, " ")); + assertFormat(expected, expected, 20); + } + + @Test + public void complex() throws BadLocationException { + String content = "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + " Apache Ant (all-in-one) ffffffffffffffffff fffffffffffffffffffffffff ggggggggggggggg\r\n" + + // + " scm:git:git.eclipse.org:/gitroot/orbit/recipes.git\r\n" + // + " apache-parent/ant/org.apache.ant\r\n" + // + " \r\n" + // + " \r\n" + // + " Sarika \r\n" + // + " Sinha\r\n" + // + " sarika.\r\n" + // + " \r\n" + // + " \r\n" + // + " sinha@in.ibm.com\r\n" + // + " IBM\r\n" + // + " \r\n" + // + " \r\n" + // + ""; + + String expected = "\r\n" + // + "\r\n" + // + " \r\n" + // + " \r\n" + // + " Apache Ant (all-in-one) ffffffffffffffffff fffffffffffffffffffffffff\r\n" + // + "ggggggggggggggg\r\n" + // + " scm:git:git.eclipse.org:/gitroot/orbit/recipes.git\r\n" + // + " apache-parent/ant/org.apache.ant\r\n" + // + " \r\n" + // + " \r\n" + // + " Sarika Sinha\r\n" + // + " sarika. sinha@in.ibm.com\r\n" + // + " IBM\r\n" + // + " \r\n" + // + " \r\n" + // + ""; + + assertFormat(content, expected, 80, // + te(1, 7, 1, 9, " "), // + te(4, 18, 4, 30, " "), // + te(4, 65, 4, 73, " "), // + te(4, 98, 4, 102, "\r\n"), // + te(9, 18, 10, 8, " "), // + te(11, 20, 14, 8, " ")); + assertFormat(expected, expected, 80); + } + + private static void assertFormat(String unformatted, String expected, int maxLineWidth, TextEdit... expectedEdits) + throws BadLocationException { + SharedSettings sharedSettings = new SharedSettings(); + sharedSettings.getFormattingSettings().setMaxLineWidth(maxLineWidth); + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, "test.xml", Boolean.FALSE, expectedEdits); + } +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterMixedContentWithTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterMixedContentWithTest.java new file mode 100644 index 0000000000..4adccf8ffa --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterMixedContentWithTest.java @@ -0,0 +1,62 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with mixed content. + * + */ +public class XMLFormatterMixedContentWithTest { + + @Test + public void mixedContent() throws BadLocationException { + String content = "abcd \r\n efgh"; + String expected = "abcd efgh"; + assertFormat(content, expected, 20); + } + + @Test + public void ignoreSpace() throws BadLocationException { + String content = ""; + String expected = "" + System.lineSeparator() + // + " " + System.lineSeparator() + // + " " + System.lineSeparator() + // + " " + System.lineSeparator() + // + ""; + assertFormat(content, expected, null); + } + + @Test + public void withMixedContent() throws BadLocationException { + String content = "A"; + String expected = "" + System.lineSeparator() + // + " A" + System.lineSeparator() + // + ""; + assertFormat(content, expected, null); + } + + private static void assertFormat(String unformatted, String actual, Integer maxLineWidth) + throws BadLocationException { + SharedSettings sharedSettings = new SharedSettings(); + if (maxLineWidth != null) { + sharedSettings.getFormattingSettings().setMaxLineWidth(maxLineWidth); + } + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(unformatted, actual, sharedSettings, "test.xml", Boolean.FALSE); + } +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveAttributeLineBreaksTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveAttributeLineBreaksTest.java new file mode 100644 index 0000000000..c5c0519738 --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveAttributeLineBreaksTest.java @@ -0,0 +1,316 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import static org.eclipse.lemminx.XMLAssert.te; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lemminx.settings.XMLFormattingOptions.EmptyElements; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with preserve attribute line + * breaks. + * + */ +public class XMLFormatterPreserveAttributeLineBreaksTest { + + @Test + public void preserveAttributeLineBreaksFormatProlog() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + String content = "\n" + // + "\n" + // + ""; + String expected = "\n" + // + "\n" + // + " \n" + // + ""; + assertFormat(content, expected, settings, // + te(0, 5, 1, 0, " "), // + te(1, 13, 2, 0, " "), // + te(3, 3, 3, 3, "\n "), // + te(3, 31, 4, 1, "\n "), // + te(4, 5, 5, 1, ""), // + te(5, 2, 6, 1, "")); + assertFormat(expected, expected, settings); + } + + @Test + public void preserveAttributeLineBreaks() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + + String content = "\n" + // + "\n" + // + "\n" + // + ""; + String expected = "\n" + // + " \n" + // + ""; + + assertFormat(content, expected, settings, // + te(0, 3, 1, 0, "\n "), // + te(1, 28, 2, 0, "\n "), // + te(2, 25, 3, 0, "\n "), // + te(3, 26, 4, 0, " ")); + assertFormat(expected, expected, settings); + } + + @Test + public void preserveAttributeLineBreaks2() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + String content = "\n" + // + " \n" + // + ""; + String expected = "\n" + // + " \n" + // + ""; + assertFormat(content, expected, settings, // + te(5, 3, 7, 0, "")); + assertFormat(expected, expected, settings); + } + + @Test + public void preserveAttributeLineBreaks3() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + String content = ""; + String expected = ""; + assertFormat(content, expected, settings, // + te(2, 14, 4, 0, "\n")); + assertFormat(expected, expected, settings); + } + + @Test + public void preserveAttributeLineBreaks4() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + String content = ""; + String expected = ""; + assertFormat(content, expected, settings, // + te(0, 2, 1, 0, "\n "), // + te(1, 4, 2, 0, ""), // + te(2, 1, 3, 0, ""), // + te(3, 7, 4, 0, "\n "), // + te(4, 4, 5, 0, ""), // + te(5, 1, 6, 0, "")); + assertFormat(expected, expected, settings); + } + + @Test + public void preserveAttributeLineBreaks5() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + String content = ""; + String expected = ""; + assertFormat(content, expected, settings, // + te(1, 7, 2, 2, "")); + assertFormat(expected, expected, settings); + } + + @Test + public void preserveAttributeLineBreaksMissingValue() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + String content = ""; + String expected = ""; + assertFormat(content, expected, settings, // + te(1, 7, 2, 3, "\n ")); + assertFormat(expected, expected, settings); + } + + @Disabled + @Test + public void preserveAttributeLineBreaksCollapseEmptyElement() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); + + String content = "\n" + // + "\n" + // + "\n" + ""; + String expected = "\n" + " \n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void preserveAttributeLineBreaksCollapseEmptyElement2() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); + + String content = "\n" + // + "\n" + // + "\n" + // + ""; + String expected = "\n" + // + " \n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void preserveAttributeLineBreaksCollapseEmptyElement3() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); + + String content = "\n" + // + ""; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Test + public void preserveAttributeLineBreaksRangeFormatting() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); + + String content = ""; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Test + public void preserveAttributeLineBreaksRangeFormattingWithEndTag() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); + + String content = "|"; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Test + public void preserveAttributeLineBreaksRangeFormattingWithEndTag2() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + settings.getFormattingSettings().setEmptyElement(EmptyElements.collapse); + + String content = "|"; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Test + public void preserveAttributeLineBreaksRangeFormattingWithEndTag3() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveAttributeLineBreaks(true); + + String content = "|"; + String expected = ""; + assertFormat(content, expected, settings); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } + +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveEmptyContentTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveEmptyContentTest.java new file mode 100644 index 0000000000..06f7fdde5b --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveEmptyContentTest.java @@ -0,0 +1,297 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with preserve empty content + * setting. + * + */ +public class XMLFormatterPreserveEmptyContentTest { + + @Disabled + @Test + public void testPreserveEmptyContentTag() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(true); + + String content = "\r" + // + " " + // + ""; + String expected = "\r" + // + " " + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testDontPreserveEmptyContentTag() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(false); + + String content = "\r" + // + " " + // + ""; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testPreserveTextContent() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(true); + + String content = "\r" + // + " aaa " + // + ""; + String expected = "\r" + // + " aaa " + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testPreserveTextContent2() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(false); + + String content = "\r" + // + " aaa " + // + ""; + String expected = "\r" + // + " aaa " + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testPreserveEmptyContentTagWithSiblings() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(true); + + String content = "\r" + // + " " + // + " " + // + " " + // + ""; + String expected = "\r" + // + " \r" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testPreserveEmptyContentTagWithSiblingContent() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(true); + + String content = "\r" + // + " zz " + // + " tt" + // + " " + // + ""; + String expected = "\r" + // + " zz\r" + // + " \r" + // + " tt\r" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testDontPreserveEmptyContentTagWithSiblingContent() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(false); + + String content = "\r" + // + " zz " + // + " tt" + // + " " + // + ""; + String expected = "\r" + // + " zz\r" + // + " \r" + // + " tt\r" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testPreserveEmptyContentTagWithSiblingWithComment() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(true); + + String content = "\r" + // + " zz " + // + " tt " + // + " " + // + ""; + String expected = "\r" + // + " zz\r" + // + " \r" + // + " tt \r" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testDontPreserveEmptyContentTagWithSiblingWithComment() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(false); + + String content = "\r" + // + " zz " + // + " tt " + // + " " + // + ""; + String expected = "\r" + // + " zz\r" + // + " \r" + // + " tt \r" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testPreserveEmptyContentWithJoinContentLines() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(true); + settings.getFormattingSettings().setJoinContentLines(true); + + String content = "\n" + // + " zz \n" + // + " zz \n" + // + " \n" + // + ""; + String expected = "\n" + // + " zz zz\n" + // + " \n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testJoinContentLinesTrue() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(false); + settings.getFormattingSettings().setJoinContentLines(true); + + String content = "\n" + // + " zz \n" + // + " zz " + // + ""; + String expected = "zz zz"; + assertFormat(content, expected, settings); + } + + @Test + public void testJoinContentLinesTrue2() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(false); + settings.getFormattingSettings().setJoinContentLines(true); + + String content = "zz zz zz"; + String expected = "zz zz zz"; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testJoinContentLinesFalse() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(false); + settings.getFormattingSettings().setJoinContentLines(false); + + String content = "\n" + // + " zz \n" + // + " zz " + // + ""; + String expected = "\n" + // + " zz \n" + // + " zz " + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testJoinContentLinesWithSiblingElementTrue() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(false); + settings.getFormattingSettings().setJoinContentLines(true); + + String content = "\n" + // + " zz \n" + // + " zz \n" + // + " \n" + // + ""; + String expected = "\n" + // + " zz zz\n" + // + " \n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testJoinContentLinesWithSiblingElementFalse() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveEmptyContent(false); + settings.getFormattingSettings().setJoinContentLines(false); + + String content = "\n" + // + " zz \n" + // + " zz \n" + // + " \n" + // + ""; + String expected = "\n" + // + " zz \n" + // + " zz\n" + // + " \n" + // + ""; + assertFormat(content, expected, settings); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveNewLinesTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveNewLinesTest.java new file mode 100644 index 0000000000..4c5624a488 --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveNewLinesTest.java @@ -0,0 +1,207 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with preserve new lines setting. + * + */ +public class XMLFormatterPreserveNewLinesTest { + + @Disabled + @Test + public void testPreserveNewlines() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testPreserveNewlines3Max() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreservedNewlines(3); + String content = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testPreserveNewlines2() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testPreserveNewlinesBothSides() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testPreserveNewlinesBothSidesMultipleTags() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testPreserveNewlinesSingleLine() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected); + } + + @Test + public void testPreserveNewlines4() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testNoSpacesOnNewLine() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + "\r\n" + // + "\r\n" + // + ""; + assertFormat(content, expected); + } + + private static void assertFormat(String unformatted, String actual, TextEdit... expectedEdits) + throws BadLocationException { + assertFormat(unformatted, actual, new SharedSettings(), expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveSpacesTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveSpacesTest.java new file mode 100644 index 0000000000..e40dbb5205 --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterPreserveSpacesTest.java @@ -0,0 +1,126 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import static org.eclipse.lemminx.XMLAssert.te; + +import java.util.Arrays; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with preserve spaces. + * + */ +public class XMLFormatterPreserveSpacesTest { + + @Test + public void noPreserveSpaces() throws BadLocationException { + String content = "b c"; + String expected = "b c"; + assertFormat(content, expected, // + te(0, 4, 0, 6, " ")); + assertFormat(expected, expected); + } + + @Test + public void preserveSpacesWithXmlSpace() throws BadLocationException { + String content = "b c"; + String expected = content; + assertFormat(content, expected); + } + + @Test + public void preserveSpacesWithXmlSpace2() throws BadLocationException { + String content = "\r\n" + // + " \r\n" + // + " c e\r\n" + // + " \r\n" + // + " \r\n" + // + " c e\r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " c e \r\n" + // + " \r\n" + // + " c e\r\n" + // + " \r\n" + // + ""; + + assertFormat(content, expected, // + te(1, 5, 2, 4, " "), // + te(2, 5, 2, 7, " "), // + te(2, 14, 2, 16, " "), // + te(2, 17, 3, 2, " ")); + assertFormat(expected, expected); + } + + @Test + public void preserveSpacesWithSettings() throws BadLocationException { + String content = "b c"; + String expected = content; + + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setPreserveSpace(Arrays.asList("a")); + assertFormat(content, expected, settings); + } + + @Test + public void preserveSpacesWithXsdString() throws Exception { + String content = "\r\n" + // + "\r\n" + + // + " a b c\r\n" + // + " a b c\r\n" + // + ""; + + String expected = "\r\n" + // + "\r\n" + + // + " a b c\r\n" + // <-- preserve space because description is xs:string + " a b c\r\n" + // <-- no preserve space because description2 is not a + // xs:string + ""; + + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setGrammarAwareFormatting(true); + assertFormat(content, expected, settings, // + te(3, 17, 3, 21, " "), // + te(3, 22, 3, 27, " ")); + } + + private static void assertFormat(String unformatted, String actual, TextEdit... expectedEdits) + throws BadLocationException { + assertFormat(unformatted, actual, new SharedSettings(), expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test.xml", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterQuoteStyleTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterQuoteStyleTest.java new file mode 100644 index 0000000000..8fe3afe755 --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterQuoteStyleTest.java @@ -0,0 +1,358 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import static java.lang.System.lineSeparator; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.EnforceQuoteStyle; +import org.eclipse.lemminx.settings.QuoteStyle; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with Quote style setting. + * + */ +public class XMLFormatterQuoteStyleTest { + + @Disabled + @Test + public void testUseDoubleQuotesFromDoubleQuotes() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + settings.getPreferences().setQuoteStyle(QuoteStyle.doubleQuotes); + + String content = " "; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUseSingleQuotesFromSingleQuotes() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + String content = " "; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUseSingleQuotesFromDoubleQuotes() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + + String content = " "; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUseDoubleQuotesFromSingleQuotes() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + String content = " "; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUseSingleQuotesNoQuotes() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + String content = " "; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUseSingleQuotesNoQuotesSplit() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setSplitAttributes(true); + String content = " "; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testAttValueOnlyStartQuote() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + String content = " "; + String expected = " "; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUseDoubleQuotesMultipleAttributes() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + String content = " "; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUseSingleQuotesMultipleAttributes() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + String content = " "; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUseDoubleQuotesMultipleAttributesSplit() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + + String content = " \n"; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUseSingleQuotesMultipleAttributesSplit() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + String content = " \n"; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUseSingleQuotesLocalDTD() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + String content = ""; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUseSingleQuotesLocalDTDWithSubset() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + String content = "\n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + "]>\n" + // + "\n" + // + ""; + String expected = "\n" + // + " \n" + // + " \n" + // + " \n" + // + " \n" + // + "]>\n" + // + "\n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testUseSingleQuotesDTDFile() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + String content = "\n" + // + "\n" + // + "\n" + // + "\n" + // + ""; + String expected = "\n" + // + "\n" + // + "\n" + // + "\n" + // + ""; + assertFormat(content, expected, settings, "test.dtd"); + } + + @Test + public void testDontFormatQuotesByDefault() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + String content = ""; + String expected = content; + assertFormat(content, expected, settings); + settings.getPreferences().setQuoteStyle(QuoteStyle.doubleQuotes); + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testAttributeNameTouchingPreviousValue() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + settings.getFormattingSettings().setSplitAttributes(true); + + String content = "\r\n" + // + " \r\n" + // + ""; + String expected = "\r\n" + // + " \r\n" + // + ""; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void enforceSingleQuoteStyle() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + + String content = ""; + String expected = ""; + assertFormat(content, expected, settings); + assertFormat(expected, expected, settings); + } + + @Disabled + @Test + public void enforceDoubleQuoteStyle() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.doubleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + + String content = ""; + String expected = ""; + assertFormat(content, expected, settings); + assertFormat(expected, expected, settings); + } + + @Disabled + @Test + public void enforceSingleQuoteStyleProlog() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + + String content = ""; + String expected = ""; + assertFormat(content, expected, settings); + assertFormat(expected, expected, settings); + } + + @Disabled + @Test + public void enforceDoubleQuoteStyleProlog() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.doubleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.preferred); + + String content = ""; + String expected = ""; + assertFormat(content, expected, settings); + assertFormat(expected, expected, settings); + } + + @Disabled + @Test + public void dontEnforceSingleQuoteStyle() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.ignore); + + String content = ""; + String expected = ""; + assertFormat(content, expected, settings); + } + + @Test + public void dontEnforceSingleQuoteStyleProlog() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.ignore); + + String content = ""; + String expected = content; + assertFormat(content, expected, settings); + } + + @Test + public void dontEnforceDoubleQuoteStyleProlog() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.singleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.ignore); + + String content = ""; + String expected = content; + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void dontEnforceDoubleQuoteStyle() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getPreferences().setQuoteStyle(QuoteStyle.doubleQuotes); + settings.getFormattingSettings().setEnforceQuoteStyle(EnforceQuoteStyle.ignore); + + String content = ""; + String expected = ""; + assertFormat(content, expected, settings); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } + +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterSetSpaceBeforeEmptyCloseTagTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterSetSpaceBeforeEmptyCloseTagTest.java new file mode 100644 index 0000000000..9436dd4c86 --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterSetSpaceBeforeEmptyCloseTagTest.java @@ -0,0 +1,145 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import static org.eclipse.lemminx.XMLAssert.te; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with setSpaceBeforeEmptyCloseTag setting. + * + */ +public class XMLFormatterSetSpaceBeforeEmptyCloseTagTest { + + @Disabled + @Test + public void testSelfCloseTagSpace() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSpaceBeforeEmptyCloseTag(true); + + String content = "\r" + // + " \r" + // + ""; + String expected = "\r" + // + " \r" + // + ""; + assertFormat(content, expected, settings); + } + + @Test + public void testSelfCloseTagAlreadyHasSpace() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSpaceBeforeEmptyCloseTag(true); + + String content = "\r" + // + " \r" + // + ""; + String expected = "\r" + // + " \r" + // + ""; + assertFormat(content, expected, settings, // + te(0, 3, 1, 1, "\r ")); + assertFormat(expected, expected, settings); + } + + @Test + public void testSelfCloseTagSpaceFalse() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSpaceBeforeEmptyCloseTag(false); + + String content = "\r" + // + " \r" + // + ""; + String expected = "\r" + // + " \r" + // + ""; + assertFormat(content, expected, settings, // + te(0, 3, 1, 1, "\r ")); + assertFormat(expected, expected, settings); + } + + @Disabled + @Test + public void testSelfCloseTagSpaceFalseAlreadyHasSpace() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSpaceBeforeEmptyCloseTag(false); + + String content = "\r" + // + " \r" + // + ""; + String expected = "\r" + // + " \r" + // + ""; + assertFormat(content, expected, settings); + } + + @Test + public void testDontAddClosingBracket() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSpaceBeforeEmptyCloseTag(false); + + String content = "\r" + // + " "; + String expected = "\r" + // + " "; + assertFormat(content, expected, settings, // + te(0, 3, 1, 1, "\r ")); + assertFormat(expected, expected, settings); + + } + + @Test + public void testEndTagMissingCloseBracket() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSpaceBeforeEmptyCloseTag(false); + + String content = "\r" + // + " Value "; + String expected = "\r" + // + " Value "; + assertFormat(content, expected, settings, // + te(0, 3, 1, 1, "\r ")); + assertFormat(expected, expected, settings); + } + + private static void assertFormat(String unformatted, String actual, TextEdit... expectedEdits) + throws BadLocationException { + assertFormat(unformatted, actual, new SharedSettings(), expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterSplitAttributesTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterSplitAttributesTest.java new file mode 100644 index 0000000000..cb94746fdc --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterSplitAttributesTest.java @@ -0,0 +1,237 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import static java.lang.System.lineSeparator; +import static org.eclipse.lemminx.XMLAssert.te; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with split attributes. + * + */ +public class XMLFormatterSplitAttributesTest { + + @Test + public void splitAttributesIndentSize0() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + settings.getFormattingSettings().setSplitAttributesIndentSize(0); + + String content = "\n"; + String expected = ""; + + assertFormat(content, expected, settings, // + te(0, 5, 0, 7, "\n"), // + te(0, 12, 0, 13, "\n"), // + te(0, 18, 0, 19, "\n"), // + te(0, 24, 0, 24, " "), // + te(0, 26, 1, 0, "")); + assertFormat(expected, expected, settings); + } + + @Test + public void splitAttributesIndentSizeNegative() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + settings.getFormattingSettings().setSplitAttributesIndentSize(-1); + + String content = "\n"; + String expected = ""; + + assertFormat(content, expected, settings, // + te(0, 5, 0, 7, "\n"), // + te(0, 12, 0, 13, "\n"), // + te(0, 18, 0, 19, "\n"), // + te(0, 24, 0, 24, " "), // + te(0, 26, 1, 0, "")); + assertFormat(expected, expected, settings); + } + + @Test + public void splitAttributesIndentSize1() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + settings.getFormattingSettings().setSplitAttributesIndentSize(1); + + String content = "\n"; + String expected = ""; + + assertFormat(content, expected, settings, // + te(0, 5, 0, 7, "\n "), // + te(0, 12, 0, 13, "\n "), // + te(0, 18, 0, 19, "\n "), // + te(0, 24, 0, 24, " "), // + te(0, 26, 1, 0, "")); + assertFormat(expected, expected, settings); + } + + @Test + public void splitAttributesIndentSizeDefault() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + + String content = "\n"; + String expected = ""; + + assertFormat(content, expected, settings, // + te(0, 5, 0, 7, "\n "), // + te(0, 12, 0, 13, "\n "), // + te(0, 18, 0, 19, "\n "), // + te(0, 24, 0, 24, " "), // + te(0, 26, 1, 0, "")); + assertFormat(expected, expected, settings); + } + + @Test + public void testSplitAttributesNested() throws BadLocationException { + String content = ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + assertFormat(content, expected, settings, // + te(0, 2, 0, 3, lineSeparator() + " "), // + te(0, 10, 0, 11, lineSeparator() + " "), // + te(0, 19, 0, 19, lineSeparator() + " "), // + te(0, 21, 0, 22, lineSeparator() + " "), // + te(0, 29, 0, 30, lineSeparator() + " "), // + te(0, 32, 0, 33, ""), // + te(0, 34, 0, 35, ""), // + te(0, 44, 0, 44, lineSeparator())); + assertFormat(expected, expected, settings); + } + + @Test + public void testNestedAttributesNoSplit() throws BadLocationException { + String content = ""; + String expected = "" + lineSeparator() + // + " " + lineSeparator() + // + ""; + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(false); + assertFormat(content, expected, settings, // + te(0, 19, 0, 19, lineSeparator() + " "), // + te(0, 32, 0, 33, ""), // + te(0, 34, 0, 35, ""), // + te(0, 44, 0, 44, lineSeparator())); + assertFormat(expected, expected, settings); + } + + @Test + public void testSplitAttributesProlog() throws BadLocationException { + String content = ""; + String expected = ""; + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + assertFormat(content, expected, settings); + } + + @Test + public void testSplitAttributesSingle() throws BadLocationException { + String content = ""; + String expected = ""; + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + assertFormat(content, expected, settings); + } + + @Test + public void testSplitAttributes() throws BadLocationException { + String content = ""; + String expected = ""; + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + assertFormat(content, expected, settings, // + te(0, 2, 0, 3, lineSeparator() + " "), // + te(0, 10, 0, 11, lineSeparator() + " ")); + assertFormat(expected, expected, settings); + } + + @Test + public void testEndTagMissingCloseBracket2() throws BadLocationException { + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSpaceBeforeEmptyCloseTag(false); + settings.getFormattingSettings().setSplitAttributes(true); + + String content = "\n" + // + " \n" + // + " sssi\n" + // + " "; + String expected = "\n" + // + " \n" + // + " sssi\n" + // + " "; + assertFormat(content, expected, settings, // + te(0, 8, 1, 9, "\n "), // + te(1, 51, 2, 9, "\n "), // + te(2, 62, 3, 9, "\n "), // + te(4, 67, 5, 9, "\n "), // + te(5, 23, 6, 9, "\n "), // + te(6, 18, 7, 13, "\n "), // + te(7, 46, 8, 9, "\n ")); + assertFormat(expected, expected, settings); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterWithRangeTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterWithRangeTest.java new file mode 100644 index 0000000000..6d88e1f3ee --- /dev/null +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/format/experimental/XMLFormatterWithRangeTest.java @@ -0,0 +1,359 @@ +/******************************************************************************* +* Copyright (c) 2022 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 +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lemminx.services.format.experimental; + +import static org.eclipse.lemminx.XMLAssert.te; + +import org.eclipse.lemminx.XMLAssert; +import org.eclipse.lemminx.commons.BadLocationException; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.TextEdit; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * XML experimental formatter services tests with range. + * + */ +public class XMLFormatterWithRangeTest { + + @Test + public void range() throws BadLocationException { + String content = "
    \n" + // + " ||\n" + // + "
    "; + String expected = "
    \n" + // + " \n" + // + "
    "; + assertFormat(content, expected, // + te(1, 6, 1, 8, " "), // + te(1, 11, 1, 12, ""), // + te(1, 13, 1, 14, ""), // + te(1, 19, 1, 19, " ")); + + content = "
    \n" + // + " ||\n" + // + "
    "; + assertFormat(content, expected); + } + + @Test + public void range2() throws BadLocationException { + String content = "
    \n" + // + " ||\n" + // + " \n" + // + "
    "; + String expected = "
    \n" + // + " \n" + // + " \n" + // + "
    "; + assertFormat(content, expected, // + te(1, 6, 1, 8, " "), // + te(1, 11, 1, 12, ""), // + te(1, 13, 1, 14, ""), // + te(1, 19, 1, 19, " ")); + + content = "
    \n" + // + " ||\n" + // + " \n" + // + "
    "; + assertFormat(content, expected); + } + + @Test + public void rangeChildrenFullSelection() throws BadLocationException { + String content = "\n" + // + " \n" + // + " License Name\n" + // + "| abcdefghijklmnop\n" + // + " repo|\n" + // + " \n" + // + ""; + + String expected = "\n" + // + " \n" + // + " License Name\n" + // + " abcdefghijklmnop\n" + // + " repo\n" + // + " \n" + // + ""; + + assertFormat(content, expected, // + te(2, 29, 3, 11, "\n "), // + te(3, 38, 4, 12, "\n ")); + assertFormat(expected, expected); + } + + @Test + public void rangeChildrenPartialSelection() throws BadLocationException { + String content = "\n" + // + " \n" + // + " Licen|se Name\n" + // + " abcdefghijklmnop\n" + // + " repo|\n" + // + " \n" + // + ""; + + String expected = "\n" + // + " \n" + // + " License Name\n" + // + " abcdefghijklmnop\n" + // + " repo\n" + // + " \n" + // + ""; + + assertFormat(content, expected, // + te(1, 11, 2, 2, "\n "), // + te(2, 27, 3, 14, "\n "), // + te(3, 41, 4, 14, "\n ")); + assertFormat(expected, expected); + } + + @Test + public void rangeSelectAll() throws BadLocationException { + String content = "\n" + // + " \n" + // + " License Name\n" + // + " abcdefghijklmnop\n" + // + " repo\n" + // + " \n" + // + " "; + + String expected = "\n" + // + " \n" + // + " License Name\n" + // + " abcdefghijklmnop\n" + // + " repo\n" + // + " \n" + // + ""; + + assertFormat(content, expected, // + te(0, 10, 1, 44, "\n "), // + te(1, 53, 2, 24, "\n "), // + te(2, 49, 3, 8, "\n "), // + te(3, 35, 4, 8, "\n "), // + te(4, 41, 5, 40, "\n "), // + te(5, 50, 6, 64, "\n")); + assertFormat(expected, expected); + } + + @Test + public void rangeSelectOnlyPartialStartTagAndChildren() throws BadLocationException { + String content = "\n" + // + " \n" + // + " License Name\n" + // + " abcdefghijklmnop\n" + // + " repo|\n" + // + " \n" + // + ""; + + String expected = "\n" + // + " \n" + // + " License Name\n" + // + " abcdefghijklmnop\n" + // + " repo\n" + // + " \n" + // + ""; + + assertFormat(content, expected, // + te(0, 10, 1, 33, "\n "), // + te(1, 42, 2, 16, "\n "), // + te(2, 41, 3, 24, "\n "), // + te(3, 51, 4, 12, "\n ")); + assertFormat(expected, expected); + } + + @Test + public void rangeSelectOnlyFullStartTagAndChildren() throws BadLocationException { + String content = "\n" + // + " |\n" + // + " License Name\n" + // + " abcdefghijklmnop\n" + // + " repo|\n" + // + " \n" + // + ""; + + String expected = "\n" + // + " \n" + // + " License Name\n" + // + " abcdefghijklmnop\n" + // + " repo\n" + // + " \n" + // + ""; + + assertFormat(content, expected, // + te(0, 10, 1, 33, "\n "), // + te(1, 42, 2, 16, "\n "), // + te(2, 41, 3, 24, "\n "), // + te(3, 51, 4, 12, "\n ")); + assertFormat(expected, expected); + } + + @Test + public void rangeSelectOnlyPartialEndTagAndChildren() throws BadLocationException { + String content = "\n" + // + " \n" + // + " License Name\n" + // + " abcdefghijklmnop\n" + // + " repo\n" + // + " \n" + // + ""; + + String expected = "\n" + // + " \n" + // + " License Name\n" + // + " abcdefghijklmnop\n" + // + " repo\n" + // + " \n" + // + ""; + + assertFormat(content, expected, // + te(1, 11, 2, 16, "\n "), // + te(2, 41, 3, 24, "\n "), // + te(3, 51, 4, 12, "\n ")); + assertFormat(expected, expected); + } + + @Test + public void rangeSelectOnlyFullEndTagAndChildren() throws BadLocationException { + String content = "\n" + // + " \n" + // + " License Name\n" + // + " abcdefghijklmnop\n" + // + " repo\n" + // + " |\n" + // + ""; + + String expected = "\n" + // + " \n" + // + " License Name\n" + // + " abcdefghijklmnop\n" + // + " repo\n" + // + " \n" + // + ""; + + assertFormat(content, expected, // + te(1, 11, 2, 16, "\n "), // + te(2, 41, 3, 24, "\n "), // + te(3, 51, 4, 12, "\n ")); + assertFormat(expected, expected); + } + + @Test + public void rangeSelectWithinText() throws BadLocationException { + String content = "\n" + // + " Lic|en|se\n" + // + ""; + + String expected = "\n" + // + " License\n" + // + ""; + + assertFormat(content, expected); + } + + @Disabled + @Test + public void rangeSelectEntityNoIndent() throws BadLocationException { + String content = "\r\n" + // + "|\r\n" + // + "]>"; + String expected = "\r\n" + // + "\r\n" + // + "]>"; + assertFormat(content, expected); + } + + @Test + public void rangeSelectEntityWithIndent() throws BadLocationException { + String content = "\r\n" + // + "|\r\n" + // + "]>"; + String expected = "\r\n" + // + "\r\n" + // + "]>"; + assertFormat(content, expected); + } + + @Disabled + @Test + public void testSplitAttributesRangeOneLine() throws BadLocationException { + String content = "\r\n" + // + " sss\r\n" + // + ""; + + String expected = "\r\n" + // + " sss\r\n" + // + ""; + + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + assertFormat(content, expected, settings); + } + + @Disabled + @Test + public void testSplitAttributesRangeMultipleLines() throws BadLocationException { + String content = "\r\n" + // + " sss\r\n" + // + ""; + + String expected = "\r\n" + // + " sss\r\n" + // + ""; + ; + + SharedSettings settings = new SharedSettings(); + settings.getFormattingSettings().setSplitAttributes(true); + assertFormat(content, expected, settings); + } + + + private static void assertFormat(String unformatted, String actual, TextEdit... expectedEdits) + throws BadLocationException { + assertFormat(unformatted, actual, new SharedSettings(), expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, "test://test.html", expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + TextEdit... expectedEdits) throws BadLocationException { + assertFormat(unformatted, expected, sharedSettings, uri, true, expectedEdits); + } + + private static void assertFormat(String unformatted, String expected, SharedSettings sharedSettings, String uri, + Boolean considerRangeFormat, TextEdit... expectedEdits) throws BadLocationException { + // Force to "experimental" formatter + sharedSettings.getFormattingSettings().setExperimental(true); + XMLAssert.assertFormat(null, unformatted, expected, sharedSettings, uri, considerRangeFormat, expectedEdits); + } + +} diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/settings/SettingsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/settings/SettingsTest.java index af9262d0e8..85ae60bfc3 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/settings/SettingsTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/settings/SettingsTest.java @@ -22,9 +22,6 @@ import java.io.File; -import com.google.gson.Gson; -import com.google.gson.JsonObject; - import org.eclipse.lemminx.XMLLanguageServer; import org.eclipse.lemminx.client.CodeLensKind; import org.eclipse.lemminx.client.ExtendedClientCapabilities; @@ -38,6 +35,9 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import com.google.gson.Gson; +import com.google.gson.JsonObject; + /** * Tests for settings. */ @@ -86,7 +86,8 @@ public void cleanup() { " \"joinCDATALines\": true,\r\n" + // " \"formatComments\": true,\r\n" + // " \"joinCommentLines\": true,\r\n" + // - " \"preserveAttributeLineBreaks\": true\r\n" + // + " \"preserveAttributeLineBreaks\": true,\r\n" + // + " \"preserveSpace\": ['xsl:text']\r\n" + // " },\r\n" + " \"server\": {\r\n" + // " \"workDir\": \"~/" + testFolder + "/Nested\"\r\n" + // " },\r\n" + " \"symbols\": {\r\n" + // @@ -151,6 +152,22 @@ private static InitializeParams createInitializeParams(String json) { return initializeParams; } + @Test + public void formatSettingsFromJson() { + // Tests load of XML format. + + InitializeParams params = createInitializeParams(json); + Object initializationOptionsSettings = InitializationOptionsSettings.getSettings(params); + XMLLanguageServer languageServer = new XMLLanguageServer(); + languageServer.updateSettings(initializationOptionsSettings); // This should set/update the sharedSettings + + XMLFormattingOptions xmlFormattingOptions = languageServer.getSharedSettings().getFormattingSettings(); + assertEquals(10, xmlFormattingOptions.getTabSize()); + assertNotNull(xmlFormattingOptions.getPreserveSpace()); + assertEquals(1, xmlFormattingOptions.getPreserveSpace().size()); + assertEquals("xsl:text", xmlFormattingOptions.getPreserveSpace().get(0)); + } + @Test public void formatSettings() { // formatting options coming from request @@ -180,13 +197,13 @@ public void formatSettings() { @Test public void formatSettingsOverride() { XMLFormattingOptions options = new XMLFormattingOptions(); - options.setPreserveAttrLineBreaks(true); + options.setPreserveAttributeLineBreaks(true); options.setSplitAttributes(false); - assertTrue(options.isPreserveAttrLineBreaks()); + assertTrue(options.isPreserveAttributeLineBreaks()); options.setSplitAttributes(true); // overridden - assertFalse(options.isPreserveAttrLineBreaks()); + assertFalse(options.isPreserveAttributeLineBreaks()); } @Test @@ -232,7 +249,7 @@ public void symbolSettingsTest() { XMLExcludedSymbolFile xmlFile = new XMLExcludedSymbolFile("**\\*.xml"); XMLExcludedSymbolFile[] expectedExcludedFiles = new XMLExcludedSymbolFile[] { xsdFile, xmlFile }; - XMLExcludedSymbolFile[] actualExpectedFiles = languageServer.getSettings().getSymbolSettings() + XMLExcludedSymbolFile[] actualExpectedFiles = languageServer.getSharedSettings().getSymbolSettings() .getExcludedFiles(); assertArrayEquals(expectedExcludedFiles, actualExpectedFiles); } @@ -255,7 +272,8 @@ public void extendedClientCapabilitiesTest() { @Test public void oldBooleanDoesntCrashSettings() { - AllXMLSettings allSettings = new Gson().fromJson("{'xml': { \"validation\": { \"schema\": false}}}", AllXMLSettings.class); + AllXMLSettings allSettings = new Gson().fromJson("{'xml': { \"validation\": { \"schema\": false}}}", + AllXMLSettings.class); JSONUtility.toModel(allSettings.getXml(), ContentModelSettings.class); } }