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 <[email protected]> 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
