From 501c800fd22b4ea2661518eb7a8be7ab98288073 Mon Sep 17 00:00:00 2001 From: Matthias Kurz Date: Thu, 1 Oct 2026 15:20:34 +0200 Subject: [PATCH] Expose tab indentation via --use-tabs CLI flag Connect the CLI to existing Style.Builder.useTabs support without changing the default space-based indentation or formatting algorithms. Document style-dependent tab stops and add parser, argument-file, stdin, line-wrapping, dry-run/replace, and imports-only regression tests. --- README.md | 4 + .../java/CommandLineOptionsParser.java | 1 + .../googlejavaformat/java/UsageException.java | 3 + .../java/CommandLineOptionsParserTest.java | 34 ++++ .../googlejavaformat/java/MainTest.java | 181 ++++++++++++++++++ 5 files changed, 223 insertions(+) diff --git a/README.md b/README.md index 4f40e001f..1ebcaeee4 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,10 @@ about other flags, such as `--aosp`, `--fix-imports-only`, `--skip-reflowing-long-strings`, `--skip-javadoc-formatting`, or the `--dry-run` and `--set-exit-if-changed`. +Use `--use-tabs` to indent with tabs instead of spaces. Tab stops are 2 columns +for Google Style and 4 columns for AOSP; alignment may still use spaces. This +option can be combined with `--style` and `--max-line-length`. + Using `@` reads options and filenames from a file, instead of arguments. diff --git a/core/src/main/java/com/google/googlejavaformat/java/CommandLineOptionsParser.java b/core/src/main/java/com/google/googlejavaformat/java/CommandLineOptionsParser.java index fe27840b7..21173e8be 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/CommandLineOptionsParser.java +++ b/core/src/main/java/com/google/googlejavaformat/java/CommandLineOptionsParser.java @@ -91,6 +91,7 @@ static CommandLineOptions parse(Iterable options) { case "--skip-reflowing-long-strings" -> optionsBuilder.reflowLongStrings(false); case "--skip-javadoc-formatting" -> optionsBuilder.formatJavadoc(false); case "--skip-reordering-modifiers" -> optionsBuilder.reorderModifiers(false); + case "--use-tabs" -> styleBuilder.useTabs(true); case "--max-line-length" -> { int length = parseInteger(it, flag, value); if (length <= 0) { diff --git a/core/src/main/java/com/google/googlejavaformat/java/UsageException.java b/core/src/main/java/com/google/googlejavaformat/java/UsageException.java index a51151ee6..f02d98102 100644 --- a/core/src/main/java/com/google/googlejavaformat/java/UsageException.java +++ b/core/src/main/java/com/google/googlejavaformat/java/UsageException.java @@ -44,6 +44,9 @@ Use AOSP style instead of Google Style (4-space indentation). --google-style and --aosp, respectively. If multiple style options are given, the last one wins. --max-line-length Maximum line length (default is 100). + --use-tabs + Use tabs for indentation instead of spaces. Tab stops are 2 columns for + Google Style and 4 columns for AOSP. Alignment may still use spaces. --fix-imports-only Fix import order and remove any unused imports, but do no other formatting. --skip-sorting-imports diff --git a/core/src/test/java/com/google/googlejavaformat/java/CommandLineOptionsParserTest.java b/core/src/test/java/com/google/googlejavaformat/java/CommandLineOptionsParserTest.java index 4ed61d1b4..b06822b7e 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/CommandLineOptionsParserTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/CommandLineOptionsParserTest.java @@ -56,6 +56,7 @@ public void defaults() { assertThat(options.formatJavadoc()).isTrue(); assertThat(options.reorderModifiers()).isTrue(); assertThat(options.maxLineLength()).isEqualTo(100); + assertThat(options.style().useTabs()).isFalse(); } @Test @@ -263,6 +264,39 @@ public void maxLineLengthNonPositive() { assertThat(e).hasMessageThat().contains("invalid max-line-length: -1 (must be positive)"); } + @Test + public void useTabs() { + assertThat(CommandLineOptionsParser.parse(Arrays.asList("--use-tabs")).style().useTabs()) + .isTrue(); + } + + @Test + public void useTabsStyleFlagOrder() { + for (String style : + ImmutableList.of("--aosp", "--google-style", "--style=aosp", "--style=google")) { + for (ImmutableList args : + ImmutableList.of( + ImmutableList.of("--use-tabs", "--max-line-length=80", style), + ImmutableList.of(style, "--max-line-length=80", "--use-tabs"))) { + CommandLineOptions options = CommandLineOptionsParser.parse(args); + assertThat(options.style().useTabs()).isTrue(); + assertThat(options.aosp()).isEqualTo(style.endsWith("aosp")); + assertThat(options.maxLineLength()).isEqualTo(80); + } + } + } + + @Test + public void useTabsParamsFile() throws IOException { + Path params = testFolder.newFile("tabs.params").toPath(); + Files.writeString(params, "--use-tabs\n--style=aosp\n--max-line-length=80\nTest.java\n", UTF_8); + CommandLineOptions options = CommandLineOptionsParser.parse(ImmutableList.of("@" + params)); + assertThat(options.style().useTabs()).isTrue(); + assertThat(options.aosp()).isTrue(); + assertThat(options.maxLineLength()).isEqualTo(80); + assertThat(options.files()).containsExactly("Test.java"); + } + @Test public void styleFlagOrder() { CommandLineOptions opt1 = diff --git a/core/src/test/java/com/google/googlejavaformat/java/MainTest.java b/core/src/test/java/com/google/googlejavaformat/java/MainTest.java index 08c366f43..a64c07746 100644 --- a/core/src/test/java/com/google/googlejavaformat/java/MainTest.java +++ b/core/src/test/java/com/google/googlejavaformat/java/MainTest.java @@ -77,6 +77,8 @@ public void testUsageOutput() { // Sanity check that a flag and description is in included. assertThat(usage).contains("--length"); assertThat(usage).contains("Character length to format."); + assertThat(usage).contains("--use-tabs"); + assertThat(usage).contains("Use tabs for indentation instead of spaces."); // Check that some of the additional text is included. assertThat(usage).contains("the result is sent to stdout"); @@ -743,4 +745,183 @@ void f() { assertThat(main.format("--max-line-length=30", "-")).isEqualTo(0); assertThat(out.toString()).isEqualTo(expected); } + + @Test + public void useTabsGoogleStyle() throws Exception { + String input = + """ + class T { + /** + * Multi-line javadoc + * comment. + */ + void f(int a, int b) { + // multi-line + // comment + int x = aaaaaaaaaa + bbbbbbbbbb + cccccccccc; + } + } + """; + String expected = + """ + class T { + \t/** + \t * Multi-line javadoc + \t * comment. + \t */ + \tvoid f(int a, int b) { + \t\t// multi-line + \t\t// comment + \t\tint x = + \t\t\t\taaaaaaaaaa + \t\t\t\t\t\t+ bbbbbbbbbb + \t\t\t\t\t\t+ cccccccccc; + \t} + } + """; + StringWriter out = new StringWriter(); + StringWriter err = new StringWriter(); + Main main = + new Main( + new PrintWriter(out, true), + new PrintWriter(err, true), + new ByteArrayInputStream(input.getBytes(UTF_8))); + assertThat(main.format("--use-tabs", "--max-line-length=30", "-")).isEqualTo(0); + assertThat(out.toString()).isEqualTo(expected); + assertThat(err.toString()).isEmpty(); + } + + @Test + public void useTabsAospStyle() throws Exception { + String input = + """ + class T { + void f() { + int x = aaaaaaaaaa + bbbbbbbbbb; + } + } + """; + String expected = + """ + class T { + \tvoid f() { + \t\tint x = + \t\t\t\taaaaaaaaaa + \t\t\t\t\t\t+ bbbbbbbbbb; + \t} + } + """; + StringWriter out = new StringWriter(); + StringWriter err = new StringWriter(); + Main main = + new Main( + new PrintWriter(out, true), + new PrintWriter(err, true), + new ByteArrayInputStream(input.getBytes(UTF_8))); + assertThat(main.format("--use-tabs", "--style=aosp", "--max-line-length=38", "-")).isEqualTo(0); + assertThat(out.toString()).isEqualTo(expected); + assertThat(err.toString()).isEmpty(); + } + + @Test + public void useTabsReflowLongStrings() throws Exception { + String input = + """ + class T { + String s = "one two three four five six seven eight"; + } + """; + String expected = + """ + class T { + \tString s = + \t\t\t"one two three four five six" + \t\t\t\t\t+ " seven eight"; + } + """; + StringWriter out = new StringWriter(); + StringWriter err = new StringWriter(); + Main main = + new Main( + new PrintWriter(out, true), + new PrintWriter(err, true), + new ByteArrayInputStream(input.getBytes(UTF_8))); + assertThat(main.format("--max-line-length=35", "--use-tabs", "-")).isEqualTo(0); + assertThat(out.toString()).isEqualTo(expected); + assertThat(err.toString()).isEmpty(); + } + + @Test + public void useTabsFiles() throws Exception { + Path path = testFolder.newFile("Test.java").toPath(); + String input = + """ + class Test { + void f() { + System.out.println("hello"); + } + } + """; + String expected = + """ + class Test { + \tvoid f() { + \t\tSystem.out.println("hello"); + \t} + } + """; + Files.writeString(path, input, UTF_8); + StringWriter out = new StringWriter(); + StringWriter err = new StringWriter(); + Main main = new Main(new PrintWriter(out, true), new PrintWriter(err, true), System.in); + + assertThat(main.format("--use-tabs", "--dry-run", "--set-exit-if-changed", path.toString())) + .isEqualTo(1); + assertThat(out.toString()).isEqualTo(path + System.lineSeparator()); + assertThat(Files.readString(path, UTF_8)).isEqualTo(input); + + out.getBuffer().setLength(0); + assertThat(main.format("--use-tabs", "--replace", path.toString())).isEqualTo(0); + assertThat(Files.readString(path, UTF_8)).isEqualTo(expected); + assertThat(out.toString()).isEmpty(); + + assertThat(main.format("--use-tabs", "--dry-run", "--set-exit-if-changed", path.toString())) + .isEqualTo(0); + assertThat(out.toString()).isEmpty(); + + assertThat(main.format("--dry-run", "--set-exit-if-changed", path.toString())).isEqualTo(1); + assertThat(out.toString()).isEqualTo(path + System.lineSeparator()); + assertThat(err.toString()).isEmpty(); + } + + @Test + public void useTabsFixImportsOnly() throws Exception { + String input = + """ + import java.util.List; + import java.util.ArrayList; + class Test { + List values = new ArrayList<>(); + } + """; + String expected = + """ + import java.util.ArrayList; + import java.util.List; + + class Test { + List values = new ArrayList<>(); + } + """; + StringWriter out = new StringWriter(); + StringWriter err = new StringWriter(); + Main main = + new Main( + new PrintWriter(out, true), + new PrintWriter(err, true), + new ByteArrayInputStream(input.getBytes(UTF_8))); + assertThat(main.format("--use-tabs", "--fix-imports-only", "-")).isEqualTo(0); + assertThat(out.toString()).isEqualTo(expected); + assertThat(err.toString()).isEmpty(); + } }