You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@royale.apache.org by jo...@apache.org on 2021/10/28 21:42:00 UTC
[royale-compiler] 01/02: formatter: MXML whitespace improvements
This is an automated email from the ASF dual-hosted git repository.
joshtynjala pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-compiler.git
commit 15d4b7ba3d0f8252cbee512be6b10d06e9ece128
Author: Josh Tynjala <jo...@apache.org>
AuthorDate: Thu Oct 28 14:08:46 2021 -0700
formatter: MXML whitespace improvements
---
.../org/apache/royale/formatter/FORMATTER.java | 94 ++++++---
.../org/apache/royale/formatter/TestMXMLTag.java | 214 +++++++++++++++++++++
2 files changed, 286 insertions(+), 22 deletions(-)
diff --git a/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java b/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java
index 31d30cd..132c246 100644
--- a/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java
+++ b/formatter/src/main/java/org/apache/royale/formatter/FORMATTER.java
@@ -1677,7 +1677,18 @@ public class FORMATTER {
prevTokenOrExtra = token;
continue;
} else if (token.getType() == MXMLTokenTypes.TOKEN_WHITESPACE) {
- numRequiredNewLines = Math.max(numRequiredNewLines, countNewLinesInExtra(token));
+ if(elementStack.isEmpty() || !elementStack.get(elementStack.size() - 1).containsText) {
+ numRequiredNewLines = Math.max(numRequiredNewLines, countNewLinesInExtra(token));
+ }
+ else {
+ // if the parent element contains text, treat whitespace
+ // the same as text, and don't reformat it
+ // text is never reformatted because some components use it
+ // without collapsing whitespace, and developers would be
+ // confused if whitespace that they deliberately added were
+ // to be removed
+ builder.append(token.getText());
+ }
if (i == (tokens.size() - 1)) {
// if the last token is whitespace, append new lines
appendNewLines(builder, numRequiredNewLines);
@@ -1721,18 +1732,16 @@ public class FORMATTER {
// characters that must appear before the token
switch (token.getType()) {
case MXMLTokenTypes.TOKEN_OPEN_TAG_START: {
- if (prevToken == null || prevToken.getType() != MXMLTokenTypes.TOKEN_TEXT) {
- numRequiredNewLines = Math.max(numRequiredNewLines, 1);
- }
inOpenTag = true;
- elementStack.add(new ElementStackItem(token.getText().substring(1)));
+ // if the parent contains text, children should be the same
+ boolean containsText = !elementStack.isEmpty() && elementStack.get(elementStack.size() - 1).containsText;
+ elementStack.add(new ElementStackItem(token, token.getText().substring(1), containsText));
break;
}
case MXMLTokenTypes.TOKEN_CLOSE_TAG_START: {
- if (prevToken == null || prevToken.getType() != MXMLTokenTypes.TOKEN_TEXT) {
- numRequiredNewLines = Math.max(numRequiredNewLines, 1);
+ if(elementStack.isEmpty() || !elementStack.get(elementStack.size() - 1).containsText) {
+ indent = decreaseIndent(indent);
}
- indent = decreaseIndent(indent);
inCloseTag = true;
break;
}
@@ -1740,12 +1749,6 @@ public class FORMATTER {
requiredSpace = true;
break;
}
- case MXMLTokenTypes.TOKEN_TAG_END: {
- break;
- }
- case MXMLTokenTypes.TOKEN_EMPTY_TAG_END: {
- break;
- }
}
if (prevToken != null) {
@@ -1762,6 +1765,8 @@ public class FORMATTER {
}
// include the token's own text
+ // no token gets reformatted before being appended
+ // whitespace is the only special case, but that's not handled here
builder.append(token.getText());
// characters that must appear after the token
@@ -1798,11 +1803,19 @@ public class FORMATTER {
break;
}
case MXMLTokenTypes.TOKEN_TAG_END: {
- if (!inOpenTag || nextToken == null || nextToken.getType() != MXMLTokenTypes.TOKEN_TEXT) {
- numRequiredNewLines = Math.max(numRequiredNewLines, 1);
- }
if (inOpenTag) {
- indent = increaseIndent(indent);
+ ElementStackItem element = elementStack.get(elementStack.size() - 1);
+ if (!element.containsText) {
+ element.containsText = elementContainsText(tokens, i + 1, element.token);
+ }
+ if(elementStack.isEmpty() || !elementStack.get(elementStack.size() - 1).containsText) {
+ indent = increaseIndent(indent);
+ }
+ }
+ else {
+ if(elementStack.isEmpty() || !elementStack.get(elementStack.size() - 1).containsText) {
+ numRequiredNewLines = Math.max(numRequiredNewLines, 1);
+ }
}
inOpenTag = false;
if (indentedAttributes) {
@@ -1813,12 +1826,14 @@ public class FORMATTER {
break;
}
case MXMLTokenTypes.TOKEN_EMPTY_TAG_END: {
- if (!inOpenTag || nextToken == null || nextToken.getType() != MXMLTokenTypes.TOKEN_TEXT) {
- numRequiredNewLines = Math.max(numRequiredNewLines, 1);
- }
if (inOpenTag) {
elementStack.remove(elementStack.size() - 1);
}
+ else {
+ if(elementStack.isEmpty() || !elementStack.get(elementStack.size() - 1).containsText) {
+ numRequiredNewLines = Math.max(numRequiredNewLines, 1);
+ }
+ }
inOpenTag = false;
// no need to change nested indent after this tag
// however, we may need to remove attribute indent
@@ -1839,6 +1854,37 @@ public class FORMATTER {
return builder.toString();
}
+ private boolean elementContainsText(List<IMXMLToken> tokens, int startIndex, IMXMLToken openTagToken) {
+ ArrayList<IMXMLToken> elementStack = new ArrayList<IMXMLToken>();
+ elementStack.add(openTagToken);
+ for(int i = startIndex; i < tokens.size(); i++) {
+ IMXMLToken token = tokens.get(i);
+ switch(token.getType()) {
+ case MXMLTokenTypes.TOKEN_TEXT:
+ if(elementStack.size() == 1) {
+ return true;
+ }
+ break;
+ case MXMLTokenTypes.TOKEN_OPEN_TAG_START:
+ elementStack.add(token);
+ break;
+ case MXMLTokenTypes.TOKEN_EMPTY_TAG_END:
+ elementStack.remove(elementStack.size() - 1);
+ if(elementStack.size() == 0) {
+ return false;
+ }
+ break;
+ case MXMLTokenTypes.TOKEN_CLOSE_TAG_START:
+ elementStack.remove(elementStack.size() - 1);
+ if(elementStack.size() == 0) {
+ return false;
+ }
+ break;
+ }
+ }
+ return false;
+ }
+
private int countNewLinesInExtra(IMXMLToken token) {
if (token == null
|| (token.getType() != MXMLTokenTypes.TOKEN_WHITESPACE && token.getType() != TOKEN_TYPE_EXTRA)) {
@@ -1854,10 +1900,14 @@ public class FORMATTER {
}
private static class ElementStackItem {
- public ElementStackItem(String elementName) {
+ public ElementStackItem(IMXMLToken token, String elementName, boolean containsText) {
+ this.token = token;
this.elementName = elementName;
+ this.containsText = containsText;
}
+ public IMXMLToken token;
public String elementName;
+ public boolean containsText = false;
}
}
\ No newline at end of file
diff --git a/formatter/src/test/java/org/apache/royale/formatter/TestMXMLTag.java b/formatter/src/test/java/org/apache/royale/formatter/TestMXMLTag.java
new file mode 100644
index 0000000..8096322
--- /dev/null
+++ b/formatter/src/test/java/org/apache/royale/formatter/TestMXMLTag.java
@@ -0,0 +1,214 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package org.apache.royale.formatter;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class TestMXMLTag extends BaseFormatterTests {
+ @Test
+ public void testSelfClosingTag() {
+ FORMATTER formatter = new FORMATTER();
+ formatter.insertSpaces = false;
+ String result = formatter.formatMXMLText(
+ // @formatter:off
+ "<s:Tag/>",
+ // @formatter:on
+ problems
+ );
+ assertEquals(
+ // @formatter:off
+ "<s:Tag/>",
+ // @formatter:on
+ result);
+ }
+
+ @Test
+ public void testTagWithEmptyText() {
+ FORMATTER formatter = new FORMATTER();
+ formatter.insertSpaces = false;
+ String result = formatter.formatMXMLText(
+ // @formatter:off
+ "<s:Tag></s:Tag>",
+ // @formatter:on
+ problems
+ );
+ assertEquals(
+ // @formatter:off
+ "<s:Tag></s:Tag>",
+ // @formatter:on
+ result);
+ }
+
+ @Test
+ public void testTagWithText() {
+ FORMATTER formatter = new FORMATTER();
+ formatter.insertSpaces = false;
+ String result = formatter.formatMXMLText(
+ // @formatter:off
+ "<s:Tag>Hello World</s:Tag>",
+ // @formatter:on
+ problems
+ );
+ assertEquals(
+ // @formatter:off
+ "<s:Tag>Hello World</s:Tag>",
+ // @formatter:on
+ result);
+ }
+
+ @Test
+ public void testTagWithTextAndNewLines() {
+ FORMATTER formatter = new FORMATTER();
+ formatter.insertSpaces = false;
+ String result = formatter.formatMXMLText(
+ // @formatter:off
+ "<s:Tag>\n" +
+ "\tHello World\n" +
+ "</s:Tag>",
+ // @formatter:on
+ problems
+ );
+ assertEquals(
+ // @formatter:off
+ "<s:Tag>\n" +
+ "\tHello World\n" +
+ "</s:Tag>",
+ // @formatter:on
+ result);
+ }
+
+ @Test
+ public void testTagWithNewLineText() {
+ FORMATTER formatter = new FORMATTER();
+ formatter.insertSpaces = false;
+ String result = formatter.formatMXMLText(
+ // @formatter:off
+ "<s:Tag>\n" +
+ "</s:Tag>",
+ // @formatter:on
+ problems
+ );
+ assertEquals(
+ // @formatter:off
+ "<s:Tag>\n" +
+ "</s:Tag>",
+ // @formatter:on
+ result);
+ }
+
+ @Test
+ public void testNewLinesBetweenTags() {
+ FORMATTER formatter = new FORMATTER();
+ formatter.insertSpaces = false;
+ String result = formatter.formatMXMLText(
+ // @formatter:off
+ "<s:Tag>\n" +
+ "\n" +
+ "</s:Tag>\n" +
+ "\n" +
+ "<s:Tag/>",
+ // @formatter:on
+ problems
+ );
+ assertEquals(
+ // @formatter:off
+ "<s:Tag>\n" +
+ "\n" +
+ "</s:Tag>\n" +
+ "\n" +
+ "<s:Tag/>",
+ // @formatter:on
+ result);
+ }
+
+ @Test
+ public void testExcessWhitespaceBetweenTags() {
+ FORMATTER formatter = new FORMATTER();
+ formatter.insertSpaces = false;
+ String result = formatter.formatMXMLText(
+ // @formatter:off
+ "<s:Tag>\t\n" +
+ "\n\t" +
+ "\t</s:Tag>",
+ // @formatter:on
+ problems
+ );
+ assertEquals(
+ // @formatter:off
+ "<s:Tag>\n" +
+ "\n" +
+ "</s:Tag>",
+ // @formatter:on
+ result);
+ }
+
+ @Test
+ public void testMixedTextAndTagChildren1() {
+ FORMATTER formatter = new FORMATTER();
+ formatter.insertSpaces = false;
+ String result = formatter.formatMXMLText(
+ // @formatter:off
+ "<s:Tag>text <s:Tag/></s:Tag>",
+ // @formatter:on
+ problems
+ );
+ assertEquals(
+ // @formatter:off
+ "<s:Tag>text <s:Tag/></s:Tag>",
+ // @formatter:on
+ result);
+ }
+
+ @Test
+ public void testMixedTextAndTagChildren2() {
+ FORMATTER formatter = new FORMATTER();
+ formatter.insertSpaces = false;
+ String result = formatter.formatMXMLText(
+ // @formatter:off
+ "<s:Tag><s:Tag/> text</s:Tag>",
+ // @formatter:on
+ problems
+ );
+ assertEquals(
+ // @formatter:off
+ "<s:Tag><s:Tag/> text</s:Tag>",
+ // @formatter:on
+ result);
+ }
+
+ @Test
+ public void testMixedTextAndTagChildren3() {
+ FORMATTER formatter = new FORMATTER();
+ formatter.insertSpaces = false;
+ String result = formatter.formatMXMLText(
+ // @formatter:off
+ "<s:Tag><s:Tag/> text <s:Tag/></s:Tag>",
+ // @formatter:on
+ problems
+ );
+ assertEquals(
+ // @formatter:off
+ "<s:Tag><s:Tag/> text <s:Tag/></s:Tag>",
+ // @formatter:on
+ result);
+ }
+}
\ No newline at end of file