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