diff --git a/CHANGELOG.md b/CHANGELOG.md
index 824aeb4..e305c0a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,75 @@ and this project adheres to
## [Unreleased]
+## [0.5.1] - 2026-06-24
+
+Bug-fix release addressing four formatter defects (five
+code-level fixes) surfaced during the 0.5.0 adoption pass
+across `senzing-commons-java` and `sz-sdk-java`. All changes
+are formatter output fixes; no spec changes.
+
+### Fixed
+
+- **Mid-statement line comments dropped in
+ `variable_declarator`.** Comments positioned between `=`
+ and the value RHS (e.g. javadoc `// @highlight region="x"`
+ snippet markers on text-block assignments) were silently
+ dropped by the AST walk, breaking `mvn javadoc:javadoc`
+ under JDK 21 when the unpaired `@end region` closers
+ failed validation. Fix: source-preserve the `= ...` region
+ verbatim when mid-statement comments are detected,
+ matching the 0.5.0 treatment of comments inside arg lists.
+- **Trailing whitespace bypassed `Emitter.newline()`'s
+ `rstrip` in source-preserve paths.** `write_raw_lines`
+ intentionally preserves trailing whitespace for text-block
+ content but had no opt-in for source-preserved CODE.
+ Fix: added `strip_trailing_ws` parameter; all
+ source-preserve-code call sites (conditions, arg lists,
+ formal parameters, non-javadoc block comments) pass `True`.
+- **Array initializer missing space before `{`.** Produced
+ `new Type[]{ X }` instead of canonical `new Type[] { X }`
+ for some inputs; idempotent on both forms, so the file
+ accumulated mixed styles. Fix: emit a single space before
+ `array_initializer` children of
+ `array_creation_expression`.
+- **Item-8 invariant not enforced for multi-arg arg lists.**
+ Calls of the shape
+ `assertEquals(arg1, longCallThatWraps(...), msg)` jammed
+ the third argument onto the wrapped second argument's
+ tail line. The 0.5.0 spec described this as
+ "width-gate handles it implicitly" but the gate misses
+ cases where the line happens to fit under 80 chars by
+ coincidence. Fix: explicit
+ "previous-arg-multi-row → break before next arg" check in
+ both `emit_p1` and `emit_p2_greedy`.
+- **Binary positional arg ignored arg-start column.** When
+ a multi-arg call's positional argument was a
+ `binary_expression`, the binary's continuation operators
+ landed at `block + 4` instead of paren-aligning under the
+ argument's first operand column. Fix: set
+ `paren_align_col` to the arg's start column for the
+ duration of a binary-typed positional arg's emit. Narrow
+ to direct `binary_expression` (no paren unwrap) to avoid
+ the idempotency drift that originally narrowed 0.5.0
+ item 10 to single-arg.
+
+### Verification
+
+- 656 formatter tests pass (was 645 at 0.5.0; +10 new
+ fixtures across `comment_preservation/`, `arg_list_wrap/`,
+ and `array_initializer/`). The first 6 lock the headline
+ fixes; the remaining 4 cover edge cases surfaced during the
+ PR review pass (block-comment between `=` and value,
+ array initializer without `new`, last-arg-multi-row in arg
+ lists, idempotency lock for the P4 paren-aligned shape).
+- `senzing-commons-java` reformat: 2151 / 2151 tests pass,
+ `mvn -Pcheckstyle validate` BUILD SUCCESS, idempotent on
+ 2nd pass, zero trailing whitespace in source, array
+ initializers normalized.
+- The sz-sdk-java demo files containing javadoc `@snippet`
+ markers no longer lose their `// @highlight` openers;
+ `mvn javadoc:javadoc` under JDK 21 succeeds.
+
## [0.5.0] - 2026-06-23
Expands the formatter from the
diff --git a/docs/faqs/building/consumer-trial-checklist.md b/docs/faqs/building/consumer-trial-checklist.md
new file mode 100644
index 0000000..3a7d646
--- /dev/null
+++ b/docs/faqs/building/consumer-trial-checklist.md
@@ -0,0 +1,171 @@
+# Consumer Trial Checklist (Before Tagging a Standards Release)
+
+## Overview
+
+When preparing to tag a new release of `java-coding-standards`,
+trial the candidate against each known adopter before the tag
+lands. The shallow gate that 0.5.0 used (`mvn -Pcheckstyle
+validate` only) missed a silent data-loss bug because checkstyle
+doesn't validate that javadoc snippet markup survives
+reformatting. The full gate below catches that class of issue.
+
+## The five gates
+
+A consumer trial PASSES only when ALL five gates pass:
+
+### 1. Checkstyle
+
+```bash
+mvn -Pcheckstyle validate
+```
+
+Must report `BUILD SUCCESS` with zero LineLength or style
+violations. If the consumer has source files that overflow at
+the canonical column under the new spec's no-fallback policy,
+the developer manually splits the long literal before this
+gate goes green.
+
+### 2. Tests
+
+```bash
+mvn test
+```
+
+Must report `BUILD SUCCESS` with zero failures, zero errors.
+A formatter change that breaks tests is a semantic regression
+(rare but possible — e.g. annotation arg order, string
+escapes).
+
+### 3. Javadoc — never skip
+
+```bash
+mvn javadoc:javadoc # adopt the consumer's default profile
+```
+
+Must report `BUILD SUCCESS`. **Run this with the consumer's
+default profile, NOT a profile that strips javadoc snippet
+markup.**
+
+The sz-sdk-java `java-17` profile sets
+`x` on the
+`maven-javadoc-plugin`, which strips `@snippet` tags entirely
+under JDK 17 — useful for back-compat, but masks the entire
+class of bug where the formatter drops `// @highlight` /
+`// @end` snippet markers. The 0.5.0 release went out with a
+real data-loss bug because the trial only ran the JDK-17
+profile.
+
+For consumers that target JDK 17+ for javadoc, run the gate
+under the profile that DOES include snippet markup (typically
+`java-18+`, `java-21`, or no profile at all). Pre-JDK-18 consumers
+can use a plain javadoc invocation — the gate is checking
+that the formatter didn't drop tokens, not that the resulting
+javadoc renders correctly under every JDK.
+
+### 4. Token round-trip
+
+Count source tokens that the formatter could plausibly drop,
+pre- and post-format. Counts must match:
+
+```bash
+# Snippet markers (javadoc @snippet markup)
+grep -cE '(@highlight|@end|@start|@link|@replace)' \
+ src/main/java -r > /tmp/pre-counts.txt
+grep -cE '(@highlight|@end|@start|@link|@replace)' \
+ src/demo/java -r > /tmp/pre-demo-counts.txt
+
+# Trailing whitespace (forbidden per spec)
+grep -rl ' $' src/main/java > /tmp/pre-trailing.txt
+
+# Format, then re-count
+python3 .java-coding-standards/tooling/scripts/format_file.py \
+ src/main/java src/test/java src/demo/java
+grep -cE '(@highlight|@end|@start|@link|@replace)' \
+ src/main/java -r > /tmp/post-counts.txt
+grep -cE '(@highlight|@end|@start|@link|@replace)' \
+ src/demo/java -r > /tmp/post-demo-counts.txt
+grep -rl ' $' src/main/java > /tmp/post-trailing.txt
+
+diff /tmp/pre-counts.txt /tmp/post-counts.txt # must be empty
+diff /tmp/pre-demo-counts.txt /tmp/post-demo-counts.txt # must be empty
+diff /tmp/post-trailing.txt /dev/null # must be empty
+```
+
+If any diff is non-empty: the formatter is dropping or
+introducing tokens — file a regression before tagging.
+
+### 5. Idempotency
+
+```bash
+python3 .java-coding-standards/tooling/scripts/format_file.py \
+ src/main/java src/test/java src/demo/java
+```
+
+Second invocation must report `0 modified`. A formatter that
+produces different output on the second pass is non-idempotent
+— file the case as a regression and don't tag.
+
+## What changed in 0.5.0 → 0.5.1
+
+0.5.0's pre-release trial against sz-sdk-java ran ONLY gate 1
+(checkstyle). 0.5.0 shipped with a bug that:
+
+- Silently dropped `// @highlight region="..."` line comments
+ positioned between `=` and a text-block opener on assignment
+ statements (e.g. `String x = // @highlight\n"""...""";`).
+- Was idempotent on the broken output (so the data loss was
+ unrecoverable by re-running the formatter).
+- Was invisible to checkstyle (no rule covers token
+ preservation).
+- Was invisible to `mvn javadoc:javadoc` under the consumer's
+ `java-17` profile (snippet tags were stripped by the profile
+ anyway).
+- Surfaced when CI ran the `java-21` profile, which doesn't
+ strip snippets — javadoc validation failed on unpaired
+ `@end region` markers.
+
+Gates 3 and 4 above are designed to catch this and similar
+data-loss bugs at consumer-trial time, before the standards
+release is tagged. Run them.
+
+## Workflow
+
+```bash
+# Setup: clone consumer with the candidate standards pin
+cd /path/to/consumer
+git checkout -b standards-trial-X.Y.Z
+git -C .java-coding-standards fetch
+git -C .java-coding-standards checkout FETCH_HEAD
+
+# Run all five gates
+mvn -Pcheckstyle validate # gate 1
+mvn test # gate 2
+mvn javadoc:javadoc -P # gate 3
+
+# Pre-format snapshot
+grep -cE '(@highlight|@end|@start|@link|@replace)' \
+ $(find src -name '*.java') > /tmp/pre-tokens.txt
+
+# Format
+python3 .java-coding-standards/tooling/scripts/format_file.py \
+ src/main/java src/test/java src/demo/java
+
+# Gate 4 — token round-trip
+grep -cE '(@highlight|@end|@start|@link|@replace)' \
+ $(find src -name '*.java') > /tmp/post-tokens.txt
+diff /tmp/pre-tokens.txt /tmp/post-tokens.txt # must be empty
+
+# Gate 5 — idempotency
+python3 .java-coding-standards/tooling/scripts/format_file.py \
+ src/main/java src/test/java src/demo/java
+# Must report "0 modified"
+```
+
+If any gate fails, file the case as a regression PR against
+the standards repo and re-run the trial after the fix. Don't
+tag until every trial passes every gate.
+
+## See also
+
+- [Java formatting standards](java-formatting-standards.md)
+- [Javadoc reflow conventions](javadoc-reflow-conventions.md)
diff --git a/tooling/scripts/format_java.py b/tooling/scripts/format_java.py
index 49f74b4..484139c 100644
--- a/tooling/scripts/format_java.py
+++ b/tooling/scripts/format_java.py
@@ -340,7 +340,7 @@ def newline(self) -> None:
Trailing spaces on the finalized line are stripped before
commit so emitters need not pre-trim them.
"""
- self._lines.append(self._current.rstrip(" "))
+ self._lines.append(self._current.rstrip(" \t"))
self._current = ""
@property
@@ -461,38 +461,42 @@ def last_lines_max_width(self, since: int) -> int:
m = len(self._current)
return m
- def write_raw_lines(self, text: str) -> None:
+ def write_raw_lines(
+ self, text: str, *, strip_trailing_ws: bool = False
+ ) -> None:
"""Append text that may contain newlines, preserved verbatim.
Used by leaf emitters for content the formatter must
reproduce byte-for-byte — text blocks ("Text Blocks /
Content preservation" spec section) and eventually block
- comments. Newlines inside `text` finalize each intermediate
- line WITHOUT stripping trailing whitespace, since that
+ comments. With the default `strip_trailing_ws=False`,
+ newlines inside `text` finalize each intermediate line
+ WITHOUT stripping trailing whitespace, since that
whitespace is the developer's content (the spec's
"Normalize spacing or alignment of content is a no-op"
rule applies to text-block contents).
+ Source-preserved CODE (argument-list verbatim preservation,
+ mid-statement-comment preservation, etc.) is not
+ whitespace-significant content — trailing whitespace
+ there is just stray bytes the source author left behind
+ and the spec's "Trailing Whitespace" rule applies. Such
+ callers pass `strip_trailing_ws=True` to apply the same
+ `rstrip(" \t")` that `newline()` does on its finalized
+ lines.
+
The in-progress line at the END of `text` (the part after
the last newline) is left open so subsequent `write()` /
- `newline()` calls continue normally. Note: trailing
- whitespace that the DEVELOPER wrote at the very end of a
- text block (after the final newline, before any
- formatter-emitted continuation) will be stripped by the
- eventual `newline()` / `finish()` — that case doesn't
- arise in well-formed Java source because every
- `string_literal` ends with a non-whitespace closing
- quote token, so the final segment passed here is never a
- bare-whitespace string. Future emitters that pass other
- kinds of verbatim multi-line content should guarantee the
- same invariant.
+ `newline()` calls continue normally.
"""
parts = text.split("\n")
# First segment continues the current line.
self._current += parts[0]
for part in parts[1:]:
- # Each intermediate line is verbatim — NO strip.
- self._lines.append(self._current)
+ if strip_trailing_ws:
+ self._lines.append(self._current.rstrip(" \t"))
+ else:
+ self._lines.append(self._current)
self._current = part
def push_indent(self) -> None:
@@ -521,7 +525,7 @@ def finish(self) -> bytes:
for files with at least one byte of real content.
"""
if self._current:
- self._lines.append(self._current.rstrip(" "))
+ self._lines.append(self._current.rstrip(" \t"))
self._current = ""
if not self._lines:
return b""
@@ -3398,7 +3402,11 @@ def _emit_comment(
if text.startswith("/**"):
_emit_javadoc_block(emitter, source, node, text)
return
- emitter.write_raw_lines(text)
+ # Non-javadoc block comments (`/* … */`) are source-preserved
+ # but trailing whitespace inside them is never intentional
+ # alignment (any deliberate ASCII-art alignment would be
+ # inside a CSOFF region with its own preservation path).
+ emitter.write_raw_lines(text, strip_trailing_ws=True)
_LINE_COMMENT_DIRECTIVE_PREFIXES: Final[tuple[str, ...]] = (
@@ -3828,7 +3836,9 @@ def _emit_array_initializer(
re-emits with the normalized spacing.
"""
if _node_spans_multiple_rows(node):
- emitter.write_raw_lines(_node_source_text(source, node))
+ emitter.write_raw_lines(
+ _node_source_text(source, node), strip_trailing_ws=True
+ )
return
elements = [c for c in node.named_children]
if not elements:
@@ -3854,10 +3864,20 @@ def _emit_array_creation_expression(
optionally followed by an `array_initializer`.
"""
if _node_spans_multiple_rows(node):
- emitter.write_raw_lines(_node_source_text(source, node))
+ emitter.write_raw_lines(
+ _node_source_text(source, node), strip_trailing_ws=True
+ )
return
emitter.write("new ")
for child in node.named_children:
+ # Spec "Whitespace and Operator Spacing": single space
+ # before the opening `{` of an array initializer that
+ # follows `[]` (or `[N]`) dimensions. `new Type[] { X }`
+ # is canonical; `new Type[]{ X }` is a 0.5.0 bug where
+ # the emitter walked `dimensions` then `array_initializer`
+ # back-to-back without inserting the required separator.
+ if child.type == "array_initializer":
+ emitter.write(" ")
_emit_node(emitter, source, child)
@@ -3989,7 +4009,9 @@ def _emit_switch_rule(
more statements. Source-preservation for multi-row source.
"""
if _node_spans_multiple_rows(node):
- emitter.write_raw_lines(_node_source_text(source, node))
+ emitter.write_raw_lines(
+ _node_source_text(source, node), strip_trailing_ws=True
+ )
return
label = None
body_children: list[Node] = []
@@ -4206,7 +4228,9 @@ def _emit_synchronized_statement(
)
emitter.write("synchronized ")
if _node_spans_multiple_rows(cond):
- emitter.write_raw_lines(_node_source_text(source, cond))
+ emitter.write_raw_lines(
+ _node_source_text(source, cond), strip_trailing_ws=True
+ )
emitter.newline()
emitter.write_indent()
_emit_node(emitter, source, body)
@@ -4818,7 +4842,10 @@ def _emit_while_statement(
if _node_spans_multiple_rows(condition):
# Preserve the developer-authored multi-line condition
# verbatim from source; switch to Allman brace.
- emitter.write_raw_lines(_node_source_text(source, condition))
+ emitter.write_raw_lines(
+ _node_source_text(source, condition),
+ strip_trailing_ws=True,
+ )
emitter.newline()
emitter.write_indent()
_emit_node(emitter, source, body)
@@ -6067,7 +6094,9 @@ def _emit_formal_parameters(
if not force_wrap and _node_spans_multiple_rows(node):
# Preserve developer-authored multi-line params from
# source. Includes opening `(` and closing `)`.
- emitter.write_raw_lines(_node_source_text(source, node))
+ emitter.write_raw_lines(
+ _node_source_text(source, node), strip_trailing_ws=True
+ )
return
params = [
c for c in node.children
@@ -6743,7 +6772,9 @@ def _emit_argument_list(
if comments_present or _is_inside_csoff_region(
source, node
):
- emitter.write_raw_lines(src_text)
+ emitter.write_raw_lines(
+ src_text, strip_trailing_ws=True
+ )
return
if emitter.paren_align_col is not None:
target_col = emitter.paren_align_col + 4
@@ -6829,7 +6860,9 @@ def _emit_argument_list(
"indent within the line limit."
),
))
- emitter.write_raw_lines("\n".join(final_lines))
+ emitter.write_raw_lines(
+ "\n".join(final_lines), strip_trailing_ws=True
+ )
return
# Source-preserved first line wouldn't fit and there
# are no comments — fall through. The wrap engine
@@ -6864,6 +6897,39 @@ def _emit_argument_list(
and args[0].type == "binary_expression"
)
+ def _emit_arg_with_optional_paren_align(arg: Node) -> None:
+ # Binary positional arg paren-align: when a positional
+ # arg is a binary expression, set `paren_align_col` to
+ # the arg's start column so the binary's continuation
+ # operators paren-align under the arg's first operand
+ # instead of falling back to the `block + 4`
+ # cumulative indent. This produces:
+ #
+ # assertTrue(cond,
+ # "msg "
+ # + var
+ # + " more");
+ #
+ # instead of:
+ #
+ # assertTrue(cond,
+ # "msg "
+ # + var + " more"); // `+` at col 8
+ #
+ # Narrow to direct binary_expression (no paren unwrap)
+ # to avoid the idempotency drift that originally
+ # narrowed the single-arg call-paren extension to
+ # binary args only.
+ if arg.type == "binary_expression":
+ arg_col = emitter.column
+ prev_align = emitter.set_paren_align_col(arg_col)
+ try:
+ _emit_node(emitter, source, arg)
+ finally:
+ emitter.set_paren_align_col(prev_align)
+ else:
+ _emit_node(emitter, source, arg)
+
def emit_p1() -> None:
emitter.write("(")
if single_arg_binary:
@@ -6874,10 +6940,29 @@ def emit_p1() -> None:
finally:
emitter.set_paren_align_col(prev_align)
else:
+ cont_col = emitter.column
+ prev_arg_multi_row = False
for index, arg in enumerate(args):
if index > 0:
- emitter.write(", ")
- _emit_node(emitter, source, arg)
+ if prev_arg_multi_row:
+ # Item 8 invariant in arg-list P1:
+ # when the previous arg emitted
+ # multi-row (a nested call / lambda /
+ # binary wrapped), break before this
+ # arg so it doesn't jam onto the
+ # wrapped construct's tail line. The
+ # break lands at the call's post-`(`
+ # column.
+ emitter.write(",")
+ emitter.newline()
+ emitter.write(" " * cont_col)
+ else:
+ emitter.write(", ")
+ operand_start = emitter.line_count
+ _emit_arg_with_optional_paren_align(arg)
+ prev_arg_multi_row = (
+ emitter.line_count > operand_start
+ )
emitter.write(")")
def emit_p4_single_arg() -> None:
@@ -6914,13 +6999,37 @@ def emit_p2_greedy() -> None:
emitter.write("(")
cont_col = emitter.column
effective_max = _MAX_LINE - emitter.tail_reserve
+ prev_arg_multi_row = False
for index, arg in enumerate(args):
if index == 0:
- _emit_node(emitter, source, arg)
+ operand_start = emitter.line_count
+ _emit_arg_with_optional_paren_align(arg)
+ prev_arg_multi_row = (
+ emitter.line_count > operand_start
+ )
+ continue
+ if prev_arg_multi_row:
+ # Item 8 invariant for arg lists: the previous
+ # arg's emission introduced newlines (a nested
+ # call / lambda / binary wrapped multi-row),
+ # so force break before this arg. Otherwise
+ # the next arg lands at whatever column the
+ # prior arg's wrap tail ended on, jamming
+ # `arg)` onto the same line as the wrapped
+ # construct's closing.
+ emitter.write(",")
+ emitter.newline()
+ emitter.write(" " * cont_col)
+ operand_start = emitter.line_count
+ _emit_arg_with_optional_paren_align(arg)
+ prev_arg_multi_row = (
+ emitter.line_count > operand_start
+ )
continue
saved = emitter.snapshot()
emitter.write(", ")
- _emit_node(emitter, source, arg)
+ operand_start = emitter.line_count
+ _emit_arg_with_optional_paren_align(arg)
widths_ok = (
emitter.last_lines_max_width(saved[0])
<= effective_max
@@ -6952,7 +7061,11 @@ def emit_p2_greedy() -> None:
emitter.write(",")
emitter.newline()
emitter.write(" " * cont_col)
- _emit_node(emitter, source, arg)
+ operand_start = emitter.line_count
+ _emit_arg_with_optional_paren_align(arg)
+ prev_arg_multi_row = (
+ emitter.line_count > operand_start
+ )
emitter.write(")")
def emit_p4_multi_arg() -> None:
@@ -6973,12 +7086,14 @@ def emit_p4_multi_arg() -> None:
emitter.write(")")
emitter.pop_indent()
- # P1 (single line) is always tried first. The wrap engine
- # measures actual rendered widths via try_priorities, so a
- # multi-line arg (lambda body, nested wrapping call) that
- # blows past 80 chars during P1 emit simply falls through
- # to the next candidate. Letting P1 try also keeps the
- # decision deterministic from the AST — earlier code
+ # P1 is the AST-deterministic single-line candidate, but
+ # may emit a multi-row layout when an intermediate arg
+ # wraps multi-row and item-8 forces a break before
+ # subsequent args. try_priorities still
+ # measures actual rendered widths, so a P1 emit that
+ # blows past 80 chars falls through to the next
+ # candidate. Letting P1 try keeps the decision
+ # deterministic from the AST — earlier code
# short-circuited P1 when any arg's SOURCE was multi-row,
# which made the decision flip between formatter passes.
candidates: list[Callable[[], None]] = [emit_p1]
@@ -7648,6 +7763,44 @@ def _emit_variable_declarator(
break
if value is None:
return
+ # Mid-statement comment preservation: when
+ # line_comment / block_comment "extras" sit between the
+ # `=` token and the value RHS (e.g. javadoc
+ # `// @highlight region="..."` snippet markers on
+ # assignment-with-text-block), the wrap engine has no way
+ # to represent comments inline with operator placement,
+ # so source-preserve the entire `= ...` region verbatim.
+ # This matches the treatment of comments inside argument
+ # lists (`_arg_list_takes_source_preserve_path`). Without
+ # this guard the comments are silently dropped
+ # since `_emit_node(value)` walks only the value subtree
+ # and never visits the extra comment children.
+ equals_token = None
+ for child in node.children:
+ if child.type == "=":
+ equals_token = child
+ break
+ if equals_token is not None:
+ mid_comments = [
+ c for c in node.children
+ if c.type in ("line_comment", "block_comment")
+ and c.start_byte > equals_token.start_byte
+ and c.start_byte < value.start_byte
+ ]
+ if mid_comments:
+ # Emit `= `.
+ # Verbatim preserves the developer's whitespace
+ # between `=`, the comments, and the value — the
+ # only safe transform when comment placement
+ # carries semantic meaning (snippet markers).
+ emitter.write(" ")
+ verbatim = source[
+ equals_token.start_byte:value.end_byte
+ ].decode("utf-8")
+ emitter.write_raw_lines(
+ verbatim, strip_trailing_ws=True
+ )
+ return
# Wrap-priority for assignment: prefer the cleanest single-
# line form over wrapping the value internally. Order:
#
diff --git a/tooling/scripts/tests/fixtures/arg_list_wrap/04_item8_prev_arg_multi_row_breaks_next/expected.java b/tooling/scripts/tests/fixtures/arg_list_wrap/04_item8_prev_arg_multi_row_breaks_next/expected.java
new file mode 100644
index 0000000..c6b5032
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/arg_list_wrap/04_item8_prev_arg_multi_row_breaks_next/expected.java
@@ -0,0 +1,10 @@
+public class Demo
+{
+ void run(String expectedText, String result, String jsonValue)
+ {
+ assertEquals(expectedText.replaceAll("\\s", ""), result.replaceAll(
+ "\\s",
+ ""),
+ "Unexpected pretty-print result: " + jsonValue);
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/arg_list_wrap/04_item8_prev_arg_multi_row_breaks_next/input.java b/tooling/scripts/tests/fixtures/arg_list_wrap/04_item8_prev_arg_multi_row_breaks_next/input.java
new file mode 100644
index 0000000..8626470
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/arg_list_wrap/04_item8_prev_arg_multi_row_breaks_next/input.java
@@ -0,0 +1,7 @@
+public class Demo
+{
+ void run(String expectedText, String result, String jsonValue)
+ {
+ assertEquals(expectedText.replaceAll("\\s", ""), result.replaceAll("\\s", ""), "Unexpected pretty-print result: " + jsonValue);
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/arg_list_wrap/05_paren_align_binary_positional_arg/expected.java b/tooling/scripts/tests/fixtures/arg_list_wrap/05_paren_align_binary_positional_arg/expected.java
new file mode 100644
index 0000000..1c9a118
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/arg_list_wrap/05_paren_align_binary_positional_arg/expected.java
@@ -0,0 +1,8 @@
+public class Demo
+{
+ void check(boolean cond, int bytes, int available)
+ {
+ assertTrue(available < bytes, "More bytes available than should be ("
+ + bytes + "): " + available);
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/arg_list_wrap/05_paren_align_binary_positional_arg/input.java b/tooling/scripts/tests/fixtures/arg_list_wrap/05_paren_align_binary_positional_arg/input.java
new file mode 100644
index 0000000..f1361fc
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/arg_list_wrap/05_paren_align_binary_positional_arg/input.java
@@ -0,0 +1,7 @@
+public class Demo
+{
+ void check(boolean cond, int bytes, int available)
+ {
+ assertTrue(available < bytes, "More bytes available than should be (" + bytes + "): " + available);
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/arg_list_wrap/06_last_arg_multi_row/expected.java b/tooling/scripts/tests/fixtures/arg_list_wrap/06_last_arg_multi_row/expected.java
new file mode 100644
index 0000000..ebbf615
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/arg_list_wrap/06_last_arg_multi_row/expected.java
@@ -0,0 +1,9 @@
+public class Demo
+{
+ void run(String first, String second)
+ {
+ assertEquals("expected", actualMethod.replaceAll("\\s", "")
+ .replaceAll("\\n", " ")
+ .trim());
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/arg_list_wrap/06_last_arg_multi_row/input.java b/tooling/scripts/tests/fixtures/arg_list_wrap/06_last_arg_multi_row/input.java
new file mode 100644
index 0000000..e3d3764
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/arg_list_wrap/06_last_arg_multi_row/input.java
@@ -0,0 +1,7 @@
+public class Demo
+{
+ void run(String first, String second)
+ {
+ assertEquals("expected", actualMethod.replaceAll("\\s", "").replaceAll("\\n", " ").trim());
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/arg_list_wrap/07_paren_align_binary_idempotency_lock/expected.java b/tooling/scripts/tests/fixtures/arg_list_wrap/07_paren_align_binary_idempotency_lock/expected.java
new file mode 100644
index 0000000..1c9a118
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/arg_list_wrap/07_paren_align_binary_idempotency_lock/expected.java
@@ -0,0 +1,8 @@
+public class Demo
+{
+ void check(boolean cond, int bytes, int available)
+ {
+ assertTrue(available < bytes, "More bytes available than should be ("
+ + bytes + "): " + available);
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/arg_list_wrap/07_paren_align_binary_idempotency_lock/input.java b/tooling/scripts/tests/fixtures/arg_list_wrap/07_paren_align_binary_idempotency_lock/input.java
new file mode 100644
index 0000000..1c9a118
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/arg_list_wrap/07_paren_align_binary_idempotency_lock/input.java
@@ -0,0 +1,8 @@
+public class Demo
+{
+ void check(boolean cond, int bytes, int available)
+ {
+ assertTrue(available < bytes, "More bytes available than should be ("
+ + bytes + "): " + available);
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/array_initializer/01_space_before_brace_after_dimensions/expected.java b/tooling/scripts/tests/fixtures/array_initializer/01_space_before_brace_after_dimensions/expected.java
new file mode 100644
index 0000000..5659429
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/array_initializer/01_space_before_brace_after_dimensions/expected.java
@@ -0,0 +1,6 @@
+public class Demo
+{
+ Class>[] types = new Class>[] { Connection.class };
+ Object[] values = new Object[] { a, b };
+ String[] empty = new String[] {};
+}
diff --git a/tooling/scripts/tests/fixtures/array_initializer/01_space_before_brace_after_dimensions/input.java b/tooling/scripts/tests/fixtures/array_initializer/01_space_before_brace_after_dimensions/input.java
new file mode 100644
index 0000000..895f8d0
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/array_initializer/01_space_before_brace_after_dimensions/input.java
@@ -0,0 +1,6 @@
+public class Demo
+{
+ Class>[] types = new Class>[]{ Connection.class };
+ Object[] values = new Object[]{ a, b };
+ String[] empty = new String[]{};
+}
diff --git a/tooling/scripts/tests/fixtures/array_initializer/02_initializer_without_new/expected.java b/tooling/scripts/tests/fixtures/array_initializer/02_initializer_without_new/expected.java
new file mode 100644
index 0000000..17f6a93
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/array_initializer/02_initializer_without_new/expected.java
@@ -0,0 +1,5 @@
+public class Demo
+{
+ int[] numbers = { 1, 2, 3 };
+ String[] empty = {};
+}
diff --git a/tooling/scripts/tests/fixtures/array_initializer/02_initializer_without_new/input.java b/tooling/scripts/tests/fixtures/array_initializer/02_initializer_without_new/input.java
new file mode 100644
index 0000000..17f6a93
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/array_initializer/02_initializer_without_new/input.java
@@ -0,0 +1,5 @@
+public class Demo
+{
+ int[] numbers = { 1, 2, 3 };
+ String[] empty = {};
+}
diff --git a/tooling/scripts/tests/fixtures/comment_preservation/01_assignment_text_block_with_snippet_markers/expected.java b/tooling/scripts/tests/fixtures/comment_preservation/01_assignment_text_block_with_snippet_markers/expected.java
new file mode 100644
index 0000000..b458e4f
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/comment_preservation/01_assignment_text_block_with_snippet_markers/expected.java
@@ -0,0 +1,16 @@
+public class Demo
+{
+ void run()
+ {
+ // get a record definition (varies by application)
+ String recordDefinition = // @highlight substring="recordDefinition"
+ // @highlight type="italic" region="recordDefinition"
+ """
+ {
+ "DATA_SOURCE": "TEST",
+ "RECORD_ID": "ABC123"
+ }
+ """;
+ // @end region="recordDefinition"
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/comment_preservation/01_assignment_text_block_with_snippet_markers/input.java b/tooling/scripts/tests/fixtures/comment_preservation/01_assignment_text_block_with_snippet_markers/input.java
new file mode 100644
index 0000000..b458e4f
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/comment_preservation/01_assignment_text_block_with_snippet_markers/input.java
@@ -0,0 +1,16 @@
+public class Demo
+{
+ void run()
+ {
+ // get a record definition (varies by application)
+ String recordDefinition = // @highlight substring="recordDefinition"
+ // @highlight type="italic" region="recordDefinition"
+ """
+ {
+ "DATA_SOURCE": "TEST",
+ "RECORD_ID": "ABC123"
+ }
+ """;
+ // @end region="recordDefinition"
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/comment_preservation/02_assignment_string_literal_with_comment/expected.java b/tooling/scripts/tests/fixtures/comment_preservation/02_assignment_string_literal_with_comment/expected.java
new file mode 100644
index 0000000..2a4977e
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/comment_preservation/02_assignment_string_literal_with_comment/expected.java
@@ -0,0 +1,8 @@
+public class Demo
+{
+ void run()
+ {
+ String greeting = // explanatory note
+ "hello, world";
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/comment_preservation/02_assignment_string_literal_with_comment/input.java b/tooling/scripts/tests/fixtures/comment_preservation/02_assignment_string_literal_with_comment/input.java
new file mode 100644
index 0000000..2a4977e
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/comment_preservation/02_assignment_string_literal_with_comment/input.java
@@ -0,0 +1,8 @@
+public class Demo
+{
+ void run()
+ {
+ String greeting = // explanatory note
+ "hello, world";
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/comment_preservation/03_snippet_region_round_trip/expected.java b/tooling/scripts/tests/fixtures/comment_preservation/03_snippet_region_round_trip/expected.java
new file mode 100644
index 0000000..0e6626e
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/comment_preservation/03_snippet_region_round_trip/expected.java
@@ -0,0 +1,16 @@
+public class Demo
+{
+ void run()
+ {
+ // @start region="example"
+ String recordDefinition = // @highlight region="recordDefinition"
+ """
+ {
+ "DATA_SOURCE": "TEST"
+ }
+ """;
+ // @end region="recordDefinition"
+ System.out.println(recordDefinition);
+ // @end region="example"
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/comment_preservation/03_snippet_region_round_trip/input.java b/tooling/scripts/tests/fixtures/comment_preservation/03_snippet_region_round_trip/input.java
new file mode 100644
index 0000000..0e6626e
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/comment_preservation/03_snippet_region_round_trip/input.java
@@ -0,0 +1,16 @@
+public class Demo
+{
+ void run()
+ {
+ // @start region="example"
+ String recordDefinition = // @highlight region="recordDefinition"
+ """
+ {
+ "DATA_SOURCE": "TEST"
+ }
+ """;
+ // @end region="recordDefinition"
+ System.out.println(recordDefinition);
+ // @end region="example"
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/comment_preservation/04_block_comment_between_eq_and_value/expected.java b/tooling/scripts/tests/fixtures/comment_preservation/04_block_comment_between_eq_and_value/expected.java
new file mode 100644
index 0000000..2daefb5
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/comment_preservation/04_block_comment_between_eq_and_value/expected.java
@@ -0,0 +1,7 @@
+public class Demo
+{
+ void run()
+ {
+ String label = /* descriptive block comment */ "hello";
+ }
+}
diff --git a/tooling/scripts/tests/fixtures/comment_preservation/04_block_comment_between_eq_and_value/input.java b/tooling/scripts/tests/fixtures/comment_preservation/04_block_comment_between_eq_and_value/input.java
new file mode 100644
index 0000000..2daefb5
--- /dev/null
+++ b/tooling/scripts/tests/fixtures/comment_preservation/04_block_comment_between_eq_and_value/input.java
@@ -0,0 +1,7 @@
+public class Demo
+{
+ void run()
+ {
+ String label = /* descriptive block comment */ "hello";
+ }
+}