From 90a6836ba0f4fdb6cb894322e9a84e74e595f666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Lars=C3=A9n?= Date: Thu, 3 Dec 2020 08:26:48 +0100 Subject: [PATCH 1/9] Add indentation test case --- .../test/prettyprinter/TestSniperPrinter.java | 31 +++++++++++++++++++ .../resources/indentation/FourSpaces.java | 10 ++++++ src/test/resources/indentation/Tabs.java | 10 ++++++ src/test/resources/indentation/TwoSpaces.java | 10 ++++++ 4 files changed, 61 insertions(+) create mode 100644 src/test/resources/indentation/FourSpaces.java create mode 100644 src/test/resources/indentation/Tabs.java create mode 100644 src/test/resources/indentation/TwoSpaces.java diff --git a/src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java b/src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java index 99ea0c45def..49381c3a0d3 100644 --- a/src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java +++ b/src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java @@ -14,6 +14,7 @@ import spoon.SpoonException; import spoon.refactoring.Refactoring; import spoon.reflect.CtModel; +import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtConstructorCall; import spoon.reflect.code.CtCodeSnippetExpression; import spoon.reflect.code.CtExpression; @@ -53,6 +54,7 @@ import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -415,6 +417,35 @@ public void testAddedImportStatementPlacedOnSeparateLineInFileWithPackageStateme testSniper("visibility.YamlRepresenter", addArrayListImport, assertImportsPrintedCorrectly); } + @Test + public void testAddedElementsIndentedWithAppropriateIndentationStyle() { + // contract: added elements in a source file should be indented with the same style of + // indentation as the rest of the file + + Consumer> addElements = type -> { + Factory fact = type.getFactory(); + fact.createField(type, new HashSet<>(), fact.Type().INTEGER_PRIMITIVE, "z", fact.createLiteral(3)); + type.getMethod("sum").getBody() + .addStatement(0, fact.createCodeSnippetStatement("System.out.println(z);")); + }; + BiConsumer, String> assertTabs = (type, result) -> { + assertThat(result, containsString("\n\tint z = 3;")); + assertThat(result, containsString("\n\t\tSystem")); + }; + BiConsumer, String> assertTwoSpaces = (type, result) -> { + assertThat(result, containsString("\n int z = 3;")); + assertThat(result, containsString("\n System")); + }; + BiConsumer, String> assertFourSpaces = (type, result) -> { + assertThat(result, containsString("\n int z = 3;")); + assertThat(result, containsString("\n System")); + }; + + testSniper("indentation.Tabs", addElements, assertTabs); + testSniper("indentation.TwoSpaces", addElements, assertTwoSpaces); + testSniper("indentation.FourSpaces", addElements, assertFourSpaces); + } + /** * 1) Runs spoon using sniper mode, * 2) runs `typeChanger` to modify the code, diff --git a/src/test/resources/indentation/FourSpaces.java b/src/test/resources/indentation/FourSpaces.java new file mode 100644 index 00000000000..933b7f62609 --- /dev/null +++ b/src/test/resources/indentation/FourSpaces.java @@ -0,0 +1,10 @@ +package indentation; + +public class FourSpaces { + private int x = 1; + private int y = 2; + + public int sum() { + return x + y; + } +} diff --git a/src/test/resources/indentation/Tabs.java b/src/test/resources/indentation/Tabs.java new file mode 100644 index 00000000000..b088ff1fe38 --- /dev/null +++ b/src/test/resources/indentation/Tabs.java @@ -0,0 +1,10 @@ +package indentation; + +public class Tabs { + private int x = 1; + private int y = 2; + + public int sum() { + return x + y; + } +} \ No newline at end of file diff --git a/src/test/resources/indentation/TwoSpaces.java b/src/test/resources/indentation/TwoSpaces.java new file mode 100644 index 00000000000..13496106033 --- /dev/null +++ b/src/test/resources/indentation/TwoSpaces.java @@ -0,0 +1,10 @@ +package indentation; + +public class TwoSpaces { + private int x = 1; + private int y = 2; + + public int sum() { + return x + y; + } +} \ No newline at end of file From d440fcab581e3bb0d4e012f1d308f01b2ef60009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Lars=C3=A9n?= Date: Thu, 3 Dec 2020 11:09:15 +0100 Subject: [PATCH 2/9] Implement indentation detection --- .../sniper/SniperJavaPrettyPrinter.java | 69 +++++++++++++++++++ .../sniper/internal/MutableTokenWriter.java | 38 +++++++++- 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java b/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java index a4de51b90dd..406db5b76b7 100644 --- a/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java +++ b/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java @@ -8,11 +8,14 @@ package spoon.support.sniper; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Deque; import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.tuple.Pair; import spoon.OutputType; import spoon.SpoonException; import spoon.compiler.Environment; @@ -44,6 +47,7 @@ import spoon.support.sniper.internal.SourceFragmentContextNormal; import spoon.support.sniper.internal.DefaultSourceFragmentPrinter; import spoon.support.sniper.internal.TokenPrinterEvent; +import spoon.support.sniper.internal.TokenSourceFragment; import spoon.support.sniper.internal.TokenType; import spoon.support.sniper.internal.TokenWriterProxy; import spoon.support.util.ModelList; @@ -132,6 +136,12 @@ public void calculate(CtCompilationUnit compilationUnit, List> types) //use line separator of origin source file setLineSeparator(detectLineSeparator(compilationUnit.getOriginalSourceCode())); + + // use indentation style of origin source file for new elements + Pair indentationInfo = detectIndentation(compilationUnit); + mutableTokenWriter.setOriginSourceTabulationSize(indentationInfo.getLeft()); + mutableTokenWriter.setOriginSourceUsesTabulations(indentationInfo.getRight()); + runInContext(new SourceFragmentContextList(mutableTokenWriter, compilationUnit, Collections.singletonList(compilationUnit.getOriginalSourceFragment()), @@ -141,6 +151,65 @@ public void calculate(CtCompilationUnit compilationUnit, List> types) }); } + private static Pair detectIndentation(CtCompilationUnit cu) { + List typeFragments = cu.getOriginalSourceFragment() + .getGroupedChildrenFragments().stream() + .filter(fragment -> fragment instanceof CollectionSourceFragment) + .flatMap(fragment -> extractTypeFragments((CollectionSourceFragment) fragment).stream()) + .collect(Collectors.toList()); + return detectIndentation(typeFragments); + } + + private static List extractTypeFragments(CollectionSourceFragment collection) { + return collection.getItems().stream() + .filter(fragment -> fragment instanceof ElementSourceFragment) + .map(fragment -> (ElementSourceFragment) fragment) + .filter(fragment -> fragment.getRoleInParent() == CtRole.DECLARED_TYPE) + .collect(Collectors.toList()); + } + + private static Pair detectIndentation(List typeFragments) { + List spacePrecedingTypeMember = new ArrayList<>(); + + for (ElementSourceFragment typeSource : typeFragments) { + assert typeSource.getRoleInParent() == CtRole.DECLARED_TYPE; + List children = typeSource.getChildrenFragments(); + for (int i = 0; i < children.size() - 1; i++) { + if (children.get(i) instanceof TokenSourceFragment + && children.get(i + 1) instanceof ElementSourceFragment) { + + TokenSourceFragment cur = (TokenSourceFragment) children.get(i); + ElementSourceFragment next = (ElementSourceFragment) children.get(i + 1); + if (cur.getType() == TokenType.SPACE && next.getRoleInParent() == CtRole.TYPE_MEMBER) { + spacePrecedingTypeMember.add(cur.getSourceCode().replace("\n", "")); + } + } + } + } + + double avgIndent = spacePrecedingTypeMember.stream() + .map(String::length) + .map(Double::valueOf) + .reduce((acc, next) -> (acc + next) / 2).orElse(4d); + + double diff1 = Math.abs(1d - avgIndent); + double diff2 = Math.abs(2d - avgIndent); + double diff4 = Math.abs(4d - avgIndent); + + int indentationSize; + if (diff1 > diff2) { + indentationSize = diff2 > diff4 ? 4 : 2; + } else { + indentationSize = 1; + } + + boolean usesTabs = spacePrecedingTypeMember.stream() + .filter(s -> s.contains("\t")) + .count() >= spacePrecedingTypeMember.size() / 2; + + return Pair.of(indentationSize, usesTabs); + } + private static final String CR = "\r"; private static final String CRLF = "\r\n"; private static final String LF = "\n"; diff --git a/src/main/java/spoon/support/sniper/internal/MutableTokenWriter.java b/src/main/java/spoon/support/sniper/internal/MutableTokenWriter.java index 6241bd8ccde..12340b3bc53 100644 --- a/src/main/java/spoon/support/sniper/internal/MutableTokenWriter.java +++ b/src/main/java/spoon/support/sniper/internal/MutableTokenWriter.java @@ -22,8 +22,36 @@ public class MutableTokenWriter implements TokenWriter { private final TokenWriter delegate; private boolean muted = false; + // indentation style to use for new elements + private boolean originSourceUsesTabulations; + private int originSourceTabulationSize; + public MutableTokenWriter(Environment env) { - this.delegate = new DefaultTokenWriter(new PrinterHelper(env)); + this.delegate = new DefaultTokenWriter(new SniperPrinterHelper(env)); + originSourceUsesTabulations = true; + originSourceTabulationSize = 1; + } + + private class SniperPrinterHelper extends PrinterHelper { + private final Environment env; + + SniperPrinterHelper(Environment env) { + super(env); + this.env = env; + } + + /** + * We override this method to use the correct style of indentation for new elements. + */ + @Override + protected void autoWriteTabs() { + int setTabulationSize = env.getTabulationSize(); + env.useTabulations(originSourceUsesTabulations); + env.setTabulationSize(originSourceTabulationSize); + super.autoWriteTabs(); + env.setTabulationSize(setTabulationSize); + env.useTabulations(true); + } } /** @@ -40,6 +68,14 @@ public void setMuted(boolean muted) { this.muted = muted; } + public void setOriginSourceUsesTabulations(boolean originSourceUsesTabulations) { + this.originSourceUsesTabulations = originSourceUsesTabulations; + } + + public void setOriginSourceTabulationSize(int originSourceTabulationSize) { + this.originSourceTabulationSize = originSourceTabulationSize; + } + @Override public TokenWriter writeSeparator(String token) { if (isMuted()) { From e69e86129d35230f255de1c2c76bce192e25c99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Lars=C3=A9n?= Date: Thu, 3 Dec 2020 11:14:26 +0100 Subject: [PATCH 3/9] Refactor indentation detection --- .../support/sniper/SniperJavaPrettyPrinter.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java b/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java index 406db5b76b7..af5bdce17d2 100644 --- a/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java +++ b/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java @@ -169,7 +169,7 @@ private static List extractTypeFragments(CollectionSource } private static Pair detectIndentation(List typeFragments) { - List spacePrecedingTypeMember = new ArrayList<>(); + List wsPrecedingTypeMembers = new ArrayList<>(); for (ElementSourceFragment typeSource : typeFragments) { assert typeSource.getRoleInParent() == CtRole.DECLARED_TYPE; @@ -181,13 +181,17 @@ private static Pair detectIndentation(List guessIndentationStyle(List wsPrecedingTypeMembers) { + double avgIndent = wsPrecedingTypeMembers.stream() .map(String::length) .map(Double::valueOf) .reduce((acc, next) -> (acc + next) / 2).orElse(4d); @@ -203,10 +207,9 @@ private static Pair detectIndentation(List s.contains("\t")) - .count() >= spacePrecedingTypeMember.size() / 2; - + .count() >= wsPrecedingTypeMembers.size() / 2; return Pair.of(indentationSize, usesTabs); } From 62d8e113f35992e5e75edb9c96848ed9c3077898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Lars=C3=A9n?= Date: Thu, 3 Dec 2020 11:19:29 +0100 Subject: [PATCH 4/9] Move indentation detection to a separate class --- .../sniper/SniperJavaPrettyPrinter.java | 65 +------------- .../sniper/internal/IndentationDetector.java | 90 +++++++++++++++++++ 2 files changed, 92 insertions(+), 63 deletions(-) create mode 100644 src/main/java/spoon/support/sniper/internal/IndentationDetector.java diff --git a/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java b/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java index af5bdce17d2..bc9d71ed14d 100644 --- a/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java +++ b/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java @@ -38,6 +38,7 @@ import spoon.support.sniper.internal.CollectionSourceFragment; import spoon.support.sniper.internal.ElementPrinterEvent; import spoon.support.sniper.internal.ElementSourceFragment; +import spoon.support.sniper.internal.IndentationDetector; import spoon.support.sniper.internal.ModificationStatus; import spoon.support.sniper.internal.MutableTokenWriter; import spoon.support.sniper.internal.PrinterEvent; @@ -138,7 +139,7 @@ public void calculate(CtCompilationUnit compilationUnit, List> types) setLineSeparator(detectLineSeparator(compilationUnit.getOriginalSourceCode())); // use indentation style of origin source file for new elements - Pair indentationInfo = detectIndentation(compilationUnit); + Pair indentationInfo = IndentationDetector.detectIndentation(compilationUnit); mutableTokenWriter.setOriginSourceTabulationSize(indentationInfo.getLeft()); mutableTokenWriter.setOriginSourceUsesTabulations(indentationInfo.getRight()); @@ -151,68 +152,6 @@ public void calculate(CtCompilationUnit compilationUnit, List> types) }); } - private static Pair detectIndentation(CtCompilationUnit cu) { - List typeFragments = cu.getOriginalSourceFragment() - .getGroupedChildrenFragments().stream() - .filter(fragment -> fragment instanceof CollectionSourceFragment) - .flatMap(fragment -> extractTypeFragments((CollectionSourceFragment) fragment).stream()) - .collect(Collectors.toList()); - return detectIndentation(typeFragments); - } - - private static List extractTypeFragments(CollectionSourceFragment collection) { - return collection.getItems().stream() - .filter(fragment -> fragment instanceof ElementSourceFragment) - .map(fragment -> (ElementSourceFragment) fragment) - .filter(fragment -> fragment.getRoleInParent() == CtRole.DECLARED_TYPE) - .collect(Collectors.toList()); - } - - private static Pair detectIndentation(List typeFragments) { - List wsPrecedingTypeMembers = new ArrayList<>(); - - for (ElementSourceFragment typeSource : typeFragments) { - assert typeSource.getRoleInParent() == CtRole.DECLARED_TYPE; - List children = typeSource.getChildrenFragments(); - for (int i = 0; i < children.size() - 1; i++) { - if (children.get(i) instanceof TokenSourceFragment - && children.get(i + 1) instanceof ElementSourceFragment) { - - TokenSourceFragment cur = (TokenSourceFragment) children.get(i); - ElementSourceFragment next = (ElementSourceFragment) children.get(i + 1); - if (cur.getType() == TokenType.SPACE && next.getRoleInParent() == CtRole.TYPE_MEMBER) { - wsPrecedingTypeMembers.add(cur.getSourceCode().replace("\n", "")); - } - } - } - } - - return guessIndentationStyle(wsPrecedingTypeMembers); - } - - private static Pair guessIndentationStyle(List wsPrecedingTypeMembers) { - double avgIndent = wsPrecedingTypeMembers.stream() - .map(String::length) - .map(Double::valueOf) - .reduce((acc, next) -> (acc + next) / 2).orElse(4d); - - double diff1 = Math.abs(1d - avgIndent); - double diff2 = Math.abs(2d - avgIndent); - double diff4 = Math.abs(4d - avgIndent); - - int indentationSize; - if (diff1 > diff2) { - indentationSize = diff2 > diff4 ? 4 : 2; - } else { - indentationSize = 1; - } - - boolean usesTabs = wsPrecedingTypeMembers.stream() - .filter(s -> s.contains("\t")) - .count() >= wsPrecedingTypeMembers.size() / 2; - return Pair.of(indentationSize, usesTabs); - } - private static final String CR = "\r"; private static final String CRLF = "\r\n"; private static final String LF = "\n"; diff --git a/src/main/java/spoon/support/sniper/internal/IndentationDetector.java b/src/main/java/spoon/support/sniper/internal/IndentationDetector.java new file mode 100644 index 00000000000..53143fc4035 --- /dev/null +++ b/src/main/java/spoon/support/sniper/internal/IndentationDetector.java @@ -0,0 +1,90 @@ +/** + * SPDX-License-Identifier: (MIT OR CECILL-C) + * + * Copyright (C) 2006-2019 INRIA and contributors + * + * Spoon is available either under the terms of the MIT License (see LICENSE-MIT.txt) of the Cecill-C License (see LICENSE-CECILL-C.txt). You as the user are entitled to choose the terms under which to adopt Spoon. + */ +package spoon.support.sniper.internal; + +import org.apache.commons.lang3.tuple.Pair; +import spoon.reflect.declaration.CtCompilationUnit; +import spoon.reflect.path.CtRole; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Class for detecting the indentation style used in a compilation unit. + */ +public class IndentationDetector { + + /** + * Detect the indentation style of the given compilation unit as 1, 2 or 4 spaces or tabs. + * + * @param cu A compilation unit. + * @return A pair on the form (indentationSize, isTabs) + */ + public static Pair detectIndentation(CtCompilationUnit cu) { + List typeFragments = cu.getOriginalSourceFragment() + .getGroupedChildrenFragments().stream() + .filter(fragment -> fragment instanceof CollectionSourceFragment) + .flatMap(fragment -> extractTypeFragments((CollectionSourceFragment) fragment).stream()) + .collect(Collectors.toList()); + return detectIndentation(typeFragments); + } + + private static Pair detectIndentation(List typeFragments) { + List wsPrecedingTypeMembers = new ArrayList<>(); + + for (ElementSourceFragment typeSource : typeFragments) { + assert typeSource.getRoleInParent() == CtRole.DECLARED_TYPE; + List children = typeSource.getChildrenFragments(); + for (int i = 0; i < children.size() - 1; i++) { + if (children.get(i) instanceof TokenSourceFragment + && children.get(i + 1) instanceof ElementSourceFragment) { + + TokenSourceFragment cur = (TokenSourceFragment) children.get(i); + ElementSourceFragment next = (ElementSourceFragment) children.get(i + 1); + if (cur.getType() == TokenType.SPACE && next.getRoleInParent() == CtRole.TYPE_MEMBER) { + wsPrecedingTypeMembers.add(cur.getSourceCode().replace("\n", "")); + } + } + } + } + + return guessIndentationStyle(wsPrecedingTypeMembers); + } + + private static Pair guessIndentationStyle(List wsPrecedingTypeMembers) { + double avgIndent = wsPrecedingTypeMembers.stream() + .map(String::length) + .map(Double::valueOf) + .reduce((acc, next) -> (acc + next) / 2).orElse(4d); + + double diff1 = Math.abs(1d - avgIndent); + double diff2 = Math.abs(2d - avgIndent); + double diff4 = Math.abs(4d - avgIndent); + + int indentationSize; + if (diff1 > diff2) { + indentationSize = diff2 > diff4 ? 4 : 2; + } else { + indentationSize = 1; + } + + boolean usesTabs = wsPrecedingTypeMembers.stream() + .filter(s -> s.contains("\t")) + .count() > wsPrecedingTypeMembers.size() / 2; + return Pair.of(indentationSize, usesTabs); + } + + private static List extractTypeFragments(CollectionSourceFragment collection) { + return collection.getItems().stream() + .filter(fragment -> fragment instanceof ElementSourceFragment) + .map(fragment -> (ElementSourceFragment) fragment) + .filter(fragment -> fragment.getRoleInParent() == CtRole.DECLARED_TYPE) + .collect(Collectors.toList()); + } +} \ No newline at end of file From 94950414dc53ecac49aec513f11ebbf809d147b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Lars=C3=A9n?= Date: Thu, 3 Dec 2020 11:36:03 +0100 Subject: [PATCH 5/9] Define default to be 1 tabs --- .../sniper/internal/IndentationDetector.java | 9 +++++---- .../test/prettyprinter/TestSniperPrinter.java | 14 ++++++++++++++ src/test/resources/indentation/NoTypeMembers.java | 4 ++++ 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 src/test/resources/indentation/NoTypeMembers.java diff --git a/src/main/java/spoon/support/sniper/internal/IndentationDetector.java b/src/main/java/spoon/support/sniper/internal/IndentationDetector.java index 53143fc4035..feeddc55de4 100644 --- a/src/main/java/spoon/support/sniper/internal/IndentationDetector.java +++ b/src/main/java/spoon/support/sniper/internal/IndentationDetector.java @@ -35,11 +35,12 @@ public static Pair detectIndentation(CtCompilationUnit cu) { return detectIndentation(typeFragments); } - private static Pair detectIndentation(List typeFragments) { + private static Pair detectIndentation(List topLevelTypeFragments) { List wsPrecedingTypeMembers = new ArrayList<>(); - for (ElementSourceFragment typeSource : typeFragments) { + for (ElementSourceFragment typeSource : topLevelTypeFragments) { assert typeSource.getRoleInParent() == CtRole.DECLARED_TYPE; + List children = typeSource.getChildrenFragments(); for (int i = 0; i < children.size() - 1; i++) { if (children.get(i) instanceof TokenSourceFragment @@ -61,7 +62,7 @@ private static Pair guessIndentationStyle(List wsPrece double avgIndent = wsPrecedingTypeMembers.stream() .map(String::length) .map(Double::valueOf) - .reduce((acc, next) -> (acc + next) / 2).orElse(4d); + .reduce((acc, next) -> (acc + next) / 2).orElse(1d); double diff1 = Math.abs(1d - avgIndent); double diff2 = Math.abs(2d - avgIndent); @@ -76,7 +77,7 @@ private static Pair guessIndentationStyle(List wsPrece boolean usesTabs = wsPrecedingTypeMembers.stream() .filter(s -> s.contains("\t")) - .count() > wsPrecedingTypeMembers.size() / 2; + .count() >= wsPrecedingTypeMembers.size() / 2; return Pair.of(indentationSize, usesTabs); } diff --git a/src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java b/src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java index 49381c3a0d3..99f906010ae 100644 --- a/src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java +++ b/src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java @@ -446,6 +446,20 @@ public void testAddedElementsIndentedWithAppropriateIndentationStyle() { testSniper("indentation.FourSpaces", addElements, assertFourSpaces); } + @Test + public void testDefaultsToSingleTabIndentationWhenThereAreNoTypeMembers() { + // contract: if there are no type members in a compilation unit, the sniper printer defaults + // to indenting with 1 tab + + Consumer> addField = type -> { + Factory fact = type.getFactory(); + fact.createField(type, new HashSet<>(), fact.Type().INTEGER_PRIMITIVE, "z", fact.createLiteral(3)); + }; + testSniper("indentation.NoTypeMembers", addField, (type, result) -> { + assertThat(result, containsString("\n\tint z = 3;")); + }); + } + /** * 1) Runs spoon using sniper mode, * 2) runs `typeChanger` to modify the code, diff --git a/src/test/resources/indentation/NoTypeMembers.java b/src/test/resources/indentation/NoTypeMembers.java new file mode 100644 index 00000000000..e434feda3a3 --- /dev/null +++ b/src/test/resources/indentation/NoTypeMembers.java @@ -0,0 +1,4 @@ +package indentation; + +public class NoTypeMembers { +} \ No newline at end of file From 34d3ca3e6c1d355dcb03bcf6d1df49f7680aa714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Lars=C3=A9n?= Date: Thu, 3 Dec 2020 11:37:58 +0100 Subject: [PATCH 6/9] Fix checkstyle errors --- .../sniper/SniperJavaPrettyPrinter.java | 3 - .../sniper/internal/IndentationDetector.java | 123 +++++++++--------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java b/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java index bc9d71ed14d..705837dbda0 100644 --- a/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java +++ b/src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java @@ -8,12 +8,10 @@ package spoon.support.sniper; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Deque; import java.util.List; -import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Pair; import spoon.OutputType; @@ -48,7 +46,6 @@ import spoon.support.sniper.internal.SourceFragmentContextNormal; import spoon.support.sniper.internal.DefaultSourceFragmentPrinter; import spoon.support.sniper.internal.TokenPrinterEvent; -import spoon.support.sniper.internal.TokenSourceFragment; import spoon.support.sniper.internal.TokenType; import spoon.support.sniper.internal.TokenWriterProxy; import spoon.support.util.ModelList; diff --git a/src/main/java/spoon/support/sniper/internal/IndentationDetector.java b/src/main/java/spoon/support/sniper/internal/IndentationDetector.java index feeddc55de4..5a03641c322 100644 --- a/src/main/java/spoon/support/sniper/internal/IndentationDetector.java +++ b/src/main/java/spoon/support/sniper/internal/IndentationDetector.java @@ -16,76 +16,79 @@ import java.util.stream.Collectors; /** - * Class for detecting the indentation style used in a compilation unit. + * Utility class for detecting the indentation style used in a compilation unit. */ public class IndentationDetector { - /** - * Detect the indentation style of the given compilation unit as 1, 2 or 4 spaces or tabs. - * - * @param cu A compilation unit. - * @return A pair on the form (indentationSize, isTabs) - */ - public static Pair detectIndentation(CtCompilationUnit cu) { - List typeFragments = cu.getOriginalSourceFragment() - .getGroupedChildrenFragments().stream() - .filter(fragment -> fragment instanceof CollectionSourceFragment) - .flatMap(fragment -> extractTypeFragments((CollectionSourceFragment) fragment).stream()) - .collect(Collectors.toList()); - return detectIndentation(typeFragments); - } + private IndentationDetector() { + } - private static Pair detectIndentation(List topLevelTypeFragments) { - List wsPrecedingTypeMembers = new ArrayList<>(); + /** + * Detect the indentation style of the given compilation unit as 1, 2 or 4 spaces or tabs. + * + * @param cu A compilation unit. + * @return A pair on the form (indentationSize, isTabs) + */ + public static Pair detectIndentation(CtCompilationUnit cu) { + List typeFragments = cu.getOriginalSourceFragment() + .getGroupedChildrenFragments().stream() + .filter(fragment -> fragment instanceof CollectionSourceFragment) + .flatMap(fragment -> extractTypeFragments((CollectionSourceFragment) fragment).stream()) + .collect(Collectors.toList()); + return detectIndentation(typeFragments); + } - for (ElementSourceFragment typeSource : topLevelTypeFragments) { - assert typeSource.getRoleInParent() == CtRole.DECLARED_TYPE; + private static Pair detectIndentation(List topLevelTypeFragments) { + List wsPrecedingTypeMembers = new ArrayList<>(); - List children = typeSource.getChildrenFragments(); - for (int i = 0; i < children.size() - 1; i++) { - if (children.get(i) instanceof TokenSourceFragment - && children.get(i + 1) instanceof ElementSourceFragment) { + for (ElementSourceFragment typeSource : topLevelTypeFragments) { + assert typeSource.getRoleInParent() == CtRole.DECLARED_TYPE; - TokenSourceFragment cur = (TokenSourceFragment) children.get(i); - ElementSourceFragment next = (ElementSourceFragment) children.get(i + 1); - if (cur.getType() == TokenType.SPACE && next.getRoleInParent() == CtRole.TYPE_MEMBER) { - wsPrecedingTypeMembers.add(cur.getSourceCode().replace("\n", "")); - } - } - } - } + List children = typeSource.getChildrenFragments(); + for (int i = 0; i < children.size() - 1; i++) { + if (children.get(i) instanceof TokenSourceFragment + && children.get(i + 1) instanceof ElementSourceFragment) { - return guessIndentationStyle(wsPrecedingTypeMembers); - } + TokenSourceFragment cur = (TokenSourceFragment) children.get(i); + ElementSourceFragment next = (ElementSourceFragment) children.get(i + 1); + if (cur.getType() == TokenType.SPACE && next.getRoleInParent() == CtRole.TYPE_MEMBER) { + wsPrecedingTypeMembers.add(cur.getSourceCode().replace("\n", "")); + } + } + } + } - private static Pair guessIndentationStyle(List wsPrecedingTypeMembers) { - double avgIndent = wsPrecedingTypeMembers.stream() - .map(String::length) - .map(Double::valueOf) - .reduce((acc, next) -> (acc + next) / 2).orElse(1d); + return guessIndentationStyle(wsPrecedingTypeMembers); + } - double diff1 = Math.abs(1d - avgIndent); - double diff2 = Math.abs(2d - avgIndent); - double diff4 = Math.abs(4d - avgIndent); + private static Pair guessIndentationStyle(List wsPrecedingTypeMembers) { + double avgIndent = wsPrecedingTypeMembers.stream() + .map(String::length) + .map(Double::valueOf) + .reduce((acc, next) -> (acc + next) / 2).orElse(1d); - int indentationSize; - if (diff1 > diff2) { - indentationSize = diff2 > diff4 ? 4 : 2; - } else { - indentationSize = 1; - } + double diff1 = Math.abs(1d - avgIndent); + double diff2 = Math.abs(2d - avgIndent); + double diff4 = Math.abs(4d - avgIndent); - boolean usesTabs = wsPrecedingTypeMembers.stream() - .filter(s -> s.contains("\t")) - .count() >= wsPrecedingTypeMembers.size() / 2; - return Pair.of(indentationSize, usesTabs); - } + int indentationSize; + if (diff1 > diff2) { + indentationSize = diff2 > diff4 ? 4 : 2; + } else { + indentationSize = 1; + } - private static List extractTypeFragments(CollectionSourceFragment collection) { - return collection.getItems().stream() - .filter(fragment -> fragment instanceof ElementSourceFragment) - .map(fragment -> (ElementSourceFragment) fragment) - .filter(fragment -> fragment.getRoleInParent() == CtRole.DECLARED_TYPE) - .collect(Collectors.toList()); - } -} \ No newline at end of file + boolean usesTabs = wsPrecedingTypeMembers.stream() + .filter(s -> s.contains("\t")) + .count() >= wsPrecedingTypeMembers.size() / 2; + return Pair.of(indentationSize, usesTabs); + } + + private static List extractTypeFragments(CollectionSourceFragment collection) { + return collection.getItems().stream() + .filter(fragment -> fragment instanceof ElementSourceFragment) + .map(fragment -> (ElementSourceFragment) fragment) + .filter(fragment -> fragment.getRoleInParent() == CtRole.DECLARED_TYPE) + .collect(Collectors.toList()); + } +} From 1a7ab79de27d28064aedeab519e08c20d43616b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Lars=C3=A9n?= Date: Thu, 3 Dec 2020 11:45:06 +0100 Subject: [PATCH 7/9] Document setters --- .../spoon/support/sniper/internal/MutableTokenWriter.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/spoon/support/sniper/internal/MutableTokenWriter.java b/src/main/java/spoon/support/sniper/internal/MutableTokenWriter.java index 12340b3bc53..67e491998d4 100644 --- a/src/main/java/spoon/support/sniper/internal/MutableTokenWriter.java +++ b/src/main/java/spoon/support/sniper/internal/MutableTokenWriter.java @@ -68,10 +68,16 @@ public void setMuted(boolean muted) { this.muted = muted; } + /** + * @param originSourceUsesTabulations whether or not the origin source uses tabs for indentation. + */ public void setOriginSourceUsesTabulations(boolean originSourceUsesTabulations) { this.originSourceUsesTabulations = originSourceUsesTabulations; } + /** + * @param originSourceTabulationSize the amount of indentation used in the origin source. + */ public void setOriginSourceTabulationSize(int originSourceTabulationSize) { this.originSourceTabulationSize = originSourceTabulationSize; } From 5fb75311858daa6e817c520a27ab653c12810ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Lars=C3=A9n?= Date: Thu, 3 Dec 2020 12:57:01 +0100 Subject: [PATCH 8/9] Add a couple of blank lines for readability --- .../java/spoon/support/sniper/internal/MutableTokenWriter.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/spoon/support/sniper/internal/MutableTokenWriter.java b/src/main/java/spoon/support/sniper/internal/MutableTokenWriter.java index 67e491998d4..859fad169f8 100644 --- a/src/main/java/spoon/support/sniper/internal/MutableTokenWriter.java +++ b/src/main/java/spoon/support/sniper/internal/MutableTokenWriter.java @@ -48,7 +48,9 @@ protected void autoWriteTabs() { int setTabulationSize = env.getTabulationSize(); env.useTabulations(originSourceUsesTabulations); env.setTabulationSize(originSourceTabulationSize); + super.autoWriteTabs(); + env.setTabulationSize(setTabulationSize); env.useTabulations(true); } From 1d3d5923a4b85d137979930b7f0b4bab46dbf8df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Lars=C3=A9n?= Date: Fri, 4 Dec 2020 10:10:29 +0100 Subject: [PATCH 9/9] Clarify indentation detection strategy --- .../spoon/support/sniper/internal/IndentationDetector.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/spoon/support/sniper/internal/IndentationDetector.java b/src/main/java/spoon/support/sniper/internal/IndentationDetector.java index 5a03641c322..93cd0696507 100644 --- a/src/main/java/spoon/support/sniper/internal/IndentationDetector.java +++ b/src/main/java/spoon/support/sniper/internal/IndentationDetector.java @@ -24,7 +24,8 @@ private IndentationDetector() { } /** - * Detect the indentation style of the given compilation unit as 1, 2 or 4 spaces or tabs. + * Detect the indentation style of the given compilation unit as 1, 2 or 4 spaces or tabs by + * inspecting the whitespace preceding type members of top-level type declarations. * * @param cu A compilation unit. * @return A pair on the form (indentationSize, isTabs)